From 9cdc372690339884636abc59aefd683c5c3b0029 Mon Sep 17 00:00:00 2001 From: zzzzrrr Date: Thu, 16 Jul 2009 14:14:09 -0400 Subject: [PATCH] fixed angle init bug --- data/15Point.svg | 72 ++++++++++++++++++++ src/org/poly2tri/MonoToneMountain.scala | 29 +++++---- src/org/poly2tri/Point.scala | 23 +++---- src/org/poly2tri/Poly2Tri.scala | 87 +++++++++++++++---------- src/org/poly2tri/QueryGraph.scala | 9 +-- src/org/poly2tri/Segment.scala | 5 +- src/org/poly2tri/Trapezoid.scala | 18 +++-- src/org/poly2tri/TrapezoidalMap.scala | 35 ++++++---- src/org/poly2tri/Triangulator.scala | 48 +++++++------- 9 files changed, 216 insertions(+), 110 deletions(-) create mode 100644 data/15Point.svg diff --git a/data/15Point.svg b/data/15Point.svg new file mode 100644 index 0000000..8016751 --- /dev/null +++ b/data/15Point.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/src/org/poly2tri/MonoToneMountain.scala b/src/org/poly2tri/MonoToneMountain.scala index c93346d..f3640d1 100644 --- a/src/org/poly2tri/MonoToneMountain.scala +++ b/src/org/poly2tri/MonoToneMountain.scala @@ -46,7 +46,7 @@ class MonotoneMountain { // Used to track which side of the line we are on var positive = false // Almost Pi! - val SLOP = 3.1 + val PI_SLOP = 3.1 // Append a point to the list def +=(point: Point) { @@ -78,6 +78,8 @@ class MonotoneMountain { // See "Computational Geometry in C", 2nd edition, by Joseph O'Rourke, page 52 def triangulate { + // Establish the proper sign + positive = initAngle // create monotone polygon - for dubug purposes genMonoPoly @@ -90,7 +92,7 @@ class MonotoneMountain { while(p != tail) { val a = angle(p) // If the point is almost colinear with it's neighbor, remove it! - if(a >= SLOP || a <= -SLOP) + if(a >= PI_SLOP || a <= -PI_SLOP) remove(p) else if(convex(p)) convexPoints.enqueue(p) @@ -100,17 +102,17 @@ class MonotoneMountain { while(!convexPoints.isEmpty) { val ear = convexPoints.dequeue - val a = ear.prev.clone - val b = ear.clone - val c = ear.next.clone + val a = ear.prev + val b = ear + val c = ear.next val triangle = Array(a, b, c) triangles += triangle // Remove ear, update angles and convex list remove(ear) - if(a.prev != null && convex(a)) convexPoints.enqueue(a); - if(c.prev != null && convex(c)) convexPoints.enqueue(c) + if(valid(a)) convexPoints.enqueue(a); + if(valid(c)) convexPoints.enqueue(c) } assert(size <= 3, "Triangulation bug") @@ -118,6 +120,8 @@ class MonotoneMountain { } } + def valid(p: Point) = (p.prev != null && p.next != null && convex(p)) + // Create the monotone polygon private def genMonoPoly { var p = head @@ -133,12 +137,15 @@ class MonotoneMountain { Math.atan2(a cross b, a dot b) } + def initAngle = { + val a = (head.next - head) + val b = (tail - head) + (Math.atan2(a cross b, a dot b) >= 0) + } + // Determines if the inslide angle is convex or reflex private def convex(p: Point) = { - val cvx = (angle(p) >= 0) - if(p.prev == head) - positive = cvx - if(positive != cvx) + if(positive != (angle(p) >= 0)) false else true diff --git a/src/org/poly2tri/Point.scala b/src/org/poly2tri/Point.scala index 438c3d7..9eadc58 100644 --- a/src/org/poly2tri/Point.scala +++ b/src/org/poly2tri/Point.scala @@ -30,21 +30,17 @@ */ package org.poly2tri -class Point(val x: Float, val y: Float, var segment: Segment) { - - def this(x: Float, y: Float) = this(x, y, null) +case class Point(val x: Float, val y: Float) { // Pointers to next and previous points in Monontone Mountain var next, prev: Point = null - /* Internal angle */ - var angle = 0f - - def -(p: Point) = new Point(x - p.x, y - p.y) - def +(p: Point) = new Point(x + p.x, y + p.y) - def +(f: Float) = new Point(x + f, y + f) - def *(f: Float) = new Point(x * f, y * f) - def /(a: Float) = new Point(x / a, y / a) + def -(p: Point) = Point(x - p.x, y - p.y) + def +(p: Point) = Point(x + p.x, y + p.y) + def +(f: Float) = Point(x + f, y + f) + def -(f: Float) = Point(x - f, y - f) + def *(f: Float) = Point(x * f, y * f) + def /(a: Float) = Point(x / a, y / a) def cross(p: Point) = x * p.y - y * p.x def dot(p: Point) = x * p.x + y * p.y def length = Math.sqrt(x * x + y * y).toFloat @@ -52,12 +48,11 @@ class Point(val x: Float, val y: Float, var segment: Segment) { def <(p: Point) = { if(p.x == x) - if(y > p.y) true + if(y <= p.y) true else false else (x < p.x) } - - override def clone = new Point(x, y) + override def clone = Point(x, y) } diff --git a/src/org/poly2tri/Poly2Tri.scala b/src/org/poly2tri/Poly2Tri.scala index c5a6d71..e22b704 100644 --- a/src/org/poly2tri/Poly2Tri.scala +++ b/src/org/poly2tri/Poly2Tri.scala @@ -86,7 +86,7 @@ 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, 4) - g.setColor(yellow); g.draw(rCirc); g.fill(rCirc) + //g.setColor(yellow); g.draw(rCirc); g.fill(rCirc) g.setColor(red) g.draw(polygon) } @@ -140,22 +140,41 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") { // Test #1 def poly { - - val scale = 1.0f - val p1 = new Point(100,300)*scale - val p2 = new Point(400,500)*scale - val p3 = new Point(260,200)*scale - val p4 = new Point(600,175)*scale - val p5 = new Point(400,300)*scale - val p6 = new Point(650,250)*scale + + val p1 = Point(400,472) + val p2 = Point(500,392) + val p3 = Point(520,272) + val p4 = Point(460,232) + val p5 = Point(580,212) + val p6 = Point(480,152) + val p7 = Point(360,172) + val p8 = Point(360,52) + val p9 = Point(300,112) + val p10 = Point(200,32) + val p11 = Point(120,92) + val p12 = Point(200,72) + val p13 = Point(340,272) + val p14 = Point(208,212) + val p15 = Point(180,352) + val p16 = Point(300,312) val segments = new ArrayBuffer[Segment] segments += new Segment(p1, p2) + segments += new Segment(p2, p3) segments += new Segment(p3, p4) - segments += new Segment(p1, p3) - segments += new Segment(p5, p2) + segments += new Segment(p4, p5) segments += new Segment(p5, p6) - segments += new Segment(p4, p6) + segments += new Segment(p6, p7) + segments += new Segment(p7, p8) + segments += new Segment(p8, p9) + segments += new Segment(p9, p10) + segments += new Segment(p10, p11) + segments += new Segment(p11, p12) + segments += new Segment(p12, p13) + segments += new Segment(p13, p14) + segments += new Segment(p14, p15) + segments += new Segment(p15, p16) + segments += new Segment(p16, p1) tesselator = new Triangulator(segments) tesselator.process @@ -163,16 +182,16 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") { def star { - val p1 = new Point(350,75) - val p2 = new Point(379,161) - val p3 = new Point(469,161) - val p4 = new Point(397,215) - val p5 = new Point(423,301) - val p6 = new Point(350,250) - val p7 = new Point(277,301) - val p8 = new Point(303,215) - val p9 = new Point(231,161) - val p10 = new Point(321,161) + val p1 = Point(350,75) + val p2 = Point(379,161) + val p3 = Point(469,161) + val p4 = Point(397,215) + val p5 = Point(423,301) + val p6 = Point(350,250) + val p7 = Point(277,301) + val p8 = Point(303,215) + val p9 = Point(231,161) + val p10 = Point(321,161) val segments = new ArrayBuffer[Segment] segments += new Segment(p1, p2) @@ -194,18 +213,18 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") { val scale = 10.0f val displace = 100 - val p1 = new Point(10,1)*scale+displace - val p2 = new Point(20,10)*scale+displace - val p3 = new Point(30,1)*scale+displace - val p4 = new Point(40,10)*scale+displace - val p5 = new Point(50,1)*scale+displace - val p6 = new Point(50,10)*scale+displace - val p7 = new Point(40,20)*scale+displace - val p8 = new Point(30,10)*scale+displace - val p9 = new Point(20,20)*scale+displace - val p10 = new Point(10,10)*scale+displace - val p11 = new Point(1,20)*scale+displace - val p12 = new Point(1,10)*scale+displace + val p1 = Point(10,1)*scale+displace + val p2 = Point(20,10)*scale+displace + val p3 = Point(30,1)*scale+displace + val p4 = Point(40,10)*scale+displace + val p5 = Point(50,1)*scale+displace + val p6 = Point(50,10)*scale+displace + val p7 = Point(40,20)*scale+displace + val p8 = Point(30,10)*scale+displace + val p9 = Point(20,20)*scale+displace + val p10 = Point(10,10)*scale+displace + val p11 = Point(1,20)*scale+displace + val p12 = Point(1,10)*scale+displace val segments = new ArrayBuffer[Segment] segments += new Segment(p1, p2) diff --git a/src/org/poly2tri/QueryGraph.scala b/src/org/poly2tri/QueryGraph.scala index 3a6bc35..172bc88 100644 --- a/src/org/poly2tri/QueryGraph.scala +++ b/src/org/poly2tri/QueryGraph.scala @@ -40,6 +40,7 @@ class QueryGraph(var head: Node) { def locate(s: Segment) = head.locate(s).trapezoid def followSegment(s: Segment) = { + assert(s.p.x < s.q.x) val trapezoids = new ArrayBuffer[Trapezoid] trapezoids += locate(s) var j = 0 @@ -68,14 +69,14 @@ class QueryGraph(var head: Node) { 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) + val qNode = new XNode(s.q, yNode, Sink.init(tList(3))) + val pNode = new XNode(s.p, Sink.init(tList(0)), qNode) replace(sink, pNode) } 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) + val pNode = new XNode(s.p, Sink.init(tList(0)), yNode) replace(sink, pNode) } @@ -86,7 +87,7 @@ class QueryGraph(var head: Node) { def case4(sink: Sink, s: Segment, tList: ArrayBuffer[Trapezoid]) { val yNode = new YNode(s, Sink.init(tList(0)), Sink.init(tList(1))) - val qNode = new XNode(new Point(s.q.x, s.q.y, s), yNode, Sink.init(tList(2))) + val qNode = new XNode(s.q, yNode, Sink.init(tList(2))) replace(sink, qNode) } } diff --git a/src/org/poly2tri/Segment.scala b/src/org/poly2tri/Segment.scala index f99e6aa..281c536 100644 --- a/src/org/poly2tri/Segment.scala +++ b/src/org/poly2tri/Segment.scala @@ -30,14 +30,13 @@ */ package org.poly2tri -import collection.jcl.ArrayList import scala.collection.mutable.HashSet // Represents a simple polygon's edge class Segment(var p: Point, var q: Point) { - // Pointer used for building trapezoidal map - var above, below, left: Trapezoid = null + // Pointers used for building trapezoidal map + var above, below: Trapezoid = null // Montone mountain points // Use a HashSet to avoid repeats diff --git a/src/org/poly2tri/Trapezoid.scala b/src/org/poly2tri/Trapezoid.scala index 5e6b1b8..96a1894 100644 --- a/src/org/poly2tri/Trapezoid.scala +++ b/src/org/poly2tri/Trapezoid.scala @@ -33,7 +33,7 @@ package org.poly2tri class Trapezoid(val leftPoint: Point, var rightPoint: Point, val top: Segment, val bottom: Segment) { var sink: Sink = null - var outside = false + var inside = true // Neighbor pointers var upperLeft: Trapezoid = null @@ -58,11 +58,15 @@ class Trapezoid(val leftPoint: Point, var rightPoint: Point, val top: Segment, v lowerRight = lr; if(lr != null) lr.lowerLeft = this } - def markNeighbors { - if(upperLeft != null) upperLeft.outside = true - if(lowerLeft != null) lowerLeft.outside = true - if(upperRight != null) upperRight.outside = true - if(lowerRight != null) lowerRight.outside = true + // Recursively trim neightbors + def trimNeighbors { + if(inside) { + inside = false + if(upperLeft != null) {upperLeft.trimNeighbors} + if(lowerLeft != null) {lowerLeft.trimNeighbors} + if(upperRight != null) {upperRight.trimNeighbors} + if(lowerRight != null) {lowerRight.trimNeighbors} + } } // Determines if this point lies inside the trapezoid @@ -85,7 +89,7 @@ class Trapezoid(val leftPoint: Point, var rightPoint: Point, val top: Segment, v } // Add points to monotone mountain - def mark { + def addPoints { if(leftPoint != bottom.p) bottom.mPoints += leftPoint if(rightPoint != bottom.q) bottom.mPoints += rightPoint if(leftPoint != top.p) top.mPoints += leftPoint diff --git a/src/org/poly2tri/TrapezoidalMap.scala b/src/org/poly2tri/TrapezoidalMap.scala index e74686c..17915a8 100644 --- a/src/org/poly2tri/TrapezoidalMap.scala +++ b/src/org/poly2tri/TrapezoidalMap.scala @@ -39,7 +39,7 @@ class TrapezoidalMap { // Trapezoid associated array val map = HashSet.empty[Trapezoid] // AABB margin - var margin = 20f + var margin = 50f // Bottom segment that spans multiple trapezoids private var bCross: Segment = null @@ -48,11 +48,13 @@ class TrapezoidalMap { // Add a trapezoid to the map def add(t: Trapezoid) { + assert(t != null) map += t } // Remove a trapezoid from the map def remove(t: Trapezoid) { + assert(t != null) map -=t } @@ -66,6 +68,7 @@ class TrapezoidalMap { def case1(t: Trapezoid, s: Segment) = { assert(s.p.x != s.q.x) + assert(s.p.x < s.q.x) val trapezoids = new ArrayBuffer[Trapezoid] trapezoids += new Trapezoid(t.leftPoint, s.p, t.top, t.bottom) @@ -88,6 +91,8 @@ class TrapezoidalMap { // break trapezoid into 3 smaller trapezoids def case2(t: Trapezoid, s: Segment) = { + assert(s.p.x < s.q.x) + val rp = if(s.q.x == t.rightPoint.x) s.q else t.rightPoint val trapezoids = new ArrayBuffer[Trapezoid] @@ -111,6 +116,7 @@ class TrapezoidalMap { def case3(t: Trapezoid, s: Segment) = { assert(s.p.x != s.q.x) + assert(s.p.x < s.q.x) val lp = if(s.p.x == t.leftPoint.x) s.p else t.leftPoint val rp = if(s.q.x == t.rightPoint.x) s.q else t.rightPoint @@ -150,6 +156,8 @@ class TrapezoidalMap { // break trapezoid into 3 smaller trapezoids def case4(t: Trapezoid, s: Segment) = { + assert(s.p.x < s.q.x) + val lp = if(s.p.x == t.leftPoint.x) s.p else t.leftPoint val topCross = (tCross == t.top) @@ -176,6 +184,9 @@ class TrapezoidalMap { trapezoids(2).update(trapezoids(0), trapezoids(1), t.upperRight, t.lowerRight) + s.above = trapezoids(0) + s.below = trapezoids(1) + trapezoids } @@ -183,21 +194,21 @@ class TrapezoidalMap { def boundingBox(segments: ArrayBuffer[Segment]): Trapezoid = { var max = segments(0).p + margin - var min = segments(0).q + margin + var min = segments(0).q - margin for(s <- segments) { - if(s.p.x > max.x) max = new Point(s.p.x + margin, max.y) - if(s.p.y > max.y) max = new Point(max.x, s.p.y + margin) - if(s.q.x > max.x) max = new Point(s.q.x+margin, max.y) - if(s.q.y > max.y) max = new Point(max.x, s.q.y+margin) - if(s.p.x < min.x) min = new Point(s.p.x-margin, min.y) - if(s.p.y < min.y) min = new Point(min.x, s.p.y-margin) - if(s.q.x < min.x) min = new Point(s.q.x-margin, min.y) - if(s.q.y < min.y) min = new Point(min.x, s.q.y-margin) + if(s.p.x > max.x) max = Point(s.p.x + margin, max.y) + if(s.p.y > max.y) max = Point(max.x, s.p.y + margin) + if(s.q.x > max.x) max = Point(s.q.x+margin, max.y) + if(s.q.y > max.y) max = Point(max.x, s.q.y+margin) + if(s.p.x < min.x) min = Point(s.p.x-margin, min.y) + if(s.p.y < min.y) min = Point(min.x, s.p.y-margin) + if(s.q.x < min.x) min = Point(s.q.x-margin, min.y) + if(s.q.y < min.y) min = Point(min.x, s.q.y-margin) } - val top = new Segment(new Point(min.x, max.y), new Point(max.x, max.y)) - val bottom = new Segment(new Point(min.x, min.y), new Point(max.x, min.y)) + val top = new Segment(Point(min.x, max.y), Point(max.x, max.y)) + val bottom = new Segment(Point(min.x, min.y), Point(max.x, min.y)) val left = bottom.p val right = top.q diff --git a/src/org/poly2tri/Triangulator.scala b/src/org/poly2tri/Triangulator.scala index 07248fc..6dfdb0d 100644 --- a/src/org/poly2tri/Triangulator.scala +++ b/src/org/poly2tri/Triangulator.scala @@ -70,12 +70,22 @@ class Triangulator(var segments: ArrayBuffer[Segment]) { tList = trapezoidalMap.case4(t, s) queryGraph.case4(t.sink, s, tList) } - // Add new trapezoids to the trapezoidal map + // Add new trapezoids to map tList.foreach(trapezoidalMap.add) } trapezoidalMap reset } - trapezoids = trim + + // Mark outside trapezoids + trapezoidalMap.map.foreach(markOutside) + + // Collect interior trapezoids + for(t <- trapezoidalMap.map) + if(t.inside) { + trapezoids += t + t addPoints + } + createMountains // Extract all the triangles into a single list @@ -89,7 +99,7 @@ class Triangulator(var segments: ArrayBuffer[Segment]) { // The trapezoidal map def trapezoidMap = trapezoidalMap.map // Trapezoid decomposition list - var trapezoids : ArrayBuffer[Trapezoid] = null + var trapezoids = new ArrayBuffer[Trapezoid] // Monotone polygons - these are monotone mountains def monoPolies: ArrayBuffer[ArrayBuffer[Point]] = { val polies = new ArrayBuffer[ArrayBuffer[Point]] @@ -101,14 +111,15 @@ class Triangulator(var segments: ArrayBuffer[Segment]) { // 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)) + trapezoidalMap add boundingBox + private val queryGraph = new QueryGraph(Sink.init(boundingBox)) private val xMonoPoly = new ArrayBuffer[MonotoneMountain] // Build a list of x-monotone mountains private def createMountains { for(s <- segments) { val mountain = new MonotoneMountain - val k = Util.msort((x: Point, y: Point) => x < y)(s.mPoints.toList) + val k = Util.msort((p1: Point, p2: Point) => p1 < p2)(s.mPoints.toList) val points = s.p :: k ::: List(s.q) points.foreach(p => mountain += p.clone) if(mountain.size > 2) { @@ -118,22 +129,11 @@ class Triangulator(var segments: ArrayBuffer[Segment]) { } } - // Trim off the extraneous trapezoids surrounding the polygon - private def trim = { - val traps = new ArrayBuffer[Trapezoid] - // Mark outside trapezoids - for(t <- trapezoidalMap.map) { + // Mark the outside trapezoids surrounding the polygon + private def markOutside(t: Trapezoid) { if(t.top == boundingBox.top || t.bottom == boundingBox.bottom) { - t.outside = true - t.markNeighbors + t trimNeighbors } - } - // Collect interior trapezoids - for(t <- trapezoidalMap.map) if(!t.outside) { - traps += t - t.mark - } - traps } private def orderSegments = { @@ -142,14 +142,12 @@ class Triangulator(var segments: ArrayBuffer[Segment]) { for(s <- segments) { // Point p must be to the left of point q if(s.p.x > s.q.x) { - val tmp = s.p - s.p = s.q - s.q = tmp - segs += s + segs += new Segment(s.q.clone, s.p.clone) } else if(s.p.x < s.q.x) - segs += s + segs += new Segment(s.p.clone, s.q.clone) } // This is actually important: See Seidel's paper - Random.shuffle(segs) + //Random.shuffle(segs) + segs } }