initial commit

This commit is contained in:
zzzrrr 2009-07-13 19:54:32 -04:00
commit f461aca3dc
12 changed files with 1182 additions and 0 deletions

View File

@ -0,0 +1,138 @@
/* Poly2Tri
* Copyright (c) 2009, Mason Green
* http://code.google.com/p/poly2tri/
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Poly2Tri nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.poly2tri
import scala.collection.mutable.{ArrayBuffer, Queue}
class MonotoneMountain {
var tail, head: Point = null
var size = 0
val convexPoints = new Queue[Point]
val triangles = new ArrayBuffer[Array[Point]]
// Append
def +=(point: Point) {
size match {
case 0 =>
head = point
case 1 =>
tail = point
tail.prev = head
head.next = tail
case _ =>
tail.next = point
point.prev = tail
tail = point
}
size += 1
}
// Remove
private def remove(point: Point) {
val next = point.next
val prev = point.prev
point.prev.next = next
point.next.prev = prev
size -= 1
}
// Determines if the inslide angle between edge v2-v3 and edge v2-v1 is convex (< PI)
private def angle(v1: Point, v2: Point, v3: Point) = {
val a = (v2 - v1)
val b = (v2 - v3)
val angle = Math.atan2(b.y,b.x).toFloat - Math.atan2(a.y,a.x).toFloat
angle
}
def lastTriangle = {
assert(size == 3, "Number of points = " + size)
val triangle = new Array[Point](3)
var i = 0
var p = head
while(p != null) {
triangle(i) = p
p = p.next
i += 1
}
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) convexPoints.enqueue(a)
c.angle -= ear.angle
if(c.angle > 0) convexPoints.enqueue(c)
}
if(size == 3) 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

@ -0,0 +1,59 @@
/* Poly2Tri
* Copyright (c) 2009, Mason Green
* http://code.google.com/p/poly2tri/
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Poly2Tri nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.poly2tri
import collection.jcl.ArrayList
// Node for a Directed Acyclic graph (DAG)
abstract class Node(var left: Node, var right: Node) {
if(left != null) left.parentList += this
if(right != null) right.parentList += this
var parentList = new ArrayList[Node]
def locate(s: Segment): Sink
// Replace a node in the graph with this node
// Make sure parent pointers are updated
def replace(node: Node) {
for(parent <- node.parentList) {
// Select the correct node (left or right child)
if(parent.left == node) {
parent.left = this
} else {
parent.right = this
}
// Decouple the node
parentList += parent
}
}
}

View File

@ -0,0 +1,58 @@
/* Poly2Tri
* Copyright (c) 2009, Mason Green
* http://code.google.com/p/poly2tri/
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Poly2Tri nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.poly2tri
class Point(val x: Float, val y: Float, var segment: Segment) {
def this(x: Float, y: Float) = this(x, y, null)
// 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 <(p: Point) = {
if(p.x == x)
if(y > p.y) true
else false
else
(x < p.x)
}
override def clone = new Point(x, y)
}

View File

@ -0,0 +1,97 @@
/* Poly2Tri
* Copyright (c) 2009, Mason Green
* http://code.google.com/p/poly2tri/
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Poly2Tri nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.poly2tri
import collection.jcl.ArrayList
// Directed Acyclic graph (DAG)
// See "Computational Geometry", 3rd edition, by Mark de Berg et al, Chapter 6.2
class QueryGraph(var head: Node) {
def locate(s: Segment): Trapezoid = {
val sink = head.locate(s)
return sink.trapezoid
}
def followSegment(s: Segment) = {
val trapezoids = new ArrayList[Trapezoid]
trapezoids += locate(s)
var j = 0
while(s.q.x > trapezoids(j).rightPoint.x) {
if(s > trapezoids(j).rightPoint) {
trapezoids += trapezoids(j).upperRight
} else {
trapezoids += trapezoids(j).lowerRight
}
j += 1
}
trapezoids
}
def replace(sink: Sink, node: Node) {
if(sink.parentList.size == 0) {
head = node
} else {
node.replace(sink)
}
}
def case1(sink: Sink, s: Segment, tList: ArrayList[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)
replace(sink, pNode)
}
def case2(sink: Sink, s: Segment, tList: ArrayList[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)
replace(sink, pNode)
}
def case3(sink: Sink, s: Segment, tList: ArrayList[Trapezoid]) {
val yNode = new YNode(s, Sink.init(tList(0)), Sink.init(tList(1)))
replace(sink, yNode)
}
def case4(sink: Sink, s: Segment, tList: ArrayList[Trapezoid]) {
val yNode = new YNode(s, Sink.init(tList(0)), Sink.init(tList(1)))
if(s.left != null) {
val pNode = new XNode(new Point(s.p.x, s.p.y, s), Sink.init(s.left), yNode)
val qNode = new XNode(new Point(s.q.x, s.q.y, s), pNode, Sink.init(tList(2)))
replace(sink, qNode)
} else {
val qNode = new XNode(new Point(s.q.x, s.q.y, s), yNode, Sink.init(tList(2)))
replace(sink, qNode)
}
}
}

View File

@ -0,0 +1,56 @@
/* Poly2Tri
* Copyright (c) 2009, Mason Green
* http://code.google.com/p/poly2tri/
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Poly2Tri nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
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
// Montone mountain points
val mPoints = HashSet(p, q)
// Equation of a line: y = m*x + b
// Slope of the line (m)
val slope = (q.y - p.y)/(q.x - p.x)
// Y intercept
val b = p.y - (p.x * slope)
// Determines if this segment lies above the given point
def > (point: Point) = (point.y < slope * point.x + b)
// Determines if this segment lies below the given point
def < (point: Point) = (point.y > slope * point.x + b)
}

View File

@ -0,0 +1,50 @@
/* Poly2Tri
* Copyright (c) 2009, Mason Green
* http://code.google.com/p/poly2tri/
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Poly2Tri nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.poly2tri
object Sink {
def init(trapezoid: Trapezoid) = {
if(trapezoid.sink != null) {
trapezoid.sink
} else {
new Sink(trapezoid)
}
}
}
class Sink(val trapezoid: Trapezoid) extends Node(null, null) {
trapezoid.sink = this
override def locate(s: Segment): Sink = this
}

View File

@ -0,0 +1,101 @@
/* Poly2Tri
* Copyright (c) 2009, Mason Green
* http://code.google.com/p/poly2tri/
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Poly2Tri nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.poly2tri
class Trapezoid(val leftPoint: Point, var rightPoint: Point, val top: Segment, val bottom: Segment) {
var sink: Sink = null
var outside = false
// Neighbor pointers
var upperLeft: Trapezoid = null
var lowerLeft: Trapezoid = null
var upperRight: Trapezoid = null
var lowerRight: Trapezoid = null
def updateNeighbors(ul: Trapezoid, ll: Trapezoid, ur: Trapezoid, lr: Trapezoid) {
if(upperLeft != null && upperLeft.top == top) upperLeft.upperRight = ul
if(lowerLeft != null && lowerLeft.bottom == bottom) lowerLeft.lowerRight = ll
if(upperRight != null && upperRight.top == top) upperRight.upperLeft = ur
if(lowerRight != null && lowerRight.bottom == bottom) lowerRight.lowerLeft = lr
}
def update(ul: Trapezoid, ll: Trapezoid, ur: Trapezoid, lr: Trapezoid) {
upperLeft = ul
lowerLeft = ll
upperRight = ur
lowerRight = lr
}
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
}
// Determines if this point lies inside the trapezoid
def contains(point: Point) = {
(point.x > leftPoint.x && point.x < rightPoint.x && top > point && bottom < point)
}
def vertices: Array[Point] = {
val verts = new Array[Point](4)
verts(0) = lineIntersect(top, leftPoint.x)
verts(1) = lineIntersect(bottom, leftPoint.x)
verts(2) = lineIntersect(bottom, rightPoint.x)
verts(3) = lineIntersect(top, rightPoint.x)
return verts
}
def lineIntersect(s: Segment, x: Float) = {
val y = s.slope * x + s.b
new Point(x, y)
}
// Add points to monotone mountain
// Use HashSet to aboid repeats
def mark {
bottom.mPoints += leftPoint
bottom.mPoints += rightPoint
top.mPoints += leftPoint
top.mPoints += rightPoint
}
// Clear points when dividing this trapezoid
def clear {
bottom.mPoints -= leftPoint
bottom.mPoints -= rightPoint
top.mPoints -= leftPoint
top.mPoints -= rightPoint
}
}

View File

@ -0,0 +1,215 @@
/* Poly2Tri
* Copyright (c) 2009, Mason Green
* http://code.google.com/p/poly2tri/
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Poly2Tri nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.poly2tri
import collection.jcl.ArrayList
import scala.collection.mutable.{Map, HashSet}
// See "Computational Geometry", 3rd edition, by Mark de Berg et al, Chapter 6.2
class TrapezoidalMap {
// Trapezoid associated array
val map = HashSet.empty[Trapezoid]
// AABB margin
var margin = 2f
// Bottom segment that spans multiple trapezoids
private var bCross: Segment = null
// Top segment that spans multiple trapezoids
private var tCross: Segment = null
// Add a trapezoid to the map
def add(t: Trapezoid) {
map += t
}
// Remove a trapezoid from the map
def remove(t: Trapezoid) {
map -=t
}
def reset {
bCross = null
tCross = null
}
// Case 1: segment completely enclosed by trapezoid
// break trapezoid into 4 smaller trapezoids
def case1(t: Trapezoid, s: Segment) = {
assert(s.p.x != s.q.x)
val trapezoids = new ArrayList[Trapezoid]
trapezoids += new Trapezoid(t.leftPoint, s.p, t.top, t.bottom)
trapezoids += new Trapezoid(s.p, s.q, t.top, s)
trapezoids += new Trapezoid(s.p, s.q, s, t.bottom)
trapezoids += new Trapezoid(s.q, t.rightPoint, t.top, t.bottom)
trapezoids(0).update(t.upperLeft, t.lowerLeft, trapezoids(1), trapezoids(2))
trapezoids(1).update(trapezoids(0), null, trapezoids(3), null)
trapezoids(2).update(null, trapezoids(0), null, trapezoids(3))
trapezoids(3).update(trapezoids(1), trapezoids(2), t.upperRight, t.lowerRight)
s.above = trapezoids(1)
s.below = trapezoids(2)
t.updateNeighbors(trapezoids(0), trapezoids(0), trapezoids(3), trapezoids(3))
trapezoids
}
// Case 2: Trapezoid contains point p, q lies outside
// break trapezoid into 3 smaller trapezoids
def case2(t: Trapezoid, s: Segment) = {
val rp = if(s.q.x == t.rightPoint.x) s.q else t.rightPoint
val trapezoids = new ArrayList[Trapezoid]
trapezoids += new Trapezoid(t.leftPoint, s.p, t.top, t.bottom)
trapezoids += new Trapezoid(s.p, rp, t.top, s)
trapezoids += new Trapezoid(s.p, rp, s, t.bottom)
trapezoids(0).update(t.upperLeft, t.lowerLeft, trapezoids(1), trapezoids(2))
trapezoids(1).update(trapezoids(0), null, t.upperRight, null)
trapezoids(2).update(null, trapezoids(0), null, t.lowerRight)
bCross = t.bottom
tCross = t.top
s.above = trapezoids(1)
s.below = trapezoids(2)
t.updateNeighbors(trapezoids(0), trapezoids(0), trapezoids(1), trapezoids(2))
trapezoids
}
// Case 3: Trapezoid is bisected
def case3(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 rp = if(s.q.x == t.rightPoint.x) s.q else t.rightPoint
val topCross = (tCross == t.top)
val bottomCross = (bCross == t.bottom)
val trapezoids = new ArrayList[Trapezoid]
trapezoids += {if(topCross) t.upperLeft else new Trapezoid(lp, rp, t.top, s)}
trapezoids += {if(bottomCross) t.lowerLeft else new Trapezoid(lp, rp, s, t.bottom)}
if(topCross) {
trapezoids(0).upperRight = t.upperRight
trapezoids(0).rightPoint = t.rightPoint
} else {
trapezoids(0).update(t.upperLeft, s.above, t.upperRight, null)
if(s.above != null) s.above.lowerRight = trapezoids(0)
}
if(bottomCross) {
trapezoids(1).lowerRight = t.lowerRight
trapezoids(1).rightPoint = t.rightPoint
} else {
trapezoids(1).update(s.below, t.lowerLeft, null, t.lowerRight)
if(s.below != null) s.below.upperRight = trapezoids(1)
}
bCross = t.bottom
tCross = t.top
s.above = trapezoids(0)
s.below = trapezoids(1)
t.updateNeighbors(trapezoids(0), trapezoids(1), trapezoids(0), trapezoids(1))
trapezoids
}
// Case 4: Trapezoid contains point q, p lies outside
// break trapezoid into 3 smaller trapezoids
def case4(t: Trapezoid, s: Segment) = {
val lp = if(s.p.x == t.leftPoint.x) s.p else t.leftPoint
val topCross = (tCross == t.top)
val bottomCross = (bCross == t.bottom)
val trapezoids = new ArrayList[Trapezoid]
trapezoids += {if(topCross) t.upperLeft else new Trapezoid(lp, s.q, t.top, s)}
trapezoids += {if(bottomCross) t.lowerLeft else new Trapezoid(lp, s.q, s, t.bottom)}
trapezoids += new Trapezoid(s.q, t.rightPoint, t.top, t.bottom)
if(topCross) {
trapezoids(0).upperRight = trapezoids(2)
trapezoids(0).rightPoint = s.q
} else {
trapezoids(0).update(t.upperLeft, s.above, trapezoids(2), null)
if(s.above != null) s.above.lowerRight = trapezoids(0)
}
if(bottomCross) {
trapezoids(1).lowerRight = trapezoids(2)
trapezoids(1).rightPoint = s.q
} else {
trapezoids(1).update(s.below, t.lowerLeft, null, trapezoids(2))
if(s.below != null) s.below.upperRight = trapezoids(1)
}
trapezoids(2).update(trapezoids(0), trapezoids(1), t.upperRight, t.lowerRight)
s.above = trapezoids(0)
s.below = trapezoids(1)
t.updateNeighbors(trapezoids(0), trapezoids(1), trapezoids(2), trapezoids(2))
trapezoids
}
// Create an AABB around segments
def boundingBox(segments: ArrayList[Segment]): Trapezoid = {
var max = segments(0).p + 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)
}
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 left = bottom.p
val right = top.q
return new Trapezoid(left, right, top, bottom)
}
}

View File

@ -0,0 +1,155 @@
/* Poly2Tri
* Copyright (c) 2009, Mason Green
* http://code.google.com/p/poly2tri/
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Poly2Tri nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.poly2tri
import collection.jcl.ArrayList
import scala.collection.mutable.{HashSet, Map, Stack, ListBuffer}
import utils.Random
// Based on Raimund Seidel's paper "A simple and fast incremental randomized
// algorithm for computing trapezoidal decompositions and for triangulating polygons"
class Triangulator(var segments: ArrayList[Segment]) {
// Trapezoid decomposition list
var trapezoids : ArrayList[Trapezoid] = null
// Triangle decomposition list
// var triangles: ArrayList[Triangle] = null
// Build the trapezoidal map and query graph
def process {
val foo = new Point(0f, 0f)
val foo2 = new Point(0f, 3f)
val foo3 = new Point(10f, 3f)
var bar = List(foo, foo3, foo2)
bar -= foo
for(s <- segments) {
val traps = queryGraph.followSegment(s)
// Remove trapezoids from trapezoidal Map
traps.foreach(t => {trapezoidalMap.remove(t); t.clear})
for(t <- traps) {
var tList: ArrayList[Trapezoid] = null
val containsP = t.contains(s.p)
val containsQ = t.contains(s.q)
if(containsP && containsQ) {
// Case 1
tList = trapezoidalMap.case1(t,s)
queryGraph.case1(t.sink, s, tList)
} else if(containsP && !containsQ) {
// Case 2
tList = trapezoidalMap.case2(t,s)
queryGraph.case2(t.sink, s, tList)
} else if(!containsP && !containsQ) {
// Case 3
tList = trapezoidalMap.case3(t, s)
queryGraph.case3(t.sink, s, tList)
} else {
// Case 4
tList = trapezoidalMap.case4(t, s)
queryGraph.case4(t.sink, s, tList)
}
// Add new trapezoids to the trapezoidal map
tList.foreach(trapezoidalMap.add)
}
trapezoidalMap reset
}
trapezoids = trim
createMountains
}
def allTrapezoids = trapezoidalMap.map
// 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))
val xMonoPoly = new ArrayList[MonotoneMountain]
segments = orderSegments
// Build a list of x-monotone mountains
private def createMountains {
for(s <- segments) {
if(s.mPoints.size > 2) {
val mountain = new MonotoneMountain
// TODO: Optomize sort? The number of points should be
// fairly small => insertion or merge?
val points = s.mPoints.toList
for(p <- points.sort((e1,e2) => e1 < e2)) {
mountain += p.clone
}
if(mountain.size > 2) {
mountain.triangulate
//mountain.monoPoly
xMonoPoly += mountain
}
}
}
}
// Trim off the extraneous trapezoids surrounding the polygon
private def trim = {
val traps = new ArrayList[Trapezoid]
// Mark outside trapezoids
for(t <- trapezoidalMap.map) {
if(t.top == boundingBox.top || t.bottom == boundingBox.bottom) {
t.outside = true
t.markNeighbors
}
}
// Collect interior trapezoids
for(t <- trapezoidalMap.map) if(!t.outside) {
traps += t
t.mark
}
traps
}
private def orderSegments = {
// Ignore vertical segments!
val segs = new ArrayList[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
} else if(s.p.x < s.q.x) {
segs += s
}
}
Random.shuffle(segs)
}
}

148
src/org/poly2tri/Util.scala Normal file
View File

@ -0,0 +1,148 @@
/* Poly2Tri
* Copyright (c) 2009, Mason Green
* http://code.google.com/p/poly2tri/
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Poly2Tri nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.poly2tri
import collection.jcl.ArrayList
import org.villane.vecmath.Vector2
object Util {
final def rotateLeft90(v:Vector2) = new Vector2( -v.y, v.x )
final def rotateRight90(v:Vector2) = new Vector2(v.y, -v.x )
final def rotate(v:Vector2, angle:Float) = {
val cos = Math.cos(angle).asInstanceOf[Float]
val sin = Math.sin(angle).asInstanceOf[Float]
val u = new Vector2((cos * v.x) - (sin * v.y), (cos * v.y) + (sin * v.x))
u
}
final def clamp(a: Float, low: Float, high: Float) =
if (a < low) low
else if (a > high) high
else a
final def left(a: Vector2, b: Vector2, c: Vector2) =
((b.x - a.x)*(c.y - a.y) - (c.x - a.x)*(b.y - a.y) > 0)
/** Melkman's Algorithm
* www.ams.sunysb.edu/~jsbm/courses/345/melkman.pdf
* Return a convex hull in ccw order
*/
def hull(V: Array[Vector2]) = {
val n = V.length
val D = new Array[Vector2](2 * n + 1)
var bot = n - 2
var top = bot + 3
D(bot) = V(2)
D(top) = V(2)
if (left(V(0), V(1), V(2))) {
D(bot+1) = V(0)
D(bot+2) = V(1)
} else {
D(bot+1) = V(1)
D(bot+2) = V(0)
}
var i = 3
while(i < n) {
while (left(D(bot), D(bot+1), V(i)) && left(D(top-1), D(top), V(i))) {
i += 1
}
while (!left(D(top-1), D(top), V(i))) top -= 1
top += 1; D(top) = V(i)
while (!left(D(bot), D(bot+1), V(i))) bot += 1
bot -= 1; D(bot) = V(i)
i += 1
}
val H = new Array[Vector2](top - bot)
var h = 0
while(h < (top - bot)) {
H(h) = D(bot + h)
h += 1
}
H
}
def svgToWorld(points: Array[Float], scale: Float) = {
val verts = new Array[Vector2](points.length/2)
var i = 0
while(i < verts.length) {
verts(i) = worldPoint(points(i*2), points(i*2+1), scale)
i += 1
}
verts
}
def worldPoint(x: Float, y: Float, scale: Float) = {
val p = Vector2(x*scale, y*scale)
p
}
}
/** The object <code>Random</code> offers a default implementation
* of scala.util.Random and random-related convenience methods.
*
* @since 2.8
*/
object Random extends scala.util.Random {
/** Returns a new sequence in random order.
* @param seq the sequence to shuffle
* @return the shuffled sequence
*/
def shuffle[T](buf: ArrayList[T]): ArrayList[T] = {
// It would be better if this preserved the shape of its container, but I have
// again been defeated by the lack of higher-kinded type inference. I can
// only make it work that way if it's called like
// shuffle[Int,List](List.range(0,100))
// which nicely defeats the "convenience" portion of "convenience method".
def swap(i1: Int, i2: Int) {
val tmp = buf(i1)
buf(i1) = buf(i2)
buf(i2) = tmp
}
for (n <- buf.length to 2 by -1) {
val k = nextInt(n)
swap(n - 1, k)
}
buf
}
}

View File

@ -0,0 +1,52 @@
/* Poly2Tri
* Copyright (c) 2009, Mason Green
* http://code.google.com/p/poly2tri/
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Poly2Tri nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.poly2tri
class XNode(point: Point, lChild: Node, rChild: Node) extends Node(lChild, rChild) {
override def locate(s: Segment): Sink = {
if(s.p.x > point.x) {
// Move to the right in the graph
return right.locate(s)
} else if(s.p.x == point.x) {
if(point.y > s.p.y) {
s.left = point.segment.below
} else {
s.left = point.segment.above
}
return right.locate(s)
} else {
// Move to the left in the graph
return left.locate(s)
}
}
}

View File

@ -0,0 +1,53 @@
/* Poly2Tri
* Copyright (c) 2009, Mason Green
* http://code.google.com/p/poly2tri/
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Poly2Tri nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.poly2tri
class YNode(segment: Segment, lChild: Node, rChild: Node) extends Node(lChild, rChild) {
override def locate(s: Segment): Sink = {
if (segment > s.p) {
// Move down the graph
right.locate(s)
} else if (segment < s.p){
// Move up the graph
left.locate(s)
} else {
// s and segment share the same endpoint
if (s.slope < segment.slope) {
// Move down the graph
return right.locate(s)
} else {
// Move up the graph
return left.locate(s)
}
}
}
}