CDT work; removed manual point drawing; added model; fixed bugs

This commit is contained in:
zzzzrrr 2009-07-29 15:07:51 -04:00
parent b31d115774
commit dc70f1ca78
11 changed files with 164 additions and 329 deletions

View File

@ -236,9 +236,9 @@
7.85981 -0.78467 7.85981 -0.78467
7.79516 -0.409667 7.79516 -0.409667
7.49774 -0.151043 7.49774 -0.151043
7.84688.042924 7.84688 0.042924
8.23481.314479 8.23481 0.314479
8.64861.702414 8.64861 0.702414
8.70034 1.09035 8.70034 1.09035
8.41585 1.42656 8.41585 1.42656
8.11843 1.62053 8.11843 1.62053

10
data/star.dat Normal file
View File

@ -0,0 +1,10 @@
350 75
379 161
469 161
397 215
423 301
350 250
277 301
303 215
231 161
321 161

16
data/strange.dat Normal file
View File

@ -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

View File

@ -39,6 +39,7 @@ import scala.io.Source
import seidel.Triangulator import seidel.Triangulator
import shapes.{Segment, Point, Triangle} import shapes.{Segment, Point, Triangle}
import earClip.EarClip import earClip.EarClip
import cdt.CDT
// TODO: Lots of documentation! // TODO: Lots of documentation!
@ -73,6 +74,11 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
val nazcaMonkey = "data/nazca_monkey.dat" val nazcaMonkey = "data/nazca_monkey.dat"
val bird = "data/bird.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 var currentModel = nazcaMonkey
def init(container: GameContainer) { def init(container: GameContainer) {
@ -171,9 +177,10 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
if(c == 'm') drawMap = !drawMap if(c == 'm') drawMap = !drawMap
if(c == '1') {currentModel = nazcaMonkey; selectModel} if(c == '1') {currentModel = nazcaMonkey; selectModel}
if(c == '2') {currentModel = bird; selectModel} if(c == '2') {currentModel = bird; selectModel}
if(c == '3') {poly; earClipPoly} if(c == '3') {currentModel = strange; selectModel}
if(c == '4') snake if(c == '4') {currentModel = snake; selectModel}
if(c == '5') star if(c == '5') {currentModel = star; selectModel}
if(c == '6') {currentModel = i18; selectModel}
if(c == 's') drawSegs = !drawSegs if(c == 's') drawSegs = !drawSegs
if(c == 'e') {drawEarClip = !drawEarClip; selectModel} if(c == 'e') {drawEarClip = !drawEarClip; selectModel}
if(c == 'h') {hertelMehlhorn = !hertelMehlhorn; selectModel} if(c == 'h') {hertelMehlhorn = !hertelMehlhorn; selectModel}
@ -185,147 +192,26 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
loadModel(nazcaMonkey, 4.5f, Point(400, 300), 1500) loadModel(nazcaMonkey, 4.5f, Point(400, 300), 1500)
case "data/bird.dat" => case "data/bird.dat" =>
loadModel(bird, 25f, Point(400, 300), 350) 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 _ => case _ =>
assert(false) 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) { def loadModel(model: String, scale: Float, center: Point, maxTriangles: Int) {
println("*** " + model + " ***") println("*** " + model + " ***")
polyX = new ArrayBuffer[Float] polyX = new ArrayBuffer[Float]
polyY = new ArrayBuffer[Float] polyY = new ArrayBuffer[Float]
val points = new ArrayBuffer[Point]
val angle = Math.Pi val angle = Math.Pi
for (line <- Source.fromFile(model).getLines) { for (line <- Source.fromFile(model).getLines) {
@ -337,24 +223,20 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
// Transform the shape // Transform the shape
polyX += (Math.cos(angle)*x - Math.sin(angle)*y).toFloat * scale + center.x 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 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] segments = new ArrayBuffer[Segment]
for(i <- 0 until polyX.size-1)
var i = 0 segments += new Segment(points(i), points(i+1))
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
}
// Connect the end points // Connect the end points
val p1 = segments(0).p segments += new Segment(points.first, points.last)
val p2 = segments(segments.length-1).q
segments += new Segment(p2, p1) val cdt = CDT.init(points)
println(cdt.points.size + "," + cdt.segments.size)
println("Number of points = " + polyX.size) println("Number of points = " + polyX.size)
println println
@ -379,8 +261,14 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
earClipResults = new Array[poly2tri.earClip.Triangle](maxTriangles) earClipResults = new Array[poly2tri.earClip.Triangle](maxTriangles)
for(i <- 0 until earClipResults.size) earClipResults(i) = new poly2tri.earClip.Triangle 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 val t1 = System.nanoTime
earClip.triangulatePolygon(xVerts, yVerts, xVerts.size, earClipResults) earClip.triangulatePolygon(xVerts, yVerts, xVerts.size, earClipResults)
@ -392,54 +280,4 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
} }
} }
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)
}
} }

View File

@ -40,69 +40,73 @@ import utils.Util
* See: Domiter, V. and Žalik, B.(2008)'Sweep-line algorithm for constrained Delaunay triangulation', * See: Domiter, V. and Žalik, B.(2008)'Sweep-line algorithm for constrained Delaunay triangulation',
* International Journal of Geographical Information Science,22:4,449 462 * 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)
}
class CDT(val points: List[Point], val segments: List[Segment], initialTriangle: Triangle) {
// The initial triangle
var initialTriangle: Triangle = null
// The point list
val points = init
// The triangle mesh // The triangle mesh
val mesh = new Mesh(initialTriangle) val mesh = new Mesh(initialTriangle)
// Used to compute inital triangle
private val ALPHA = 0.3f
// Sweep points; build mesh // Sweep points; build mesh
sweep sweep
// Finalize triangulation // Finalize triangulation
finalization 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 // Implement sweep-line paradigm
private def sweep { private def sweep {
} }
@ -110,8 +114,5 @@ class CDT(segments: ArrayBuffer[Segment]) {
private def finalization { private def finalization {
} }
// Prevents any two distinct endpoints from lying on a common horizontal line, and avoiding def triangles = mesh.map
// 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)
} }

View File

@ -32,7 +32,7 @@ package org.poly2tri.cdt
import scala.collection.mutable.HashSet import scala.collection.mutable.HashSet
import shapes.Point import shapes.{Point, Triangle}
class Mesh(initialTriangle: Triangle) { class Mesh(initialTriangle: Triangle) {

View File

@ -30,18 +30,16 @@
*/ */
package org.poly2tri.shapes package org.poly2tri.shapes
object Event extends Enumeration {
val point, edge = Value
}
case class Point(val x: Float, val y: Float) { case class Point(val x: Float, val y: Float) {
// Pointers to next and previous points in Monontone Mountain // Pointers to next and previous points in Monontone Mountain
var next, prev: Point = null var next, prev: Point = null
// The setment this point belongs to // The setment this point belongs to
var segment: Segment = null var segment: Segment = null
// Point type for CDT // Edge event pointer for CDT
var eventType: Event.Value = _ 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)
@inline def +(p: Point) = Point(x + p.x, y + p.y) @inline def +(p: Point) = Point(x + p.x, y + p.y)

View File

@ -35,9 +35,6 @@ import scala.collection.mutable.{ArrayBuffer}
// Represents a simple polygon's edge // Represents a simple polygon's edge
class Segment(var p: Point, var q: Point) { class Segment(var p: Point, var q: Point) {
p.segment = this
q.segment = this
// Pointers used for building trapezoidal map // Pointers used for building trapezoidal map
var above, below: Trapezoid = null var above, below: Trapezoid = null
// Montone mountain points // Montone mountain points
@ -53,27 +50,4 @@ class Segment(var p: Point, var q: Point) {
// Determines if this segment lies below the given point // Determines if this segment lies below the given point
def < (point: Point) = (Math.floor(point.y) > Math.floor(slope * point.x + b)) 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
}
} }

View File

@ -30,8 +30,6 @@
*/ */
package org.poly2tri.shapes package org.poly2tri.shapes
import shapes.Point
// Triangle-based data structures are know to have better performance than quad-edge structures // 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" // See: J. Shewchuk, "Triangle: Engineering a 2D Quality Mesh Generator and Delaunay Triangulator"
// "Triangulations in CGAL" // "Triangulations in CGAL"