From 2752b9b021c32613b78df79afa4e0f1c4c1b8a3c Mon Sep 17 00:00:00 2001 From: zzzrrr Date: Wed, 15 Jul 2009 00:21:16 -0400 Subject: [PATCH] bug hunting --- src/org/poly2tri/MonoToneMountain.scala | 122 ++++++++++++------------ src/org/poly2tri/Poly2Tri.scala | 47 ++++++++- src/org/poly2tri/Segment.scala | 3 +- src/org/poly2tri/Sink.scala | 1 - src/org/poly2tri/Trapezoid.scala | 9 +- src/org/poly2tri/Triangulator.scala | 12 +-- src/org/poly2tri/Util.scala | 17 +++- 7 files changed, 131 insertions(+), 80 deletions(-) diff --git a/src/org/poly2tri/MonoToneMountain.scala b/src/org/poly2tri/MonoToneMountain.scala index aa0cecd..fc0e820 100644 --- a/src/org/poly2tri/MonoToneMountain.scala +++ b/src/org/poly2tri/MonoToneMountain.scala @@ -32,6 +32,7 @@ package org.poly2tri import scala.collection.mutable.{ArrayBuffer, Queue} +// Doubly linked list class MonotoneMountain { var tail, head: Point = null @@ -40,7 +41,7 @@ class MonotoneMountain { val convexPoints = new Queue[Point] val triangles = new ArrayBuffer[Array[Point]] - // Append + // Append a point to the list def +=(point: Point) { size match { case 0 => @@ -57,8 +58,8 @@ class MonotoneMountain { size += 1 } - // Remove - private def remove(point: Point) { + // Remove a point from the list + def remove(point: Point) { val next = point.next val prev = point.prev point.prev.next = next @@ -66,16 +67,66 @@ class MonotoneMountain { size -= 1 } - // Determines if the inslide angle between edge v2-v3 and edge v2-v1 is convex (< PI) - private def angle(v1: Point, v2: Point, v3: Point) = { - val a = (v2 - v1) - val b = (v2 - v3) - val angle = Math.atan2(b.y,b.x).toFloat - Math.atan2(a.y,a.x).toFloat - angle + // Partition a x-monotone mountain into triangles O(n) + // See "Computational Geometry in C", 2nd edition, by Joseph O'Rourke, page 52 + def triangulate { + + if(size == 3) { + lastTriangle + } else { + // Initialize internal angles at each nonbase vertex + var p = head.next + while(p != tail) { + // Link strictly convex vertices into a list + if(convex(p)) convexPoints.enqueue(p) + p = p.next + } + + while(!convexPoints.isEmpty) { + + val ear = convexPoints.dequeue + val a = ear.prev.clone + val b = ear + val c = ear.next.clone + 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(size == 3)lastTriangle + } + } + + // Return the monotone polygon + def monoPoly: Array[Point] = { + val poly = new Array[Point](size) + var i = 0 + var p = head + while(p != null) { + poly(i) = 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 + if(p.y >= head.y) { + (angle < 0) + } else { + !(angle < 0) + } } - def lastTriangle = { - assert(size == 3, "Number of points = " + size) + private def lastTriangle { val triangle = new Array[Point](3) var i = 0 var p = head @@ -86,53 +137,4 @@ class MonotoneMountain { } triangles += triangle } - - // Partition a x-monotone mountain into triangles o(n) - // See "Computational Geometry in C", 2nd edition, by Joseph O'Rourke, page 52 - def triangulate { - if(size == 3) { - lastTriangle - } else { - // Initialize internal angles at each nonbase vertex - var p = head.next - while(p != tail) { - p.angle = Math.abs(angle(p.prev, p, p.next)) - println("angle = " + p.angle) - // Link strictly convex vertices into a list - if(p.angle >= 0 && p.angle <= Math.Pi) convexPoints.enqueue(p) - p = p.next - } - - while(!convexPoints.isEmpty) { - val ear = convexPoints.dequeue - val a = ear.prev.clone - val b = ear - val c = ear.next.clone - val triangle = Array(a, b, c) - triangles += triangle - - // Remove ear, update angles and convex list - remove(ear) - a.angle -= ear.angle - if(a.angle > 0 && a != head && a != tail) convexPoints.enqueue(a) - c.angle -= ear.angle - if(c.angle > 0 && c != head && c != tail) convexPoints.enqueue(c) - } - - if(size > 2)lastTriangle - } - } - - def monoPoly { - val triangle = new Array[Point](size) - var i = 0 - var p = head - while(p != null) { - triangle(i) = p - p = p.next - i += 1 - } - triangles += triangle - println(size) - } } diff --git a/src/org/poly2tri/Poly2Tri.scala b/src/org/poly2tri/Poly2Tri.scala index 68318f3..6f47c9b 100644 --- a/src/org/poly2tri/Poly2Tri.scala +++ b/src/org/poly2tri/Poly2Tri.scala @@ -61,8 +61,10 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") { var drawMap = false def init(container: GameContainer) { - testTesselator - //snake + // TODO: Add text file point loader + //poly + //poly2 + snake } def update(gc: GameContainer, delta: Int) { @@ -92,14 +94,19 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") { } } + var i = 0 for(x <- tesselator.xMonoPoly) { var t = x.triangles + var j = 0 for(t <- x.triangles) { val triangle = new Polygon() t.foreach(p => triangle.addPoint(p.x, p.y)) - g.setColor(green) + val color = if(i == 0 && j == 3) blue else green + g.setColor(color) g.draw(triangle) + j += 1 } + i += 1 } } @@ -109,7 +116,8 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") { if(c == 'm') drawMap = !drawMap } - def testTesselator { + // Test #1 + def poly { val scale = 1.0f val p1 = new Point(100,300)*scale @@ -131,6 +139,37 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") { tesselator.process } + def poly2 { + + val scale = 1.0f + val displace = 0f + val p1 = new Point(350,75)*scale+displace + val p2 = new Point(379,161)*scale+displace + val p3 = new Point(469,161)*scale+displace + val p4 = new Point(397,215)*scale+displace + val p5 = new Point(423,301)*scale+displace + val p6 = new Point(350,250)*scale+displace + val p7 = new Point(277,301)*scale+displace + val p8 = new Point(303,215)*scale+displace + val p9 = new Point(231,161)*scale+displace + val p10 = new Point(321,161)*scale+displace + + val segments = new ArrayList[Segment] + segments += new Segment(p1, p2) + segments += new Segment(p2, p3) + segments += new Segment(p3, p4) + segments += new Segment(p4, p5) + segments += new Segment(p5, 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, p1) + tesselator = new Triangulator(segments) + tesselator.process + } + + // Test #2 def snake { val scale = 10.0f diff --git a/src/org/poly2tri/Segment.scala b/src/org/poly2tri/Segment.scala index 8263b84..f99e6aa 100644 --- a/src/org/poly2tri/Segment.scala +++ b/src/org/poly2tri/Segment.scala @@ -40,7 +40,8 @@ class Segment(var p: Point, var q: Point) { var above, below, left: Trapezoid = null // Montone mountain points - val mPoints = HashSet(p, q) + // Use a HashSet to avoid repeats + val mPoints = HashSet.empty[Point] // Equation of a line: y = m*x + b // Slope of the line (m) diff --git a/src/org/poly2tri/Sink.scala b/src/org/poly2tri/Sink.scala index 8e7cf79..805f377 100644 --- a/src/org/poly2tri/Sink.scala +++ b/src/org/poly2tri/Sink.scala @@ -39,7 +39,6 @@ object Sink { new Sink(trapezoid) } } - } class Sink(val trapezoid: Trapezoid) extends Node(null, null) { diff --git a/src/org/poly2tri/Trapezoid.scala b/src/org/poly2tri/Trapezoid.scala index 8fbb191..c179d28 100644 --- a/src/org/poly2tri/Trapezoid.scala +++ b/src/org/poly2tri/Trapezoid.scala @@ -82,11 +82,10 @@ class Trapezoid(val leftPoint: Point, var rightPoint: Point, val top: Segment, v } // Add points to monotone mountain - // Use HashSet to aboid repeats def mark { - bottom.mPoints += leftPoint - bottom.mPoints += rightPoint - top.mPoints += leftPoint - top.mPoints += rightPoint + if(leftPoint != bottom.p) bottom.mPoints += leftPoint + if(rightPoint != bottom.q) bottom.mPoints += rightPoint + if(leftPoint != top.p) top.mPoints += leftPoint + if(rightPoint != top.q) top.mPoints += rightPoint } } diff --git a/src/org/poly2tri/Triangulator.scala b/src/org/poly2tri/Triangulator.scala index 97832e8..547c5de 100644 --- a/src/org/poly2tri/Triangulator.scala +++ b/src/org/poly2tri/Triangulator.scala @@ -93,15 +93,11 @@ class Triangulator(var segments: ArrayList[Segment]) { // Build a list of x-monotone mountains private def createMountains { for(s <- segments) { - println(s.mPoints.size) - if(s.mPoints.size > 2) { + if(s.mPoints.size > 0) { val mountain = new MonotoneMountain - // TODO: Optomize sort? The number of points should be - // fairly small => insertion or merge? - val points = s.mPoints.toList - for(p <- points.sort((e1,e2) => e1 < e2)) { - mountain += p.clone - } + 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 diff --git a/src/org/poly2tri/Util.scala b/src/org/poly2tri/Util.scala index 68794fd..4a8c089 100644 --- a/src/org/poly2tri/Util.scala +++ b/src/org/poly2tri/Util.scala @@ -3,6 +3,21 @@ package org.poly2tri import collection.jcl.ArrayList +object Util { + + // From "Scala By Example," by Martin Odersky + def msort[A](less: (A, A) => Boolean)(xs: List[A]): List[A] = { + def merge(xs1: List[A], xs2: List[A]): List[A] = + if (xs1.isEmpty) xs2 + else if (xs2.isEmpty) xs1 + else if (less(xs1.head, xs2.head)) xs1.head :: merge(xs1.tail, xs2) + else xs2.head :: merge(xs1, xs2.tail) + val n = xs.length/2 + if (n == 0) xs + else merge(msort(less)(xs take n), msort(less)(xs drop n)) + } +} + /** The object Random offers a default implementation * of scala.util.Random and random-related convenience methods. * @@ -33,5 +48,5 @@ object Random extends scala.util.Random { swap(n - 1, k) } buf - } + } }