mirror of
https://github.com/jhasse/poly2tri.git
synced 2024-11-26 15:26:12 +01:00
added holes; restructured code; bug hunting
This commit is contained in:
parent
7f999024b9
commit
b67a9b4495
94
data/dude.dat
Normal file
94
data/dude.dat
Normal 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
96
data/dude.svg
Normal 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 |
@ -58,6 +58,8 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
|
||||
// Sedidel Triangulator
|
||||
var seidel: Triangulator = null
|
||||
var segments: ArrayBuffer[Segment] = null
|
||||
var chestSegs: ArrayBuffer[Segment] = null
|
||||
var headSegs: ArrayBuffer[Segment] = null
|
||||
|
||||
// EarClip Triangulator
|
||||
val earClip = new EarClip
|
||||
@ -86,8 +88,9 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
|
||||
val strange = "data/strange.dat"
|
||||
val i18 = "data/i.18"
|
||||
val tank = "data/tank.dat"
|
||||
val dude = "data/dude.dat"
|
||||
|
||||
var currentModel = nazcaHeron
|
||||
var currentModel = dude
|
||||
var doCDT = true
|
||||
|
||||
var mouseButton = 0
|
||||
@ -112,12 +115,12 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
|
||||
|
||||
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("'m' to show trapezoidal map (Seidel debug mode)", 10, 544)
|
||||
g.drawString("'e' to switch Seidel / EarClip", 10, 556)
|
||||
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.translate(deltaX, deltaY)
|
||||
@ -177,7 +180,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
|
||||
val draw = if(drawcdtMesh) slCDT.triangleMesh else slCDT.triangles
|
||||
|
||||
draw.foreach( t => {
|
||||
|
||||
if(true) {
|
||||
for(i <- 0 to 2) {
|
||||
val s = t.points(i)
|
||||
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.drawLine(s.x,s.y,e.x,e.y)
|
||||
}
|
||||
/*
|
||||
} else {
|
||||
val triangle = new Polygon
|
||||
triangle.addPoint(t.points(0).x, t.points(0).y)
|
||||
triangle.addPoint(t.points(1).x, t.points(1).y)
|
||||
triangle.addPoint(t.points(2).x, t.points(2).y)
|
||||
g.setColor(red)
|
||||
g.draw(triangle)
|
||||
*/
|
||||
}
|
||||
})
|
||||
|
||||
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 == '7') selectModel(nazcaHeron)
|
||||
if(c == '8') selectModel(tank)
|
||||
if(c == '9') selectModel(dude)
|
||||
if(c == 's') drawSegs = !drawSegs
|
||||
if(c == 'c') drawcdtMesh = !drawcdtMesh
|
||||
if(c == 'e') {drawEarClip = !drawEarClip; drawCDT = false; selectModel(currentModel)}
|
||||
@ -297,50 +313,47 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
|
||||
def selectModel(model: String) {
|
||||
model match {
|
||||
case "data/nazca_monkey.dat" =>
|
||||
CDT.clearPoint = 50
|
||||
loadModel(nazcaMonkey, 4.5f, Point(400, 300), 1500)
|
||||
val clearPoint = Point(418, 282)
|
||||
loadModel(nazcaMonkey, 4.5f, Point(400, 300), 1500, clearPoint)
|
||||
case "data/bird.dat" =>
|
||||
CDT.clearPoint = 80
|
||||
loadModel(bird, 25f, Point(400, 300), 350)
|
||||
val clearPoint = Point(400, 300)
|
||||
loadModel(bird, 25f, Point(400, 300), 350, clearPoint)
|
||||
case "data/i.snake" =>
|
||||
doCDT = true; drawCDT = true
|
||||
CDT.clearPoint = 6
|
||||
loadModel(snake, 10f, Point(600, 300), 10)
|
||||
val clearPoint = Point(336f, 196f)
|
||||
loadModel(snake, 10f, Point(600, 300), 10, clearPoint)
|
||||
case "data/star.dat" =>
|
||||
doCDT = true; drawCDT = true
|
||||
CDT.clearPoint = 6
|
||||
loadModel(star, -1f, Point(0f, 0f), 10)
|
||||
val clearPoint = Point(400, 204)
|
||||
loadModel(star, -1f, Point(0f, 0f), 10, clearPoint)
|
||||
case "data/strange.dat" =>
|
||||
doCDT = true; drawCDT = true
|
||||
CDT.clearPoint = 13
|
||||
loadModel(strange, -1f, Point(0f, 0f), 15)
|
||||
val clearPoint = Point(400, 268)
|
||||
loadModel(strange, -1f, Point(0f, 0f), 15, clearPoint)
|
||||
case "data/i.18" =>
|
||||
doCDT = true; drawCDT = true
|
||||
CDT.clearPoint = 7
|
||||
loadModel(i18, 20f, Point(600f, 500f), 20)
|
||||
val clearPoint = Point(510, 385)
|
||||
loadModel(i18, 20f, Point(600f, 500f), 20, clearPoint)
|
||||
case "data/nazca_heron_old.dat" =>
|
||||
//doCDT = false; drawCDT = false; drawcdtMesh = false
|
||||
CDT.clearPoint = 100
|
||||
loadModel(nazcaHeron, 4.2f, Point(400f, 300f), 1500)
|
||||
val clearPoint = Point(85, 290)
|
||||
loadModel(nazcaHeron, 4.2f, Point(400f, 300f), 1500, clearPoint)
|
||||
case "data/tank.dat" =>
|
||||
//doCDT = false; drawCDT = false; drawcdtMesh = false
|
||||
doCDT = true; drawCDT = true
|
||||
CDT.clearPoint = 38
|
||||
loadModel(tank, -1f, Point(100f, 0f), 10)
|
||||
val clearPoint = Point(450, 350)
|
||||
loadModel(tank, -1f, Point(100f, 0f), 10, clearPoint)
|
||||
case "data/dude.dat" =>
|
||||
val clearPoint = Point(365, 427)
|
||||
loadModel(dude, -1f, Point(100f, -200f), 10, clearPoint)
|
||||
case _ =>
|
||||
assert(false)
|
||||
|
||||
}
|
||||
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("************** " + model + " **************")
|
||||
|
||||
polyX = new ArrayBuffer[Float]
|
||||
polyY = new ArrayBuffer[Float]
|
||||
val points = new ArrayBuffer[Point]
|
||||
var points = new ArrayBuffer[Point]
|
||||
|
||||
val angle = Math.Pi
|
||||
for (line <- Source.fromFile(model).getLines) {
|
||||
@ -359,7 +372,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
|
||||
}
|
||||
|
||||
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.first, points.last)
|
||||
|
||||
@ -367,9 +380,49 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
|
||||
println
|
||||
|
||||
if(doCDT) {
|
||||
|
||||
val pts = points.toArray
|
||||
|
||||
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
|
||||
|
||||
println("CDT average (ms) = " + runTime*1e-6)
|
||||
println("Number of triangles = " + slCDT.triangles.size)
|
||||
println
|
||||
|
@ -101,7 +101,7 @@ class AFront(iTriangle: Triangle) {
|
||||
var marked = false
|
||||
|
||||
// Scan the advancing front and update Node triangle pointers
|
||||
while(node != null && node != eNode.next) {
|
||||
while(node != null && node != eNode) {
|
||||
|
||||
T2.foreach(t => {
|
||||
if(t.contains(node.point, node.next.point))
|
||||
|
@ -30,11 +30,10 @@
|
||||
*/
|
||||
package org.poly2tri.cdt
|
||||
|
||||
import scala.collection.mutable.{ArrayBuffer, Set}
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
import shapes.{Segment, Point, Triangle}
|
||||
import utils.Util
|
||||
import seidel.MonotoneMountain
|
||||
|
||||
/**
|
||||
* 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)
|
||||
// and triangle traversal respectively. See figure 14(a) from Domiter et al.
|
||||
// Although it may not be necessary for simple polygons....
|
||||
object CDT {
|
||||
|
||||
// Inital triangle factor
|
||||
val ALPHA = 0.3f
|
||||
var clearPoint = 0
|
||||
class CDT(polyLine: Array[Point], clearPoint: Point) {
|
||||
|
||||
// Triangulate simple polygon
|
||||
def init(points: ArrayBuffer[Point]): CDT = {
|
||||
// Triangle list
|
||||
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 ymax, ymin = points.first.y
|
||||
@ -71,33 +81,39 @@ object CDT {
|
||||
val p1 = Point(xmin - deltaX, ymin - deltaY)
|
||||
val p2 = Point(xmax + deltaX, p1.y)
|
||||
|
||||
val segments = initSegments(points)
|
||||
val sortedPoints = pointSort(points)
|
||||
// Sort the points along y-axis
|
||||
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
|
||||
private def initSegments(points: ArrayBuffer[Point]): List[Segment] = {
|
||||
// Create edges and connect end points; update edge event pointer
|
||||
private def initEdges(pts: Array[Point]) {
|
||||
|
||||
var segments = List[Segment]()
|
||||
|
||||
for(i <- 0 until points.size-1) {
|
||||
val endPoints = validatePoints(points(i), points(i+1))
|
||||
segments = new Segment(endPoints(0), endPoints(1)) :: segments
|
||||
endPoints(1).edges += segments.first
|
||||
// Connect pts
|
||||
for(i <- 0 until pts.size-1) {
|
||||
val endPoints = validatePoints(pts(i), pts(i+1))
|
||||
val edge = new Segment(endPoints(0), endPoints(1))
|
||||
endPoints(1).edges += edge
|
||||
}
|
||||
|
||||
val endPoints = validatePoints(points.first, points.last)
|
||||
segments = new Segment(endPoints(0), endPoints(1)) :: segments
|
||||
endPoints(1).edges += segments.first
|
||||
// Connect endpoints
|
||||
val endPoints = validatePoints(pts.first, pts.last)
|
||||
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) {
|
||||
// For CDT we want q to be the point with > y
|
||||
@ -108,6 +124,7 @@ object CDT {
|
||||
if(p1.x > p2.x) {
|
||||
return List(p2, p1)
|
||||
} else if(p1.x == p2.x) {
|
||||
println(p1 + "," + p2)
|
||||
throw new Exception("Duplicate point")
|
||||
}
|
||||
}
|
||||
@ -115,50 +132,17 @@ object CDT {
|
||||
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)
|
||||
private def pointSort(points: ArrayBuffer[Point]): List[Point] = {
|
||||
if(points.size < 10)
|
||||
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
|
||||
private def pointSort: List[Point] =
|
||||
Util.msort((p1: Point, p2: Point) => p1 > p2)(points)
|
||||
|
||||
// Implement sweep-line
|
||||
private def sweep {
|
||||
|
||||
for(i <- 1 until points.size) {
|
||||
for(i <- 1 until 36 /*points.size*/) {
|
||||
val point = points(i)
|
||||
// Process Point event
|
||||
val node = pointEvent(point)
|
||||
if(i == CDT.clearPoint) {cleanTri = node.triangle; mesh.debug += cleanTri}
|
||||
// Process edge events
|
||||
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
|
||||
// Clean exterior triangles
|
||||
private def finalization {
|
||||
|
||||
mesh.map.foreach(m => m.markNeighborEdges)
|
||||
var found = false
|
||||
mesh.map.foreach(m => {
|
||||
if(!found)
|
||||
if(m.pointIn(clearPoint)) {
|
||||
found = true
|
||||
cleanTri = m
|
||||
}
|
||||
m.markNeighborEdges
|
||||
})
|
||||
mesh clean cleanTri
|
||||
|
||||
}
|
||||
|
||||
// Point event
|
||||
@ -207,11 +197,8 @@ class CDT(val points: List[Point], val segments: List[Segment], iTriangle: Trian
|
||||
tList += firstTriangle
|
||||
|
||||
// Not sure why tList.last is null sometimes....
|
||||
while(tList.last != null && !tList.last.contains(edge.p))
|
||||
tList += tList.last.findNeighbor(edge.p - edge.q)
|
||||
|
||||
if(tList.last == null)
|
||||
tList -= tList.last
|
||||
while(!tList.last.contains(edge.p))
|
||||
tList += tList.last.findNeighbor(edge.p)
|
||||
|
||||
// Neighbor triangles
|
||||
val nTriangles = new ArrayBuffer[Triangle]
|
||||
@ -221,7 +208,9 @@ class CDT(val points: List[Point], val segments: List[Segment], iTriangle: Trian
|
||||
tList.foreach(t => {
|
||||
t.neighbors.foreach(n => if(n != null && !tList.contains(n)) nTriangles += n)
|
||||
mesh.map -= t
|
||||
//mesh.debug += t
|
||||
})
|
||||
//nTriangles.foreach(n => mesh.debug += n)
|
||||
|
||||
val lPoints = 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 point2 = if(ahead) edge.p else edge.q
|
||||
|
||||
val sNode = aFront.locate(point1)
|
||||
val eNode = aFront.locate(point2)
|
||||
val sNode = if(ahead) node else aFront.locate(point1).prev
|
||||
val eNode = aFront.locate(point2).next
|
||||
|
||||
//mesh.debug += sNode.triangle
|
||||
//mesh.debug += eNode.triangle
|
||||
|
||||
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)
|
||||
T2.last markEdge(point1, point2)
|
||||
// Copy constraied edges from old triangles
|
||||
T1.foreach(t => t.markEdge(tList))
|
||||
T2.foreach(t => t.markEdge(tList))
|
||||
T1.foreach(t => {t.markEdge(tList)/*;mesh.debug += t*/})
|
||||
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) {
|
||||
|
||||
@ -323,11 +321,11 @@ class CDT(val points: List[Point], val segments: List[Segment], iTriangle: Trian
|
||||
|
||||
// Update neigbor pointers for edge event
|
||||
// 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(t2 <- T)
|
||||
t1.markNeighbor(t2)
|
||||
t2.markNeighbor(t1)
|
||||
|
||||
for(i <- 0 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.
|
||||
// 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
|
||||
|
||||
@ -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
|
||||
|
||||
}
|
||||
|
@ -34,10 +34,10 @@ import scala.collection.mutable.{HashSet, ArrayBuffer}
|
||||
|
||||
import shapes.{Point, Triangle}
|
||||
|
||||
class Mesh(initialTriangle: Triangle) {
|
||||
class Mesh {
|
||||
|
||||
// Triangles that constitute the mesh
|
||||
val map = HashSet(initialTriangle)
|
||||
val map = HashSet.empty[Triangle]
|
||||
// Debug triangles
|
||||
val debug = HashSet.empty[Triangle]
|
||||
val triangles = new ArrayBuffer[Triangle]
|
||||
|
@ -155,15 +155,18 @@ class Triangle(val points: Array[Point]) {
|
||||
|
||||
// Locate next triangle crossed by edge
|
||||
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)
|
||||
else if(Util.orient2d(points(1), points(2), e) < 0)
|
||||
else if(Util.orient2d(points(2), points(1), e) > 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)
|
||||
else
|
||||
// Point must reside inside this triangle
|
||||
this
|
||||
throw new Exception("Point not found")
|
||||
|
||||
}
|
||||
|
||||
// The neighbor clockwise to given point
|
||||
@ -337,4 +340,10 @@ class Triangle(val points: Array[Point]) {
|
||||
(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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ object Util {
|
||||
// negative if point a, b, and c are clockwise
|
||||
// zero if points are collinear
|
||||
// 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 bcx = b.x - p.x
|
||||
val acy = a.y - p.y
|
||||
|
Loading…
Reference in New Issue
Block a user