bug hunting

This commit is contained in:
zzzzrrr 2009-07-17 16:29:31 -04:00
parent dfcec3e307
commit 7af2a3f5b5
8 changed files with 108 additions and 121 deletions

View File

@ -39,9 +39,9 @@ class MonotoneMountain {
var size = 0 var size = 0
val convexPoints = new Queue[Point] val convexPoints = new Queue[Point]
// Monotone mountain points // Monotone mountain points
val monoPoly = new ArrayBuffer[Point] val monoPoly = new ArrayBuffer[Point]
// Triangles that constitute the mountain // Triangles that constitute the mountain
val triangles = new ArrayBuffer[Array[Point]] val triangles = new ArrayBuffer[Array[Point]]
// Used to track which side of the line we are on // Used to track which side of the line we are on
var positive = false var positive = false
@ -53,14 +53,14 @@ class MonotoneMountain {
size match { size match {
case 0 => case 0 =>
head = point head = point
case 1 => case 1 =>
tail = point tail = point
tail.prev = head tail.prev = head
head.next = tail head.next = tail
case _ => case _ =>
tail.next = point tail.next = point
point.prev = tail point.prev = tail
tail = point tail = point
} }
size += 1 size += 1
} }
@ -79,54 +79,53 @@ class MonotoneMountain {
def triangulate { def triangulate {
// Establish the proper sign // Establish the proper sign
positive = initAngle positive = angleSign
// create monotone polygon - for dubug purposes // create monotone polygon - for dubug purposes
genMonoPoly genMonoPoly
if(size == 3) { if(size == 3) {
lastTriangle lastTriangle
} else { } else {
// Initialize internal angles at each nonbase vertex // Initialize internal angles at each nonbase vertex
// Link strictly convex vertices into a list, ignore reflex vertices // Link strictly convex vertices into a list, ignore reflex vertices
var p = head.next var p = head.next
while(p != tail) { while(p != tail) {
val a = angle(p) val a = angle(p)
// If the point is almost colinear with it's neighbor, remove it! // If the point is almost colinear with it's neighbor, remove it!
if(a >= PI_SLOP || a <= -PI_SLOP) if(a >= PI_SLOP || a <= -PI_SLOP)
remove(p) remove(p)
else else
if(convex(p)) convexPoints.enqueue(p) if(convex(p)) convexPoints.enqueue(p)
p = p.next p = p.next
} }
while(!convexPoints.isEmpty) { while(!convexPoints.isEmpty) {
val ear = convexPoints.dequeue val ear = convexPoints.dequeue
val a = ear.prev val a = ear.prev
val b = ear val b = ear
val c = ear.next val c = ear.next
val triangle = Array(a, b, c) val triangle = Array(a, b, c)
triangles += triangle triangles += triangle
// Remove ear, update angles and convex list // Remove ear, update angles and convex list
remove(ear) remove(ear)
if(valid(a)) convexPoints.enqueue(a); if(valid(a)) convexPoints.enqueue(a)
if(valid(c)) convexPoints.enqueue(c) if(valid(c)) convexPoints.enqueue(c)
}
} assert(size <= 3, "Triangulation bug")
assert(size <= 2, "Triangulation bug") }
} }
}
def valid(p: Point) = (p.prev != null && p.next != null && convex(p)) def valid(p: Point) = (p.prev != null && p.next != null && convex(p))
// Create the monotone polygon // Create the monotone polygon
private def genMonoPoly { private def genMonoPoly {
var p = head var p = head
while(p != null) { while(p != null) {
monoPoly += p monoPoly += p
p = p.next p = p.next
} }
} }
@ -136,7 +135,7 @@ class MonotoneMountain {
Math.atan2(a cross b, a dot b) Math.atan2(a cross b, a dot b)
} }
def initAngle = { def angleSign = {
val a = (head.next - head) val a = (head.next - head)
val b = (tail - head) val b = (tail - head)
(Math.atan2(a cross b, a dot b) >= 0) (Math.atan2(a cross b, a dot b) >= 0)
@ -144,20 +143,18 @@ class MonotoneMountain {
// Determines if the inslide angle is convex or reflex // Determines if the inslide angle is convex or reflex
private def convex(p: Point) = { private def convex(p: Point) = {
if(positive != (angle(p) >= 0)) if(positive != (angle(p) >= 0)) false
false else true
else }
true
}
private def lastTriangle { private def lastTriangle {
val triangle = new Array[Point](3) val triangle = new Array[Point](3)
var i = 0 var i = 0
var p = head var p = head
while(p != null) { while(p != null) {
triangle(i) = p triangle(i) = p
p = p.next p = p.next
i += 1 i += 1
} }
triangles += triangle triangles += triangle
} }

View File

@ -30,7 +30,7 @@
*/ */
package org.poly2tri package org.poly2tri
import collection.jcl.ArrayList import scala.collection.mutable.ArrayBuffer
// Node for a Directed Acyclic graph (DAG) // Node for a Directed Acyclic graph (DAG)
abstract class Node(var left: Node, var right: Node) { abstract class Node(var left: Node, var right: Node) {
@ -38,7 +38,7 @@ abstract class Node(var left: Node, var right: Node) {
if(left != null) left.parentList += this if(left != null) left.parentList += this
if(right != null) right.parentList += this if(right != null) right.parentList += this
var parentList = new ArrayList[Node] var parentList = new ArrayBuffer[Node]
def locate(s: Segment): Sink def locate(s: Segment): Sink
@ -46,13 +46,9 @@ abstract class Node(var left: Node, var right: Node) {
// Make sure parent pointers are updated // Make sure parent pointers are updated
def replace(node: Node) { def replace(node: Node) {
for(parent <- node.parentList) { for(parent <- node.parentList) {
// Select the correct node (left or right child) // Select the correct node to replace (left or right child)
if(parent.left == node) { if(parent.left == node) parent.left = this
parent.left = this else parent.right = this
} else {
parent.right = this
}
// Decouple the node
parentList += parent parentList += parent
} }
} }

View File

@ -64,7 +64,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
var hiLighter = 0 var hiLighter = 0
def init(container: GameContainer) { def init(container: GameContainer) {
snake poly
} }
def update(gc: GameContainer, delta: Int) { def update(gc: GameContainer, delta: Int) {
@ -81,7 +81,6 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
if(debug) { if(debug) {
val draw = if(drawMap) tesselator.trapezoidMap else tesselator.trapezoids val draw = if(drawMap) tesselator.trapezoidMap else tesselator.trapezoids
for(t <- draw) { for(t <- draw) {
assert(t.rightPoint != t.leftPoint)
val polygon = new Polygon() val polygon = new Polygon()
for(v <- t.vertices) { for(v <- t.vertices) {
polygon.addPoint(v.x, v.y) polygon.addPoint(v.x, v.y)
@ -93,14 +92,13 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
g.setColor(yellow); g.draw(rCirc); g.fill(rCirc) g.setColor(yellow); g.draw(rCirc); g.fill(rCirc)
} }
g.setColor(red) g.setColor(red)
g.draw(polygon) g.draw(polygon)
} }
} }
if(!debug) { if(!debug) {
var i = 0 var i = 0
for(t <- tesselator.triangles) { for(t <- tesselator.triangles) {
if(t.size < 3) println("wtf")
val triangle = new Polygon val triangle = new Polygon
t.foreach(p => triangle.addPoint(p.x, p.y)) t.foreach(p => triangle.addPoint(p.x, p.y))
g.setColor(red) g.setColor(red)
@ -154,7 +152,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
// Test #1 // Test #1
def poly { def poly {
val p1 = Point(400,472) val p1 = Point(400,472)
val p2 = Point(500,392) val p2 = Point(500,392)
val p3 = Point(520,272) val p3 = Point(520,272)
@ -224,7 +222,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
// Test #2 // Test #2
def snake { def snake {
val scale = 10.0f val scale = 10.0f
val displace = 100 val displace = 100
val p1 = Point(10,1)*scale+displace val p1 = Point(10,1)*scale+displace

View File

@ -44,17 +44,13 @@ class QueryGraph(var head: Node) {
val trapezoids = new ArrayBuffer[Trapezoid] val trapezoids = new ArrayBuffer[Trapezoid]
trapezoids += locate(s) trapezoids += locate(s)
var j = 0 var j = 0
try { while(s.q.x > trapezoids(j).rightPoint.x) {
while(s.q.x > trapezoids(j).rightPoint.x) { if(s > trapezoids(j).rightPoint) {
if(s > trapezoids(j).rightPoint) { trapezoids += trapezoids(j).upperRight
trapezoids += trapezoids(j).upperRight } else {
} else { trapezoids += trapezoids(j).lowerRight
trapezoids += trapezoids(j).lowerRight }
} j += 1
j += 1
}
} catch {
case e => println("# of Trapezoids = " + j)
} }
trapezoids trapezoids
} }
@ -63,31 +59,32 @@ class QueryGraph(var head: Node) {
if(sink.parentList.size == 0) { if(sink.parentList.size == 0) {
head = node head = node
} else { } else {
node.replace(sink) node replace sink
} }
} }
def case1(sink: Sink, s: Segment, tList: ArrayBuffer[Trapezoid]) { def case1(sink: Sink, s: Segment, tList: Array[Trapezoid]) {
val yNode = new YNode(s, Sink.init(tList(1)), Sink.init(tList(2))) val yNode = new YNode(s, Sink.init(tList(1)), Sink.init(tList(2)))
val qNode = new XNode(s.q, yNode, Sink.init(tList(3))) val qNode = new XNode(s.q, yNode, Sink.init(tList(3)))
val pNode = new XNode(s.p, Sink.init(tList(0)), qNode) val pNode = new XNode(s.p, Sink.init(tList(0)), qNode)
replace(sink, pNode) replace(sink, pNode)
} }
def case2(sink: Sink, s: Segment, tList: ArrayBuffer[Trapezoid]) { def case2(sink: Sink, s: Segment, tList: Array[Trapezoid]) {
val yNode = new YNode(s, Sink.init(tList(1)), Sink.init(tList(2))) val yNode = new YNode(s, Sink.init(tList(1)), Sink.init(tList(2)))
val pNode = new XNode(s.p, Sink.init(tList(0)), yNode) val pNode = new XNode(s.p, Sink.init(tList(0)), yNode)
replace(sink, pNode) replace(sink, pNode)
} }
def case3(sink: Sink, s: Segment, tList: ArrayBuffer[Trapezoid]) { def case3(sink: Sink, s: Segment, tList: Array[Trapezoid]) {
val yNode = new YNode(s, Sink.init(tList(0)), Sink.init(tList(1))) val yNode = new YNode(s, Sink.init(tList(0)), Sink.init(tList(1)))
replace(sink, yNode) replace(sink, yNode)
} }
def case4(sink: Sink, s: Segment, tList: ArrayBuffer[Trapezoid]) { def case4(sink: Sink, s: Segment, tList: Array[Trapezoid]) {
val yNode = new YNode(s, Sink.init(tList(0)), Sink.init(tList(1))) val yNode = new YNode(s, Sink.init(tList(0)), Sink.init(tList(1)))
val qNode = new XNode(s.q, yNode, Sink.init(tList(2))) val qNode = new XNode(s.q, yNode, Sink.init(tList(2)))
replace(sink, qNode) replace(sink, qNode)
} }
} }

View File

@ -33,11 +33,10 @@ package org.poly2tri
object Sink { object Sink {
def init(trapezoid: Trapezoid) = { def init(trapezoid: Trapezoid) = {
if(trapezoid.sink != null) { if(trapezoid.sink != null)
trapezoid.sink trapezoid.sink
} else { else
new Sink(trapezoid) new Sink(trapezoid)
}
} }
} }

View File

@ -30,7 +30,7 @@
*/ */
package org.poly2tri package org.poly2tri
import scala.collection.mutable.{Map, HashSet, ArrayBuffer} import scala.collection.mutable.{HashSet, ArrayBuffer}
// See "Computational Geometry", 3rd edition, by Mark de Berg et al, Chapter 6.2 // See "Computational Geometry", 3rd edition, by Mark de Berg et al, Chapter 6.2
@ -70,11 +70,11 @@ class TrapezoidalMap {
assert(s.p.x != s.q.x) assert(s.p.x != s.q.x)
assert(s.p.x < s.q.x) assert(s.p.x < s.q.x)
val trapezoids = new ArrayBuffer[Trapezoid] val trapezoids = new Array[Trapezoid](4)
trapezoids += new Trapezoid(t.leftPoint, s.p, t.top, t.bottom) trapezoids(0) = new Trapezoid(t.leftPoint, s.p, t.top, t.bottom)
trapezoids += new Trapezoid(s.p, s.q, t.top, s) trapezoids(1) = new Trapezoid(s.p, s.q, t.top, s)
trapezoids += new Trapezoid(s.p, s.q, s, t.bottom) trapezoids(2) = new Trapezoid(s.p, s.q, s, t.bottom)
trapezoids += new Trapezoid(s.q, t.rightPoint, t.top, t.bottom) trapezoids(3) = new Trapezoid(s.q, t.rightPoint, t.top, t.bottom)
trapezoids(0).update(t.upperLeft, t.lowerLeft, trapezoids(1), trapezoids(2)) trapezoids(0).update(t.upperLeft, t.lowerLeft, trapezoids(1), trapezoids(2))
trapezoids(1).update(trapezoids(0), null, trapezoids(3), null) trapezoids(1).update(trapezoids(0), null, trapezoids(3), null)
@ -93,10 +93,10 @@ class TrapezoidalMap {
assert(s.p.x < s.q.x) assert(s.p.x < s.q.x)
val trapezoids = new ArrayBuffer[Trapezoid] val trapezoids = new Array[Trapezoid](3)
trapezoids += new Trapezoid(t.leftPoint, s.p, t.top, t.bottom) trapezoids(0) = new Trapezoid(t.leftPoint, s.p, t.top, t.bottom)
trapezoids += new Trapezoid(s.p, t.rightPoint, t.top, s) trapezoids(1) = new Trapezoid(s.p, t.rightPoint, t.top, s)
trapezoids += new Trapezoid(s.p, t.rightPoint, s, t.bottom) trapezoids(2) = new Trapezoid(s.p, t.rightPoint, s, t.bottom)
trapezoids(0).update(t.upperLeft, t.lowerLeft, trapezoids(1), trapezoids(2)) trapezoids(0).update(t.upperLeft, t.lowerLeft, trapezoids(1), trapezoids(2))
trapezoids(1).update(trapezoids(0), null, t.upperRight, null) trapezoids(1).update(trapezoids(0), null, t.upperRight, null)
@ -119,9 +119,9 @@ class TrapezoidalMap {
val topCross = (tCross == t.top) val topCross = (tCross == t.top)
val bottomCross = (bCross == t.bottom) val bottomCross = (bCross == t.bottom)
val trapezoids = new ArrayBuffer[Trapezoid] val trapezoids = new Array[Trapezoid](2)
trapezoids += {if(topCross) t.upperLeft else new Trapezoid(t.leftPoint, t.rightPoint, t.top, s)} trapezoids(0) = if(topCross) t.upperLeft else new Trapezoid(t.leftPoint, t.rightPoint, t.top, s)
trapezoids += {if(bottomCross) t.lowerLeft else new Trapezoid(t.leftPoint, t.rightPoint, s, t.bottom)} trapezoids(1) = if(bottomCross) t.lowerLeft else new Trapezoid(t.leftPoint, t.rightPoint, s, t.bottom)
if(topCross) { if(topCross) {
trapezoids(0).upperRight = t.upperRight trapezoids(0).upperRight = t.upperRight
@ -156,10 +156,10 @@ class TrapezoidalMap {
val topCross = (tCross == t.top) val topCross = (tCross == t.top)
val bottomCross = (bCross == t.bottom) val bottomCross = (bCross == t.bottom)
val trapezoids = new ArrayBuffer[Trapezoid] val trapezoids = new Array[Trapezoid](3)
trapezoids += {if(topCross) t.upperLeft else new Trapezoid(t.leftPoint, s.q, t.top, s)} trapezoids(0) = if(topCross) t.upperLeft else new Trapezoid(t.leftPoint, s.q, t.top, s)
trapezoids += {if(bottomCross) t.lowerLeft else new Trapezoid(t.leftPoint, s.q, s, t.bottom)} trapezoids(1) = if(bottomCross) t.lowerLeft else new Trapezoid(t.leftPoint, s.q, s, t.bottom)
trapezoids += new Trapezoid(s.q, t.rightPoint, t.top, t.bottom) trapezoids(2) = new Trapezoid(s.q, t.rightPoint, t.top, t.bottom)
if(topCross) { if(topCross) {
trapezoids(0).upperRight = trapezoids(2) trapezoids(0).upperRight = trapezoids(2)

View File

@ -40,17 +40,17 @@ class Triangulator(segments: ArrayBuffer[Segment]) {
var triangles = new ArrayBuffer[Array[Point]] var triangles = new ArrayBuffer[Array[Point]]
// Order and randomize the segments // Order and randomize the segments
val segs = orderSegments val segmentList = orderSegments
// Build the trapezoidal map and query graph // Build the trapezoidal map and query graph
def process { def process {
for(s <- segs) { for(s <- segmentList) {
val traps = queryGraph.followSegment(s) var traps = queryGraph.followSegment(s)
// Remove trapezoids from trapezoidal Map // Remove trapezoids from trapezoidal Map
traps.foreach(trapezoidalMap.remove) traps.foreach(trapezoidalMap.remove)
for(t <- traps) { for(t <- traps) {
var tList: ArrayBuffer[Trapezoid] = null var tList: Array[Trapezoid] = null
val containsP = t.contains(s.p) val containsP = t.contains(s.p)
val containsQ = t.contains(s.q) val containsQ = t.contains(s.q)
if(containsP && containsQ) { if(containsP && containsQ) {
@ -75,7 +75,7 @@ class Triangulator(segments: ArrayBuffer[Segment]) {
} }
trapezoidalMap reset trapezoidalMap reset
} }
// Mark outside trapezoids // Mark outside trapezoids
trapezoidalMap.map.foreach(markOutside) trapezoidalMap.map.foreach(markOutside)
@ -93,13 +93,14 @@ class Triangulator(segments: ArrayBuffer[Segment]) {
for(t <- xMonoPoly(i).triangles) for(t <- xMonoPoly(i).triangles)
triangles += t triangles += t
println("# triangles = " + triangles.size) //println("# triangles = " + triangles.size)
} }
// The trapezoidal map // The trapezoidal map
def trapezoidMap = trapezoidalMap.map def trapezoidMap = trapezoidalMap.map
// Trapezoid decomposition list // Trapezoid decomposition list
var trapezoids = new ArrayBuffer[Trapezoid] var trapezoids = new ArrayBuffer[Trapezoid]
// Monotone polygons - these are monotone mountains // Monotone polygons - these are monotone mountains
def monoPolies: ArrayBuffer[ArrayBuffer[Point]] = { def monoPolies: ArrayBuffer[ArrayBuffer[Point]] = {
val polies = new ArrayBuffer[ArrayBuffer[Point]] val polies = new ArrayBuffer[ArrayBuffer[Point]]
@ -116,15 +117,15 @@ class Triangulator(segments: ArrayBuffer[Segment]) {
// Build a list of x-monotone mountains // Build a list of x-monotone mountains
private def createMountains { private def createMountains {
for(s <- segments) { for(s <- segmentList) {
if(s.mPoints.size > 0) {
val mountain = new MonotoneMountain val mountain = new MonotoneMountain
val k = Util.msort((p1: Point, p2: Point) => p1 < p2)(s.mPoints.toList) val k = Util.msort((p1: Point, p2: Point) => p1 < p2)(s.mPoints.toList)
val points = s.p :: k ::: List(s.q) val points = s.p :: k ::: List(s.q)
points.foreach(p => mountain += p.clone) points.foreach(p => mountain += p.clone)
if(mountain.size > 2) { mountain.triangulate
mountain.triangulate xMonoPoly += mountain
xMonoPoly += mountain }
}
} }
} }
@ -141,15 +142,14 @@ class Triangulator(segments: ArrayBuffer[Segment]) {
for(s <- segments) for(s <- segments)
// Point p must be to the left of point q // Point p must be to the left of point q
if(s.p.x > s.q.x) { if(s.p.x > s.q.x) {
val tmp = s.p segs += new Segment(s.q.clone, s.p.clone)
s.p = s.q
s.q = tmp
segs += s
} else if(s.p.x < s.q.x) } else if(s.p.x < s.q.x)
segs += s segs += new Segment(s.p.clone, s.q.clone)
// Randomized triangulation improves performance // Randomized triangulation improves performance
// See Seidel's paper, or O'Rourke's book, p. 57 // See Seidel's paper, or O'Rourke's book, p. 57
// Turn this off for now because of stupid pointer bug somewhere! // Turn this off for now because of pointer bug somewhere in DAG?
// The solution to this bug may be more apparent with a better Trapezoidal Map
// data structure... Maybe a modified doubly connected edge list?
//Random.shuffle(segs) //Random.shuffle(segs)
segs segs
} }

View File

@ -40,7 +40,7 @@ class YNode(segment: Segment, lChild: Node, rChild: Node) extends Node(lChild, r
// Move up the graph // Move up the graph
left.locate(s) left.locate(s)
} else { } else {
// s and segment share the same endpoint // s and segment share the same endpoint, p
if (s.slope < segment.slope) { if (s.slope < segment.slope) {
// Move down the graph // Move down the graph
return right.locate(s) return right.locate(s)