diff --git a/python/bc.sh b/python/bc.sh new file mode 100644 index 0000000..7bf4065 --- /dev/null +++ b/python/bc.sh @@ -0,0 +1,5 @@ +#!/bin/sh +touch framework/framework.pyx +rm framework/*.c +rm -rf build +python setup.py build_ext -i \ No newline at end of file diff --git a/python/framework/framework.pyx b/python/framework/framework.pyx new file mode 100644 index 0000000..5683b24 --- /dev/null +++ b/python/framework/framework.pyx @@ -0,0 +1,138 @@ +## +## GL convenience layer +## +from math import pi as PI + +from gl cimport * + +#from triangulator import Point + +include "triangulator.pyx" + +cdef extern from 'math.h': + double cos(double) + double sin(double) + +SEGMENTS = 25 +INCREMENT = 2.0 * PI / SEGMENTS + +def init_gl(width, height): + #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) + glHint (GL_LINE_SMOOTH_HINT, GL_NICEST) + +def reset_zoom(float zoom, center, size): + + zinv = 1.0 / zoom + left = -size[0] * zinv + right = size[0] * zinv + bottom = -size[1] * zinv + top = size[1] * zinv + + # Reset viewport + glLoadIdentity() + glMatrixMode(GL_PROJECTION) + glLoadIdentity() + + # Reset ortho view + glOrtho(left, right, bottom, top, 1, -1) + glTranslatef(-center[0], -center[1], 0) + glMatrixMode(GL_MODELVIEW) + glDisable(GL_DEPTH_TEST) + glLoadIdentity() + + # Clear the screen + glClear(GL_COLOR_BUFFER_BIT) + +def draw_polygon(verts, color): + r, g, b = color + glColor3f(r, g, b) + glBegin(GL_LINE_LOOP) + for v in verts: + glVertex2f(v[0], v[1]) + glEnd() + +## +## Game engine / main loop / UI +## +from glfw cimport * + +import sys + +cdef extern from 'math.h': + double cos(double) + double sin(double) + double sqrt(double) + +# Keyboard callback wrapper +kbd_callback_method = None + +cdef extern void __stdcall kbd_callback(int id, int state): + kbd_callback_method(id, state) + + +cdef class Game: + + title = "Poly2Tri" + + def __init__(self, window_width, window_height): + + p1 = Point(12, 10) + p2 = Point(50, 47) + print p1.cross(p2) + + glfwInit() + + # 16 bit color, no depth, alpha or stencil buffers, windowed + if not glfwOpenWindow(window_width, window_height, 8, 8, 8, 8, 24, 0, GLFW_WINDOW): + glfwTerminate() + raise SystemError('Unable to create GLFW window') + + glfwEnable(GLFW_STICKY_KEYS) + glfwSwapInterval(1) #VSync on + + def register_kbd_callback(self, f): + global kbd_callback_method + glfwSetKeyCallback(kbd_callback) + kbd_callback_method = f + + def main_loop(self): + + frame_count = 1 + start_time = glfwGetTime() + + running = True + while running: + + current_time = glfwGetTime() + + #Calculate and display FPS (frames per second) + if (current_time - start_time) > 1 or frame_count == 0: + frame_rate = frame_count / (current_time - start_time) + t = self.title + " (%d FPS)" % frame_rate + glfwSetWindowTitle(t) + start_time = current_time + frame_count = 0 + + frame_count = frame_count + 1 + + # Check if the ESC key was pressed or the window was closed + running = ((not glfwGetKey(GLFW_KEY_ESC)) + and glfwGetWindowParam(GLFW_OPENED)) + + self.update() + self.render() + + glfwSwapBuffers() + + + glfwTerminate() + + property window_title: + def __set__(self, title): self.title = title + + property time: + def __get__(self): return glfwGetTime() + def __set__(self, t): glfwSetTime(t) \ No newline at end of file diff --git a/python/include/gl.pxd b/python/framework/gl.pxd similarity index 100% rename from python/include/gl.pxd rename to python/framework/gl.pxd diff --git a/python/include/glfw.pxd b/python/framework/glfw.pxd similarity index 100% rename from python/include/glfw.pxd rename to python/framework/glfw.pxd diff --git a/python/framework/triangulator.pyx b/python/framework/triangulator.pyx new file mode 100644 index 0000000..ad09313 --- /dev/null +++ b/python/framework/triangulator.pyx @@ -0,0 +1,496 @@ +# +# 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, +# self list of conditions and the following disclaimer. +# Redistributions in binary form must reproduce the above copyright notice, +# self 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 self 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. +# +from math import 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) + +cdef extern from 'math.h': + double cos(double) + double sin(double) + double sqrt(double) + +cdef list merge_sort(list l): + cdef list lleft, lright + cdef int p1, p2, p + if len(l)>1 : + lleft = merge_sort(l[:len(l)/2]) + lright = merge_sort(l[len(l)/2:]) + #do merge here + p1,p2,p = 0,0,0 + while p1 p.y: + return False + else: + if x < p.x: + return True + else: + return False + ''' + + 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) + +cdef class Edge: + + cdef Point p, q + cdef bool above, below + cdef float slope, b + + mpoints = [] + + def __init__(self, Point p, Point q): + self.p = p + self.q = q + self.slope = (q.y - p.y)/(q.x - p.x) + self.b = p.y - (p.x * self.slope) + + property p: + def __get__(self): return self.p + + property q: + def __get__(self): return self.q + + property above: + def __get__(self): return self.above + + property below: + def __get__(self): return self.below + + cdef bool is_above(self, Point point): + return (floor(point.y) < floor(self.slope * point.x + self.b)) + cdef bool is_below(self, Point point): + return (floor(point.y) > floor(self.slope * point.x + self.b)) + + cdef float intersect(self, Point c, Point d): + cdef float a1, a2, a3, a4, t + cdef Point a, b + a = self.p + 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: + a3 = self.signed_area(c, d, a) + a4 = a3 + a2 - a1 + if a3 * a4 < 0: + t = a3 / (a3 - a4) + return a + ((b - a) * t) + return 0.0 + + cdef float signed_area(self, Point a, Point b, Point c): + return (a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x) + +cdef Point line_intersect(Edge e, float x): + cdef float y = e.slope * x + e.b + return Point(x, y) + +cdef class Trapezoid: + + cdef: + Point lpoint, rpoint + Edge top, bottom + Trapezoid upper_left, lower_left + Trapezoid upper_right, lower_right + bool inside + + sink = None + + def __init__(self, Point lpoint, Point rpoint, Edge top, Edge bottom): + self.lpoint = lpoint + self.rpoint = rpoint + self.top = top + self.bottom = bottom + self.upper_left = None + self.upper_right = None + self.lower_left = None + self.lower_right = None + self.inside = True + + property upper_left: + def __get__(self): return self.upper_left + def __set__(self, Trapezoid other): self.upper_left = other + + property upper_right: + def __get__(self): return self.upper_right + def __set__(self, Trapezoid other): self.upper_right = other + + property lower_left: + def __get__(self): return self.lower_left + def __set__(self, Trapezoid other): self.lower_left = other + + property lower_right: + def __get__(self): return self.lower_right + def __set__(self, Trapezoid other): self.lower_right = other + + def update_left(self, Trapezoid ul, Trapezoid ll): + self.upper_left = ul + self.lower_left = ll + if ul != None: ul.upper_right = self + if ll != None: ll.lower_right = self + + def update_right(self, Trapezoid ur, Trapezoid lr): + self.upper_right = ur + self.lower_right = lr + if ur != None: ur.upper_left = self + if lr != None: lr.lower_left = self + + def update_left_right(self, Trapezoid ul, Trapezoid ll, Trapezoid ur, Trapezoid lr): + self.upper_left = ul + self.lower_left = ll + self.upper_right = ur + self.lower_right = lr + if ul != None: ul.upper_right = self + if ll != None: ll.lower_right = self + if ur != None: ur.upper_left = self + if lr != None: lr.lower_left = self + + def trim_neighbors(self): + if self.inside: + self.inside = False + if self.upper_left != None: self.upper_left.trim_neighbors() + if self.lower_left != None: self.lower_left.trim_neighbors() + if self.upper_right != None: self.upper_right.trim_neighbors() + if self.lower_right != None: self.lower_right.trim_neighbors() + + def contains(self, Point point): + return (point.x > self.lpoint.x and point.x < self.rpoint.x and + self.top.is_above(point) and self.bottom.is_below(point)) + + def vertices(self): + cdef list verts = [] + verts.append(line_intersect(self.top, self.lpoint.x)) + verts.append(line_intersect(self.bottom, self.lpoint.x)) + verts.append(line_intersect(self.bottom, self.rpoint.x)) + verts.append(line_intersect(self.top, self.rpoint.x)) + return verts + + def add_points(self): + if self.lpoint != self.bottom.p: + self.bottom.mpoints.append(self.lpoint.clone) + if self.rpoint != self.bottom.q: + self.bottom.mpoints.append(self.rpoint.clone) + if self.lpoint != self.top.p: + self.top.mpoints.append(self.lpoint.clone) + if self.rpoint != self.top.q: + self.top.mpoints.append(self.rpoint.clone) + +class TrapezoidalMap: + + map = {} + margin = 50 + bcross = None + tcross = None + + def clear(self): + self.bcross = None + self.tcross = None + + def case1(self, t, e): + trapezoids = [None, None, None, None] + trapezoids.append(Trapezoid(t.lpoint, e.p, t.top, t.bottom)) + trapezoids.append(Trapezoid(e.p, e.q, t.top, e)) + trapezoids.append(Trapezoid(e.p, e.q, e, t.bottom)) + trapezoids.append(Trapezoid(e.q, t.rpoint, t.top, t.bottom)) + trapezoids[0].update_left(t.upper_left, t.lower_left) + trapezoids[1].update_left_right(trapezoids[0], None, trapezoids[3], None) + trapezoids[2].update_left_right(None, trapezoids[0], None, trapezoids[3]) + trapezoids[3].update_right(t.upper_right, t.lower_right) + return trapezoids + + def case2(self, t, e): + rp = e.q if e.q.x == t.rpoint.x else t.rpoint + trapezoids = [None, None, None] + trapezoids.append(Trapezoid(t.lpoint, e.p, t.top, t.bottom)) + trapezoids.append(Trapezoid(e.p, rp, t.top, e)) + trapezoids.append(Trapezoid(e.p, rp, e, t.bottom)) + trapezoids[0].update_left(t.upper_left, t.lower_left) + trapezoids[1].update_left_right(trapezoids[0], None, t.upper_right, None) + trapezoids[2].update_left_right(None, trapezoids[0], None, t.lower_right) + self.bcross = t.bottom + self.tcross = t.top + e.above = trapezoids[1] + e.below = trapezoids[2] + return trapezoids + + def case3(self, t, e): + lp = e.p if e.p.x == t.lpoint.x else t.lpoint + rp = e.q if e.q.x == t.rpoint.x else t.rpoint + trapezoids = [None, None] + if self.tcross is t.top: + trapezoids[0] = t.upper_left + trapezoids[0].update_right(t.upper_right, None) + trapezoids[0].rpoint = rp + else: + trapezoids[0] = Trapezoid(lp, rp, t.top, e) + trapezoids[0].update_left_right(t.upper_left, e.above, t.upper_right, None) + if self.bcross is t.bottom: + trapezoids[1] = t.lower_left + trapezoids[1].update_right(None, t.lower_right) + trapezoids[1].rpoint = rp + else: + trapezoids[1] = Trapezoid(lp, rp, e, t.bottom) + trapezoids[1].update_left_right(e.below, t.lower_left, None, t.lower_right) + self.bcross = t.bottom + self.tcross = t.top + e.above = trapezoids[0] + e.below = trapezoids[1] + return trapezoids + + def case4(self, t, e): + lp = e.p if e.p.x == t.lpoint.x else t.lpoint + trapezoids = [None, None, None] + if self.tcross is t.top: + trapezoids[0] = t.upper_left + trapezoids[0].rpoint = e.q + else: + trapezoids[0] = Trapezoid(lp, e.q, t.top, e) + trapezoids[0].update_left(t.upper_left, e.above) + if self.bcross is t.bottom: + trapezoids[1] = t.lower_left + trapezoids[1].rpoint = e.q + else: + trapezoids[1] = Trapezoid(lp, e.q, e, t.bottom) + trapezoids[1].update_left(e.below, t.lower_left) + trapezoids[2] = Trapezoid(e.q, t.rpoint, t.top, t.bottom) + trapezoids[2].update_left_right(trapezoids[0], trapezoids[1], t.upper_right, t.lower_right) + + return trapezoids + + def bounding_box(self, edges): + margin = self.margin + max = edges[0].p + margin + min = edges[0].q - 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) + +class Node: + + parent_list = [] + + def __init__(self, left, right): + self.left = left + self.right = right + if left is not None: + left.parent_list.append(self) + if right is not None: + right.parent_list.append(self) + + def replace(self, node): + for parent in node.parent_list: + if parent.left is node: + parent.left = self + else: + parent.right = self + self.parent_list.append(parent) + +class Sink(Node): + + def __new__(cls, trapezoid): + if trapezoid.sink is not None: + return trapezoid.sink + else: + return Sink(trapezoid) + + def __init__(self, trapezoid): + Node.__init__(self, None, None) + trapezoid.sink = self + + def locate(self, e): + return self + +class XNode(Node): + + def __init__(self, point, lchild, rchild): + Node.__init__(self, lchild, rchild) + self.point = point + self.lchild = lchild + self.rchild = rchild + + def locate(self, e): + if e.p.x >= self.point.x: + return self.right.locate(e) + else: + return self.left.locate(e) + +class YNode(Node): + + def __init__(self, edge, lchild, rchild): + Node.__init__(self, lchild, rchild) + self.edge = edge + self.lchild = lchild + self.rchild = rchild + + def locate(self, e): + if self.edge.is_above(e.p): + return self.right.locate(e) + elif self.edge.is_below(e.p): + return self.left.locate(e) + else: + if e.slope < self.edge.slope: + return self.right.locate(e) + else: + return self.left.locate(e) + +class QueryGraph: + + head = None + + def __init__(self, head): + self.head = head + + def locate(self, e): + return self.head.locate(e).trapezoid + + def follow_segment(self, e): + trapezoids = [self.locate(e)] + j = 0 + while(e.q.x > trapezoids[j].right_point.x): + if e > trapezoids[j].right_point: + trapezoids.append(trapezoids[j].upper_right) + else: + trapezoids .append(trapezoids[j].lower_right) + j += 1 + return trapezoids + + def replace(self, sink, node): + if not sink.parent_list: + self.head = node + else: + node.replace(sink) + + def case1(self, sink, e, tlist): + yNode = YNode(e, Sink(tlist[1]), Sink(tlist[2])) + qNode = XNode(e.q, yNode, Sink(tlist[3])) + pNode = XNode(e.p, Sink(tlist[0]), qNode) + self.replace(sink, pNode) + + def case2(self, sink, e, tlist): + yNode = YNode(e, Sink(tlist[1]), Sink(tlist[2])) + pNode = XNode(e.p, Sink(tlist[0]), yNode) + self.replace(sink, pNode) + + def case3(self, sink, e, tlist): + yNode = YNode(e, Sink(tlist[0]), Sink(tlist[1])) + self.replace(sink, yNode) + + def case4(self, sink, e, tlist): + yNode = YNode(e, Sink(tlist[0]), Sink(tlist[1])) + qNode = XNode(e.q, yNode, Sink(tlist[2])) + self.replace(sink, qNode) + diff --git a/python/include/framework.pyx b/python/include/framework.pyx deleted file mode 100644 index d64c479..0000000 --- a/python/include/framework.pyx +++ /dev/null @@ -1 +0,0 @@ -import "triangulator.pyx" \ No newline at end of file diff --git a/python/include/triangulator.pyx b/python/include/triangulator.pyx deleted file mode 100644 index b0c8451..0000000 --- a/python/include/triangulator.pyx +++ /dev/null @@ -1,647 +0,0 @@ -/* 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. - */ -from math import floor - -### -### Based on Raimund Seidel's paper "A simple and fast incremental randomized -### algorithm for computing trapezoidal decompositions and for triangulating polygons" -### (Ported from poly2tri) - -class Triangulator(object) { - - def __init__(points): - - # Convex polygon list - self.polygons = [] - # Order and randomize the Edges - self.EdgeList = initEdges() - - # Initialize trapezoidal map and query structure - self.trapezoidalMap = new TrapezoidalMap - self.bounding_box = trapezoidalMap.bounding_box(EdgeList) - self.queryGraph = QueryGraph(Sink.init(bounding_box)) - self.xMonoPoly = [] - - # The trapezoidal map - self.trapezoidMap = trapezoidalMap.map - # Trapezoid decomposition list - self.trapezoids = [] - - self.process() - - // Build the trapezoidal map and query graph - def process(self): - - i = 0 - while(i < len(EdgeList)): - - s = EdgeList(i) - traps = queryGraph.followEdge(s) - - // Remove trapezoids from trapezoidal Map - for j in range(len(traps)): - trapezoidalMap.map -= traps(j) - - for j in range(len(traps)): - t = traps(j) - tList = [] - containsP = t.contains(s.p) - containsQ = t.contains(s.q) - if containsP and containsQ: - // Case 1 - tList = trapezoidalMap.case1(t,s) - queryGraph.case1(t.sink, s, tList) - elif containsP and !containsQ: - // Case 2 - tList = trapezoidalMap.case2(t,s) - queryGraph.case2(t.sink, s, tList) - elif !containsP and !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 map - for k in range(len(tList)): - trapezoidalMap.map += tList[k] - - trapezoidalMap.clear - i += 1 - - for t in trapezoidalMap.map - markOutside(t) - - for t in trapezoidalMap.map - if t.inside: - trapezoids.append(t) - t.addPoints() - - createMountains() - - } - - // Monotone polygons - these are monotone mountains - def monoPolies(self): - polies = [] - for i in range(len(self.xMonoPoly)): - polies.append(self.xMonoPoly(i).monoPoly) - return polies - - - // Build a list of x-monotone mountains - private def createMountains { - - var i = 0 - while(i < EdgeList.size) { - - val s = EdgeList(i) - - if(s.mPoints.size > 0) { - - mountain = MonotoneMountain() - - - if len(s.mPoints) < 10: - k = insertSort((p1: Point, p2: Point) => p1 < p2)(s.mPoints).toList - else - k = msort((p1: Point, p2: Point) => p1 < p2)(s.mPoints.toList) - - points = s.p :: k ::: List(s.q) - - for p in points: - mountain.add(p) - - mountain.process() - - // Extract the triangles into a single list - j = 0 - while(j < mountain.triangles.size) { - polygons += mountain.triangles(j) - j += 1 - } - - xMonoPoly += mountain - } - i += 1 - } - } - - // Mark the outside trapezoids surrounding the polygon - private def markOutside(t: Trapezoid) { - if(t.top == bounding_box.top || t.bottom == bounding_box.bottom) { - t trimNeighbors - } - } - - // Create Edges and connect end points; update edge event pointer - private def initEdges: ArrayBuffer[Edge] = { - var Edges = List[Edge]() - for(i <- 0 until points.size-1) - Edges = new Edge(points(i), points(i+1)) :: Edges - Edges = new Edge(points.first, points.last) :: Edges - orderEdges(Edges) - } - - private def orderEdges(Edges: List[Edge]) = { - - // Ignore vertical Edges! - val segs = new ArrayBuffer[Edge] - for(s <- Edges) { - val p = shearTransform(s.p) - val q = shearTransform(s.q) - // Point p must be to the left of point q - if(p.x > q.x) { - segs += new Edge(q, p) - } else if(p.x < q.x) { - segs += new Edge(p, q) - } - } - // Randomized triangulation improves performance - // See Seidel's paper, or O'Rourke's book, p. 57 - Random.shuffle(segs) - segs - } - - // Prevents any two distinct endpoints from lying on a common vertical line, and avoiding - // the degenerate case. See Mark de Berg et al, Chapter 6.3 - //val SHEER = 0.0001f - def shearTransform(point: Point) = Point(point.x + 0.0001f * point.y, point.y) - -} - -// Doubly linked list -class MonotoneMountain { - - var tail, head: Point = None - var size = 0 - - private val convexPoints = new ArrayBuffer[Point] - // Monotone mountain points - val monoPoly = new ArrayBuffer[Point] - // Triangles that constitute the mountain - val triangles = new ArrayBuffer[Array[Point]] - // Convex polygons that constitute the mountain - val convexPolies = new ArrayBuffer[Array[Point]] - // Used to track which side of the line we are on - private var positive = false - // Almost Pi! - private val PI_SLOP = 3.1 - - // Append a point to the list - def +=(point: Point) { - size match { - case 0 => - head = point - size += 1 - case 1 => - // Keep repeat points out of the list - if(point ! head) { - tail = point - tail.prev = head - head.next = tail - size += 1 - } - case _ => - // Keep repeat points out of the list - if(point ! tail) { - tail.next = point - point.prev = tail - tail = point - size += 1 - } - } - } - - // Remove a point from the list - def remove(point: Point) { - val next = point.next - val prev = point.prev - point.prev.next = next - point.next.prev = prev - size -= 1 - } - - // Partition a x-monotone mountain into triangles O(n) - // See "Computational Geometry in C", 2nd edition, by Joseph O'Rourke, page 52 - def process { - - // Establish the proper sign - positive = angleSign - // create monotone polygon - for dubug purposes - genMonoPoly - - // Initialize internal angles at each nonbase vertex - // Link strictly convex vertices into a list, ignore reflex vertices - var p = head.next - 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 - p = p.next - } - - triangulate - - } - - private def triangulate { - - while(!convexPoints.isEmpty) { - - val ear = convexPoints.remove(0) - 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(valid(a)) convexPoints += a - if(valid(c)) convexPoints += c - } - assert(size <= 3, "Triangulation bug, please report") - - } - - private def valid(p: Point) = (p != head && p != tail && convex(p)) - - // Create the monotone polygon - private def genMonoPoly { - var p = head - while(p != None) { - monoPoly += p - p = p.next - } - } - - private def angle(p: Point) = { - val a = (p.next - p) - val b = (p.prev - p) - Math.atan2(a cross b, a dot b) - } - - private def angleSign = { - 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) = { - if(positive != (angle(p) >= 0)) false - else true - } - -} - -# Node for a Directed Acyclic graph (DAG) -class Node(object): - - def __init__(self, left, right): - self.left = left - self.right = right - if left is not None: - left.parentList.append(self) - if right is not None: - right.parentList.append(self) - parentList = [] - - def replace(self, node): - for parent in node.parentList: - if(parent.left == node): - parent.left = self - else: - parent.right = self - parentList.append(parent) - -# 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: Edge) = head.locate(s).trapezoid - - def followEdge(s: Edge) = { - - val trapezoids = new ArrayBuffer[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: Edge, tList: Array[Trapezoid]) { - val yNode = new YNode(s, Sink.init(tList(1)), Sink.init(tList(2))) - 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: Edge, tList: Array[Trapezoid]) { - val yNode = new YNode(s, Sink.init(tList(1)), Sink.init(tList(2))) - val pNode = new XNode(s.p, Sink.init(tList(0)), yNode) - replace(sink, pNode) - } - - def case3(sink: Sink, s: Edge, tList: Array[Trapezoid]) { - val yNode = new YNode(s, Sink.init(tList(0)), Sink.init(tList(1))) - replace(sink, yNode) - } - - def case4(sink: Sink, s: Edge, tList: Array[Trapezoid]) { - val yNode = new YNode(s, Sink.init(tList(0)), Sink.init(tList(1))) - val qNode = new XNode(s.q, yNode, Sink.init(tList(2))) - replace(sink, qNode) - } - -} - -class Sink(Node): - - def __new__(cls, trapezoid): - if trapezoid.sink is not None: - return trapezoid.sink - else - return Sink(trapezoid) - - def __init__(self, trapezoid): - Node.__init__(self, None, None) - trapezoid.sink = self - - def locate(e): - return self - -class TrapezoidalMap(object): - - map = {} - margin = 50 - bcross = None - tcross = None - - def clear(self): - self.bcross = None - self.tcross = None - - def case1(self, t, e): - trapezoids = (None, None, None, None) - trapezoids.append(Trapezoid(t.leftPoint, e.p, t.top, t.bottom)) - trapezoids.append(Trapezoid(e.p, e.q, t.top, e)) - trapezoids.append(Trapezoid(e.p, e.q, e, t.bottom)) - trapezoids.append(Trapezoid(e.q, t.rightPoint, t.top, t.bottom)) - trapezoids[0].updateLeft(t.upperLeft, t.lowerLeft) - trapezoids[1].updateLeftRight(trapezoids[0], None, trapezoids[3], None) - trapezoids[2].updateLeftRight(None, trapezoids[0], None, trapezoids[3]) - trapezoids[3].updateRight(t.upperRight, t.lowerRight) - return trapezoids - - def case2(self, t, e): - val rp = e.q if e.q.x == t.rightPoint.x else t.rightPoint - trapezoids = (None, None, None) - trapezoids.append(Trapezoid(t.leftPoint, e.p, t.top, t.bottom)) - trapezoids.append(Trapezoid(e.p, rp, t.top, e)) - trapezoids.append(Trapezoid(e.p, rp, e, t.bottom)) - trapezoids[0].updateLeft(t.upperLeft, t.lowerLeft) - trapezoids[1].updateLeftRight(trapezoids[0], None, t.upperRight, None) - trapezoids[2].updateLeftRight(None, trapezoids[0], None, t.lowerRight) - self.bcross = t.bottom - self.tcross = t.top - e.above = trapezoids[1] - e.below = trapezoids[2] - return trapezoids - - def case3(self, t, e): - lp = s.p if s.p.x == t.leftPoint.x else t.leftPoint - rp = s.q if s.q.x == t.rightPoint.x else t.rightPoint - trapezoids = (None, None) - if self.tcross is t.top: - trapezoids[0] = t.upperLeft - trapezoids[0].updateRight(t.upperRight, None) - trapezoids[0].rightPoint = rp - else: - trapezoids[0] = Trapezoid(lp, rp, t.top, s) - trapezoids[0].updateLeftRight(t.upperLeft, s.above, t.upperRight, None) - if self.bcross is t.bottom: - trapezoids[1] = t.lowerLeft - trapezoids[1].updateRight(None, t.lowerRight) - trapezoids[1].rightPoint = rp - else: - trapezoids[1] = Trapezoid(lp, rp, s, t.bottom) - trapezoids[1].updateLeftRight(s.below, t.lowerLeft, None, t.lowerRight) - self.bcross = t.bottom - self.tcross = t.top - s.above = trapezoids[0] - s.below = trapezoids[1] - return trapezoids - - def case4(self, t, e): - lp = s.p if s.p.x == t.leftPoint.x else t.leftPoint - trapezoids = (None, None, None) - if self.tcross is t.top: - trapezoids[0] = t.upperLeft - trapezoids[0].rightPoint = s.q - else: - trapezoids[0] = Trapezoid(lp, s.q, t.top, s) - trapezoids[0].updateLeft(t.upperLeft, s.above) - if self.bcross is t.bottom: - trapezoids[1] = t.lowerLeft - trapezoids[1].rightPoint = s.q - else: - trapezoids[1] = Trapezoid(lp, s.q, s, t.bottom) - trapezoids[1].updateLeft(s.below, t.lowerLeft) - trapezoids[2] = Trapezoid(s.q, t.rightPoint, t.top, t.bottom) - trapezoids[2].updateLeftRight(trapezoids[0], trapezoids[1], t.upperRight, t.lowerRight) - - return trapezoids - - def bounding_box(self, edges): - max = edges[0].p + margin - min = edges[0].q - margin - for s in edges: - 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) - 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) - -class XNode(Node): - - def __init__(self, point, lchild, rchild): - Node.__init__(self, lChild, rChild) - self.point = point - self.lchild = lchild - self.rchild = rchils - - def locate(self, e): - if e.p.x >= self.point.x: - return self.right.locate(e) - else: - return self.left.locate(e) - -class YNode(Node): - - def __init__(self, edge, lchild, rchild): - Node.__init__(self, lChild, rChild) - self.edge = edge - self.lchild = lchild - self.rchile = rchild - - def locate(self, e): - if edge > e.p: - return self.right.locate(e) - elif edge < e.p: - return self.left.locate(e) - else: - if e.slope < self.edge.slope: - return self.right.locate(e) - else: - return self.left.locate(e) - -cdef class Point(object): - - def __init__(self, x, y): - self.x = x - self.y = y - next = None - prev = None - Edge = None - edges = [] - - cdef __sub__(self, Point p): - return Point(self.x - p.x, self.y - p.y) - - cdef __sub__(self, float f): - return Point(self.x - f, self.y - f) - - cdef __add__(self, Point p): - return Point(self.x + p.x, self.y + p.y) - - cdef __add__(self, float f): - return Point(self.x + f, self.y + f) - - cdef __mul__(self, float f): - return Point(self.x * f, self.y * f) - - cdef __div__(self, float a): - return Point(self.x / a, self.y / a) - - cdef cross(self, Point p): - return self.x * p.y - self.y * p.x - - cdef dot(self, Point p): - return self.x * p.x + self.y * p.y - - cdef length(self): - return math.sqrt(self.x * self.x + self.y * self.y) - - cdef normalize(self): - return self / length - - cdef __lt__(self, Point p): - return self.x < p.x - - # Sort along y axis - cdef >(p: Point): - if y < p.y: - return True - elif y > p.y: - return False - else { - if x < p.x: - return True - else - return False - - cdef !(p: Point) = !(p.x == x && p.y == y) - cdef clone = Point(x, y) - - -// Represents a simple polygon's edge -// TODO: Rename this class to Edge? -class Edge(object): - - def __init__(self, p, q): - self.p = p - self.q = q - self.above, self.below = None - mPoints = [] - self.slope = (q.y - p.y)/(q.x - p.x) - self.b = p.y - (p.x * self.slope) - - def __gt__(self, point): - return (floor(point.y) < floor(slope * point.x + b)) - def __lt__(self, point): - return (floor(point.y) > floor(slope * point.x + b)) - - def intersect(self, c, d): - a = self.p - b = self.q - a1 = _signed_area(a, b, d) - a2 = _signed_area(a, b, c) - if a1 != 0 and a2 != 0 and a1 * a2 < 0: - a3 = _signed_area(c, d, a) - a4 = a3 + a2 - a1 - if a3 * a4 < 0: - t = a3 / (a3 - a4) - return a + ((b - a) * t) - - def _signed_area(self, a, b, c): - return (a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x) \ No newline at end of file diff --git a/python/p2t.sh b/python/p2t.sh new file mode 100644 index 0000000..67dd588 --- /dev/null +++ b/python/p2t.sh @@ -0,0 +1,2 @@ +#!/bin/sh +python -O poly2tri.py diff --git a/python/poly2tri.py b/python/poly2tri.py new file mode 100644 index 0000000..dc9700a --- /dev/null +++ b/python/poly2tri.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python2.6 +from framework import Game + +class Poly2Tri(Game): + + #Screen size + screen_size = 800.0, 600.0 + + def __init__(self): + super(Poly2Tri, self).__init__(*self.screen_size) + self.main_loop() + + def update(self): + pass + + def render(self): + pass + +if __name__ == '__main__': + demo = Poly2Tri() \ No newline at end of file