diff --git a/data/basic.dat b/data/basic.dat new file mode 100644 index 0000000..9088255 --- /dev/null +++ b/data/basic.dat @@ -0,0 +1,5 @@ +100.0 100.0 +-100.0 100.0 +-100.0 -100.0 +0.0 0.0 +100.0 -100.0 \ No newline at end of file diff --git a/python/framework/framework.pyx b/python/framework/framework.pyx index f3d2ac3..eaa5ed2 100644 --- a/python/framework/framework.pyx +++ b/python/framework/framework.pyx @@ -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) diff --git a/python/poly2tri.py b/python/poly2tri.py index 7400aaa..73570ec 100644 --- a/python/poly2tri.py +++ b/python/poly2tri.py @@ -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,11 +30,20 @@ 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") points = [] diff --git a/python/seidel.py b/python/seidel.py index 793be25..367d45f 100644 --- a/python/seidel.py +++ b/python/seidel.py @@ -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): @@ -76,23 +77,21 @@ 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 @@ -117,12 +116,13 @@ class Edge(object): return (a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x) class Trapezoid(object): - + def __init__(self, left_point, right_point, top, bottom): self.left_point = left_point 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,26 +132,26 @@ class Trapezoid(object): def update_left(self, ul, ll): self.upper_left = ul - self.lower_left = ll if ul != None: ul.upper_right = self - if ll != None: ll.lower_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 - if lr != None: lr.lower_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): if self.inside: self.inside = False @@ -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): @@ -214,13 +213,11 @@ class Triangulator(object): # Build the trapezoidal map and query graph def process(self): 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 + traps = self.query_graph.follow_edge(edge) 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,19 +234,22 @@ 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() + + #TODO remove invalid/extra trapezoids + #print len(self.trapezoidal_map.map) - # Mark outside trapezoids - for t in 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() - + # Generate the triangles self.create_mountains() @@ -260,11 +260,10 @@ class Triangulator(object): return polies def create_mountains(self): - for edge in self.edge_list: - if len(edge.mpoints) > 0: + for edge in self.edge_list: + 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,21 +406,23 @@ 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): def __init__(self, left, right): @@ -533,9 +530,8 @@ class QueryGraph: qNode = XNode(edge.q, yNode, isink(tlist[2])) self.replace(sink, qNode) - PI_SLOP = 3.1 - + class MonotoneMountain: def __init__(self): @@ -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 - return True + if self.positive != (self.angle(p) >= 0): + return False + return True \ No newline at end of file diff --git a/scala/src/org/poly2tri/Poly2Tri.scala b/scala/src/org/poly2tri/Poly2Tri.scala index e2cce5e..466bbd5 100644 --- a/scala/src/org/poly2tri/Poly2Tri.scala +++ b/scala/src/org/poly2tri/Poly2Tri.scala @@ -79,16 +79,17 @@ 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 // The current algorithm @@ -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,7 +288,8 @@ 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 if(c == 't') drawMap = !drawMap @@ -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) diff --git a/scala/src/org/poly2tri/seidel/MonotoneMountain.scala b/scala/src/org/poly2tri/seidel/MonotoneMountain.scala index 00b09ff..7b587ea 100644 --- a/scala/src/org/poly2tri/seidel/MonotoneMountain.scala +++ b/scala/src/org/poly2tri/seidel/MonotoneMountain.scala @@ -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 } diff --git a/scala/src/org/poly2tri/seidel/Triangulator.scala b/scala/src/org/poly2tri/seidel/Triangulator.scala index 3de449b..59e1b87 100644 --- a/scala/src/org/poly2tri/seidel/Triangulator.scala +++ b/scala/src/org/poly2tri/seidel/Triangulator.scala @@ -112,7 +112,7 @@ class Triangulator(points: ArrayBuffer[Point]) { i += 1 } - + // Mark outside trapezoids for(t <- trapezoidalMap.map) markOutside(t) @@ -147,7 +147,7 @@ class Triangulator(points: ArrayBuffer[Point]) { val s = segmentList(i) if(s.mPoints.size > 0) { - + val mountain = new MonotoneMountain var k: List[Point] = null @@ -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)