diff --git a/src/org/poly2tri/MonoToneMountain.scala b/src/org/poly2tri/MonoToneMountain.scala index fc0e820..2aec0b8 100644 --- a/src/org/poly2tri/MonoToneMountain.scala +++ b/src/org/poly2tri/MonoToneMountain.scala @@ -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 { diff --git a/src/org/poly2tri/Poly2Tri.scala b/src/org/poly2tri/Poly2Tri.scala index 6f47c9b..3cef552 100644 --- a/src/org/poly2tri/Poly2Tri.scala +++ b/src/org/poly2tri/Poly2Tri.scala @@ -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) diff --git a/src/org/poly2tri/QueryGraph.scala b/src/org/poly2tri/QueryGraph.scala index 8c3b922..dd2e127 100644 --- a/src/org/poly2tri/QueryGraph.scala +++ b/src/org/poly2tri/QueryGraph.scala @@ -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) diff --git a/src/org/poly2tri/TrapezoidalMap.scala b/src/org/poly2tri/TrapezoidalMap.scala index 43d5ec4..7af7bb3 100644 --- a/src/org/poly2tri/TrapezoidalMap.scala +++ b/src/org/poly2tri/TrapezoidalMap.scala @@ -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 diff --git a/src/org/poly2tri/Triangulator.scala b/src/org/poly2tri/Triangulator.scala index 547c5de..5715de8 100644 --- a/src/org/poly2tri/Triangulator.scala +++ b/src/org/poly2tri/Triangulator.scala @@ -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) { diff --git a/src/org/poly2tri/Util.scala b/src/org/poly2tri/Util.scala index 4a8c089..5696dd4 100644 --- a/src/org/poly2tri/Util.scala +++ b/src/org/poly2tri/Util.scala @@ -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