fixed angle init bug

This commit is contained in:
zzzzrrr 2009-07-16 14:14:09 -04:00
parent 5f651233d8
commit 9cdc372690
9 changed files with 216 additions and 110 deletions

72
data/15Point.svg Normal file
View File

@ -0,0 +1,72 @@
<?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="15Point.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" />
</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="0.7"
inkscape:cx="375"
inkscape:cy="829.15262"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:window-width="795"
inkscape:window-height="711"
inkscape:window-x="284"
inkscape:window-y="20">
<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 400,472.36218 L 500,392.36218 L 520,272.36218 L 460,232.36218 L 580,212.36218 L 480,152.36218 L 360,172.36218 L 360,52.362183 L 300,112.36218 L 200,32.362183 L 120,92.362183 L 200,72.362183 L 340,272.36218 L 200,212.36218 L 180,352.36218 L 300,312.36218 L 400,472.36218 z"
id="path2385" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -46,7 +46,7 @@ class MonotoneMountain {
// Used to track which side of the line we are on
var positive = false
// Almost Pi!
val SLOP = 3.1
val PI_SLOP = 3.1
// Append a point to the list
def +=(point: Point) {
@ -78,6 +78,8 @@ class MonotoneMountain {
// See "Computational Geometry in C", 2nd edition, by Joseph O'Rourke, page 52
def triangulate {
// Establish the proper sign
positive = initAngle
// create monotone polygon - for dubug purposes
genMonoPoly
@ -90,7 +92,7 @@ class MonotoneMountain {
while(p != tail) {
val a = angle(p)
// If the point is almost colinear with it's neighbor, remove it!
if(a >= SLOP || a <= -SLOP)
if(a >= PI_SLOP || a <= -PI_SLOP)
remove(p)
else
if(convex(p)) convexPoints.enqueue(p)
@ -100,17 +102,17 @@ class MonotoneMountain {
while(!convexPoints.isEmpty) {
val ear = convexPoints.dequeue
val a = ear.prev.clone
val b = ear.clone
val c = ear.next.clone
val a = ear.prev
val b = ear
val c = ear.next
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(valid(a)) convexPoints.enqueue(a);
if(valid(c)) convexPoints.enqueue(c)
}
assert(size <= 3, "Triangulation bug")
@ -118,6 +120,8 @@ class MonotoneMountain {
}
}
def valid(p: Point) = (p.prev != null && p.next != null && convex(p))
// Create the monotone polygon
private def genMonoPoly {
var p = head
@ -133,12 +137,15 @@ class MonotoneMountain {
Math.atan2(a cross b, a dot b)
}
def initAngle = {
val a = (head.next - head)
val b = (tail - head)
(Math.atan2(a cross b, a dot b) >= 0)
}
// Determines if the inslide angle is convex or reflex
private def convex(p: Point) = {
val cvx = (angle(p) >= 0)
if(p.prev == head)
positive = cvx
if(positive != cvx)
if(positive != (angle(p) >= 0))
false
else
true

View File

@ -30,21 +30,17 @@
*/
package org.poly2tri
class Point(val x: Float, val y: Float, var segment: Segment) {
def this(x: Float, y: Float) = this(x, y, null)
case class Point(val x: Float, val y: Float) {
// Pointers to next and previous points in Monontone Mountain
var next, prev: Point = null
/* Internal angle */
var angle = 0f
def -(p: Point) = new Point(x - p.x, y - p.y)
def +(p: Point) = new Point(x + p.x, y + p.y)
def +(f: Float) = new Point(x + f, y + f)
def *(f: Float) = new Point(x * f, y * f)
def /(a: Float) = new Point(x / a, y / a)
def -(p: Point) = Point(x - p.x, y - p.y)
def +(p: Point) = Point(x + p.x, y + p.y)
def +(f: Float) = Point(x + f, y + f)
def -(f: Float) = Point(x - f, y - f)
def *(f: Float) = Point(x * f, y * f)
def /(a: Float) = Point(x / a, y / a)
def cross(p: Point) = x * p.y - y * p.x
def dot(p: Point) = x * p.x + y * p.y
def length = Math.sqrt(x * x + y * y).toFloat
@ -52,12 +48,11 @@ class Point(val x: Float, val y: Float, var segment: Segment) {
def <(p: Point) = {
if(p.x == x)
if(y > p.y) true
if(y <= p.y) true
else false
else
(x < p.x)
}
override def clone = new Point(x, y)
override def clone = Point(x, y)
}

View File

@ -86,7 +86,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
val lCirc = new Circle(t.leftPoint.x, t.leftPoint.y, 4)
g.setColor(blue); g.draw(lCirc); g.fill(lCirc)
val rCirc = new Circle(t.rightPoint.x, t.rightPoint.y, 4)
g.setColor(yellow); g.draw(rCirc); g.fill(rCirc)
//g.setColor(yellow); g.draw(rCirc); g.fill(rCirc)
g.setColor(red)
g.draw(polygon)
}
@ -141,21 +141,40 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
// Test #1
def poly {
val scale = 1.0f
val p1 = new Point(100,300)*scale
val p2 = new Point(400,500)*scale
val p3 = new Point(260,200)*scale
val p4 = new Point(600,175)*scale
val p5 = new Point(400,300)*scale
val p6 = new Point(650,250)*scale
val p1 = Point(400,472)
val p2 = Point(500,392)
val p3 = Point(520,272)
val p4 = Point(460,232)
val p5 = Point(580,212)
val p6 = Point(480,152)
val p7 = Point(360,172)
val p8 = Point(360,52)
val p9 = Point(300,112)
val p10 = Point(200,32)
val p11 = Point(120,92)
val p12 = Point(200,72)
val p13 = Point(340,272)
val p14 = Point(208,212)
val p15 = Point(180,352)
val p16 = Point(300,312)
val segments = new ArrayBuffer[Segment]
segments += new Segment(p1, p2)
segments += new Segment(p2, p3)
segments += new Segment(p3, p4)
segments += new Segment(p1, p3)
segments += new Segment(p5, p2)
segments += new Segment(p4, p5)
segments += new Segment(p5, p6)
segments += new Segment(p4, 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, p11)
segments += new Segment(p11, p12)
segments += new Segment(p12, p13)
segments += new Segment(p13, p14)
segments += new Segment(p14, p15)
segments += new Segment(p15, p16)
segments += new Segment(p16, p1)
tesselator = new Triangulator(segments)
tesselator.process
@ -163,16 +182,16 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
def star {
val p1 = new Point(350,75)
val p2 = new Point(379,161)
val p3 = new Point(469,161)
val p4 = new Point(397,215)
val p5 = new Point(423,301)
val p6 = new Point(350,250)
val p7 = new Point(277,301)
val p8 = new Point(303,215)
val p9 = new Point(231,161)
val p10 = new Point(321,161)
val p1 = Point(350,75)
val p2 = Point(379,161)
val p3 = Point(469,161)
val p4 = Point(397,215)
val p5 = Point(423,301)
val p6 = Point(350,250)
val p7 = Point(277,301)
val p8 = Point(303,215)
val p9 = Point(231,161)
val p10 = Point(321,161)
val segments = new ArrayBuffer[Segment]
segments += new Segment(p1, p2)
@ -194,18 +213,18 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
val scale = 10.0f
val displace = 100
val p1 = new Point(10,1)*scale+displace
val p2 = new Point(20,10)*scale+displace
val p3 = new Point(30,1)*scale+displace
val p4 = new Point(40,10)*scale+displace
val p5 = new Point(50,1)*scale+displace
val p6 = new Point(50,10)*scale+displace
val p7 = new Point(40,20)*scale+displace
val p8 = new Point(30,10)*scale+displace
val p9 = new Point(20,20)*scale+displace
val p10 = new Point(10,10)*scale+displace
val p11 = new Point(1,20)*scale+displace
val p12 = new Point(1,10)*scale+displace
val p1 = Point(10,1)*scale+displace
val p2 = Point(20,10)*scale+displace
val p3 = Point(30,1)*scale+displace
val p4 = Point(40,10)*scale+displace
val p5 = Point(50,1)*scale+displace
val p6 = Point(50,10)*scale+displace
val p7 = Point(40,20)*scale+displace
val p8 = Point(30,10)*scale+displace
val p9 = Point(20,20)*scale+displace
val p10 = Point(10,10)*scale+displace
val p11 = Point(1,20)*scale+displace
val p12 = Point(1,10)*scale+displace
val segments = new ArrayBuffer[Segment]
segments += new Segment(p1, p2)

View File

@ -40,6 +40,7 @@ class QueryGraph(var head: Node) {
def locate(s: Segment) = head.locate(s).trapezoid
def followSegment(s: Segment) = {
assert(s.p.x < s.q.x)
val trapezoids = new ArrayBuffer[Trapezoid]
trapezoids += locate(s)
var j = 0
@ -68,14 +69,14 @@ class QueryGraph(var head: Node) {
def case1(sink: Sink, s: Segment, tList: ArrayBuffer[Trapezoid]) {
val yNode = new YNode(s, Sink.init(tList(1)), Sink.init(tList(2)))
val qNode = new XNode(new Point(s.q.x, s.q.y, s), yNode, Sink.init(tList(3)))
val pNode = new XNode(new Point(s.p.x, s.p.y, s), Sink.init(tList(0)), qNode)
val qNode = new XNode(s.q, yNode, Sink.init(tList(3)))
val pNode = new XNode(s.p, Sink.init(tList(0)), qNode)
replace(sink, pNode)
}
def case2(sink: Sink, s: Segment, tList: ArrayBuffer[Trapezoid]) {
val yNode = new YNode(s, Sink.init(tList(1)), Sink.init(tList(2)))
val pNode = new XNode(new Point(s.p.x, s.p.y, s), Sink.init(tList(0)), yNode)
val pNode = new XNode(s.p, Sink.init(tList(0)), yNode)
replace(sink, pNode)
}
@ -86,7 +87,7 @@ class QueryGraph(var head: Node) {
def case4(sink: Sink, s: Segment, tList: ArrayBuffer[Trapezoid]) {
val yNode = new YNode(s, Sink.init(tList(0)), Sink.init(tList(1)))
val qNode = new XNode(new Point(s.q.x, s.q.y, s), yNode, Sink.init(tList(2)))
val qNode = new XNode(s.q, yNode, Sink.init(tList(2)))
replace(sink, qNode)
}
}

View File

@ -30,14 +30,13 @@
*/
package org.poly2tri
import collection.jcl.ArrayList
import scala.collection.mutable.HashSet
// Represents a simple polygon's edge
class Segment(var p: Point, var q: Point) {
// Pointer used for building trapezoidal map
var above, below, left: Trapezoid = null
// Pointers used for building trapezoidal map
var above, below: Trapezoid = null
// Montone mountain points
// Use a HashSet to avoid repeats

View File

@ -33,7 +33,7 @@ package org.poly2tri
class Trapezoid(val leftPoint: Point, var rightPoint: Point, val top: Segment, val bottom: Segment) {
var sink: Sink = null
var outside = false
var inside = true
// Neighbor pointers
var upperLeft: Trapezoid = null
@ -58,11 +58,15 @@ class Trapezoid(val leftPoint: Point, var rightPoint: Point, val top: Segment, v
lowerRight = lr; if(lr != null) lr.lowerLeft = this
}
def markNeighbors {
if(upperLeft != null) upperLeft.outside = true
if(lowerLeft != null) lowerLeft.outside = true
if(upperRight != null) upperRight.outside = true
if(lowerRight != null) lowerRight.outside = true
// Recursively trim neightbors
def trimNeighbors {
if(inside) {
inside = false
if(upperLeft != null) {upperLeft.trimNeighbors}
if(lowerLeft != null) {lowerLeft.trimNeighbors}
if(upperRight != null) {upperRight.trimNeighbors}
if(lowerRight != null) {lowerRight.trimNeighbors}
}
}
// Determines if this point lies inside the trapezoid
@ -85,7 +89,7 @@ class Trapezoid(val leftPoint: Point, var rightPoint: Point, val top: Segment, v
}
// Add points to monotone mountain
def mark {
def addPoints {
if(leftPoint != bottom.p) bottom.mPoints += leftPoint
if(rightPoint != bottom.q) bottom.mPoints += rightPoint
if(leftPoint != top.p) top.mPoints += leftPoint

View File

@ -39,7 +39,7 @@ class TrapezoidalMap {
// Trapezoid associated array
val map = HashSet.empty[Trapezoid]
// AABB margin
var margin = 20f
var margin = 50f
// Bottom segment that spans multiple trapezoids
private var bCross: Segment = null
@ -48,11 +48,13 @@ class TrapezoidalMap {
// Add a trapezoid to the map
def add(t: Trapezoid) {
assert(t != null)
map += t
}
// Remove a trapezoid from the map
def remove(t: Trapezoid) {
assert(t != null)
map -=t
}
@ -66,6 +68,7 @@ class TrapezoidalMap {
def case1(t: Trapezoid, s: Segment) = {
assert(s.p.x != s.q.x)
assert(s.p.x < s.q.x)
val trapezoids = new ArrayBuffer[Trapezoid]
trapezoids += new Trapezoid(t.leftPoint, s.p, t.top, t.bottom)
@ -88,6 +91,8 @@ class TrapezoidalMap {
// break trapezoid into 3 smaller trapezoids
def case2(t: Trapezoid, s: Segment) = {
assert(s.p.x < s.q.x)
val rp = if(s.q.x == t.rightPoint.x) s.q else t.rightPoint
val trapezoids = new ArrayBuffer[Trapezoid]
@ -111,6 +116,7 @@ class TrapezoidalMap {
def case3(t: Trapezoid, s: Segment) = {
assert(s.p.x != s.q.x)
assert(s.p.x < s.q.x)
val lp = if(s.p.x == t.leftPoint.x) s.p else t.leftPoint
val rp = if(s.q.x == t.rightPoint.x) s.q else t.rightPoint
@ -150,6 +156,8 @@ class TrapezoidalMap {
// break trapezoid into 3 smaller trapezoids
def case4(t: Trapezoid, s: Segment) = {
assert(s.p.x < s.q.x)
val lp = if(s.p.x == t.leftPoint.x) s.p else t.leftPoint
val topCross = (tCross == t.top)
@ -176,6 +184,9 @@ class TrapezoidalMap {
trapezoids(2).update(trapezoids(0), trapezoids(1), t.upperRight, t.lowerRight)
s.above = trapezoids(0)
s.below = trapezoids(1)
trapezoids
}
@ -183,21 +194,21 @@ class TrapezoidalMap {
def boundingBox(segments: ArrayBuffer[Segment]): Trapezoid = {
var max = segments(0).p + margin
var min = segments(0).q + margin
var min = segments(0).q - margin
for(s <- segments) {
if(s.p.x > max.x) max = new Point(s.p.x + margin, max.y)
if(s.p.y > max.y) max = new Point(max.x, s.p.y + margin)
if(s.q.x > max.x) max = new Point(s.q.x+margin, max.y)
if(s.q.y > max.y) max = new Point(max.x, s.q.y+margin)
if(s.p.x < min.x) min = new Point(s.p.x-margin, min.y)
if(s.p.y < min.y) min = new Point(min.x, s.p.y-margin)
if(s.q.x < min.x) min = new Point(s.q.x-margin, min.y)
if(s.q.y < min.y) min = new Point(min.x, s.q.y-margin)
if(s.p.x > max.x) max = Point(s.p.x + margin, max.y)
if(s.p.y > max.y) max = Point(max.x, s.p.y + margin)
if(s.q.x > max.x) max = Point(s.q.x+margin, max.y)
if(s.q.y > max.y) max = Point(max.x, s.q.y+margin)
if(s.p.x < min.x) min = Point(s.p.x-margin, min.y)
if(s.p.y < min.y) min = Point(min.x, s.p.y-margin)
if(s.q.x < min.x) min = Point(s.q.x-margin, min.y)
if(s.q.y < min.y) min = Point(min.x, s.q.y-margin)
}
val top = new Segment(new Point(min.x, max.y), new Point(max.x, max.y))
val bottom = new Segment(new Point(min.x, min.y), new Point(max.x, min.y))
val top = new Segment(Point(min.x, max.y), Point(max.x, max.y))
val bottom = new Segment(Point(min.x, min.y), Point(max.x, min.y))
val left = bottom.p
val right = top.q

View File

@ -70,12 +70,22 @@ class Triangulator(var segments: ArrayBuffer[Segment]) {
tList = trapezoidalMap.case4(t, s)
queryGraph.case4(t.sink, s, tList)
}
// Add new trapezoids to the trapezoidal map
// Add new trapezoids to map
tList.foreach(trapezoidalMap.add)
}
trapezoidalMap reset
}
trapezoids = trim
// Mark outside trapezoids
trapezoidalMap.map.foreach(markOutside)
// Collect interior trapezoids
for(t <- trapezoidalMap.map)
if(t.inside) {
trapezoids += t
t addPoints
}
createMountains
// Extract all the triangles into a single list
@ -89,7 +99,7 @@ class Triangulator(var segments: ArrayBuffer[Segment]) {
// The trapezoidal map
def trapezoidMap = trapezoidalMap.map
// Trapezoid decomposition list
var trapezoids : ArrayBuffer[Trapezoid] = null
var trapezoids = new ArrayBuffer[Trapezoid]
// Monotone polygons - these are monotone mountains
def monoPolies: ArrayBuffer[ArrayBuffer[Point]] = {
val polies = new ArrayBuffer[ArrayBuffer[Point]]
@ -101,14 +111,15 @@ class Triangulator(var segments: ArrayBuffer[Segment]) {
// Initialize trapezoidal map and query structure
private val trapezoidalMap = new TrapezoidalMap
private val boundingBox = trapezoidalMap.boundingBox(segments)
private val queryGraph = new QueryGraph(new Sink(boundingBox))
trapezoidalMap add boundingBox
private val queryGraph = new QueryGraph(Sink.init(boundingBox))
private val xMonoPoly = new ArrayBuffer[MonotoneMountain]
// Build a list of x-monotone mountains
private def createMountains {
for(s <- segments) {
val mountain = new MonotoneMountain
val k = Util.msort((x: Point, y: Point) => x < y)(s.mPoints.toList)
val k = Util.msort((p1: Point, p2: Point) => p1 < p2)(s.mPoints.toList)
val points = s.p :: k ::: List(s.q)
points.foreach(p => mountain += p.clone)
if(mountain.size > 2) {
@ -118,23 +129,12 @@ class Triangulator(var segments: ArrayBuffer[Segment]) {
}
}
// Trim off the extraneous trapezoids surrounding the polygon
private def trim = {
val traps = new ArrayBuffer[Trapezoid]
// Mark outside trapezoids
for(t <- trapezoidalMap.map) {
// Mark the outside trapezoids surrounding the polygon
private def markOutside(t: Trapezoid) {
if(t.top == boundingBox.top || t.bottom == boundingBox.bottom) {
t.outside = true
t.markNeighbors
t trimNeighbors
}
}
// Collect interior trapezoids
for(t <- trapezoidalMap.map) if(!t.outside) {
traps += t
t.mark
}
traps
}
private def orderSegments = {
// Ignore vertical segments!
@ -142,14 +142,12 @@ class Triangulator(var segments: ArrayBuffer[Segment]) {
for(s <- segments) {
// Point p must be to the left of point q
if(s.p.x > s.q.x) {
val tmp = s.p
s.p = s.q
s.q = tmp
segs += s
segs += new Segment(s.q.clone, s.p.clone)
} else if(s.p.x < s.q.x)
segs += s
segs += new Segment(s.p.clone, s.q.clone)
}
// This is actually important: See Seidel's paper
Random.shuffle(segs)
//Random.shuffle(segs)
segs
}
}