diff --git a/data/bird.dat b/data/bird.dat index 0adb5e2..3f76eae 100644 --- a/data/bird.dat +++ b/data/bird.dat @@ -236,9 +236,9 @@ 7.85981 -0.78467 7.79516 -0.409667 7.49774 -0.151043 -7.84688.042924 -8.23481.314479 -8.64861.702414 +7.84688 0.042924 +8.23481 0.314479 +8.64861 0.702414 8.70034 1.09035 8.41585 1.42656 8.11843 1.62053 diff --git a/data/i.18 b/data/i.18 index a464fd1..79ae282 100644 --- a/data/i.18 +++ b/data/i.18 @@ -1,18 +1,18 @@ -0 0 -10 7 -12 3 -20 8 -13 17 -10 12 -12 14 -14 9 -8 10 -6 14 -10 15 -7 18 -0 16 -1 13 -3 15 -5 8 --2 9 -5 5 +0 0 +10 7 +12 3 +20 8 +13 17 +10 12 +12 14 +14 9 +8 10 +6 14 +10 15 +7 18 +0 16 +1 13 +3 15 +5 8 +-2 9 +5 5 diff --git a/data/i.snake b/data/i.snake index 83524fc..94633e7 100644 --- a/data/i.snake +++ b/data/i.snake @@ -1,12 +1,12 @@ -10 0 -20 10 -30 0 -40 10 -50 0 -50 10 -40 20 -30 10 -20 20 -10 10 -0 20 -0 10 +10 0 +20 10 +30 0 +40 10 +50 0 +50 10 +40 20 +30 10 +20 20 +10 10 +0 20 +0 10 diff --git a/data/star.dat b/data/star.dat new file mode 100644 index 0000000..f74f958 --- /dev/null +++ b/data/star.dat @@ -0,0 +1,10 @@ +350 75 +379 161 +469 161 +397 215 +423 301 +350 250 +277 301 +303 215 +231 161 +321 161 diff --git a/data/strange.dat b/data/strange.dat new file mode 100644 index 0000000..9966eb1 --- /dev/null +++ b/data/strange.dat @@ -0,0 +1,16 @@ +400 472 +500 392 +520 272 +460 232 +580 212 +480 152 +360 172 +360 52 +300 112 +200 32 +120 92 +200 72 +340 272 +208 212 +180 352 +300 312 diff --git a/src/org/poly2tri/Poly2Tri.scala b/src/org/poly2tri/Poly2Tri.scala index 5a8a41a..679ef54 100644 --- a/src/org/poly2tri/Poly2Tri.scala +++ b/src/org/poly2tri/Poly2Tri.scala @@ -39,6 +39,7 @@ import scala.io.Source import seidel.Triangulator import shapes.{Segment, Point, Triangle} import earClip.EarClip +import cdt.CDT // TODO: Lots of documentation! @@ -73,6 +74,11 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") { val nazcaMonkey = "data/nazca_monkey.dat" val bird = "data/bird.dat" + val snake = "data/i.snake" + val star = "data/star.dat" + val strange = "data/strange.dat" + val i18 = "data/i.18" + var currentModel = nazcaMonkey def init(container: GameContainer) { @@ -171,9 +177,10 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") { if(c == 'm') drawMap = !drawMap if(c == '1') {currentModel = nazcaMonkey; selectModel} if(c == '2') {currentModel = bird; selectModel} - if(c == '3') {poly; earClipPoly} - if(c == '4') snake - if(c == '5') star + if(c == '3') {currentModel = strange; selectModel} + if(c == '4') {currentModel = snake; selectModel} + if(c == '5') {currentModel = star; selectModel} + if(c == '6') {currentModel = i18; selectModel} if(c == 's') drawSegs = !drawSegs if(c == 'e') {drawEarClip = !drawEarClip; selectModel} if(c == 'h') {hertelMehlhorn = !hertelMehlhorn; selectModel} @@ -185,147 +192,26 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") { loadModel(nazcaMonkey, 4.5f, Point(400, 300), 1500) case "data/bird.dat" => loadModel(bird, 25f, Point(400, 300), 350) + case "data/i.snake" => + loadModel(snake, 10f, Point(600, 300), 10) + case "data/star.dat" => + loadModel(star, -1f, Point(0f, 0f), 10) + case "data/strange.dat" => + loadModel(strange, -1f, Point(0f, 0f), 15) + case "data/i.18" => + loadModel(i18, 15f, Point(500f, 400f), 20) case _ => assert(false) } } - - // Test #1 - def poly { - - 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) - - segments = new ArrayBuffer[Segment] - segments += new Segment(p16, p1) - segments += new Segment(p9, p10) - segments += new Segment(p13, p14) - segments += new Segment(p5, p6) - segments += new Segment(p2, p3) - segments += new Segment(p1, p2) - segments += new Segment(p4, p5) - segments += new Segment(p7, p8) - segments += new Segment(p8, p9) - segments += new Segment(p10, p11) - segments += new Segment(p11, p12) - segments += new Segment(p12, p13) - segments += new Segment(p3, p4) - segments += new Segment(p15, p16) - segments += new Segment(p14, p15) - segments += new Segment(p6, p7) - - tesselator = new Triangulator(segments) - tesselator.buildTriangles = hertelMehlhorn - - val t1 = System.nanoTime - tesselator process - val t2 = System.nanoTime - println - println("**Poly1**") - println("Poly2Tri total (ms) = " + (t2-t1)*1e-6) - - } - - def star { - - 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) - - segments = new ArrayBuffer[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.buildTriangles = hertelMehlhorn - - val t1 = System.nanoTime - tesselator process - val t2 = System.nanoTime - println - println("**Star**") - println("Poly2Tri total (ms) = " + (t2-t1)*1e-6) - } - - // Test #2 - def snake { - - val scale = 10.0f - val displace = 100 - 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 - - segments = new ArrayBuffer[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, p11) - segments += new Segment(p11, p12) - segments += new Segment(p12, p1) - - tesselator = new Triangulator(segments) - tesselator.buildTriangles = hertelMehlhorn - - val t1 = System.nanoTime - tesselator process - val t2 = System.nanoTime - println - println("**Snake**") - println("Poly2Tri total (ms) = " + (t2-t1)*1e-6) - - } - + def loadModel(model: String, scale: Float, center: Point, maxTriangles: Int) { println("*** " + model + " ***") polyX = new ArrayBuffer[Float] polyY = new ArrayBuffer[Float] + val points = new ArrayBuffer[Point] val angle = Math.Pi for (line <- Source.fromFile(model).getLines) { @@ -337,24 +223,20 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") { // Transform the shape polyX += (Math.cos(angle)*x - Math.sin(angle)*y).toFloat * scale + center.x polyY += (Math.sin(angle)*x + Math.cos(angle)*y).toFloat * scale + center.y + points += new Point(polyX.last, polyY.last) + } else { + throw new Exception("Bad input file") } } - + points.foreach(println) segments = new ArrayBuffer[Segment] - - var i = 0 - val numPoints = polyX.size - while(i < polyX.size-2) { - val p1 = new Point(polyX(i), polyY(i)) - val p2 = new Point(polyX(i+1), polyY(i+1)) - segments += new Segment(p1, p2) - i += 1 - } - + for(i <- 0 until polyX.size-1) + segments += new Segment(points(i), points(i+1)) // Connect the end points - val p1 = segments(0).p - val p2 = segments(segments.length-1).q - segments += new Segment(p2, p1) + segments += new Segment(points.first, points.last) + + val cdt = CDT.init(points) + println(cdt.points.size + "," + cdt.segments.size) println("Number of points = " + polyX.size) println @@ -379,8 +261,14 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") { earClipResults = new Array[poly2tri.earClip.Triangle](maxTriangles) for(i <- 0 until earClipResults.size) earClipResults(i) = new poly2tri.earClip.Triangle - val xVerts = polyX.toArray.reverse - val yVerts = polyY.toArray.reverse + + var xVerts = polyX.toArray + var yVerts = polyY.toArray + + if(currentModel != strange) { + xVerts = xVerts.reverse + yVerts = yVerts.reverse + } val t1 = System.nanoTime earClip.triangulatePolygon(xVerts, yVerts, xVerts.size, earClipResults) @@ -391,55 +279,5 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") { println("Number of triangles = " + earClip.numTriangles) } } - - def earClipPoly { - - val polyX = Array(400f, 500f, 520f, 460f, 580f, 480f, 360f, 360f, 300f, 200f, 120f, 200f, 340f, 208f, 180f, 300f) - val polyY = Array(472f, 392f, 272f, 232f, 212f, 152f, 172f, 52f, 112f, 32f, 92f, 72f, 272f, 212f, 352f, 312f) - - val earClipResults = new Array[poly2tri.earClip.Triangle](14) - for(i <- 0 until earClipResults.size) earClipResults(i) = new poly2tri.earClip.Triangle - val t1 = System.nanoTime - earClip.triangulatePolygon(polyX, polyY, polyX.size, earClipResults) - val t2 = System.nanoTime - println("Earclip total (ms) = " + (t2-t1)*1e-6) - } - - def earClipSnake { - - val polyX = Array(200f, 300f, 400f, 500f, 600f, 600f, 500f, 400f, 300f, 200f, 110f, 110f) - val polyY = Array(110f, 200f, 110f, 200f, 110f, 200f, 300f, 200f, 300f, 200f, 300f, 200f) - - val earClipResults = new Array[poly2tri.earClip.Triangle](14) - for(i <- 0 until earClipResults.size) earClipResults(i) = new poly2tri.earClip.Triangle - val t1 = System.nanoTime - earClip.triangulatePolygon(polyX, polyY, polyX.size, earClipResults) - val t2 = System.nanoTime - println("Earclip total (ms) = " + (t2-t1)*1e-6) - } - - def earClipStar { - - 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 polyX = Array(350f, 379f, 469f, 397f, 423f, 350f, 277f, 303f, 231f, 321f) - val polyY = Array(75f, 161f, 161f, 215f, 301f, 250f, 301f,215f, 161f, 161f) - - val earClipResults = new Array[poly2tri.earClip.Triangle](14) - for(i <- 0 until earClipResults.size) earClipResults(i) = new poly2tri.earClip.Triangle - val t1 = System.nanoTime - earClip.triangulatePolygon(polyX, polyY, polyX.size, earClipResults) - val t2 = System.nanoTime - println("Earclip total (ms) = " + (t2-t1)*1e-6) - } - + } \ No newline at end of file diff --git a/src/org/poly2tri/cdt/CDT.scala b/src/org/poly2tri/cdt/CDT.scala index 83f6a70..5774f50 100644 --- a/src/org/poly2tri/cdt/CDT.scala +++ b/src/org/poly2tri/cdt/CDT.scala @@ -40,69 +40,73 @@ import utils.Util * See: Domiter, V. and Žalik, B.(2008)'Sweep-line algorithm for constrained Delaunay triangulation', * International Journal of Geographical Information Science,22:4,449 — 462 */ -class CDT(segments: ArrayBuffer[Segment]) { +object CDT { + + // Inital triangle factor + val ALPHA = 0.3f + + def init(points: ArrayBuffer[Point]): CDT = { + + var xmax, xmin = 0f + var ymax, ymin = 0f + + // Calculate bounds + for(i <- 0 until points.size) { + points(i) = shearTransform(points(i)) + val p = points(i) + if(p.x > xmax) xmax = p.x + if(p.x < xmin) xmin = p.x + if(p.y > ymax) ymax = p.x + if(p.y < ymin) ymin = p.x + } + + val deltaX = ALPHA * (xmax - xmin) + val deltaY = ALPHA * (ymax - ymin) + val p1 = Point(xmin - deltaX, ymin - deltaY) + val p2 = Point(xmax - deltaX, ymin - deltaY) + + val initialTriangle = new Triangle(Array(p2, points(0), p1), null) + val segments = initSegments(points) + val sortedPoints = pointSort(points) + new CDT(sortedPoints, segments, initialTriangle) + } + + // Create segments and connect end points + private def initSegments(points: ArrayBuffer[Point]): List[Segment] = { + var segments = List[Segment]() + for(i <- 0 until points.size-1) + segments = new Segment(points(i), points(i+1)) :: segments + segments = new Segment(points.first, points.last) :: segments + segments + } + + // Insertion sort is one of the fastest algorithms for sorting arrays containing + // fewer than ten elements, or for lists that are already mostly sorted. + // Merge sort: O(n log n) + private def pointSort(pts: ArrayBuffer[Point]): List[Point] = { + if(pts.size < 10) + Util.insertSort((p1: Point, p2: Point) => p1 > p2)(pts).toList + else + Util.msort((p1: Point, p2: Point) => p1 > p2)(pts.toList) + } + + // Prevents any two distinct endpoints from lying on a common horizontal line, and avoiding + // the degenerate case. See Mark de Berg et al, Chapter 6.3 + //val SHEER = 0.0001f + private def shearTransform(point: Point) = Point(point.x, point.y + point.x * 0.0001f) + +} - // The initial triangle - var initialTriangle: Triangle = null - // The point list - val points = init +class CDT(val points: List[Point], val segments: List[Segment], initialTriangle: Triangle) { + // The triangle mesh val mesh = new Mesh(initialTriangle) - - // Used to compute inital triangle - private val ALPHA = 0.3f - + // Sweep points; build mesh sweep // Finalize triangulation finalization - // Initialize and sort point list - private def init: List[Point] = { - - var xmax, xmin = segments(0).p.x - var ymax, ymin = segments(0).p.y - val pts = new ArrayBuffer[Point] - - for(i <- 0 until segments.size) { - - val p = segments(i).p - val q = segments(i).q - - if(p.x > xmax) xmax = p.x - if(q.x > xmax) xmax = q.x - if(p.x < xmin) xmin = p.x - if(q.x < xmin) xmin = q.x - - if(p.y > ymax) ymax = p.x - if(q.y > ymax) ymax = q.x - if(p.y < ymin) ymin = p.x - if(q.y < ymin) ymin = q.x - - pts += shearTransform(p) - pts += shearTransform(q) - } - - var points: List[Point] = null - - if(pts.size < 10) - // Insertion sort is one of the fastest algorithms for sorting arrays containing - // fewer than ten elements, or for lists that are already mostly sorted. - points = Util.insertSort((p1: Point, p2: Point) => p1 > p2)(pts).toList - else - // Merge sort: O(n log n) - points = Util.msort((p1: Point, p2: Point) => p1 > p2)(pts.toList) - - val deltaX = ALPHA * (xmax - xmin) - val deltaY = ALPHA * (ymax - ymin) - - val p1 = Point(xmin - deltaX, ymin - deltaY) - val p2 = Point(xmax - deltaX, ymin - deltaY) - - initialTriangle = new Triangle(Array(p2, points(0), p1), null) - points - } - // Implement sweep-line paradigm private def sweep { } @@ -110,8 +114,5 @@ class CDT(segments: ArrayBuffer[Segment]) { private def finalization { } - // Prevents any two distinct endpoints from lying on a common horizontal line, and avoiding - // the degenerate case. See Mark de Berg et al, Chapter 6.3 - //val SHEER = 0.0001f - def shearTransform(point: Point) = Point(point.x, point.y + point.x * 0.0001f) + def triangles = mesh.map } diff --git a/src/org/poly2tri/cdt/Mesh.scala b/src/org/poly2tri/cdt/Mesh.scala index 9bfd389..826e58b 100644 --- a/src/org/poly2tri/cdt/Mesh.scala +++ b/src/org/poly2tri/cdt/Mesh.scala @@ -32,7 +32,7 @@ package org.poly2tri.cdt import scala.collection.mutable.HashSet -import shapes.Point +import shapes.{Point, Triangle} class Mesh(initialTriangle: Triangle) { diff --git a/src/org/poly2tri/shapes/Point.scala b/src/org/poly2tri/shapes/Point.scala index b2111c9..dd98ad3 100644 --- a/src/org/poly2tri/shapes/Point.scala +++ b/src/org/poly2tri/shapes/Point.scala @@ -30,18 +30,16 @@ */ package org.poly2tri.shapes -object Event extends Enumeration { - val point, edge = Value -} - case class Point(val x: Float, val y: Float) { // Pointers to next and previous points in Monontone Mountain var next, prev: Point = null // The setment this point belongs to var segment: Segment = null - // Point type for CDT - var eventType: Event.Value = _ + // Edge event pointer for CDT + var eEvent: Segment = null + // Point event pointer for CDT + var pEvent: Segment = null @inline def -(p: Point) = Point(x - p.x, y - p.y) @inline def +(p: Point) = Point(x + p.x, y + p.y) diff --git a/src/org/poly2tri/shapes/Segment.scala b/src/org/poly2tri/shapes/Segment.scala index e23686a..e27444c 100644 --- a/src/org/poly2tri/shapes/Segment.scala +++ b/src/org/poly2tri/shapes/Segment.scala @@ -34,10 +34,7 @@ import scala.collection.mutable.{ArrayBuffer} // Represents a simple polygon's edge class Segment(var p: Point, var q: Point) { - - p.segment = this - q.segment = this - + // Pointers used for building trapezoidal map var above, below: Trapezoid = null // Montone mountain points @@ -53,27 +50,4 @@ class Segment(var p: Point, var q: Point) { // Determines if this segment lies below the given point def < (point: Point) = (Math.floor(point.y) > Math.floor(slope * point.x + b)) - // Assign point type for CDT - if(p.y > q.y) - pEdge - else if (p.y < q.y) - pPoint - else - if(p.x < q.x) - pPoint - else if (p.x > q.x) - pEdge - else - throw new Exception("Invalid segment") - - private def pPoint { - p.eventType = Event.point - q.eventType = Event.edge - } - - private def pEdge { - p.eventType = Event.edge - q.eventType = Event.point - } - } diff --git a/src/org/poly2tri/shapes/Triangle.scala b/src/org/poly2tri/shapes/Triangle.scala index 0570d91..b0097c7 100644 --- a/src/org/poly2tri/shapes/Triangle.scala +++ b/src/org/poly2tri/shapes/Triangle.scala @@ -30,8 +30,6 @@ */ package org.poly2tri.shapes -import shapes.Point - // Triangle-based data structures are know to have better performance than quad-edge structures // See: J. Shewchuk, "Triangle: Engineering a 2D Quality Mesh Generator and Delaunay Triangulator" // "Triangulations in CGAL"