bug hunting

This commit is contained in:
zzzrrr 2009-07-15 00:21:16 -04:00
parent 2dcfa82af2
commit 2752b9b021
7 changed files with 131 additions and 80 deletions

View File

@ -32,6 +32,7 @@ package org.poly2tri
import scala.collection.mutable.{ArrayBuffer, Queue} import scala.collection.mutable.{ArrayBuffer, Queue}
// Doubly linked list
class MonotoneMountain { class MonotoneMountain {
var tail, head: Point = null var tail, head: Point = null
@ -40,7 +41,7 @@ class MonotoneMountain {
val convexPoints = new Queue[Point] val convexPoints = new Queue[Point]
val triangles = new ArrayBuffer[Array[Point]] val triangles = new ArrayBuffer[Array[Point]]
// Append // Append a point to the list
def +=(point: Point) { def +=(point: Point) {
size match { size match {
case 0 => case 0 =>
@ -57,8 +58,8 @@ class MonotoneMountain {
size += 1 size += 1
} }
// Remove // Remove a point from the list
private def remove(point: Point) { def remove(point: Point) {
val next = point.next val next = point.next
val prev = point.prev val prev = point.prev
point.prev.next = next point.prev.next = next
@ -66,16 +67,66 @@ class MonotoneMountain {
size -= 1 size -= 1
} }
// Determines if the inslide angle between edge v2-v3 and edge v2-v1 is convex (< PI) // Partition a x-monotone mountain into triangles O(n)
private def angle(v1: Point, v2: Point, v3: Point) = { // See "Computational Geometry in C", 2nd edition, by Joseph O'Rourke, page 52
val a = (v2 - v1) def triangulate {
val b = (v2 - v3)
val angle = Math.atan2(b.y,b.x).toFloat - Math.atan2(a.y,a.x).toFloat if(size == 3) {
angle lastTriangle
} else {
// Initialize internal angles at each nonbase vertex
var p = head.next
while(p != tail) {
// Link strictly convex vertices into a list
if(convex(p)) convexPoints.enqueue(p)
p = p.next
} }
def lastTriangle = { while(!convexPoints.isEmpty) {
assert(size == 3, "Number of points = " + size)
val ear = convexPoints.dequeue
val a = ear.prev.clone
val b = ear
val c = ear.next.clone
val triangle = Array(a, b, c)
triangles += triangle
// Remove ear, update angles and convex list
remove(ear)
if(a.prev != null && convex(a)) convexPoints.enqueue(a);
if(c.prev != null && convex(c)) convexPoints.enqueue(c)
}
if(size == 3)lastTriangle
}
}
// Return the monotone polygon
def monoPoly: Array[Point] = {
val poly = new Array[Point](size)
var i = 0
var p = head
while(p != null) {
poly(i) = p
p = p.next
i += 1
}
poly
}
// Determines if the inslide angle between edge v2-v3 and edge v2-v1 is convex
private def convex(p: Point) = {
val a = (p.next - p)
val b = (p.prev - p)
var angle = Math.atan2(b.y,b.x).toFloat - Math.atan2(a.y,a.x).toFloat
if(p.y >= head.y) {
(angle < 0)
} else {
!(angle < 0)
}
}
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
@ -86,53 +137,4 @@ class MonotoneMountain {
} }
triangles += triangle triangles += triangle
} }
// Partition a x-monotone mountain into triangles o(n)
// See "Computational Geometry in C", 2nd edition, by Joseph O'Rourke, page 52
def triangulate {
if(size == 3) {
lastTriangle
} else {
// Initialize internal angles at each nonbase vertex
var p = head.next
while(p != tail) {
p.angle = Math.abs(angle(p.prev, p, p.next))
println("angle = " + p.angle)
// Link strictly convex vertices into a list
if(p.angle >= 0 && p.angle <= Math.Pi) convexPoints.enqueue(p)
p = p.next
}
while(!convexPoints.isEmpty) {
val ear = convexPoints.dequeue
val a = ear.prev.clone
val b = ear
val c = ear.next.clone
val triangle = Array(a, b, c)
triangles += triangle
// Remove ear, update angles and convex list
remove(ear)
a.angle -= ear.angle
if(a.angle > 0 && a != head && a != tail) convexPoints.enqueue(a)
c.angle -= ear.angle
if(c.angle > 0 && c != head && c != tail) convexPoints.enqueue(c)
}
if(size > 2)lastTriangle
}
}
def monoPoly {
val triangle = new Array[Point](size)
var i = 0
var p = head
while(p != null) {
triangle(i) = p
p = p.next
i += 1
}
triangles += triangle
println(size)
}
} }

View File

@ -61,8 +61,10 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
var drawMap = false var drawMap = false
def init(container: GameContainer) { def init(container: GameContainer) {
testTesselator // TODO: Add text file point loader
//snake //poly
//poly2
snake
} }
def update(gc: GameContainer, delta: Int) { def update(gc: GameContainer, delta: Int) {
@ -92,14 +94,19 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
} }
} }
var i = 0
for(x <- tesselator.xMonoPoly) { for(x <- tesselator.xMonoPoly) {
var t = x.triangles var t = x.triangles
var j = 0
for(t <- x.triangles) { for(t <- x.triangles) {
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(green) val color = if(i == 0 && j == 3) blue else green
g.setColor(color)
g.draw(triangle) g.draw(triangle)
j += 1
} }
i += 1
} }
} }
@ -109,7 +116,8 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
if(c == 'm') drawMap = !drawMap if(c == 'm') drawMap = !drawMap
} }
def testTesselator { // Test #1
def poly {
val scale = 1.0f val scale = 1.0f
val p1 = new Point(100,300)*scale val p1 = new Point(100,300)*scale
@ -131,6 +139,37 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
tesselator.process tesselator.process
} }
def poly2 {
val scale = 1.0f
val displace = 0f
val p1 = new Point(350,75)*scale+displace
val p2 = new Point(379,161)*scale+displace
val p3 = new Point(469,161)*scale+displace
val p4 = new Point(397,215)*scale+displace
val p5 = new Point(423,301)*scale+displace
val p6 = new Point(350,250)*scale+displace
val p7 = new Point(277,301)*scale+displace
val p8 = new Point(303,215)*scale+displace
val p9 = new Point(231,161)*scale+displace
val p10 = new Point(321,161)*scale+displace
val segments = new ArrayList[Segment]
segments += new Segment(p1, p2)
segments += new Segment(p2, p3)
segments += new Segment(p3, p4)
segments += new Segment(p4, p5)
segments += new Segment(p5, p6)
segments += new Segment(p6, p7)
segments += new Segment(p7, p8)
segments += new Segment(p8, p9)
segments += new Segment(p9, p10)
segments += new Segment(p10, p1)
tesselator = new Triangulator(segments)
tesselator.process
}
// Test #2
def snake { def snake {
val scale = 10.0f val scale = 10.0f

View File

@ -40,7 +40,8 @@ class Segment(var p: Point, var q: Point) {
var above, below, left: Trapezoid = null var above, below, left: Trapezoid = null
// Montone mountain points // Montone mountain points
val mPoints = HashSet(p, q) // Use a HashSet to avoid repeats
val mPoints = HashSet.empty[Point]
// Equation of a line: y = m*x + b // Equation of a line: y = m*x + b
// Slope of the line (m) // Slope of the line (m)

View File

@ -39,7 +39,6 @@ object Sink {
new Sink(trapezoid) new Sink(trapezoid)
} }
} }
} }
class Sink(val trapezoid: Trapezoid) extends Node(null, null) { class Sink(val trapezoid: Trapezoid) extends Node(null, null) {

View File

@ -82,11 +82,10 @@ class Trapezoid(val leftPoint: Point, var rightPoint: Point, val top: Segment, v
} }
// Add points to monotone mountain // Add points to monotone mountain
// Use HashSet to aboid repeats
def mark { def mark {
bottom.mPoints += leftPoint if(leftPoint != bottom.p) bottom.mPoints += leftPoint
bottom.mPoints += rightPoint if(rightPoint != bottom.q) bottom.mPoints += rightPoint
top.mPoints += leftPoint if(leftPoint != top.p) top.mPoints += leftPoint
top.mPoints += rightPoint if(rightPoint != top.q) top.mPoints += rightPoint
} }
} }

View File

@ -93,15 +93,11 @@ class Triangulator(var segments: ArrayList[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 <- segments) {
println(s.mPoints.size) if(s.mPoints.size > 0) {
if(s.mPoints.size > 2) {
val mountain = new MonotoneMountain val mountain = new MonotoneMountain
// TODO: Optomize sort? The number of points should be val k = Util.msort((x: Point, y: Point) => x < y)(s.mPoints.toList)
// fairly small => insertion or merge? val points = s.p :: k ::: List(s.q)
val points = s.mPoints.toList points.foreach(p => mountain += p.clone)
for(p <- points.sort((e1,e2) => e1 < e2)) {
mountain += p.clone
}
if(mountain.size > 2) { if(mountain.size > 2) {
mountain.triangulate mountain.triangulate
//mountain.monoPoly //mountain.monoPoly

View File

@ -3,6 +3,21 @@ package org.poly2tri
import collection.jcl.ArrayList import collection.jcl.ArrayList
object Util {
// From "Scala By Example," by Martin Odersky
def msort[A](less: (A, A) => Boolean)(xs: List[A]): List[A] = {
def merge(xs1: List[A], xs2: List[A]): List[A] =
if (xs1.isEmpty) xs2
else if (xs2.isEmpty) xs1
else if (less(xs1.head, xs2.head)) xs1.head :: merge(xs1.tail, xs2)
else xs2.head :: merge(xs1, xs2.tail)
val n = xs.length/2
if (n == 0) xs
else merge(msort(less)(xs take n), msort(less)(xs drop n))
}
}
/** The object <code>Random</code> offers a default implementation /** The object <code>Random</code> offers a default implementation
* of scala.util.Random and random-related convenience methods. * of scala.util.Random and random-related convenience methods.
* *