fixed Python Seidel algo

This commit is contained in:
zzzzrrr 2009-11-18 13:58:59 -05:00
parent bdcfc0eb04
commit 48805d55db
7 changed files with 152 additions and 133 deletions

5
data/basic.dat Normal file
View File

@ -0,0 +1,5 @@
100.0 100.0
-100.0 100.0
-100.0 -100.0
0.0 0.0
100.0 -100.0

View File

@ -14,7 +14,7 @@ SEGMENTS = 25
INCREMENT = 2.0 * PI / SEGMENTS
def init_gl(width, height):
#glEnable(GL_LINE_SMOOTH)
glEnable(GL_LINE_SMOOTH)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glClearColor(0.0, 0.0, 0.0, 0.0)

View File

@ -5,22 +5,24 @@ from seidel import Triangulator
class Poly2Tri(Game):
#Screen size
screen_size = 800.0, 600.0
def __init__(self):
super(Poly2Tri, self).__init__(*self.screen_size)
# Load point set
file_name = "../data/star.dat"
points = self.load_points(file_name)
file_name = "../data/dude.dat"
self.points = self.load_points(file_name)
# Triangulate
t1 = self.time
seidel = Triangulator(points)
seidel = Triangulator(self.points)
dt = (self.time - t1) * 1000.0
self.triangles = seidel.triangles()
dt = self.time - t1
print "time = %f , num triangles = %d" % (dt, len(self.triangles))
self.trapezoids = seidel.trapezoids
#self.trapezoids = seidel.trapezoidal_map.map
print "time (ms) = %f , num triangles = %d" % (dt, len(self.triangles))
self.main_loop()
@ -28,10 +30,19 @@ class Poly2Tri(Game):
pass
def render(self):
reset_zoom(1.0, (0,0), self.screen_size)
reset_zoom(2.0, (400, 500), self.screen_size)
red = 255, 0, 0
for t in self.triangles:
draw_polygon(t, red)
green = 0, 255, 0
#draw_polygon(self.points, green)
'''
yellow = 255, 255, 0
for t in self.trapezoids:
#verts = self.trapezoids[key].vertices()
verts = t.vertices()
draw_polygon(verts, yellow)
'''
def load_points(self, file_name):
infile = open(file_name, "r")

View File

@ -32,10 +32,11 @@
from random import shuffle
from math import atan2, floor
###
### Based on Raimund Seidel'e paper "A simple and fast incremental randomized
### algorithm for computing trapezoidal decompositions and for triangulating polygons"
### (Ported from poly2tri)
##
## Based on Raimund Seidel'e paper "A simple and fast incremental randomized
## algorithm for computing trapezoidal decompositions and for triangulating polygons"
## (Ported from poly2tri)
##
class Point(object):
@ -77,22 +78,20 @@ class Point(object):
def less(self, p):
return self.x < p.x
def not_equal(self, p):
return not (p.x == self.x and p.y == self.y)
def clone(self):
return Point(self.x, self.y)
class Edge(object):
mpoints = []
above, below = None, None
def __init__(self, p, q):
self.p = p
self.q = q
self.slope = 0.0 if (q.x - p.x) == 0 else (q.y - p.y)/(q.x - p.x)
self.slope = 0.0 if (q.x - p.x) == 0.0 else (q.y - p.y)/(q.x - p.x)
self.b = p.y - (p.x * self.slope)
self.above, self.below = None, None
self.mpoints = []
self.mpoints.append(p)
self.mpoints.append(q)
def is_above(self, point):
return (floor(point.y) < floor(self.slope * point.x + self.b))
@ -105,10 +104,10 @@ class Edge(object):
b = self.q
a1 = self.signed_area(a, b, d)
a2 = self.signed_area(a, b, c)
if a1 != 0 and a2 != 0 and (a1 * a2) < 0:
if a1 != 0.0 and a2 != 0.0 and (a1 * a2) < 0.0:
a3 = self.signed_area(c, d, a)
a4 = a3 + a2 - a1
if a3 * a4 < 0:
if a3 * a4 < 0.0:
t = a3 / (a3 - a4)
return a + ((b - a) * t)
return 0.0
@ -123,6 +122,7 @@ class Trapezoid(object):
self.right_point = right_point
self.top = top
self.bottom = bottom
self.hash = hash(self)
self.upper_left = None
self.upper_right = None
self.lower_left = None
@ -132,24 +132,24 @@ class Trapezoid(object):
def update_left(self, ul, ll):
self.upper_left = ul
self.lower_left = ll
if ul != None: ul.upper_right = self
self.lower_left = ll
if ll != None: ll.lower_right = self
def update_right(self, ur, lr):
self.upper_right = ur
self.lower_right = lr
if ur != None: ur.upper_left = self
self.lower_right = lr
if lr != None: lr.lower_left = self
def update_left_right(self, ul, ll, ur, lr):
self.upper_left = ul
self.lower_left = ll
self.upper_right = ur
self.lower_right = lr
if ul != None: ul.upper_right = self
self.lower_left = ll
if ll != None: ll.lower_right = self
self.upper_right = ur
if ur != None: ur.upper_left = self
self.lower_right = lr
if lr != None: lr.lower_left = self
def trim_neighbors(self):
@ -165,12 +165,11 @@ class Trapezoid(object):
self.top.is_above(point) and self.bottom.is_below(point))
def vertices(self):
verts = []
verts.append(line_intersect(self.top, self.left_point.x))
verts.append(line_intersect(self.bottom, self.left_point.x))
verts.append(line_intersect(self.bottom, self.right_point.x))
verts.append(line_intersect(self.top, self.right_point.x))
return verts
v1 = line_intersect(self.top, self.left_point.x)
v2 = line_intersect(self.bottom, self.left_point.x)
v3 = line_intersect(self.bottom, self.right_point.x)
v4 = line_intersect(self.top, self.right_point.x)
return v1, v2, v3, v4
def add_points(self):
if self.left_point != self.bottom.p:
@ -184,19 +183,19 @@ class Trapezoid(object):
def line_intersect(edge, x):
y = edge.slope * x + edge.b
return Point(x, y)
return x, y
class Triangulator(object):
def __init__(self, poly_line):
self.polygons = []
self.edge_list = self.init_edges(poly_line)
self.trapezoids = []
self.xmono_poly = []
self.edge_list = self.init_edges(poly_line)
self.trapezoidal_map = TrapezoidalMap()
self.bounding_box = self.trapezoidal_map.bounding_box(self.edge_list)
self.query_graph = QueryGraph(isink(self.bounding_box))
self.xmono_poly = []
self.process()
def triangles(self):
@ -216,11 +215,9 @@ class Triangulator(object):
for edge in self.edge_list:
traps = self.query_graph.follow_edge(edge)
for t in traps:
try:
self.trapezoidal_map.map.remove(t)
except:
pass
for t in traps:
# Remove old trapezods
del self.trapezoidal_map.map[t.hash]
# Bisect old trapezoids and create new
cp = t.contains(edge.p)
cq = t.contains(edge.q)
if cp and cq:
@ -237,15 +234,18 @@ class Triangulator(object):
self.query_graph.case4(t.sink, edge, tlist)
# Add new trapezoids to map
for t in tlist:
self.trapezoidal_map.map.append(t)
self.trapezoidal_map.map[t.hash] = t
self.trapezoidal_map.clear()
# Mark outside trapezoids
for t in self.trapezoidal_map.map:
#TODO remove invalid/extra trapezoids
#print len(self.trapezoidal_map.map)
# Mark outside trapezoids w/ depth-first search
for k, t in self.trapezoidal_map.map.items():
self.mark_outside(t)
# Collect interior trapezoids
for t in self.trapezoidal_map.map:
for k, t in self.trapezoidal_map.map.items():
if t.inside:
self.trapezoids.append(t)
t.add_points()
@ -261,10 +261,9 @@ class Triangulator(object):
def create_mountains(self):
for edge in self.edge_list:
if len(edge.mpoints) > 0:
if len(edge.mpoints) > 2:
mountain = MonotoneMountain()
k = merge_sort(edge.mpoints)
points = [edge.p] + k + [edge.q]
points = merge_sort(edge.mpoints)
for p in points:
mountain.add(p)
mountain.process()
@ -296,7 +295,7 @@ class Triangulator(object):
edges.append(Edge(q, p))
elif p.x < q.x:
edges.append(Edge(p, q))
shuffle(edges)
#shuffle(edges)
return edges
def shear_transform(self, point):
@ -323,18 +322,17 @@ def merge_sort(l):
class TrapezoidalMap(object):
map = []
margin = 50
bcross = None
tcross = None
def __init__(self):
self.map = {}
self.margin = 50.0
self.bcross = None
self.tcross = None
def clear(self):
self.bcross = None
self.tcross = None
map = []
def case1(self, trapezoid, edge):
t = trapezoid; e = edge
def case1(self, t, e):
trapezoids = []
trapezoids.append(Trapezoid(t.left_point, e.p, t.top, t.bottom))
trapezoids.append(Trapezoid(e.p, e.q, t.top, e))
@ -346,8 +344,7 @@ class TrapezoidalMap(object):
trapezoids[3].update_right(t.upper_right, t.lower_right)
return trapezoids
def case2(self, trapezoid, edge):
t = trapezoid; e = edge
def case2(self, t, e):
rp = e.q if e.q.x == t.right_point.x else t.right_point
trapezoids = []
trapezoids.append(Trapezoid(t.left_point, e.p, t.top, t.bottom))
@ -362,8 +359,7 @@ class TrapezoidalMap(object):
e.below = trapezoids[2]
return trapezoids
def case3(self, trapezoid, edge):
t = trapezoid; e = edge
def case3(self, t, e):
lp = e.p if e.p.x == t.left_point.x else t.left_point
rp = e.q if e.q.x == t.right_point.x else t.right_point
trapezoids = []
@ -387,8 +383,7 @@ class TrapezoidalMap(object):
e.below = trapezoids[1]
return trapezoids
def case4(self, trapezoid, edge):
t = trapezoid; e = edge
def case4(self, t, e):
lp = e.p if e.p.x == t.left_point.x else t.left_point
trapezoids = []
if self.tcross is t.top:
@ -411,20 +406,22 @@ class TrapezoidalMap(object):
margin = self.margin
max = edges[0].p + margin
min = edges[0].q - margin
for edge in edges:
if edge.p.x > max.x: max = Point(edge.p.x + margin, max.y)
if edge.p.y > max.y: max = Point(max.x, edge.p.y + margin)
if edge.q.x > max.x: max = Point(edge.q.x + margin, max.y)
if edge.q.y > max.y: max = Point(max.x, edge.q.y + margin)
if edge.p.x < min.x: min = Point(edge.p.x - margin, min.y)
if edge.p.y < min.y: min = Point(min.x, edge.p.y - margin)
if edge.q.x < min.x: min = Point(edge.q.x - margin, min.y)
if edge.q.y < min.y: min = Point(min.x, edge.q.y - margin)
for e in edges:
if e.p.x > max.x: max = Point(e.p.x + margin, max.y)
if e.p.y > max.y: max = Point(max.x, e.p.y + margin)
if e.q.x > max.x: max = Point(e.q.x + margin, max.y)
if e.q.y > max.y: max = Point(max.x, e.q.y + margin)
if e.p.x < min.x: min = Point(e.p.x - margin, min.y)
if e.p.y < min.y: min = Point(min.x, e.p.y - margin)
if e.q.x < min.x: min = Point(e.q.x - margin, min.y)
if e.q.y < min.y: min = Point(min.x, e.q.y - margin)
top = Edge(Point(min.x, max.y), Point(max.x, max.y))
bottom = Edge(Point(min.x, min.y), Point(max.x, min.y))
left = bottom.p
right = top.q
return Trapezoid(left, right, top, bottom)
trap = Trapezoid(left, right, top, bottom)
self.map[hash(trap)] = trap
return trap
class Node(object):
@ -533,7 +530,6 @@ class QueryGraph:
qNode = XNode(edge.q, yNode, isink(tlist[2]))
self.replace(sink, qNode)
PI_SLOP = 3.1
class MonotoneMountain:
@ -549,17 +545,17 @@ class MonotoneMountain:
self.convex_polies = []
def add(self, point):
if self.size == 0:
if self.size is 0:
self.head = point
self.size += 1
elif self.size == 1:
if point.not_equal(self.head):
self.size = 1
elif self.size is 1:
if point != self.head:
self.tail = point
self.tail.prev = self.head
self.head.next = self.tail
self.size += 1
self.size = 2
else:
if point.not_equal(self.tail):
if point != self.tail:
self.tail.next = point
point.prev = self.tail
self.tail = point
@ -576,9 +572,9 @@ class MonotoneMountain:
self.positive = self.angle_sign()
self.gen_mono_poly()
p = self.head.next
while p is not self.tail:
while p != self.tail:
a = self.angle(p)
if a >= PI_SLOP or a <= -PI_SLOP:
if a >= PI_SLOP or a <= -PI_SLOP or a == 0:
self.remove(p)
elif self.is_convex(p):
self.convex_points.append(p)
@ -586,16 +582,18 @@ class MonotoneMountain:
self.triangulate()
def triangulate(self):
while len(self.convex_points) > 0:
while self.convex_points:
ear = self.convex_points.pop(0)
a = ear.prev
b = ear
c = ear.next
triangle = [a, b, c]
triangle = (a, b, c)
self.triangles.append(triangle)
self.remove(ear)
if self.valid(a): self.convex_points.append(a)
if self.valid(c): self.convex_points.append(c)
if self.valid(a):
self.convex_points.append(a)
if self.valid(c):
self.convex_points.append(c)
assert(self.size <= 3, "Triangulation bug, please report")
def valid(self, p):
@ -618,5 +616,6 @@ class MonotoneMountain:
return atan2(a.cross(b), a.dot(b)) >= 0
def is_convex(self, p):
if self.positive != (self.angle(p) >= 0): return False
if self.positive != (self.angle(p) >= 0):
return False
return True

View File

@ -79,15 +79,16 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
var drawSegs = true
var drawCDTMesh = false
val nazcaMonkey = "data/nazca_monkey.dat"
val nazcaHeron = "data/nazca_heron_old.dat"
val bird = "data/bird.dat"
val snake = "data/i.snake"
val star = "data/star.dat"
val strange = "data/strange.dat"
val i18 = "data/i.18"
val tank = "data/tank.dat"
val dude = "data/dude.dat"
val nazcaMonkey = "../data/nazca_monkey.dat"
val nazcaHeron = "../data/nazca_heron_old.dat"
val bird = "../data/bird.dat"
val snake = "../data/i.snake"
val star = "../data/star.dat"
val strange = "../data/strange.dat"
val i18 = "../data/i.18"
val tank = "../data/tank.dat"
val dude = "../data/dude.dat"
val basic = "../data/basic.dat"
var currentModel = dude
var doCDT = true
@ -207,7 +208,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
}
}
if(currentModel == "data/dude.dat" && drawSegs) {
if(currentModel == "../data/dude.dat" && drawSegs) {
g.setColor(green)
for(i <- 0 until chestSegs.size) {
val s = chestSegs(i)
@ -287,6 +288,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
if(c == '7') selectModel(nazcaHeron)
if(c == '8') selectModel(tank)
if(c == '9') selectModel(dude)
if(c == '0') selectModel(basic)
if(c == 'd') drawSegs = !drawSegs
if(c == 'm') drawCDTMesh = !drawCDTMesh
@ -303,33 +305,36 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
def selectModel(model: String) {
model match {
case "data/nazca_monkey.dat" =>
case "../data/nazca_monkey.dat" =>
val clearPoint = Point(418, 282)
loadModel(nazcaMonkey, 4.5f, Point(400, 300), 1500, clearPoint)
case "data/bird.dat" =>
case "../data/bird.dat" =>
val clearPoint = Point(400, 300)
loadModel(bird, 25f, Point(400, 300), 350, clearPoint)
case "data/i.snake" =>
case "../data/i.snake" =>
val clearPoint = Point(336f, 196f)
loadModel(snake, 10f, Point(600, 300), 10, clearPoint)
case "data/star.dat" =>
case "../data/star.dat" =>
val clearPoint = Point(400, 204)
loadModel(star, -1f, Point(0f, 0f), 10, clearPoint)
case "data/strange.dat" =>
case "../data/strange.dat" =>
val clearPoint = Point(400, 268)
loadModel(strange, -1f, Point(0f, 0f), 15, clearPoint)
case "data/i.18" =>
case "../data/i.18" =>
val clearPoint = Point(510, 385)
loadModel(i18, 20f, Point(600f, 500f), 20, clearPoint)
case "data/nazca_heron_old.dat" =>
case "../data/nazca_heron_old.dat" =>
val clearPoint = Point(85, 290)
loadModel(nazcaHeron, 4.2f, Point(400f, 300f), 1500, clearPoint)
case "data/tank.dat" =>
case "../data/tank.dat" =>
val clearPoint = Point(450, 350)
loadModel(tank, -1f, Point(100f, 0f), 10, clearPoint)
case "data/dude.dat" =>
case "../data/dude.dat" =>
val clearPoint = Point(365, 427)
loadModel(dude, -1f, Point(100f, -200f), 10, clearPoint)
case "../data/basic.dat" =>
val clearPoint = Point(365, 427)
loadModel(basic, -1f, Point(400f, 300f), 10, clearPoint)
case _ =>
}
currentModel = model
@ -378,7 +383,7 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
slCDT = new CDT(pts, clearPoint)
// 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 chestHole = Array(Point(320.72342f,480f), Point(338.90617f,465.96863f),
@ -443,8 +448,8 @@ class Poly2TriDemo extends BasicGame("Poly2Tri") {
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 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)

View File

@ -101,10 +101,10 @@ class MonotoneMountain {
while(p != tail) {
val a = angle(p)
// If the point is almost colinear with it's neighbor, remove it!
if(a >= PI_SLOP || a <= -PI_SLOP)
remove(p)
else
if(convex(p)) convexPoints += p
if(a >= PI_SLOP || a <= -PI_SLOP || a == 0.0)
remove(p)
else if(convex(p))
convexPoints += p
p = p.next
}

View File

@ -162,7 +162,6 @@ class Triangulator(points: ArrayBuffer[Point]) {
k = Util.msort((p1: Point, p2: Point) => p1 < p2)(s.mPoints.toList)
val points = s.p :: k ::: List(s.q)
var j = 0
while(j < points.size) {
mountain += points(j)