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
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,25 +180,25 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
val draw = if(drawcdtMesh) slCDT.triangleMesh else slCDT.triangles
draw.foreach( t => {
for(i <- 0 to 2) {
val s = t.points(i)
val e = if(i == 2) t.points(0) else t.points(i + 1)
val j = if(i == 0) 2 else if(i == 1) 0 else 1
if(t.edges(j))
g.setColor(yellow)
else
g.setColor(red)
g.drawLine(s.x,s.y,e.x,e.y)
}
/*
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)
*/
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)
val j = if(i == 0) 2 else if(i == 1) 0 else 1
if(t.edges(j))
g.setColor(yellow)
else
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

View File

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

View File

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

View File

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

View File

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

View File

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