added holes; restructured code; bug hunting

This commit is contained in:
zzzzrrr 2009-08-19 18:06:17 -04:00
parent 7f999024b9
commit b67a9b4495
8 changed files with 402 additions and 139 deletions

94
data/dude.dat Normal file
View File

@ -0,0 +1,94 @@
280.35714 648.79075
286.78571 662.8979
263.28607 661.17871
262.31092 671.41548
250.53571 677.00504
250.53571 683.43361
256.42857 685.21933
297.14286 669.50504
289.28571 649.50504
285 631.6479
285 608.79075
292.85714 585.21932
306.42857 563.79075
323.57143 548.79075
339.28571 545.21932
357.85714 547.36218
375 550.21932
391.42857 568.07647
404.28571 588.79075
413.57143 612.36218
417.14286 628.07647
438.57143 619.1479
438.03572 618.96932
437.5 609.50504
426.96429 609.86218
424.64286 615.57647
419.82143 615.04075
420.35714 605.04075
428.39286 598.43361
437.85714 599.68361
443.57143 613.79075
450.71429 610.21933
431.42857 575.21932
405.71429 550.21932
372.85714 534.50504
349.28571 531.6479
346.42857 521.6479
346.42857 511.6479
350.71429 496.6479
367.85714 476.6479
377.14286 460.93361
385.71429 445.21932
388.57143 404.50504
360 352.36218
337.14286 325.93361
330.71429 334.50504
347.14286 354.50504
337.85714 370.21932
333.57143 359.50504
319.28571 353.07647
312.85714 366.6479
350.71429 387.36218
368.57143 408.07647
375.71429 431.6479
372.14286 454.50504
366.42857 462.36218
352.85714 462.36218
336.42857 456.6479
332.85714 438.79075
338.57143 423.79075
338.57143 411.6479
327.85714 405.93361
320.71429 407.36218
315.71429 423.07647
314.28571 440.21932
325 447.71932
324.82143 460.93361
317.85714 470.57647
304.28571 483.79075
287.14286 491.29075
263.03571 498.61218
251.60714 503.07647
251.25 533.61218
260.71429 533.61218
272.85714 528.43361
286.07143 518.61218
297.32143 508.25504
297.85714 507.36218
298.39286 506.46932
307.14286 496.6479
312.67857 491.6479
317.32143 503.07647
322.5 514.1479
325.53571 521.11218
327.14286 525.75504
326.96429 535.04075
311.78571 540.04075
291.07143 552.71932
274.82143 568.43361
259.10714 592.8979
254.28571 604.50504
251.07143 621.11218
250.53571 649.1479
268.1955 654.36208

96
data/dude.svg Normal file
View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="744.09448819"
height="1052.3622047"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.46"
sodipodi:docname="dude.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<defs
id="defs4">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective10" />
<inkscape:perspective
id="perspective2473"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 526.18109 : 1"
sodipodi:type="inkscape:persp3d" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
gridtolerance="10000"
guidetolerance="10"
objecttolerance="10"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.979899"
inkscape:cx="450.63058"
inkscape:cy="608.37669"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:snap-global="false"
inkscape:window-width="1271"
inkscape:window-height="749"
inkscape:window-x="0"
inkscape:window-y="0">
<inkscape:grid
type="xygrid"
id="grid2383" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 320.72342,481.626 L 338.90617,465.96863 L 347.99754,480.61584 L 329.8148,510.41534 L 339.91632,480.11077 L 334.86556,478.09046 L 320.72342,481.626 z"
id="chestHole"
inkscape:label="#path2486" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 325,437.8979 L 320.71429,423.61218 L 329.82143,413.61218 L 332.67857,423.79075 L 325,437.8979 z"
id="headHole"
inkscape:label="#path2498" />
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="dude">
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 280.35714,648.79075 L 286.78571,662.8979 L 263.28607,661.17871 L 262.31092,671.41548 L 250.53571,677.00504 L 250.53571,683.43361 L 256.42857,685.21933 L 297.14286,669.50504 L 289.28571,649.50504 L 285,631.6479 L 285,608.79075 L 292.85714,585.21932 L 306.42857,563.79075 L 323.57143,548.79075 L 339.28571,545.21932 L 357.85714,547.36218 L 375,550.21932 L 391.42857,568.07647 L 404.28571,588.79075 L 413.57143,612.36218 L 417.14286,628.07647 C 438.57143,619.1479 438.03572,618.96932 438.03572,618.96932 L 437.5,609.50504 L 426.96429,609.86218 L 424.64286,615.57647 L 419.82143,615.04075 L 420.35714,605.04075 L 428.39286,598.43361 L 437.85714,599.68361 L 443.57143,613.79075 L 450.71429,610.21933 L 431.42857,575.21932 L 405.71429,550.21932 L 372.85714,534.50504 L 349.28571,531.6479 L 346.42857,521.6479 L 346.42857,511.6479 L 350.71429,496.6479 L 367.85714,476.6479 L 377.14286,460.93361 L 385.71429,445.21932 L 388.57143,404.50504 L 360,352.36218 L 337.14286,325.93361 L 330.71429,334.50504 L 347.14286,354.50504 L 347.14286,374.50504 L 337.85714,370.21932 L 333.57143,359.50504 L 319.28571,353.07647 L 312.85714,366.6479 L 350.71429,387.36218 L 368.57143,408.07647 L 375.71429,431.6479 L 372.14286,454.50504 L 366.42857,462.36218 L 352.85714,462.36218 L 336.42857,456.6479 L 332.85714,438.79075 L 338.57143,423.79075 L 338.57143,411.6479 L 327.85714,405.93361 L 320.71429,407.36218 L 315.71429,423.07647 L 314.28571,440.21932 L 325,447.71932 L 324.82143,460.93361 L 317.85714,470.57647 L 304.28571,483.79075 L 287.14286,491.29075 L 263.03571,498.61218 L 251.60714,503.07647 L 251.25,533.61218 L 260.71429,533.61218 L 272.85714,528.43361 L 286.07143,518.61218 C 286.07143,518.61218 297.32143,508.25504 297.85714,507.36218 C 298.39286,506.46932 307.14286,496.6479 307.14286,496.6479 L 312.67857,491.6479 L 317.32143,503.07647 L 322.5,514.1479 L 325.53571,521.11218 L 327.14286,525.75504 L 326.96429,535.04075 L 311.78571,540.04075 L 291.07143,552.71932 L 274.82143,568.43361 L 259.10714,592.8979 L 254.28571,604.50504 L 251.07143,621.11218 L 250.53571,649.1479 L 268.1955,654.36208 L 280.35714,648.79075 z"
id="path2482"
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccscccccccccccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -58,6 +58,8 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
// Sedidel Triangulator // Sedidel Triangulator
var seidel: Triangulator = null var seidel: Triangulator = null
var segments: ArrayBuffer[Segment] = null var segments: ArrayBuffer[Segment] = null
var chestSegs: ArrayBuffer[Segment] = null
var headSegs: ArrayBuffer[Segment] = null
// EarClip Triangulator // EarClip Triangulator
val earClip = new EarClip val earClip = new EarClip
@ -86,8 +88,9 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
val strange = "data/strange.dat" val strange = "data/strange.dat"
val i18 = "data/i.18" val i18 = "data/i.18"
val tank = "data/tank.dat" val tank = "data/tank.dat"
val dude = "data/dude.dat"
var currentModel = nazcaHeron var currentModel = dude
var doCDT = true var doCDT = true
var mouseButton = 0 var mouseButton = 0
@ -112,12 +115,12 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
def render(container: GameContainer, g: Graphics) { def render(container: GameContainer, g: Graphics) {
g.drawString("'1-8' to cycle models, mouse to pan & zoom", 10, 520) g.drawString("'1-9' to cycle models, mouse to pan & zoom", 10, 520)
g.drawString("'SPACE' to show Seidel debug info", 10, 532) g.drawString("'SPACE' to show Seidel debug info", 10, 532)
g.drawString("'m' to show trapezoidal map (Seidel debug mode)", 10, 544) g.drawString("'m' to show trapezoidal map (Seidel debug mode)", 10, 544)
g.drawString("'e' to switch Seidel / EarClip", 10, 556) g.drawString("'e' to switch Seidel / EarClip", 10, 556)
g.drawString("'d' to switch CDT / Seidel", 10, 568) g.drawString("'d' to switch CDT / Seidel", 10, 568)
g.drawString("'c' to how CDT mesh", 10, 580) g.drawString("'c' to show CDT mesh, 's' to draw edges", 10, 580)
g.scale(scaleFactor, scaleFactor) g.scale(scaleFactor, scaleFactor)
g.translate(deltaX, deltaY) g.translate(deltaX, deltaY)
@ -177,7 +180,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
val draw = if(drawcdtMesh) slCDT.triangleMesh else slCDT.triangles val draw = if(drawcdtMesh) slCDT.triangleMesh else slCDT.triangles
draw.foreach( t => { draw.foreach( t => {
if(true) {
for(i <- 0 to 2) { for(i <- 0 to 2) {
val s = t.points(i) val s = t.points(i)
val e = if(i == 2) t.points(0) else t.points(i + 1) val e = if(i == 2) t.points(0) else t.points(i + 1)
@ -188,14 +191,14 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
g.setColor(red) g.setColor(red)
g.drawLine(s.x,s.y,e.x,e.y) g.drawLine(s.x,s.y,e.x,e.y)
} }
/* } else {
val triangle = new Polygon val triangle = new Polygon
triangle.addPoint(t.points(0).x, t.points(0).y) triangle.addPoint(t.points(0).x, t.points(0).y)
triangle.addPoint(t.points(1).x, t.points(1).y) triangle.addPoint(t.points(1).x, t.points(1).y)
triangle.addPoint(t.points(2).x, t.points(2).y) triangle.addPoint(t.points(2).x, t.points(2).y)
g.setColor(red) g.setColor(red)
g.draw(triangle) g.draw(triangle)
*/ }
}) })
slCDT.debugTriangles.foreach( t => { slCDT.debugTriangles.foreach( t => {
@ -217,6 +220,18 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
} }
} }
if(currentModel == "data/dude.dat" && drawSegs) {
g.setColor(green)
for(i <- 0 until chestSegs.size) {
val s = chestSegs(i)
g.drawLine(s.p.x,s.p.y,s.q.x,s.q.y)
}
for(i <- 0 until headSegs.size) {
val s = headSegs(i)
g.drawLine(s.p.x,s.p.y,s.q.x,s.q.y)
}
}
} }
/** /**
@ -289,6 +304,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
if(c == '6') selectModel(i18) if(c == '6') selectModel(i18)
if(c == '7') selectModel(nazcaHeron) if(c == '7') selectModel(nazcaHeron)
if(c == '8') selectModel(tank) if(c == '8') selectModel(tank)
if(c == '9') selectModel(dude)
if(c == 's') drawSegs = !drawSegs if(c == 's') drawSegs = !drawSegs
if(c == 'c') drawcdtMesh = !drawcdtMesh if(c == 'c') drawcdtMesh = !drawcdtMesh
if(c == 'e') {drawEarClip = !drawEarClip; drawCDT = false; selectModel(currentModel)} if(c == 'e') {drawEarClip = !drawEarClip; drawCDT = false; selectModel(currentModel)}
@ -297,50 +313,47 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
def selectModel(model: String) { def selectModel(model: String) {
model match { model match {
case "data/nazca_monkey.dat" => case "data/nazca_monkey.dat" =>
CDT.clearPoint = 50 val clearPoint = Point(418, 282)
loadModel(nazcaMonkey, 4.5f, Point(400, 300), 1500) loadModel(nazcaMonkey, 4.5f, Point(400, 300), 1500, clearPoint)
case "data/bird.dat" => case "data/bird.dat" =>
CDT.clearPoint = 80 val clearPoint = Point(400, 300)
loadModel(bird, 25f, Point(400, 300), 350) loadModel(bird, 25f, Point(400, 300), 350, clearPoint)
case "data/i.snake" => case "data/i.snake" =>
doCDT = true; drawCDT = true val clearPoint = Point(336f, 196f)
CDT.clearPoint = 6 loadModel(snake, 10f, Point(600, 300), 10, clearPoint)
loadModel(snake, 10f, Point(600, 300), 10)
case "data/star.dat" => case "data/star.dat" =>
doCDT = true; drawCDT = true val clearPoint = Point(400, 204)
CDT.clearPoint = 6 loadModel(star, -1f, Point(0f, 0f), 10, clearPoint)
loadModel(star, -1f, Point(0f, 0f), 10)
case "data/strange.dat" => case "data/strange.dat" =>
doCDT = true; drawCDT = true val clearPoint = Point(400, 268)
CDT.clearPoint = 13 loadModel(strange, -1f, Point(0f, 0f), 15, clearPoint)
loadModel(strange, -1f, Point(0f, 0f), 15)
case "data/i.18" => case "data/i.18" =>
doCDT = true; drawCDT = true val clearPoint = Point(510, 385)
CDT.clearPoint = 7 loadModel(i18, 20f, Point(600f, 500f), 20, clearPoint)
loadModel(i18, 20f, Point(600f, 500f), 20)
case "data/nazca_heron_old.dat" => case "data/nazca_heron_old.dat" =>
//doCDT = false; drawCDT = false; drawcdtMesh = false val clearPoint = Point(85, 290)
CDT.clearPoint = 100 loadModel(nazcaHeron, 4.2f, Point(400f, 300f), 1500, clearPoint)
loadModel(nazcaHeron, 4.2f, Point(400f, 300f), 1500)
case "data/tank.dat" => case "data/tank.dat" =>
//doCDT = false; drawCDT = false; drawcdtMesh = false val clearPoint = Point(450, 350)
doCDT = true; drawCDT = true loadModel(tank, -1f, Point(100f, 0f), 10, clearPoint)
CDT.clearPoint = 38 case "data/dude.dat" =>
loadModel(tank, -1f, Point(100f, 0f), 10) val clearPoint = Point(365, 427)
loadModel(dude, -1f, Point(100f, -200f), 10, clearPoint)
case _ => case _ =>
assert(false) assert(false)
} }
currentModel = model currentModel = model
} }
def loadModel(model: String, scale: Float, center: Point, maxTriangles: Int) { def loadModel(model: String, scale: Float, center: Point, maxTriangles: Int, clearPoint: Point) {
println println
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] var points = new ArrayBuffer[Point]
val angle = Math.Pi val angle = Math.Pi
for (line <- Source.fromFile(model).getLines) { for (line <- Source.fromFile(model).getLines) {
@ -359,7 +372,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
} }
segments = new ArrayBuffer[Segment] segments = new ArrayBuffer[Segment]
for(i <- 0 until polyX.size-1) for(i <- 0 until points.size-1)
segments += new Segment(points(i), points(i+1)) segments += new Segment(points(i), points(i+1))
segments += new Segment(points.first, points.last) segments += new Segment(points.first, points.last)
@ -367,9 +380,49 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
println println
if(doCDT) { if(doCDT) {
val pts = points.toArray
val t1 = System.nanoTime val t1 = System.nanoTime
slCDT = CDT.init(points) slCDT = new CDT(pts, clearPoint)
// Add some holes....
if(model == "data/dude.dat") {
val headHole = Array(Point(325f,437f), Point(320f,423f), Point(329f,413f), Point(332f,423f))
val chestHole = Array(Point(320.72342f,480f), Point(338.90617f,465.96863f),
Point(347.99754f,480.61584f), Point(329.8148f,510.41534f),
Point(339.91632f,480.11077f), Point(334.86556f,478.09046f))
// Tramsform the points
for(i <- 0 until headHole.size) {
val hx = -headHole(i).x*scale + center.x
val hy = -headHole(i).y*scale + center.y
headHole(i) = Point(hx, hy)
}
for(i <- 0 until chestHole.size) {
val cx = -chestHole(i).x*scale + center.x
val cy = -chestHole(i).y*scale + center.y
chestHole(i) = Point(cx, cy)
}
chestSegs = new ArrayBuffer[Segment]
for(i <- 0 until chestHole.size-1)
chestSegs += new Segment(chestHole(i), chestHole(i+1))
chestSegs += new Segment(chestHole.first, chestHole.last)
headSegs = new ArrayBuffer[Segment]
for(i <- 0 until headHole.size-1)
chestSegs += new Segment(headHole(i), headHole(i+1))
chestSegs += new Segment(headHole.first, headHole.last)
slCDT.addHole(headHole)
slCDT.addHole(chestHole)
}
slCDT triangulate
val runTime = System.nanoTime - t1 val runTime = System.nanoTime - t1
println("CDT average (ms) = " + runTime*1e-6) println("CDT average (ms) = " + runTime*1e-6)
println("Number of triangles = " + slCDT.triangles.size) println("Number of triangles = " + slCDT.triangles.size)
println println

View File

@ -101,7 +101,7 @@ class AFront(iTriangle: Triangle) {
var marked = false var marked = false
// Scan the advancing front and update Node triangle pointers // Scan the advancing front and update Node triangle pointers
while(node != null && node != eNode.next) { while(node != null && node != eNode) {
T2.foreach(t => { T2.foreach(t => {
if(t.contains(node.point, node.next.point)) if(t.contains(node.point, node.next.point))

View File

@ -30,11 +30,10 @@
*/ */
package org.poly2tri.cdt package org.poly2tri.cdt
import scala.collection.mutable.{ArrayBuffer, Set} import scala.collection.mutable.ArrayBuffer
import shapes.{Segment, Point, Triangle} import shapes.{Segment, Point, Triangle}
import utils.Util import utils.Util
import seidel.MonotoneMountain
/** /**
* Sweep-line, Constrained Delauney Triangulation (CDT) * Sweep-line, Constrained Delauney Triangulation (CDT)
@ -45,14 +44,25 @@ import seidel.MonotoneMountain
// NOTE: May need to implement edge insertion which combines advancing front (AF) // NOTE: May need to implement edge insertion which combines advancing front (AF)
// and triangle traversal respectively. See figure 14(a) from Domiter et al. // and triangle traversal respectively. See figure 14(a) from Domiter et al.
// Although it may not be necessary for simple polygons.... // Although it may not be necessary for simple polygons....
object CDT {
// Inital triangle factor class CDT(polyLine: Array[Point], clearPoint: Point) {
val ALPHA = 0.3f
var clearPoint = 0
// Triangulate simple polygon // Triangle list
def init(points: ArrayBuffer[Point]): CDT = { def triangles = mesh.triangles
def triangleMesh = mesh.map
def debugTriangles = mesh.debug
// Initialize edges
initEdges(polyLine)
// Add a hole
def addHole(holePolyLine: Array[Point]) {
initEdges(holePolyLine)
points = points ++ holePolyLine.toList
}
// Triangulate simple polygon with holes
def triangulate {
var xmax, xmin = points.first.x var xmax, xmin = points.first.x
var ymax, ymin = points.first.y var ymax, ymin = points.first.y
@ -71,33 +81,39 @@ object CDT {
val p1 = Point(xmin - deltaX, ymin - deltaY) val p1 = Point(xmin - deltaX, ymin - deltaY)
val p2 = Point(xmax + deltaX, p1.y) val p2 = Point(xmax + deltaX, p1.y)
val segments = initSegments(points) // Sort the points along y-axis
val sortedPoints = pointSort(points) points = pointSort
// Initial triangle
val iTriangle = new Triangle(Array(points(0), p1, p2))
mesh.map += iTriangle
aFront = new AFront(iTriangle)
// Sweep points; build mesh
sweep
// Finalize triangulation
finalization
val tPoints = Array(sortedPoints(0), p1, p2)
val iTriangle = new Triangle(tPoints)
new CDT(sortedPoints, segments, iTriangle)
} }
// Create segments and connect end points; update edge event pointer // Create edges and connect end points; update edge event pointer
private def initSegments(points: ArrayBuffer[Point]): List[Segment] = { private def initEdges(pts: Array[Point]) {
var segments = List[Segment]() // Connect pts
for(i <- 0 until pts.size-1) {
for(i <- 0 until points.size-1) { val endPoints = validatePoints(pts(i), pts(i+1))
val endPoints = validatePoints(points(i), points(i+1)) val edge = new Segment(endPoints(0), endPoints(1))
segments = new Segment(endPoints(0), endPoints(1)) :: segments endPoints(1).edges += edge
endPoints(1).edges += segments.first
} }
val endPoints = validatePoints(points.first, points.last) // Connect endpoints
segments = new Segment(endPoints(0), endPoints(1)) :: segments val endPoints = validatePoints(pts.first, pts.last)
endPoints(1).edges += segments.first val edge = new Segment(endPoints(0), endPoints(1))
endPoints(1).edges += edge
segments
} }
def validatePoints(p1: Point, p2: Point): List[Point] = { private def validatePoints(p1: Point, p2: Point): List[Point] = {
if(p1.y > p2.y) { if(p1.y > p2.y) {
// For CDT we want q to be the point with > y // For CDT we want q to be the point with > y
@ -108,6 +124,7 @@ object CDT {
if(p1.x > p2.x) { if(p1.x > p2.x) {
return List(p2, p1) return List(p2, p1)
} else if(p1.x == p2.x) { } else if(p1.x == p2.x) {
println(p1 + "," + p2)
throw new Exception("Duplicate point") throw new Exception("Duplicate point")
} }
} }
@ -115,50 +132,17 @@ object CDT {
List(p1, p2) List(p1, p2)
} }
// 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) // Merge sort: O(n log n)
private def pointSort(points: ArrayBuffer[Point]): List[Point] = { private def pointSort: List[Point] =
if(points.size < 10) Util.msort((p1: Point, p2: Point) => p1 > p2)(points)
Util.insertSort((p1: Point, p2: Point) => p1 > p2)(points).toList
else
Util.msort((p1: Point, p2: Point) => p1 > p2)(points.toList)
}
}
class CDT(val points: List[Point], val segments: List[Segment], iTriangle: Triangle) {
// Triangle list
def triangles = mesh.triangles
def triangleMesh = mesh.map
def debugTriangles = mesh.debug
// The triangle mesh
private val mesh = new Mesh(iTriangle)
// Advancing front
private val aFront = new AFront(iTriangle)
private val PI_2 = Math.Pi/2
private val PI_34 = Math.Pi*3/4
// Triangle used to clean interior
var cleanTri: Triangle = null
// Sweep points; build mesh
sweep
// Finalize triangulation
finalization
// Implement sweep-line // Implement sweep-line
private def sweep { private def sweep {
for(i <- 1 until points.size) { for(i <- 1 until 36 /*points.size*/) {
val point = points(i) val point = points(i)
// Process Point event // Process Point event
val node = pointEvent(point) val node = pointEvent(point)
if(i == CDT.clearPoint) {cleanTri = node.triangle; mesh.debug += cleanTri}
// Process edge events // Process edge events
point.edges.foreach(e => edgeEvent(e, node)) point.edges.foreach(e => edgeEvent(e, node))
} }
@ -168,10 +152,16 @@ class CDT(val points: List[Point], val segments: List[Segment], iTriangle: Trian
// Final step in the sweep-line CDT algo // Final step in the sweep-line CDT algo
// Clean exterior triangles // Clean exterior triangles
private def finalization { private def finalization {
var found = false
mesh.map.foreach(m => m.markNeighborEdges) mesh.map.foreach(m => {
if(!found)
if(m.pointIn(clearPoint)) {
found = true
cleanTri = m
}
m.markNeighborEdges
})
mesh clean cleanTri mesh clean cleanTri
} }
// Point event // Point event
@ -207,11 +197,8 @@ class CDT(val points: List[Point], val segments: List[Segment], iTriangle: Trian
tList += firstTriangle tList += firstTriangle
// Not sure why tList.last is null sometimes.... // Not sure why tList.last is null sometimes....
while(tList.last != null && !tList.last.contains(edge.p)) while(!tList.last.contains(edge.p))
tList += tList.last.findNeighbor(edge.p - edge.q) tList += tList.last.findNeighbor(edge.p)
if(tList.last == null)
tList -= tList.last
// Neighbor triangles // Neighbor triangles
val nTriangles = new ArrayBuffer[Triangle] val nTriangles = new ArrayBuffer[Triangle]
@ -221,7 +208,9 @@ class CDT(val points: List[Point], val segments: List[Segment], iTriangle: Trian
tList.foreach(t => { tList.foreach(t => {
t.neighbors.foreach(n => if(n != null && !tList.contains(n)) nTriangles += n) t.neighbors.foreach(n => if(n != null && !tList.contains(n)) nTriangles += n)
mesh.map -= t mesh.map -= t
//mesh.debug += t
}) })
//nTriangles.foreach(n => mesh.debug += n)
val lPoints = new ArrayBuffer[Point] val lPoints = new ArrayBuffer[Point]
val rPoints = new ArrayBuffer[Point] val rPoints = new ArrayBuffer[Point]
@ -261,8 +250,11 @@ class CDT(val points: List[Point], val segments: List[Segment], iTriangle: Trian
val point1 = if(ahead) edge.q else edge.p val point1 = if(ahead) edge.q else edge.p
val point2 = if(ahead) edge.p else edge.q val point2 = if(ahead) edge.p else edge.q
val sNode = aFront.locate(point1) val sNode = if(ahead) node else aFront.locate(point1).prev
val eNode = aFront.locate(point2) val eNode = aFront.locate(point2).next
//mesh.debug += sNode.triangle
//mesh.debug += eNode.triangle
aFront.constrainedEdge(sNode, eNode, T1, T2, edge) aFront.constrainedEdge(sNode, eNode, T1, T2, edge)
@ -270,8 +262,14 @@ class CDT(val points: List[Point], val segments: List[Segment], iTriangle: Trian
T1.last markEdge(point1, point2) T1.last markEdge(point1, point2)
T2.last markEdge(point1, point2) T2.last markEdge(point1, point2)
// Copy constraied edges from old triangles // Copy constraied edges from old triangles
T1.foreach(t => t.markEdge(tList)) T1.foreach(t => {t.markEdge(tList)/*;mesh.debug += t*/})
T2.foreach(t => t.markEdge(tList)) T2.foreach(t => {t.markEdge(tList)/*;mesh.debug += t*/})
var n = sNode
while(n != eNode) {
mesh.debug += n.triangle
n = n.next
}
} else if(firstTriangle == null) { } else if(firstTriangle == null) {
@ -323,11 +321,11 @@ class CDT(val points: List[Point], val segments: List[Segment], iTriangle: Trian
// Update neigbor pointers for edge event // Update neigbor pointers for edge event
// Inneficient, but it works well... // Inneficient, but it works well...
def edgeNeighbors(nTriangles: ArrayBuffer[Triangle], T: ArrayBuffer[Triangle]) { private def edgeNeighbors(nTriangles: ArrayBuffer[Triangle], T: ArrayBuffer[Triangle]) {
for(t1 <- nTriangles) for(t1 <- nTriangles)
for(t2 <- T) for(t2 <- T)
t1.markNeighbor(t2) t2.markNeighbor(t1)
for(i <- 0 until T.size) for(i <- 0 until T.size)
for(j <- i+1 until T.size) for(j <- i+1 until T.size)
@ -411,7 +409,7 @@ class CDT(val points: List[Point], val segments: List[Segment], iTriangle: Trian
// Circumcircle test. // Circumcircle test.
// Determines if point d lies inside triangle abc's circumcircle // Determines if point d lies inside triangle abc's circumcircle
def illegal(a: Point, b: Point, c: Point, d: Point): Boolean = { private def illegal(a: Point, b: Point, c: Point, d: Point): Boolean = {
val ccw = Util.orient2d(a, b, c) > 0 val ccw = Util.orient2d(a, b, c) > 0
@ -476,4 +474,17 @@ class CDT(val points: List[Point], val segments: List[Segment], iTriangle: Trian
} }
// The triangle mesh
private val mesh = new Mesh
// Advancing front
private var aFront: AFront = null
// Sorted point list
private var points = polyLine.toList
// Half Pi
private val PI_2 = Math.Pi/2
// Inital triangle factor
private val ALPHA = 0.3f
// Triangle used to clean interior
private var cleanTri: Triangle = null
} }

View File

@ -34,10 +34,10 @@ import scala.collection.mutable.{HashSet, ArrayBuffer}
import shapes.{Point, Triangle} import shapes.{Point, Triangle}
class Mesh(initialTriangle: Triangle) { class Mesh {
// Triangles that constitute the mesh // Triangles that constitute the mesh
val map = HashSet(initialTriangle) val map = HashSet.empty[Triangle]
// Debug triangles // Debug triangles
val debug = HashSet.empty[Triangle] val debug = HashSet.empty[Triangle]
val triangles = new ArrayBuffer[Triangle] val triangles = new ArrayBuffer[Triangle]

View File

@ -155,15 +155,18 @@ class Triangle(val points: Array[Point]) {
// Locate next triangle crossed by edge // Locate next triangle crossed by edge
def findNeighbor(e: Point): Triangle = { def findNeighbor(e: Point): Triangle = {
if(Util.orient2d(points(0), points(1), e) < 0)
if(contains(e)) return this
if(Util.orient2d(points(1), points(0), e) > 0)
return neighbors(2) return neighbors(2)
else if(Util.orient2d(points(1), points(2), e) < 0) else if(Util.orient2d(points(2), points(1), e) > 0)
return neighbors(0) return neighbors(0)
else if(Util.orient2d(points(2), points(0), e) < 0) else if(Util.orient2d(points(0), points(2), e) > 0)
return neighbors(1) return neighbors(1)
else else
// Point must reside inside this triangle throw new Exception("Point not found")
this
} }
// The neighbor clockwise to given point // The neighbor clockwise to given point
@ -337,4 +340,10 @@ class Triangle(val points: Array[Point]) {
(b*h*0.5f) (b*h*0.5f)
} }
def centroid: Point = {
val cx = (points(0).x + points(1).x + points(2).x)/3f
val cy = (points(0).y + points(1).y + points(2).y)/3f
Point(cx, cy)
}
} }

View File

@ -85,7 +85,7 @@ object Util {
// negative if point a, b, and c are clockwise // negative if point a, b, and c are clockwise
// zero if points are collinear // zero if points are collinear
// See: http://www-2.cs.cmu.edu/~quake/robust.html // See: http://www-2.cs.cmu.edu/~quake/robust.html
def orient(b: Point, a: Point, p: Point): Float = { def orient(a: Point, b: Point, p: Point): Float = {
val acx = a.x - p.x val acx = a.x - p.x
val bcx = b.x - p.x val bcx = b.x - p.x
val acy = a.y - p.y val acy = a.y - p.y