mirror of
https://github.com/jhasse/poly2tri.git
synced 2024-12-28 13:43:30 +01:00
fixed nemerical robustness issue in triangulation
This commit is contained in:
parent
2752b9b021
commit
e484852d39
@ -39,7 +39,12 @@ class MonotoneMountain {
|
||||
var size = 0
|
||||
|
||||
val convexPoints = new Queue[Point]
|
||||
// Monotone mountain points
|
||||
val monoPoly = new ArrayBuffer[Point]
|
||||
// Triangles that constitute the mountain
|
||||
val triangles = new ArrayBuffer[Array[Point]]
|
||||
|
||||
var angle = 0f
|
||||
|
||||
// Append a point to the list
|
||||
def +=(point: Point) {
|
||||
@ -70,7 +75,10 @@ class MonotoneMountain {
|
||||
// Partition a x-monotone mountain into triangles O(n)
|
||||
// See "Computational Geometry in C", 2nd edition, by Joseph O'Rourke, page 52
|
||||
def triangulate {
|
||||
|
||||
|
||||
// create monotone polygon - for dubug purposes
|
||||
genMonoPoly
|
||||
|
||||
if(size == 3) {
|
||||
lastTriangle
|
||||
} else {
|
||||
@ -96,29 +104,30 @@ class MonotoneMountain {
|
||||
if(a.prev != null && convex(a)) convexPoints.enqueue(a);
|
||||
if(c.prev != null && convex(c)) convexPoints.enqueue(c)
|
||||
|
||||
}
|
||||
}
|
||||
assert(size <= 3, "Triangulation bug")
|
||||
if(size == 3)lastTriangle
|
||||
}
|
||||
}
|
||||
|
||||
// Return the monotone polygon
|
||||
def monoPoly: Array[Point] = {
|
||||
val poly = new Array[Point](size)
|
||||
var i = 0
|
||||
private def genMonoPoly {
|
||||
var p = head
|
||||
while(p != null) {
|
||||
poly(i) = p
|
||||
monoPoly += p
|
||||
p = p.next
|
||||
i += 1
|
||||
}
|
||||
poly
|
||||
}
|
||||
|
||||
// Determines if the inslide angle between edge v2-v3 and edge v2-v1 is convex
|
||||
private def convex(p: Point) = {
|
||||
val a = (p.next - p)
|
||||
val b = (p.prev - p)
|
||||
var angle = Math.atan2(b.y,b.x).toFloat - Math.atan2(a.y,a.x).toFloat
|
||||
angle = Math.atan2(b.y,b.x).toFloat - Math.atan2(a.y,a.x).toFloat
|
||||
if(angle < 0) while(angle < -Math.Pi) angle += Math.Pi.toFloat
|
||||
if(angle > 0) while(angle > Math.Pi) angle -= Math.Pi.toFloat
|
||||
// For numerical robustness....
|
||||
angle = 0.01f * Math.round( angle * 10.0f)
|
||||
if(p.y >= head.y) {
|
||||
(angle < 0)
|
||||
} else {
|
||||
|
@ -38,7 +38,7 @@ package org.poly2tri
|
||||
import org.newdawn.slick.{BasicGame, GameContainer, Graphics, Color, AppGameContainer}
|
||||
import org.newdawn.slick.geom.{Polygon, Circle}
|
||||
|
||||
import collection.jcl.ArrayList
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
// TODO: Lots of documentation!
|
||||
|
||||
@ -61,9 +61,6 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
|
||||
var drawMap = false
|
||||
|
||||
def init(container: GameContainer) {
|
||||
// TODO: Add text file point loader
|
||||
//poly
|
||||
//poly2
|
||||
snake
|
||||
}
|
||||
|
||||
@ -79,7 +76,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
|
||||
val yellow = new Color(1f, 1f, 0f)
|
||||
|
||||
if(debug) {
|
||||
val draw = if(drawMap) tesselator.allTrapezoids else tesselator.trapezoids
|
||||
val draw = if(drawMap) tesselator.trapezoidMap else tesselator.trapezoids
|
||||
for(t <- draw) {
|
||||
val polygon = new Polygon()
|
||||
for(v <- t.vertices) {
|
||||
@ -88,32 +85,37 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
|
||||
val lCirc = new Circle(t.leftPoint.x, t.leftPoint.y, 4)
|
||||
g.setColor(blue); g.draw(lCirc); g.fill(lCirc)
|
||||
val rCirc = new Circle(t.rightPoint.x, t.rightPoint.y, 6)
|
||||
g.setColor(yellow); g.draw(rCirc); g.fill(rCirc)
|
||||
//g.setColor(yellow); g.draw(rCirc); g.fill(rCirc)
|
||||
g.setColor(red)
|
||||
g.draw(polygon)
|
||||
}
|
||||
}
|
||||
|
||||
var i = 0
|
||||
for(x <- tesselator.xMonoPoly) {
|
||||
var t = x.triangles
|
||||
var j = 0
|
||||
for(t <- x.triangles) {
|
||||
val triangle = new Polygon()
|
||||
if(!debug) {
|
||||
for(t <- tesselator.triangles) {
|
||||
val triangle = new Polygon
|
||||
t.foreach(p => triangle.addPoint(p.x, p.y))
|
||||
val color = if(i == 0 && j == 3) blue else green
|
||||
g.setColor(color)
|
||||
g.setColor(red)
|
||||
g.draw(triangle)
|
||||
j += 1
|
||||
}
|
||||
i += 1
|
||||
} else {
|
||||
for(mp <- tesselator.monoPolies) {
|
||||
val poly = new Polygon
|
||||
mp.foreach(p => poly.addPoint(p.x, p.y))
|
||||
g.setColor(yellow)
|
||||
g.draw(poly)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override def keyPressed(key:Int, c:Char) {
|
||||
if(key == 1) quit = true
|
||||
if(key == 57) debug = !debug
|
||||
if(c == 'm') drawMap = !drawMap
|
||||
if(c == '1') poly
|
||||
if(c == '2') snake
|
||||
if(c == '3') star
|
||||
}
|
||||
|
||||
// Test #1
|
||||
@ -127,7 +129,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
|
||||
val p5 = new Point(400,300)*scale
|
||||
val p6 = new Point(650,250)*scale
|
||||
|
||||
val segments = new ArrayList[Segment]
|
||||
val segments = new ArrayBuffer[Segment]
|
||||
segments += new Segment(p1, p2)
|
||||
segments += new Segment(p3, p4)
|
||||
segments += new Segment(p1, p3)
|
||||
@ -139,8 +141,8 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
|
||||
tesselator.process
|
||||
}
|
||||
|
||||
def poly2 {
|
||||
|
||||
def star {
|
||||
|
||||
val scale = 1.0f
|
||||
val displace = 0f
|
||||
val p1 = new Point(350,75)*scale+displace
|
||||
@ -154,7 +156,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
|
||||
val p9 = new Point(231,161)*scale+displace
|
||||
val p10 = new Point(321,161)*scale+displace
|
||||
|
||||
val segments = new ArrayList[Segment]
|
||||
val segments = new ArrayBuffer[Segment]
|
||||
segments += new Segment(p1, p2)
|
||||
segments += new Segment(p2, p3)
|
||||
segments += new Segment(p3, p4)
|
||||
@ -187,7 +189,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
|
||||
val p11 = new Point(1,20)*scale+displace
|
||||
val p12 = new Point(1,10)*scale+displace
|
||||
|
||||
val segments = new ArrayList[Segment]
|
||||
val segments = new ArrayBuffer[Segment]
|
||||
segments += new Segment(p1, p2)
|
||||
segments += new Segment(p2, p3)
|
||||
segments += new Segment(p3, p4)
|
||||
|
@ -30,29 +30,30 @@
|
||||
*/
|
||||
package org.poly2tri
|
||||
|
||||
import collection.jcl.ArrayList
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
// Directed Acyclic graph (DAG)
|
||||
// See "Computational Geometry", 3rd edition, by Mark de Berg et al, Chapter 6.2
|
||||
|
||||
class QueryGraph(var head: Node) {
|
||||
|
||||
def locate(s: Segment): Trapezoid = {
|
||||
val sink = head.locate(s)
|
||||
return sink.trapezoid
|
||||
}
|
||||
def locate(s: Segment) = head.locate(s).trapezoid
|
||||
|
||||
def followSegment(s: Segment) = {
|
||||
val trapezoids = new ArrayList[Trapezoid]
|
||||
val trapezoids = new ArrayBuffer[Trapezoid]
|
||||
trapezoids += locate(s)
|
||||
var j = 0
|
||||
while(s.q.x > trapezoids(j).rightPoint.x) {
|
||||
if(s > trapezoids(j).rightPoint) {
|
||||
trapezoids += trapezoids(j).upperRight
|
||||
} else {
|
||||
trapezoids += trapezoids(j).lowerRight
|
||||
}
|
||||
j += 1
|
||||
try {
|
||||
while(s.q.x > trapezoids(j).rightPoint.x) {
|
||||
if(s > trapezoids(j).rightPoint) {
|
||||
trapezoids += trapezoids(j).upperRight
|
||||
} else {
|
||||
trapezoids += trapezoids(j).lowerRight
|
||||
}
|
||||
j += 1
|
||||
}
|
||||
} catch {
|
||||
case e => println("# of Trapezoids = " + j)
|
||||
}
|
||||
trapezoids
|
||||
}
|
||||
@ -65,25 +66,25 @@ class QueryGraph(var head: Node) {
|
||||
}
|
||||
}
|
||||
|
||||
def case1(sink: Sink, s: Segment, tList: ArrayList[Trapezoid]) {
|
||||
def case1(sink: Sink, s: Segment, tList: ArrayBuffer[Trapezoid]) {
|
||||
val yNode = new YNode(s, Sink.init(tList(1)), Sink.init(tList(2)))
|
||||
val qNode = new XNode(new Point(s.q.x, s.q.y, s), yNode, Sink.init(tList(3)))
|
||||
val pNode = new XNode(new Point(s.p.x, s.p.y, s), Sink.init(tList(0)), qNode)
|
||||
replace(sink, pNode)
|
||||
}
|
||||
|
||||
def case2(sink: Sink, s: Segment, tList: ArrayList[Trapezoid]) {
|
||||
def case2(sink: Sink, s: Segment, tList: ArrayBuffer[Trapezoid]) {
|
||||
val yNode = new YNode(s, Sink.init(tList(1)), Sink.init(tList(2)))
|
||||
val pNode = new XNode(new Point(s.p.x, s.p.y, s), Sink.init(tList(0)), yNode)
|
||||
replace(sink, pNode)
|
||||
}
|
||||
|
||||
def case3(sink: Sink, s: Segment, tList: ArrayList[Trapezoid]) {
|
||||
def case3(sink: Sink, s: Segment, tList: ArrayBuffer[Trapezoid]) {
|
||||
val yNode = new YNode(s, Sink.init(tList(0)), Sink.init(tList(1)))
|
||||
replace(sink, yNode)
|
||||
}
|
||||
|
||||
def case4(sink: Sink, s: Segment, tList: ArrayList[Trapezoid]) {
|
||||
def case4(sink: Sink, s: Segment, tList: ArrayBuffer[Trapezoid]) {
|
||||
val yNode = new YNode(s, Sink.init(tList(0)), Sink.init(tList(1)))
|
||||
if(s.left != null) {
|
||||
val pNode = new XNode(new Point(s.p.x, s.p.y, s), Sink.init(s.left), yNode)
|
||||
|
@ -30,8 +30,7 @@
|
||||
*/
|
||||
package org.poly2tri
|
||||
|
||||
import collection.jcl.ArrayList
|
||||
import scala.collection.mutable.{Map, HashSet}
|
||||
import scala.collection.mutable.{Map, HashSet, ArrayBuffer}
|
||||
|
||||
// See "Computational Geometry", 3rd edition, by Mark de Berg et al, Chapter 6.2
|
||||
|
||||
@ -68,7 +67,7 @@ class TrapezoidalMap {
|
||||
|
||||
assert(s.p.x != s.q.x)
|
||||
|
||||
val trapezoids = new ArrayList[Trapezoid]
|
||||
val trapezoids = new ArrayBuffer[Trapezoid]
|
||||
trapezoids += new Trapezoid(t.leftPoint, s.p, t.top, t.bottom)
|
||||
trapezoids += new Trapezoid(s.p, s.q, t.top, s)
|
||||
trapezoids += new Trapezoid(s.p, s.q, s, t.bottom)
|
||||
@ -92,7 +91,7 @@ class TrapezoidalMap {
|
||||
|
||||
val rp = if(s.q.x == t.rightPoint.x) s.q else t.rightPoint
|
||||
|
||||
val trapezoids = new ArrayList[Trapezoid]
|
||||
val trapezoids = new ArrayBuffer[Trapezoid]
|
||||
trapezoids += new Trapezoid(t.leftPoint, s.p, t.top, t.bottom)
|
||||
trapezoids += new Trapezoid(s.p, rp, t.top, s)
|
||||
trapezoids += new Trapezoid(s.p, rp, s, t.bottom)
|
||||
@ -121,7 +120,7 @@ class TrapezoidalMap {
|
||||
val topCross = (tCross == t.top)
|
||||
val bottomCross = (bCross == t.bottom)
|
||||
|
||||
val trapezoids = new ArrayList[Trapezoid]
|
||||
val trapezoids = new ArrayBuffer[Trapezoid]
|
||||
trapezoids += {if(topCross) t.upperLeft else new Trapezoid(lp, rp, t.top, s)}
|
||||
trapezoids += {if(bottomCross) t.lowerLeft else new Trapezoid(lp, rp, s, t.bottom)}
|
||||
|
||||
@ -159,7 +158,7 @@ class TrapezoidalMap {
|
||||
val topCross = (tCross == t.top)
|
||||
val bottomCross = (bCross == t.bottom)
|
||||
|
||||
val trapezoids = new ArrayList[Trapezoid]
|
||||
val trapezoids = new ArrayBuffer[Trapezoid]
|
||||
trapezoids += {if(topCross) t.upperLeft else new Trapezoid(lp, s.q, t.top, s)}
|
||||
trapezoids += {if(bottomCross) t.lowerLeft else new Trapezoid(lp, s.q, s, t.bottom)}
|
||||
trapezoids += new Trapezoid(s.q, t.rightPoint, t.top, t.bottom)
|
||||
@ -189,7 +188,7 @@ class TrapezoidalMap {
|
||||
}
|
||||
|
||||
// Create an AABB around segments
|
||||
def boundingBox(segments: ArrayList[Segment]): Trapezoid = {
|
||||
def boundingBox(segments: ArrayBuffer[Segment]): Trapezoid = {
|
||||
|
||||
var max = segments(0).p + margin
|
||||
var min = segments(0).q + margin
|
||||
|
@ -30,17 +30,16 @@
|
||||
*/
|
||||
package org.poly2tri
|
||||
|
||||
import collection.jcl.ArrayList
|
||||
import scala.collection.mutable.{HashSet, Map, Stack, ListBuffer}
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
// Based on Raimund Seidel's paper "A simple and fast incremental randomized
|
||||
// algorithm for computing trapezoidal decompositions and for triangulating polygons"
|
||||
class Triangulator(var segments: ArrayList[Segment]) {
|
||||
class Triangulator(var segments: ArrayBuffer[Segment]) {
|
||||
|
||||
// Trapezoid decomposition list
|
||||
var trapezoids : ArrayList[Trapezoid] = null
|
||||
var trapezoids : ArrayBuffer[Trapezoid] = null
|
||||
// Triangle decomposition list
|
||||
// var triangles: ArrayList[Triangle] = null
|
||||
var triangles = new ArrayBuffer[Array[Point]]
|
||||
|
||||
// Build the trapezoidal map and query graph
|
||||
def process {
|
||||
@ -50,7 +49,7 @@ class Triangulator(var segments: ArrayList[Segment]) {
|
||||
// Remove trapezoids from trapezoidal Map
|
||||
traps.foreach(trapezoidalMap.remove)
|
||||
for(t <- traps) {
|
||||
var tList: ArrayList[Trapezoid] = null
|
||||
var tList: ArrayBuffer[Trapezoid] = null
|
||||
val containsP = t.contains(s.p)
|
||||
val containsQ = t.contains(s.q)
|
||||
if(containsP && containsQ) {
|
||||
@ -77,39 +76,48 @@ class Triangulator(var segments: ArrayList[Segment]) {
|
||||
}
|
||||
trapezoids = trim
|
||||
createMountains
|
||||
|
||||
// Extract all the triangles into a single list
|
||||
for(i <- 0 until xMonoPoly.size)
|
||||
for(t <- xMonoPoly(i).triangles)
|
||||
triangles += t
|
||||
}
|
||||
|
||||
def allTrapezoids = trapezoidalMap.map
|
||||
// For debugging
|
||||
def trapezoidMap = trapezoidalMap.map
|
||||
// Monotone polygons - these are monotone mountains
|
||||
def monoPolies: ArrayBuffer[ArrayBuffer[Point]] = {
|
||||
val polies = new ArrayBuffer[ArrayBuffer[Point]]
|
||||
for(i <- 0 until xMonoPoly.size)
|
||||
polies += xMonoPoly(i).monoPoly
|
||||
return polies
|
||||
}
|
||||
|
||||
// Initialize trapezoidal map and query structure
|
||||
private val trapezoidalMap = new TrapezoidalMap
|
||||
private val boundingBox = trapezoidalMap.boundingBox(segments)
|
||||
private val queryGraph = new QueryGraph(new Sink(boundingBox))
|
||||
|
||||
val xMonoPoly = new ArrayList[MonotoneMountain]
|
||||
private val xMonoPoly = new ArrayBuffer[MonotoneMountain]
|
||||
|
||||
segments = orderSegments
|
||||
|
||||
// Build a list of x-monotone mountains
|
||||
private def createMountains {
|
||||
for(s <- segments) {
|
||||
if(s.mPoints.size > 0) {
|
||||
val mountain = new MonotoneMountain
|
||||
val k = Util.msort((x: Point, y: Point) => x < y)(s.mPoints.toList)
|
||||
val points = s.p :: k ::: List(s.q)
|
||||
points.foreach(p => mountain += p.clone)
|
||||
if(mountain.size > 2) {
|
||||
mountain.triangulate
|
||||
//mountain.monoPoly
|
||||
xMonoPoly += mountain
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Trim off the extraneous trapezoids surrounding the polygon
|
||||
private def trim = {
|
||||
val traps = new ArrayList[Trapezoid]
|
||||
val traps = new ArrayBuffer[Trapezoid]
|
||||
// Mark outside trapezoids
|
||||
for(t <- trapezoidalMap.map) {
|
||||
if(t.top == boundingBox.top || t.bottom == boundingBox.bottom) {
|
||||
@ -127,7 +135,7 @@ class Triangulator(var segments: ArrayList[Segment]) {
|
||||
|
||||
private def orderSegments = {
|
||||
// Ignore vertical segments!
|
||||
val segs = new ArrayList[Segment]
|
||||
val segs = new ArrayBuffer[Segment]
|
||||
for(s <- segments) {
|
||||
// Point p must be to the left of point q
|
||||
if(s.p.x > s.q.x) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
package org.poly2tri
|
||||
|
||||
import collection.jcl.ArrayList
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
object Util {
|
||||
|
||||
@ -30,7 +30,7 @@ object Random extends scala.util.Random {
|
||||
* @param seq the sequence to shuffle
|
||||
* @return the shuffled sequence
|
||||
*/
|
||||
def shuffle[T](buf: ArrayList[T]): ArrayList[T] = {
|
||||
def shuffle[T](buf: ArrayBuffer[T]): ArrayBuffer[T] = {
|
||||
// It would be better if this preserved the shape of its container, but I have
|
||||
// again been defeated by the lack of higher-kinded type inference. I can
|
||||
// only make it work that way if it's called like
|
||||
|
Loading…
Reference in New Issue
Block a user