updated demo framework

This commit is contained in:
Mason 2009-09-18 22:34:34 -04:00
parent 1f418be366
commit 6054e161da
3 changed files with 302 additions and 347 deletions

View File

@ -46,7 +46,7 @@ import cdt.CDT
object Poly2Tri { object Poly2Tri {
def main(args: Array[String]) { def main(args: Array[String]) {
val container = new AppGameContainer(new Poly2TriDemo()) val container = new AppGameContainer(new Poly2TriDemo())
container.setDisplayMode(800,600,false) container.setDisplayMode(800,600,false)
container.start() container.start()
} }
@ -54,7 +54,13 @@ object Poly2Tri {
} }
class Poly2TriDemo extends BasicGame("Poly2Tri") { class Poly2TriDemo extends BasicGame("Poly2Tri") {
object Algo extends Enumeration {
type Algo = Value
val CDT, Seidel, EarClip = Value
}
import Algo._
// Sedidel Triangulator // Sedidel Triangulator
var seidel: Triangulator = null var seidel: Triangulator = null
var segments: ArrayBuffer[Segment] = null var segments: ArrayBuffer[Segment] = null
@ -68,17 +74,10 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
// Sweep Line Constraied Delauney Triangulator (CDT) // Sweep Line Constraied Delauney Triangulator (CDT)
var slCDT: CDT = null var slCDT: CDT = null
var polyX: ArrayBuffer[Float] = null
var polyY: ArrayBuffer[Float] = null
var quit = false var quit = false
var debug = false
var drawMap = false var drawMap = false
var drawSegs = true var drawSegs = true
var hiLighter = 0 var drawCDTMesh = false
var drawEarClip = false
var drawCDT = true
var drawcdtMesh = false
val nazcaMonkey = "data/nazca_monkey.dat" val nazcaMonkey = "data/nazca_monkey.dat"
val nazcaHeron = "data/nazca_heron_old.dat" val nazcaHeron = "data/nazca_heron_old.dat"
@ -92,6 +91,8 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
var currentModel = dude var currentModel = dude
var doCDT = true var doCDT = true
// The current algorithm
var algo = CDT
var mouseButton = 0 var mouseButton = 0
var mousePressed = false var mousePressed = false
@ -115,129 +116,107 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
def render(container: GameContainer, g: Graphics) { def render(container: GameContainer, g: Graphics) {
g.drawString("'1-9' to cycle models, mouse to pan & zoom", 10, 520) g.drawString("'1-9' to cycle models, mouse to pan & zoom", 10, 522)
g.drawString("'SPACE' to show Seidel debug info", 10, 532) g.drawString("'c,s,e' to switch CDT / Seidel / EarClip algos", 10, 537)
g.drawString("'m' to show trapezoidal map (Seidel debug mode)", 10, 544) g.drawString("'t' to show trapezoidal map (Seidel)", 10, 552)
g.drawString("'e' to switch Seidel / EarClip", 10, 556) g.drawString("'m' to show triangle mesh (CDT)", 10, 567)
g.drawString("'d' to switch CDT / Seidel", 10, 568) g.drawString("'d' to draw edges", 10, 582)
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)
val red = new Color(1f, 0f,0.0f) val red = new Color(1f, 0f,0.0f)
val blue = new Color(0f, 0f, 1f) val blue = new Color(0f, 0f, 1f)
val green = new Color(0f, 1f, 0f) val green = new Color(0f, 1f, 0f)
val yellow = new Color(1f, 1f, 0f) val yellow = new Color(1f, 1f, 0f)
if(debug) { algo match {
val draw = if(drawMap) seidel.trapezoidMap else seidel.trapezoids
for(t <- draw) { case Algo.Seidel => {
val polygon = new Polygon() for(t <- seidel.polygons) {
for(v <- t.vertices) { val poly = new Polygon
polygon.addPoint(v.x, v.y) t.foreach(p => poly.addPoint(p.x, p.y))
} g.setColor(red)
if(!drawMap) { g.draw(poly)
val lCirc = new Circle(t.leftPoint.x, t.leftPoint.y, 4) }
g.setColor(blue); g.draw(lCirc); g.fill(lCirc) if (drawMap) {
val rCirc = new Circle(t.rightPoint.x+5, t.rightPoint.y, 4) for(t <- seidel.trapezoidMap) {
g.setColor(yellow); g.draw(rCirc); g.fill(rCirc) val polygon = new Polygon()
} for(v <- t.vertices) {
g.setColor(red) polygon.addPoint(v.x, v.y)
g.draw(polygon) }
} g.setColor(red)
} g.draw(polygon)
}/*
if(!debug && !drawEarClip && !drawCDT) { for(mp <- seidel.monoPolies) {
var i = 0 val poly = new Polygon
for(t <- seidel.polygons) { mp.foreach(p => poly.addPoint(p.x, p.y))
val poly = new Polygon g.setColor(yellow)
t.foreach(p => poly.addPoint(p.x, p.y)) g.draw(poly)
g.setColor(red) }*/
g.draw(poly) }
} }
} else if (debug && drawMap && !drawEarClip){
for(mp <- seidel.monoPolies) { case Algo.EarClip => {
val poly = new Polygon earClipResults.foreach(t => {
mp.foreach(p => poly.addPoint(p.x, p.y)) val triangle = new Polygon
g.setColor(yellow) triangle.addPoint(t.x(0), t.y(0))
g.draw(poly) triangle.addPoint(t.x(1), t.y(1))
triangle.addPoint(t.x(2), t.y(2))
g.setColor(red)
g.draw(triangle)
})
}
case Algo.CDT => {
val draw = if(drawCDTMesh) slCDT.triangleMesh else slCDT.triangles
draw.foreach( t => {
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 => {
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(blue)
g.draw(triangle)
})
for(i <- 0 until slCDT.cList.size) {
val circ = new Circle(slCDT.cList(i).x, slCDT.cList(i).y, 0.5f)
g.setColor(blue); g.draw(circ); g.fill(circ)
}
}
case _ =>
}
if(drawSegs) {
g.setColor(green)
for(i <- 0 until segments.size) {
val s = segments(i)
g.drawLine(s.p.x,s.p.y,s.q.x,s.q.y)
} }
} }
if(drawEarClip) if(currentModel == "data/dude.dat" && drawSegs) {
earClipResults.foreach(t => { g.setColor(green)
val triangle = new Polygon for(i <- 0 until chestSegs.size) {
triangle.addPoint(t.x(0), t.y(0)) val s = chestSegs(i)
triangle.addPoint(t.x(1), t.y(1)) g.drawLine(s.p.x,s.p.y,s.q.x,s.q.y)
triangle.addPoint(t.x(2), t.y(2)) }
g.setColor(red) for(i <- 0 until headSegs.size) {
g.draw(triangle) val s = headSegs(i)
}) g.drawLine(s.p.x,s.p.y,s.q.x,s.q.y)
}
if(drawCDT) { }
val draw = if(drawcdtMesh) slCDT.triangleMesh else slCDT.triangles
draw.foreach( t => {
if(false) {
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 => {
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(blue)
g.draw(triangle)
})
//slCDT.cList.foreach(c => {
for(i <- 0 until slCDT.cList.size) {
val circ = new Circle(slCDT.cList(i).x, slCDT.cList(i).y, 0.5f)
g.setColor(blue); g.draw(circ); g.fill(circ)
}
//})
}
if(drawSegs) {
g.setColor(green)
for(i <- 0 until segments.size) {
val s = segments(i)
g.drawLine(s.p.x,s.p.y,s.q.x,s.q.y)
}
}
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)
}
}
} }
@ -246,6 +225,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
* @param p The screen location that the mouse is down at. * @param p The screen location that the mouse is down at.
*/ */
override def mousePressed(b: Int, x: Int, y: Int) { override def mousePressed(b: Int, x: Int, y: Int) {
mouseButton = b mouseButton = b
mousePressed = true mousePressed = true
mousePosOld = mousePos mousePosOld = mousePos
@ -278,7 +258,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
mousePosOld = mousePos mousePosOld = mousePos
mousePos = Point(x,y) mousePos = Point(x,y)
if(mousePressed) { if(mousePressed) {
deltaX += mousePos.x - mousePosOld.x deltaX += mousePos.x - mousePosOld.x
deltaY += mousePos.y - mousePosOld.y deltaY += mousePos.y - mousePosOld.y
} }
} }
@ -296,23 +276,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
// ESC // ESC
if(key == 1) quit = true if(key == 1) quit = true
// SPACE
if(key == 57) debug = !debug
// UP
if(key == 200) {
hiLighter += 1
if (hiLighter == seidel.polygons.size)
hiLighter = 0
}
// DOWN
if(key == 208) {
hiLighter -= 1
if (hiLighter == -1)
hiLighter = seidel.polygons.size-1
}
if(c == 'm') drawMap = !drawMap
if(c == 'd') drawCDT = !drawCDT
if(c == '1') selectModel(nazcaMonkey) if(c == '1') selectModel(nazcaMonkey)
if(c == '2') selectModel(bird) if(c == '2') selectModel(bird)
if(c == '3') selectModel(strange) if(c == '3') selectModel(strange)
@ -322,9 +286,16 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
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 == '9') selectModel(dude)
if(c == 's') drawSegs = !drawSegs
if(c == 'c') drawcdtMesh = !drawcdtMesh if(c == 'd') drawSegs = !drawSegs
if(c == 'e') {drawEarClip = !drawEarClip; drawCDT = false; selectModel(currentModel)} if(c == 'm') drawCDTMesh = !drawCDTMesh
if(c == 't') drawMap = !drawMap
if(c == 's') {algo = Seidel; selectModel(currentModel) }
if(c == 'c') {algo = CDT; selectModel(currentModel) }
if(c == 'e') {algo = EarClip; selectModel(currentModel) }
// Experimental...
if(c == 'r') slCDT.refine if(c == 'r') slCDT.refine
} }
@ -358,9 +329,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
case "data/dude.dat" => case "data/dude.dat" =>
val clearPoint = Point(365, 427) val clearPoint = Point(365, 427)
loadModel(dude, -1f, Point(100f, -200f), 10, clearPoint) loadModel(dude, -1f, Point(100f, -200f), 10, clearPoint)
case _ => case _ =>
assert(false)
} }
currentModel = model currentModel = model
} }
@ -370,8 +339,8 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
println println
println("************** " + model + " **************") println("************** " + model + " **************")
polyX = new ArrayBuffer[Float] val polyX = new ArrayBuffer[Float]
polyY = new ArrayBuffer[Float] val polyY = new ArrayBuffer[Float]
var points = new ArrayBuffer[Point] var points = new ArrayBuffer[Point]
val angle = Math.Pi val angle = Math.Pi
@ -379,12 +348,12 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
val s = line.replaceAll("\n", "") val s = line.replaceAll("\n", "")
val tokens = s.split("[ ]+") val tokens = s.split("[ ]+")
if(tokens.size == 2) { if(tokens.size == 2) {
var x = tokens(0).toFloat var x = tokens(0).toFloat
var y = tokens(1).toFloat var y = tokens(1).toFloat
// 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) points += new Point(polyX.last, polyY.last)
} else { } else {
throw new Exception("Bad input file") throw new Exception("Bad input file")
} }
@ -392,91 +361,100 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
segments = new ArrayBuffer[Segment] segments = new ArrayBuffer[Segment]
for(i <- 0 until points.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)
println("Number of points = " + polyX.size) println("Number of points = " + polyX.size)
println println
if(doCDT) { algo match {
case Algo.CDT => {
val pts = points.toArray val pts = points.toArray
val t1 = System.nanoTime val t1 = System.nanoTime
slCDT = new CDT(pts, clearPoint) slCDT = new CDT(pts, clearPoint)
// Add some holes.... // Add some holes....
if(model == "data/dude.dat") { if(model == "data/dude.dat") {
val headHole = Array(Point(325f,437f), Point(320f,423f), Point(329f,413f), Point(332f,423f)) 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), val chestHole = Array(Point(320.72342f,480f), Point(338.90617f,465.96863f),
Point(347.99754f,480.61584f), Point(329.8148f,510.41534f), Point(347.99754f,480.61584f), Point(329.8148f,510.41534f),
Point(339.91632f,480.11077f), Point(334.86556f,478.09046f)) Point(339.91632f,480.11077f), Point(334.86556f,478.09046f))
// Tramsform the points // Tramsform the points
for(i <- 0 until headHole.size) { for(i <- 0 until headHole.size) {
val hx = -headHole(i).x*scale + center.x val hx = -headHole(i).x*scale + center.x
val hy = -headHole(i).y*scale + center.y val hy = -headHole(i).y*scale + center.y
headHole(i) = Point(hx, hy) 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)
} }
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] chestSegs = new ArrayBuffer[Segment]
for(i <- 0 until chestHole.size-1) for(i <- 0 until chestHole.size-1)
chestSegs += new Segment(chestHole(i), chestHole(i+1)) chestSegs += new Segment(chestHole(i), chestHole(i+1))
chestSegs += new Segment(chestHole.first, chestHole.last) chestSegs += new Segment(chestHole.first, chestHole.last)
headSegs = new ArrayBuffer[Segment] headSegs = new ArrayBuffer[Segment]
for(i <- 0 until headHole.size-1) for(i <- 0 until headHole.size-1)
headSegs += new Segment(headHole(i), headHole(i+1)) headSegs += new Segment(headHole(i), headHole(i+1))
headSegs += new Segment(headHole.first, headHole.last) headSegs += new Segment(headHole.first, headHole.last)
// Add the holes // Add the holes
slCDT.addHole(headHole) slCDT.addHole(headHole)
slCDT.addHole(chestHole) slCDT.addHole(chestHole)
} }
slCDT triangulate slCDT triangulate
val runTime = System.nanoTime - t1
println("CDT average (ms) = " + runTime*1e-6)
println("Number of triangles = " + slCDT.triangles.size)
println
}
if(!drawEarClip) {
// Sediel triangulation
val t1 = System.nanoTime
seidel = new Triangulator(points)
val runTime = System.nanoTime - t1 val runTime = System.nanoTime - t1
println("Poly2Tri average (ms) = " + runTime*1e-6) println("CDT average (ms) = " + runTime*1e-6)
println("Number of triangles = " + seidel.polygons.size) println("Number of triangles = " + slCDT.triangles.size)
println
} else { }
// Earclip case Algo.Seidel => {
earClipResults = new Array[poly2tri.earClip.Triangle](maxTriangles) // Sediel triangulation
val t1 = System.nanoTime
for(i <- 0 until earClipResults.size) earClipResults(i) = new poly2tri.earClip.Triangle seidel = new Triangulator(points)
val runTime = System.nanoTime - t1
var xVerts = polyX.toArray
var yVerts = polyY.toArray println("Seidel average (ms) = " + runTime*1e-6)
println("Number of triangles = " + seidel.polygons.size)
val t1 = System.nanoTime
earClip.triangulatePolygon(xVerts, yVerts, xVerts.size, earClipResults) }
val runTime = System.nanoTime - t1
case Algo.EarClip => {
println
println("Earclip average (ms) = " + runTime*1e-6) // Earclip
println("Number of triangles = " + earClip.numTriangles)
} earClipResults = new Array[poly2tri.earClip.Triangle](maxTriangles)
for(i <- 0 until earClipResults.size) earClipResults(i) = new poly2tri.earClip.Triangle
var xVerts = polyX.toArray
var yVerts = polyY.toArray
val xv = if(currentModel != "data/strange.dat") xVerts.reverse.toArray else xVerts
val yv = if(currentModel != "data/strange.dat") yVerts.reverse.toArray else yVerts
val t1 = System.nanoTime
earClip.triangulatePolygon(xv, yv, xVerts.size, earClipResults)
val runTime = System.nanoTime - t1
println
println("Earclip average (ms) = " + runTime*1e-6)
println("Number of triangles = " + earClip.numTriangles)
}
case _ =>
} }
}
} }

View File

@ -109,76 +109,65 @@ class CDT(polyLine: Array[Point], clearPoint: Point) {
// Create edges and connect end points; update edge event pointer // Create edges and connect end points; update edge event pointer
private def initEdges(pts: Array[Point]) { private def initEdges(pts: Array[Point]) {
// Connect pts // Connect pts
for(i <- 0 until pts.size-1) { for(i <- 0 until pts.size-1) {
val endPoints = validatePoints(pts(i), pts(i+1)) val endPoints = validatePoints(pts(i), pts(i+1))
val edge = new Segment(endPoints(0), endPoints(1)) val edge = new Segment(endPoints(0), endPoints(1))
endPoints(1).edges += edge endPoints(1).edges += edge
} }
// Connect endpoints // Connect endpoints
val endPoints = validatePoints(pts.first, pts.last) val endPoints = validatePoints(pts.first, pts.last)
val edge = new Segment(endPoints(0), endPoints(1)) val edge = new Segment(endPoints(0), endPoints(1))
endPoints(1).edges += edge endPoints(1).edges += edge
} }
private 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
return List(p2, p1) return List(p2, p1)
} else if(p1.y == p2.y) { } else if(p1.y == p2.y) {
// If y values are equal, make sure point with smaller x value // If y values are equal, make sure point with smaller x value
// is to the left // is to the left
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) {
throw new Exception("Duplicate point") throw new Exception("Duplicate point")
} }
} }
List(p1, p2) List(p1, p2)
} }
// Merge sort: O(n log n) // Merge sort: O(n log n)
private def pointSort: List[Point] = private def pointSort: List[Point] =
Util.msort((p1: Point, p2: Point) => p1 > p2)(points) Util.msort((p1: Point, p2: Point) => p1 > p2)(points)
// Implement sweep-line // Implement sweep-line
private def sweep { private def sweep {
val size = if(refined) 1 else points.size val size = if(refined) 1 else points.size
for(i <- 1 until points.size) { for(i <- 1 until points.size) {
val point = points(i) val point = points(i)
// Process Point event // Process Point event
val node = pointEvent(point) val node = pointEvent(point)
// Process edge events // Process edge events
point.edges.foreach(e => edgeEvent(e, node)) point.edges.foreach(e => edgeEvent(e, node))
} }
} }
// Final step in the sweep-line CDT // Final step in the sweep-line CDT
// Clean exterior triangles // Clean exterior triangles
private def finalization { private def finalization {
var found = false var found = false
mesh.map.foreach(m => { mesh.map.foreach(m => {
if(!found) if(!found)
// Mark the originating clean triangle // Mark the originating clean triangle
if(m.pointIn(clearPoint)) { if(m.pointIn(clearPoint)) {
found = true found = true
cleanTri = m cleanTri = m
} }
m.markNeighborEdges m.markNeighborEdges
}) })
// Collect interior triangles constrained by edges // Collect interior triangles constrained by edges
mesh clean cleanTri mesh clean cleanTri
} }
// Delauney Refinement: Refine triangules using Steiner points // Delauney Refinement: Refine triangules using Steiner points
@ -191,13 +180,13 @@ class CDT(polyLine: Array[Point], clearPoint: Point) {
mesh.triangles.foreach(t => { mesh.triangles.foreach(t => {
if(t.thin) { if(t.thin) {
val center = Util.circumcenter(t.points(0), t.points(1), t.points(2)) val center = Util.circumcenter(t.points(0), t.points(1), t.points(2))
cList += center cList += center
addPoint(center) addPoint(center)
} }
}) })
// Retriangulate // Retriangulate
if(cList.size > 0) if(cList.size > 0)
triangulate triangulate
} }
// Point event // Point event
@ -224,63 +213,63 @@ class CDT(polyLine: Array[Point], clearPoint: Point) {
// Locate the first intersected triangle // Locate the first intersected triangle
val firstTriangle = if(!node.triangle.contains(edge.q)) val firstTriangle = if(!node.triangle.contains(edge.q))
node.triangle node.triangle
else else
node.triangle.locateFirst(edge) node.triangle.locateFirst(edge)
if(firstTriangle != null && !firstTriangle.contains(edge)) { if(firstTriangle != null && !firstTriangle.contains(edge)) {
// Interior mesh traversal - edge is "burried" in the mesh // Interior mesh traversal - edge is "burried" in the mesh
// Constrained edge lies below the advancing front. Traverse through intersected triangles, // Constrained edge lies below the advancing front. Traverse through intersected triangles,
// form empty pseudo-polygons, and re-triangulate // form empty pseudo-polygons, and re-triangulate
// Collect intersected triangles // Collect intersected triangles
val tList = new ArrayBuffer[Triangle] val tList = new ArrayBuffer[Triangle]
tList += firstTriangle tList += firstTriangle
while(tList.last != null && !tList.last.contains(edge.p)) while(tList.last != null && !tList.last.contains(edge.p))
tList += tList.last.findNeighbor(edge.p) tList += tList.last.findNeighbor(edge.p)
// TODO: Finish implementing edge insertion which combines advancing front (AF) // TODO: Finish implementing 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.
// Should only occur with complex patterns of interior points // Should only occur with complex patterns of interior points
// Already added provision for transitioning from AFront traversal to // Already added provision for transitioning from AFront traversal to
// interior mesh traversal - may need to add the opposite case // interior mesh traversal - may need to add the opposite case
if(tList.last == null) if(tList.last == null)
throw new Exception("Not implemented yet - interior points too complex") throw new Exception("Not implemented yet - interior points too complex")
// Neighbor triangles // Neighbor triangles
// HashMap or set may improve performance // HashMap or set may improve performance
val nTriangles = new ArrayBuffer[Triangle] val nTriangles = new ArrayBuffer[Triangle]
// Remove old triangles; collect neighbor triangles // Remove old triangles; collect neighbor triangles
// Keep duplicates out // Keep duplicates out
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
}) })
// Using a hashMap or set may improve performance // Using a hashMap or set may improve performance
val lPoints = new ArrayBuffer[Point] val lPoints = new ArrayBuffer[Point]
val rPoints = new ArrayBuffer[Point] val rPoints = new ArrayBuffer[Point]
// Collect points left and right of edge // Collect points left and right of edge
tList.foreach(t => { tList.foreach(t => {
t.points.foreach(p => { t.points.foreach(p => {
if(p != edge.q && p != edge.p) { if(p != edge.q && p != edge.p) {
if(Util.orient2d(edge.q, edge.p, p) > 0 ) { if(Util.orient2d(edge.q, edge.p, p) > 0 ) {
// Keep duplicate points out // Keep duplicate points out
if(!lPoints.contains(p)) { if(!lPoints.contains(p)) {
lPoints += p lPoints += p
}
} else {
// Keep duplicate points out
if(!rPoints.contains(p))
rPoints += p
} }
} } else {
}) // Keep duplicate points out
}) if(!rPoints.contains(p))
rPoints += p
}
}
})
})
// Triangulate empty areas. // Triangulate empty areas.
val T1 = new ArrayBuffer[Triangle] val T1 = new ArrayBuffer[Triangle]
@ -296,8 +285,8 @@ class CDT(polyLine: Array[Point], clearPoint: Point) {
// Update advancing front // Update advancing front
val ahead = (edge.p.x > edge.q.x) val ahead = (edge.p.x > edge.q.x)
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 = if(ahead) node else aFront.locate(point1) val sNode = if(ahead) node else aFront.locate(point1)
val eNode = aFront.locate(point2) val eNode = aFront.locate(point2)
@ -333,23 +322,23 @@ class CDT(polyLine: Array[Point], clearPoint: Point) {
if(ahead) { if(ahead) {
// Scan right // Scan right
pNode = pNode.next pNode = pNode.next
while(pNode.point != edge.p && !aboveEdge) { while(pNode.point != edge.p && !aboveEdge) {
points += pNode.point points += pNode.point
nTriangles += pNode.triangle nTriangles += pNode.triangle
pNode = pNode.next pNode = pNode.next
aboveEdge = edge < pNode.point aboveEdge = edge < pNode.point
} }
} else { } else {
// Scan left // Scan left
pNode = pNode.prev pNode = pNode.prev
while(pNode.point != edge.p && !aboveEdge) { while(pNode.point != edge.p && !aboveEdge) {
points += pNode.point points += pNode.point
nTriangles += pNode.triangle nTriangles += pNode.triangle
pNode = pNode.prev pNode = pNode.prev
aboveEdge = edge < pNode.point aboveEdge = edge < pNode.point
} }
nTriangles += pNode.triangle nTriangles += pNode.triangle
} }
val point2 = if(aboveEdge) { val point2 = if(aboveEdge) {
val p1 = pNode.point val p1 = pNode.point
@ -396,11 +385,11 @@ class CDT(polyLine: Array[Point], clearPoint: Point) {
private 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)
t2.markNeighbor(t1) 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)
T(i).markNeighbor(T(j)) T(i).markNeighbor(T(j))
} }
@ -455,7 +444,7 @@ class CDT(polyLine: Array[Point], clearPoint: Point) {
if(node2.prev != null) { if(node2.prev != null) {
var angle = 0.0 var angle = 0.0
do { do {
angle = fill(node2) angle = fill(node2)
node2 = node2.prev node2 = node2.prev
} while(angle <= PI_2 && angle >= -PI_2 && node2.prev != null) } while(angle <= PI_2 && angle >= -PI_2 && node2.prev != null)
} }
@ -468,31 +457,28 @@ class CDT(polyLine: Array[Point], clearPoint: Point) {
val a = (node.prev.point - node.point) val a = (node.prev.point - node.point)
val b = (node.next.point - node.point) val b = (node.next.point - node.point)
val angle = Math.atan2(a cross b, a dot b) val angle = Math.atan2(a cross b, a dot b)
// Is the angle acute? // Is the angle acute?
if(angle <= PI_2 && angle >= -PI_2) { if(angle <= PI_2 && angle >= -PI_2) {
val points = Array(node.prev.point, node.point, node.next.point) val points = Array(node.prev.point, node.point, node.next.point)
val triangle = new Triangle(points) val triangle = new Triangle(points)
// Update neighbor pointers // Update neighbor pointers
node.prev.triangle.markNeighbor(triangle) node.prev.triangle.markNeighbor(triangle)
node.triangle.markNeighbor(triangle) node.triangle.markNeighbor(triangle)
mesh.map += triangle mesh.map += triangle
aFront -= (node.prev, node, triangle) aFront -= (node.prev, node, triangle)
} }
angle angle
} }
// Circumcircle test. // Circumcircle test.
// Determines if point d lies inside triangle abc's circumcircle // Determines if point d lies inside triangle abc's circumcircle
private 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
// Make sure abc is oriented counter-clockwise // Make sure abc is oriented counter-clockwise
if(ccw) if(ccw)
Util.incircle(a, b, c, d) Util.incircle(a, b, c, d)
else else
Util.incircle(a, c, b, d) Util.incircle(a, c, b, d)
} }
// Ensure adjacent triangles are legal // Ensure adjacent triangles are legal
@ -509,33 +495,33 @@ class CDT(polyLine: Array[Point], clearPoint: Point) {
if(illegal && !t2.finalized) { if(illegal && !t2.finalized) {
// Flip edge and rotate everything clockwise // Flip edge and rotate everything clockwise
// Legalize points // Legalize points
t1.legalize(oPoint) t1.legalize(oPoint)
t2.legalize(oPoint, point) t2.legalize(oPoint, point)
// Update neighbors // Update neighbors
// Copy old neighbors // Copy old neighbors
val neighbors = List(t2.neighbors(0), t2.neighbors(1), t2.neighbors(2)) val neighbors = List(t2.neighbors(0), t2.neighbors(1), t2.neighbors(2))
// Clear old neighbors // Clear old neighbors
t2.clearNeighbors t2.clearNeighbors
// Update new neighbors // Update new neighbors
for(n <- neighbors) { for(n <- neighbors) {
if(n != null) { if(n != null) {
t1.markNeighbor(n) t1.markNeighbor(n)
t2.markNeighbor(n) t2.markNeighbor(n)
} }
} }
t2.markNeighbor(t1) t2.markNeighbor(t1)
// Don't legalize these triangles again // Don't legalize these triangles again
t2.finalized = true t2.finalized = true
t1.finalized = true t1.finalized = true
// Update advancing front // Update advancing front
aFront.insertLegalized(t1.points(1), t1, node) aFront.insertLegalized(t1.points(1), t1, node)
} else { } else {
@ -545,7 +531,6 @@ class CDT(polyLine: Array[Point], clearPoint: Point) {
aFront.insert(point, t1, node) aFront.insert(point, t1, node)
} }
} }
// The triangle mesh // The triangle mesh

View File

@ -38,21 +38,10 @@ class EarClip {
var numTriangles = 0 var numTriangles = 0
def triangulatePolygon(x: Array[Float], y: Array[Float], vn: Int, results: Array[Triangle]): Int = { def triangulatePolygon(xv: Array[Float], yv: Array[Float], vn: Int, results: Array[Triangle]): Int = {
/* if (vn < 3) return 0
val p1 = Point(x(0), y(0)) var vNum = vn
val p2 = Point(x(1), y(1))
val p3 = Point(x(2), y(2))
val ccw = Util.orient2d(p1, p2, p3) > 0
*/
val xv = x.reverse.toArray
val yv = y.reverse.toArray
if (vn < 3) return 0
var vNum = vn
//Recurse and split on pinch points //Recurse and split on pinch points
val pA = new Poly val pA = new Poly
@ -133,15 +122,18 @@ class EarClip {
System.out.println("Couldn't find an ear, dumping remaining poly:\n"); System.out.println("Couldn't find an ear, dumping remaining poly:\n");
System.out.println("Please submit this dump to ewjordan at Box2d forums\n"); System.out.println("Please submit this dump to ewjordan at Box2d forums\n");
assert(false)
for (i <- 0 until bufferSize) { for (i <- 0 until bufferSize) {
results(i).set(buffer(i)); results(i).set(buffer(i));
} }
if (bufferSize > 0) return bufferSize; if (bufferSize > 0) return bufferSize;
else { else {
numTriangles = -1 numTriangles = -1
return numTriangles return numTriangles
} }
} }
// Clip off the ear: // Clip off the ear: