From 1a00411ebeb5efe33af57cd064efa45197860088 Mon Sep 17 00:00:00 2001 From: C. Scott Ananian Date: Mon, 03 Nov 2008 22:25:39 +0000 Subject: Fix dos line endings for pippy.physics and regenerate MANIFEST. --- (limited to 'library') diff --git a/library/pippy/physics/add_objects.py b/library/pippy/physics/add_objects.py index 9e90e03..66f3b8f 100644 --- a/library/pippy/physics/add_objects.py +++ b/library/pippy/physics/add_objects.py @@ -1,500 +1,500 @@ -""" -This file is part of the 'Elements' Project -Elements is a 2D Physics API for Python (supporting Box2D2) - -Copyright (C) 2008, The Elements Team, - -Home: http://elements.linuxuser.at -IRC: #elements on irc.freenode.org - -Code: http://www.assembla.com/wiki/show/elements - svn co http://svn2.assembla.com/svn/elements - -License: GPLv3 | See LICENSE for the full text -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -""" -from locals import * -from elements import box2d - -# Imports -from math import pi -from math import sqrt -from math import asin - -import tools_poly - -class Add: - element_count = 0 - - def __init__(self, parent): - self.parent = parent - - def ground(self): - """ Add a static ground to the scene - - Return: box2d.b2Body - """ - return self._rect((-10.0, 0.0), 50.0, .1, dynamic=False) - - def triangle(self, pos, sidelength, dynamic=True, density=1.0, restitution=0.16, friction=0.5, screenCoord=True): - """ Add a triangle | pos & a in the current input unit system (meters or pixels) - - Parameters: - pos .... position (x,y) - sidelength ...... sidelength - other .. see [physics parameters] - - Return: box2d.b2Body - """ +""" +This file is part of the 'Elements' Project +Elements is a 2D Physics API for Python (supporting Box2D2) + +Copyright (C) 2008, The Elements Team, + +Home: http://elements.linuxuser.at +IRC: #elements on irc.freenode.org + +Code: http://www.assembla.com/wiki/show/elements + svn co http://svn2.assembla.com/svn/elements + +License: GPLv3 | See LICENSE for the full text +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" +from locals import * +from elements import box2d + +# Imports +from math import pi +from math import sqrt +from math import asin + +import tools_poly + +class Add: + element_count = 0 + + def __init__(self, parent): + self.parent = parent + + def ground(self): + """ Add a static ground to the scene + + Return: box2d.b2Body + """ + return self._rect((-10.0, 0.0), 50.0, .1, dynamic=False) + + def triangle(self, pos, sidelength, dynamic=True, density=1.0, restitution=0.16, friction=0.5, screenCoord=True): + """ Add a triangle | pos & a in the current input unit system (meters or pixels) + + Parameters: + pos .... position (x,y) + sidelength ...... sidelength + other .. see [physics parameters] + + Return: box2d.b2Body + """ vertices = [(-sidelength, 0.0), (sidelength, 0.0), (0.0, 2*sidelength)] - return self.poly(pos, vertices, dynamic, density, restitution, friction, screenCoord) - - def ball(self, pos, radius, dynamic=True, density=1.0, restitution=0.16, friction=0.5, screenCoord=True): - """ Add a dynamic ball at pos after correcting the positions and legths to the internal - meter system if neccessary (if INPUT_PIXELS), then call self._add_ball(...) - - Parameters: - pos ..... position (x,y) - radius .. circle radius - other ... see [physics parameters] - - Return: box2d.b2Body - """ - # Bring coordinates into the world coordinate system (flip, camera offset, ...) - if screenCoord: x, y = self.parent.to_world(pos) - else: x, y = pos - - - if self.parent.input == INPUT_PIXELS: - x /= self.parent.ppm - y /= self.parent.ppm - radius /= self.parent.ppm - - return self._ball((x,y), radius, dynamic, density, restitution, friction) - - def _ball(self, pos, radius, dynamic=True, density=1.0, restitution=0.16, friction=0.5): - # Add a ball without correcting any settings - # meaning, pos and vertices are in meters - # Define the body - x, y = pos - bodyDef = box2d.b2BodyDef() - bodyDef.position.Set(x, y) - bodyDef.sleepFlag = True -# bodyDef.allowSleep(True) - - userData = { 'color' : self.parent.get_color() } - bodyDef.userData = userData - - # Create the Body - if not dynamic: - density = 0 - - body = self.parent.world.CreateBody(bodyDef) - - self.parent.element_count += 1 - - # Add a shape to the Body - circleDef = box2d.b2CircleDef() - circleDef.density = density - circleDef.radius = radius - circleDef.restitution = restitution - circleDef.friction = friction - - body.CreateShape(circleDef) - body.SetMassFromShapes(); - - return body - - def rect(self, pos, width, height, angle=0, dynamic=True, density=1.0, restitution=0.16, friction=0.5, screenCoord=True): - """ Add a dynamic rectangle with input unit according to self.input (INPUT_PIXELS or INPUT_METERS) - Correcting the positions to meters and calling self._add_rect() - - Parameters: - pos ..... position (x,y) - width ....... horizontal line - height ....... vertical line - angle ........ in degrees (0 .. 360) - other ... see [physics parameters] - - Return: box2d.b2Body - """ - # Bring coordinates into the world coordinate system (flip, camera offset, ...) - if screenCoord: x, y = self.parent.to_world(pos) - else: x, y = pos - - # If required, translate pixel -> meters - if self.parent.input == INPUT_PIXELS: - x /= self.parent.ppm - y /= self.parent.ppm - width /= self.parent.ppm - height /= self.parent.ppm - - # grad -> radians - angle = (angle * pi) / 180 - - return self._rect((x,y), width, height, angle, dynamic, density, restitution, friction) - - - def wall(self, pos1, pos2, width=5, density=1.0, restitution=0.16, friction=0.5, screenCoord=True): - """ Add a static rectangle between two arbitrary points with input unit according to self.input - (INPUT_PIXELS or INPUT_METERS) Correcting the positions to meters and calling self._add_rect() - - Return: box2d.b2Body - """ - if width < 5: width = 5 - - if (pos1[0] < pos2[0]): - x1, y1 = pos1 - x2, y2 = pos2 - else: - x1, y1 = pos2 - x2, y2 = pos1 - - # Bring coordinates into the world coordinate system (flip, camera offset, ...) - if screenCoord: - x1, y1 = self.parent.to_world((x1, y1)) - x2, y2 = self.parent.to_world((x2, y2)) - - # If required, translate pixel -> meters - if self.parent.input == INPUT_PIXELS: - x1 /= self.parent.ppm - y1 /= self.parent.ppm - x2 /= self.parent.ppm - y2 /= self.parent.ppm - width /= self.parent.ppm - - length = sqrt( (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) )*0.5 - - if width > 0: - halfX = x1 + (x2-x1)*0.5 - halfY = y1 + (y2-y1)*0.5 - - angle = asin( (y2-halfY)/length ) - return self._rect((halfX, halfY), length, width, angle, False, density, restitution, friction) - - def _rect(self, pos, width, height, angle=0, dynamic=True, density=1.0, restitution=0.16, friction=0.5): - # Add a rect without correcting any settings - # meaning, pos and vertices are in meters - # angle is now in radians ((degrees * pi) / 180)) - x, y = pos - bodyDef = box2d.b2BodyDef() - bodyDef.position.Set(x, y) - - userData = { 'color' : self.parent.get_color() } - bodyDef.userData = userData - - # Create the Body - if not dynamic: - density = 0 - - bodyDef.sleepFlag = True - - body = self.parent.world.CreateBody(bodyDef) - - self.parent.element_count += 1 - - # Add a shape to the Body - boxDef = box2d.b2PolygonDef() - - boxDef.SetAsBox(width, height, box2d.b2Vec2(0,0), angle) - boxDef.density = density - boxDef.restitution = restitution - boxDef.friction = friction - body.CreateShape(boxDef) - - body.SetMassFromShapes() - - return body - - def poly(self, pos, vertices, dynamic=True, density=1.0, restitution=0.16, friction=0.5, screenCoord=True): - """ Add a dynamic polygon, which has the vertices arranged around the poly's center at pos - Correcting the positions to meters if INPUT_PIXELS, and calling self._add_poly() - - Parameters: - pos ....... position (x,y) - vertices .. vertices arranged around the center - other ... see [physics parameters] - - Return: box2d.b2Body - """ - # Bring coordinates into the world coordinate system (flip, camera offset, ...) - if screenCoord: x, y = self.parent.to_world(pos) - else: x, y = pos + return self.poly(pos, vertices, dynamic, density, restitution, friction, screenCoord) + + def ball(self, pos, radius, dynamic=True, density=1.0, restitution=0.16, friction=0.5, screenCoord=True): + """ Add a dynamic ball at pos after correcting the positions and legths to the internal + meter system if neccessary (if INPUT_PIXELS), then call self._add_ball(...) + + Parameters: + pos ..... position (x,y) + radius .. circle radius + other ... see [physics parameters] + + Return: box2d.b2Body + """ + # Bring coordinates into the world coordinate system (flip, camera offset, ...) + if screenCoord: x, y = self.parent.to_world(pos) + else: x, y = pos + + + if self.parent.input == INPUT_PIXELS: + x /= self.parent.ppm + y /= self.parent.ppm + radius /= self.parent.ppm + + return self._ball((x,y), radius, dynamic, density, restitution, friction) + + def _ball(self, pos, radius, dynamic=True, density=1.0, restitution=0.16, friction=0.5): + # Add a ball without correcting any settings + # meaning, pos and vertices are in meters + # Define the body + x, y = pos + bodyDef = box2d.b2BodyDef() + bodyDef.position.Set(x, y) + bodyDef.sleepFlag = True +# bodyDef.allowSleep(True) + + userData = { 'color' : self.parent.get_color() } + bodyDef.userData = userData + + # Create the Body + if not dynamic: + density = 0 + + body = self.parent.world.CreateBody(bodyDef) + + self.parent.element_count += 1 + + # Add a shape to the Body + circleDef = box2d.b2CircleDef() + circleDef.density = density + circleDef.radius = radius + circleDef.restitution = restitution + circleDef.friction = friction + + body.CreateShape(circleDef) + body.SetMassFromShapes(); + + return body + + def rect(self, pos, width, height, angle=0, dynamic=True, density=1.0, restitution=0.16, friction=0.5, screenCoord=True): + """ Add a dynamic rectangle with input unit according to self.input (INPUT_PIXELS or INPUT_METERS) + Correcting the positions to meters and calling self._add_rect() + + Parameters: + pos ..... position (x,y) + width ....... horizontal line + height ....... vertical line + angle ........ in degrees (0 .. 360) + other ... see [physics parameters] + + Return: box2d.b2Body + """ + # Bring coordinates into the world coordinate system (flip, camera offset, ...) + if screenCoord: x, y = self.parent.to_world(pos) + else: x, y = pos + + # If required, translate pixel -> meters + if self.parent.input == INPUT_PIXELS: + x /= self.parent.ppm + y /= self.parent.ppm + width /= self.parent.ppm + height /= self.parent.ppm + + # grad -> radians + angle = (angle * pi) / 180 + + return self._rect((x,y), width, height, angle, dynamic, density, restitution, friction) + + + def wall(self, pos1, pos2, width=5, density=1.0, restitution=0.16, friction=0.5, screenCoord=True): + """ Add a static rectangle between two arbitrary points with input unit according to self.input + (INPUT_PIXELS or INPUT_METERS) Correcting the positions to meters and calling self._add_rect() + + Return: box2d.b2Body + """ + if width < 5: width = 5 + + if (pos1[0] < pos2[0]): + x1, y1 = pos1 + x2, y2 = pos2 + else: + x1, y1 = pos2 + x2, y2 = pos1 + + # Bring coordinates into the world coordinate system (flip, camera offset, ...) + if screenCoord: + x1, y1 = self.parent.to_world((x1, y1)) + x2, y2 = self.parent.to_world((x2, y2)) + + # If required, translate pixel -> meters + if self.parent.input == INPUT_PIXELS: + x1 /= self.parent.ppm + y1 /= self.parent.ppm + x2 /= self.parent.ppm + y2 /= self.parent.ppm + width /= self.parent.ppm + + length = sqrt( (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) )*0.5 + + if width > 0: + halfX = x1 + (x2-x1)*0.5 + halfY = y1 + (y2-y1)*0.5 + + angle = asin( (y2-halfY)/length ) + return self._rect((halfX, halfY), length, width, angle, False, density, restitution, friction) + + def _rect(self, pos, width, height, angle=0, dynamic=True, density=1.0, restitution=0.16, friction=0.5): + # Add a rect without correcting any settings + # meaning, pos and vertices are in meters + # angle is now in radians ((degrees * pi) / 180)) + x, y = pos + bodyDef = box2d.b2BodyDef() + bodyDef.position.Set(x, y) + + userData = { 'color' : self.parent.get_color() } + bodyDef.userData = userData + + # Create the Body + if not dynamic: + density = 0 + + bodyDef.sleepFlag = True + + body = self.parent.world.CreateBody(bodyDef) + + self.parent.element_count += 1 + + # Add a shape to the Body + boxDef = box2d.b2PolygonDef() + + boxDef.SetAsBox(width, height, box2d.b2Vec2(0,0), angle) + boxDef.density = density + boxDef.restitution = restitution + boxDef.friction = friction + body.CreateShape(boxDef) + + body.SetMassFromShapes() + + return body + + def poly(self, pos, vertices, dynamic=True, density=1.0, restitution=0.16, friction=0.5, screenCoord=True): + """ Add a dynamic polygon, which has the vertices arranged around the poly's center at pos + Correcting the positions to meters if INPUT_PIXELS, and calling self._add_poly() + + Parameters: + pos ....... position (x,y) + vertices .. vertices arranged around the center + other ... see [physics parameters] + + Return: box2d.b2Body + """ + # Bring coordinates into the world coordinate system (flip, camera offset, ...) + if screenCoord: x, y = self.parent.to_world(pos) + else: x, y = pos #pos, vertices, dynamic=True, density=1.0, restitution=0.16, friction=0. #5, screenCoord=True print "self.world.add.poly((", x,",", y, "), ", vertices, ", dynamic=True, density=1.0, restitution=0.16, friction=0.5, screenCoord=True)" -#"x = " x "y = " y "vertices = " - # If required, translate pixel -> meters - if self.parent.input == INPUT_PIXELS: - # translate pixel -> meters - x /= self.parent.ppm - y /= self.parent.ppm - - # Translate vertices from pixels to meters - v_new = [] - for v in vertices: - vx, vy = v - v_new.append((vx/self.parent.ppm, vy/self.parent.ppm)) - vertices = v_new - - return self._poly((x,y), vertices, dynamic, density, restitution, friction) - - def _poly(self, pos, vertices, dynamic=True, density=1.0, restitution=0.16, friction=0.5): - # add a centered poly at pos without correcting any settings - # meaning, pos and vertices are in meters - x, y = pos - bodyDef = box2d.b2BodyDef() - bodyDef.position.Set(x, y) - bodyDef.sleepFlag = True - - userData = { 'color' : self.parent.get_color() } - bodyDef.userData = userData - - # Create the Body - if not dynamic: - density = 0 - - body = self.parent.world.CreateBody(bodyDef) - - self.parent.element_count += 1 - - # Add a shape to the Body - polyDef = box2d.b2PolygonDef() - polyDef.vertexCount = len(vertices) - for i in range(len(vertices)): - vx, vy = vertices[i] - polyDef.setVertex(i, box2d.b2Vec2(vx, vy)) - - polyDef.density = density - polyDef.restitution = restitution - polyDef.friction = friction - - body.CreateShape(polyDef) - body.SetMassFromShapes() - - return body - - def concavePoly(self, vertices, dynamic=True, density=1.0, restitution=0.16, friction=0.5, screenCoord=True): - # 1. Step: Reduce - # Detect if the polygon is closed or open - if vertices[0] != vertices[-1]: - is_closed = False - else: - is_closed = True - - # Continue reducing the vertecs - x, y = c = tools_poly.calc_center(vertices) - vertices = tools_poly.poly_center_vertices(vertices) - - # Bring coordinates into the world coordinate system (flip, camera offset, ...) - if screenCoord: x, y = self.parent.to_world(c) - else: x, y = c - - # If required, translate pixel -> meters - if self.parent.input == INPUT_PIXELS: - # translate pixel -> meters - x /= self.parent.ppm - y /= self.parent.ppm - - # Let's add the body - bodyDef = box2d.b2BodyDef() - bodyDef.position.Set(x, y) - bodyDef.sleepFlag = True - - userData = { 'color' : self.parent.get_color() } - bodyDef.userData = userData - - # Create the Body - if not dynamic: - density = 0 - - body = self.parent.world.CreateBody(bodyDef) - - self.parent.element_count += 1 - - # Create the reusable Box2D polygon and circle definitions - polyDef = box2d.b2PolygonDef() - polyDef.vertexCount = 4 # rectangle - polyDef.density = density - polyDef.restitution = restitution - polyDef.friction = friction - - circleDef = box2d.b2CircleDef() - circleDef.density = density - circleDef.radius = 0.086 - circleDef.restitution = restitution - circleDef.friction = friction - - # Set the scale factor - factor = 8.0 - - v2 = box2d.b2Vec2().fromTuple(vertices[0]) - for v in vertices[1:]: - v1 = v2.copy() - v2 = box2d.b2Vec2().fromTuple(v) - - vdir = v2-v1 # (v2x-v1x, v2y-v1y) - vdir.Normalize() - - # we need a little size for the end part - vn = box2d.b2Vec2(-vdir.y*factor, vdir.x*factor) - - v = [ v1+vn, v1-vn, v2-vn, v2+vn ] - - # Create a line (rect) for each part of the polygon, - # and attach it to the body - for i in range(len(v)): - polyDef.setVertex(i, v[i] / self.parent.ppm) - - if not tools_poly.checkDef(polyDef): - print "concavePoly: Created an invalid polygon!" - return [], 0 - - body.CreateShape(polyDef) - - # Now add a circle to the points between the rects - # to avoid sharp edges and gaps - if not is_closed and v2.tuple() == vertices[-1]: - # Don't add a circle at the end - break - - circleDef.localPosition = v2 / self.parent.ppm - body.CreateShape(circleDef) - - # Now, all shapes have been attached - body.SetMassFromShapes() - - # Return hard and soft reduced vertices - return body - - def complexPoly(self, vertices, dynamic=True, density=1.0, restitution=0.16, friction=0.5): - # 1. Step: Reduce - # 2. Step: See if start and end are close, if so then close the polygon - # 3. Step: Detect if convex or concave - # 4. Step: Start self.convexPoly or self.concavePoly - vertices, is_convex = tools_poly.reduce_poly_by_angle(vertices) - #print "->", is_convex - - # If start and endpoints are close to each other, close polygon - x1, y1 = vertices[0] - x2, y2 = vertices[-1] - dx = x2 - x1 - dy = y2 - y1 - l = sqrt((dx*dx)+(dy*dy)) - - if l < 50: - vertices[-1] = vertices[0] - else: - # Never convex if open (we decide so :) - is_convex = False - - if tools_poly.is_line(vertices): - # Lines shall be drawn by self.concavePoly(...) - print "is line" - is_convex = False - - if is_convex: - print "convex" - return self.convexPoly(vertices, dynamic, density, restitution, friction), vertices - else: - print "concave" - return self.concavePoly(vertices, dynamic, density, restitution, friction), vertices - - - def convexPoly(self, vertices, dynamic=True, density=1.0, restitution=0.16, friction=0.5): - """ Add a complex polygon with vertices in absolute positions (meters or pixels, according - to INPUT_PIXELS or INPUT_METERS). This function does the reduction and convec hulling - of the poly, and calls add_poly(...) - - Parameters: - vertices .. absolute vertices positions - other ..... see [physics parameters] - - Return: box2d.b2Body - """ - # NOTE: Box2D has a maximum poly vertex count, defined in Common/box2d.b2Settings.h (box2d.b2_maxPolygonVertices) - # We need to make sure, that we reach that by reducing the poly with increased tolerance - # Reduce Polygon - tolerance = 10 #5 - v_new = vertices - while len(v_new) > box2d.b2_maxPolygonVertices: - tolerance += 1 - v_new = tools_poly.reduce_poly(vertices, tolerance) - - print "convexPoly: Polygon reduced from %i to %i vertices | tolerance: %i" % (len(vertices), len(v_new), tolerance) - vertices = v_new - - # So poly should be alright now - # Continue reducing the vertecs - vertices_orig_reduced = vertices - vertices = tools_poly.poly_center_vertices(vertices) - - vertices = tools_poly.convex_hull(vertices) - - if len(vertices) < 3: - return - - # Define the body - x, y = c = tools_poly.calc_center(vertices_orig_reduced) +#"x = " x "y = " y "vertices = " + # If required, translate pixel -> meters + if self.parent.input == INPUT_PIXELS: + # translate pixel -> meters + x /= self.parent.ppm + y /= self.parent.ppm + + # Translate vertices from pixels to meters + v_new = [] + for v in vertices: + vx, vy = v + v_new.append((vx/self.parent.ppm, vy/self.parent.ppm)) + vertices = v_new + + return self._poly((x,y), vertices, dynamic, density, restitution, friction) + + def _poly(self, pos, vertices, dynamic=True, density=1.0, restitution=0.16, friction=0.5): + # add a centered poly at pos without correcting any settings + # meaning, pos and vertices are in meters + x, y = pos + bodyDef = box2d.b2BodyDef() + bodyDef.position.Set(x, y) + bodyDef.sleepFlag = True + + userData = { 'color' : self.parent.get_color() } + bodyDef.userData = userData + + # Create the Body + if not dynamic: + density = 0 + + body = self.parent.world.CreateBody(bodyDef) + + self.parent.element_count += 1 + + # Add a shape to the Body + polyDef = box2d.b2PolygonDef() + polyDef.vertexCount = len(vertices) + for i in range(len(vertices)): + vx, vy = vertices[i] + polyDef.setVertex(i, box2d.b2Vec2(vx, vy)) + + polyDef.density = density + polyDef.restitution = restitution + polyDef.friction = friction + + body.CreateShape(polyDef) + body.SetMassFromShapes() + + return body + + def concavePoly(self, vertices, dynamic=True, density=1.0, restitution=0.16, friction=0.5, screenCoord=True): + # 1. Step: Reduce + # Detect if the polygon is closed or open + if vertices[0] != vertices[-1]: + is_closed = False + else: + is_closed = True + + # Continue reducing the vertecs + x, y = c = tools_poly.calc_center(vertices) + vertices = tools_poly.poly_center_vertices(vertices) + + # Bring coordinates into the world coordinate system (flip, camera offset, ...) + if screenCoord: x, y = self.parent.to_world(c) + else: x, y = c + + # If required, translate pixel -> meters + if self.parent.input == INPUT_PIXELS: + # translate pixel -> meters + x /= self.parent.ppm + y /= self.parent.ppm + + # Let's add the body + bodyDef = box2d.b2BodyDef() + bodyDef.position.Set(x, y) + bodyDef.sleepFlag = True + + userData = { 'color' : self.parent.get_color() } + bodyDef.userData = userData + + # Create the Body + if not dynamic: + density = 0 + + body = self.parent.world.CreateBody(bodyDef) + + self.parent.element_count += 1 + + # Create the reusable Box2D polygon and circle definitions + polyDef = box2d.b2PolygonDef() + polyDef.vertexCount = 4 # rectangle + polyDef.density = density + polyDef.restitution = restitution + polyDef.friction = friction + + circleDef = box2d.b2CircleDef() + circleDef.density = density + circleDef.radius = 0.086 + circleDef.restitution = restitution + circleDef.friction = friction + + # Set the scale factor + factor = 8.0 + + v2 = box2d.b2Vec2().fromTuple(vertices[0]) + for v in vertices[1:]: + v1 = v2.copy() + v2 = box2d.b2Vec2().fromTuple(v) + + vdir = v2-v1 # (v2x-v1x, v2y-v1y) + vdir.Normalize() + + # we need a little size for the end part + vn = box2d.b2Vec2(-vdir.y*factor, vdir.x*factor) + + v = [ v1+vn, v1-vn, v2-vn, v2+vn ] + + # Create a line (rect) for each part of the polygon, + # and attach it to the body + for i in range(len(v)): + polyDef.setVertex(i, v[i] / self.parent.ppm) + + if not tools_poly.checkDef(polyDef): + print "concavePoly: Created an invalid polygon!" + return [], 0 + + body.CreateShape(polyDef) + + # Now add a circle to the points between the rects + # to avoid sharp edges and gaps + if not is_closed and v2.tuple() == vertices[-1]: + # Don't add a circle at the end + break + + circleDef.localPosition = v2 / self.parent.ppm + body.CreateShape(circleDef) + + # Now, all shapes have been attached + body.SetMassFromShapes() + + # Return hard and soft reduced vertices + return body + + def complexPoly(self, vertices, dynamic=True, density=1.0, restitution=0.16, friction=0.5): + # 1. Step: Reduce + # 2. Step: See if start and end are close, if so then close the polygon + # 3. Step: Detect if convex or concave + # 4. Step: Start self.convexPoly or self.concavePoly + vertices, is_convex = tools_poly.reduce_poly_by_angle(vertices) + #print "->", is_convex + + # If start and endpoints are close to each other, close polygon + x1, y1 = vertices[0] + x2, y2 = vertices[-1] + dx = x2 - x1 + dy = y2 - y1 + l = sqrt((dx*dx)+(dy*dy)) + + if l < 50: + vertices[-1] = vertices[0] + else: + # Never convex if open (we decide so :) + is_convex = False + + if tools_poly.is_line(vertices): + # Lines shall be drawn by self.concavePoly(...) + print "is line" + is_convex = False + + if is_convex: + print "convex" + return self.convexPoly(vertices, dynamic, density, restitution, friction), vertices + else: + print "concave" + return self.concavePoly(vertices, dynamic, density, restitution, friction), vertices + + + def convexPoly(self, vertices, dynamic=True, density=1.0, restitution=0.16, friction=0.5): + """ Add a complex polygon with vertices in absolute positions (meters or pixels, according + to INPUT_PIXELS or INPUT_METERS). This function does the reduction and convec hulling + of the poly, and calls add_poly(...) + + Parameters: + vertices .. absolute vertices positions + other ..... see [physics parameters] + + Return: box2d.b2Body + """ + # NOTE: Box2D has a maximum poly vertex count, defined in Common/box2d.b2Settings.h (box2d.b2_maxPolygonVertices) + # We need to make sure, that we reach that by reducing the poly with increased tolerance + # Reduce Polygon + tolerance = 10 #5 + v_new = vertices + while len(v_new) > box2d.b2_maxPolygonVertices: + tolerance += 1 + v_new = tools_poly.reduce_poly(vertices, tolerance) + + print "convexPoly: Polygon reduced from %i to %i vertices | tolerance: %i" % (len(vertices), len(v_new), tolerance) + vertices = v_new + + # So poly should be alright now + # Continue reducing the vertecs + vertices_orig_reduced = vertices + vertices = tools_poly.poly_center_vertices(vertices) + + vertices = tools_poly.convex_hull(vertices) + + if len(vertices) < 3: + return + + # Define the body + x, y = c = tools_poly.calc_center(vertices_orig_reduced) return self.poly((x,y), vertices, dynamic, density, restitution, friction) def to_b2vec(self,pt): pt = self.parent.to_world(pt) ptx, pty = pt - ptx /= self.parent.ppm + ptx /= self.parent.ppm pty /= self.parent.ppm pt = box2d.b2Vec2(ptx, pty) return pt # Alex Levenson's added joint methods: - def distanceJoint(self,b1,b2,p1,p2): - # Distance Joint - p1 = self.to_b2vec(p1) - p2 = self.to_b2vec(p2) - - jointDef = box2d.b2DistanceJointDef() - jointDef.Initialize(b1, b2, p1, p2) - jointDef.collideConnected = True + def distanceJoint(self,b1,b2,p1,p2): + # Distance Joint + p1 = self.to_b2vec(p1) + p2 = self.to_b2vec(p2) + + jointDef = box2d.b2DistanceJointDef() + jointDef.Initialize(b1, b2, p1, p2) + jointDef.collideConnected = True self.parent.world.CreateJoint(jointDef) def fixedJoint(self, *args): if len(args) == 2: - # Fixed Joint to the Background, don't assume the center of the body - b1 = self.parent.world.GetGroundBody() - b2 = args[0] + # Fixed Joint to the Background, don't assume the center of the body + b1 = self.parent.world.GetGroundBody() + b2 = args[0] p1 = self.to_b2vec(args[1]) - - jointDef = box2d.b2RevoluteJointDef() + + jointDef = box2d.b2RevoluteJointDef() + jointDef.Initialize(b1, b2, p1) + self.parent.world.CreateJoint(jointDef) + elif len(args) == 1: + # Fixed Joint to the Background, assume the center of the body + b1 = self.parent.world.GetGroundBody() + b2 = args[0] + p1 = b2.GetWorldCenter() + + jointDef = box2d.b2RevoluteJointDef() jointDef.Initialize(b1, b2, p1) - self.parent.world.CreateJoint(jointDef) - elif len(args) == 1: - # Fixed Joint to the Background, assume the center of the body - b1 = self.parent.world.GetGroundBody() - b2 = args[0] - p1 = b2.GetWorldCenter() - - jointDef = box2d.b2RevoluteJointDef() - jointDef.Initialize(b1, b2, p1) - + self.parent.world.CreateJoint(jointDef) def revoluteJoint(self,b1,b2,p1): - # revolute joint between to bodies + # revolute joint between to bodies p1 = self.to_b2vec(p1) - - jointDef = box2d.b2RevoluteJointDef() - jointDef.Initialize(b1, b2, p1) - + + jointDef = box2d.b2RevoluteJointDef() + jointDef.Initialize(b1, b2, p1) + self.parent.world.CreateJoint(jointDef) # prismatic joint + pully not fully functional at this point @@ -522,16 +522,16 @@ class Add: self.parent.world.CreateJoint(jointDef) def motor(self, body,pt,torque=900,speed=-10): - # Fixed Joint to the Background with a motor on it - b1 = self.parent.world.GetGroundBody() + # Fixed Joint to the Background with a motor on it + b1 = self.parent.world.GetGroundBody() pt = self.to_b2vec(pt) - - jointDef = box2d.b2RevoluteJointDef() + + jointDef = box2d.b2RevoluteJointDef() jointDef.Initialize(b1, body, pt) jointDef.maxMotorTorque = torque jointDef.motorSpeed = speed jointDef.enableMotor = True - self.parent.world.CreateJoint(jointDef) + self.parent.world.CreateJoint(jointDef) #def jointMotor(self,b1,b2,p1,speed): # p1 = self.tob2vec(p1) # jointDef = box2d.b2RevoluteJointDef() @@ -539,63 +539,63 @@ class Add: # jointDef. # def joint(self, *args): - print "* Add Joint:", args - - if len(args) == 4: - # Distance Joint - b1, b2, p1, p2 = args - - p1 = self.parent.to_world(p1) - p2 = self.parent.to_world(p2) - - p1x, p1y = p1 - p2x, p2y = p2 - - p1x /= self.parent.ppm - p1y /= self.parent.ppm - p2x /= self.parent.ppm - p2y /= self.parent.ppm - - p1 = box2d.b2Vec2(p1x, p1y) - p2 = box2d.b2Vec2(p2x, p2y) - - jointDef = box2d.b2DistanceJointDef() - jointDef.Initialize(b1, b2, p1, p2) - jointDef.collideConnected = True - - self.parent.world.CreateJoint(jointDef) - - elif len(args) == 3: - # Revolute Joint - pass - - elif len(args) == 1: - # Fixed Joint to the Background, assume the center of the body - b1 = self.parent.world.GetGroundBody() - b2 = args[0] - p1 = b2.GetWorldCenter() - - jointDef = box2d.b2RevoluteJointDef() - jointDef.Initialize(b1, b2, p1) - - self.parent.world.CreateJoint(jointDef) - - def mouseJoint(self, body, pos): - pos = self.parent.to_world(pos) - x, y = pos - x /= self.parent.ppm - y /= self.parent.ppm - - mj = box2d.b2MouseJointDef() - mj.body1 = self.parent.world.GetGroundBody() - mj.body2 = body - mj.target = box2d.b2Vec2(x, y) - mj.maxForce = 100.0 * body.GetMass() # give humans POWER! - self.parent.mouseJoint = self.parent.world.CreateJoint(mj).getAsType() - body.WakeUp() - - def remove_mouseJoint(self): - if self.parent.mouseJoint: - self.parent.world.DestroyJoint(self.parent.mouseJoint) - self.parent.mouseJoint = None - + print "* Add Joint:", args + + if len(args) == 4: + # Distance Joint + b1, b2, p1, p2 = args + + p1 = self.parent.to_world(p1) + p2 = self.parent.to_world(p2) + + p1x, p1y = p1 + p2x, p2y = p2 + + p1x /= self.parent.ppm + p1y /= self.parent.ppm + p2x /= self.parent.ppm + p2y /= self.parent.ppm + + p1 = box2d.b2Vec2(p1x, p1y) + p2 = box2d.b2Vec2(p2x, p2y) + + jointDef = box2d.b2DistanceJointDef() + jointDef.Initialize(b1, b2, p1, p2) + jointDef.collideConnected = True + + self.parent.world.CreateJoint(jointDef) + + elif len(args) == 3: + # Revolute Joint + pass + + elif len(args) == 1: + # Fixed Joint to the Background, assume the center of the body + b1 = self.parent.world.GetGroundBody() + b2 = args[0] + p1 = b2.GetWorldCenter() + + jointDef = box2d.b2RevoluteJointDef() + jointDef.Initialize(b1, b2, p1) + + self.parent.world.CreateJoint(jointDef) + + def mouseJoint(self, body, pos): + pos = self.parent.to_world(pos) + x, y = pos + x /= self.parent.ppm + y /= self.parent.ppm + + mj = box2d.b2MouseJointDef() + mj.body1 = self.parent.world.GetGroundBody() + mj.body2 = body + mj.target = box2d.b2Vec2(x, y) + mj.maxForce = 100.0 * body.GetMass() # give humans POWER! + self.parent.mouseJoint = self.parent.world.CreateJoint(mj).getAsType() + body.WakeUp() + + def remove_mouseJoint(self): + if self.parent.mouseJoint: + self.parent.world.DestroyJoint(self.parent.mouseJoint) + self.parent.mouseJoint = None + diff --git a/library/pippy/physics/tools_poly.py b/library/pippy/physics/tools_poly.py index cd6c4c6..97a0cea 100755 --- a/library/pippy/physics/tools_poly.py +++ b/library/pippy/physics/tools_poly.py @@ -1,440 +1,440 @@ -""" -This file is part of the 'Elements' Project -Elements is a 2D Physics API for Python (supporting Box2D2) - -Copyright (C) 2008, The Elements Team, - -Home: http://elements.linuxuser.at -IRC: #elements on irc.freenode.org - -Code: http://www.assembla.com/wiki/show/elements - svn co http://svn2.assembla.com/svn/elements - -License: GPLv3 | See LICENSE for the full text -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -""" -from functools import partial - -from math import fabs -from math import sqrt -from math import atan -from math import atan2 -from math import degrees -from math import acos - -from locals import * -from elements import box2d - -def ComputeCentroid(pd): - count = pd.vertexCount - - if count < 3: - return False - - c = box2d.b2Vec2(0, 0) - area = 0.0 - - # pRef is the reference point for forming triangles. - # It's location doesn't change the result (except for rounding error). - pRef = box2d.b2Vec2(0.0, 0.0) - - inv3 = 1.0 / 3.0 - - for i in range(count): - # Triangle vertices. - p1 = pRef - p2 = pd.getVertex(i) - if i + 1 < count: - p3 = pd.getVertex(i+1) - else: p3 = pd.getVertex(0) - - e1 = p2 - p1 - e2 = p3 - p1 - - D = box2d.b2Cross(e1, e2) - - triangleArea = 0.5 * D - area += triangleArea - - # Area weighted centroid - c += triangleArea * inv3 * (p1 + p2 + p3) - - # Centroid - # if area < FLT_EPSILON: - #raise ValueError, "ComputeCentroid: area < FLT_EPSILON" - - return c / area - -def checkDef(pd): - """Check the polygon definition for invalid vertices, etc. - - Return: True if valid, False if invalid - """ - -# if pd.vertexCount < 3 or pd.vertexCount > box2d.b2_maxPolygonVertices: - #raise ValueError, "Invalid vertexCount" - - threshold = FLT_EPSILON * FLT_EPSILON - verts = pd.getVertices_b2Vec2() - normals = [] - v0 = verts[0] - for i in range(pd.vertexCount): - if i == pd.vertexCount-1: - v1 = verts[0] - else: v1 = verts[i+1] - edge=v1 - v0 -# if edge.LengthSquared() < threshold: -# raise ValueError, "edge.LengthSquared < FLT_EPSILON**2" - normals.append( box2d.b2Cross(edge, 1.0) ) - normals[-1].Normalize() - v0=v1 - - centroid = ComputeCentroid(pd) - - d=box2d.b2Vec2() - for i in range(pd.vertexCount): - i1 = i - 1 - if i1 < 0: i1 = pd.vertexCount - 1 - i2 = i - n1 = normals[i1] - n2 = normals[i2] - v = verts[i] - centroid - - d.x = box2d.b2Dot(n1, v) - box2d.b2_toiSlop - d.y = box2d.b2Dot(n2, v) - box2d.b2_toiSlop - - # Shifting the edge inward by b2_toiSlop should - # not cause the plane to pass the centroid. - - # Your shape has a radius/extent less than b2_toiSlop. -# if d.x < 0.0 or d.y <= 0.0: -# raise ValueError, "Your shape has a radius/extent less than b2_toiSlop." - - A = box2d.b2Mat22() - A.col1.x = n1.x; A.col2.x = n1.y - A.col1.y = n2.x; A.col2.y = n2.y - #coreVertices[i] = A.Solve(d) + m_centroid - - return True - -def calc_center(points): - """ Calculate the center of a polygon - - Return: The center (x,y) - """ - tot_x, tot_y = 0,0 - for p in points: - tot_x += p[0] - tot_y += p[1] - n = len(points) - return (tot_x/n, tot_y/n) - -def poly_center_vertices(pointlist): - """ Rearranges vectors around the center - - Return: pointlist ([(x, y), ...]) - """ - poly_points_center = [] - center = cx, cy = calc_center(pointlist) - - for p in pointlist: - x = p[0] - cx - y = cy - p[1] - poly_points_center.append((x, y)) - - return poly_points_center - -def is_line(vertices, tolerance=25.0): - """ Check if passed vertices are a line. Done by comparing - the angles of all vectors and check tolerance. - - Parameters: - vertices ... a list of vertices (x, y) - tolerance .. how many degrees should be allowed max to be a line - - Returns: True if line, False if no line - """ - if len(vertices) <= 2: - return True - - # Step 1: Points -> Vectors - p_old = vertices[0] - alphas = [] - - - for p in vertices[1:]: - x1, y1 = p_old - x2, y2 = p - p_old = p - - # Create Vector - vx, vy = (x2-x1, y2-y1) - - # Check Length - l = sqrt((vx*vx) + (vy*vy)) - if l == 0.0: continue - - # Normalize vector - vx /= l - vy /= l - - # Append angle - if fabs(vx) < 0.2: alpha = 90.0 - else: alpha = degrees(atan(vy / vx)) - - alphas.append(fabs(alpha)) - - # Sort angles - alphas.sort() - - # Get maximum difference - alpha_diff = fabs(alphas[-1] - alphas[0]) - print "alpha difference:", alpha_diff - - if alpha_diff < tolerance: - return True - else: - return False - -def reduce_poly_by_angle(vertices, tolerance=10.0, minlen=20): - """ This function reduces a poly by the angles of the vectors (detect lines) - If the angle difference from one vector to the last > tolerance: use last point - If the angle is quite the same, it's on the line - - Parameters: - vertices ... a list of vertices (x, y) - tolerance .. how many degrees should be allowed max - - Returns: (1) New Pointlist, (2) Soft reduced pointlist (reduce_poly()) - """ - v_last = vertices[-1] - vertices = vxx = reduce_poly(vertices, minlen) - - p_new = [] - p_new.append(vertices[0]) - - dir = None - is_convex = True - - for i in xrange(len(vertices)-1): - if i == 0: - p_old = vertices[i] - continue - - x1, y1 = p_old - x2, y2 = vertices[i] - x3, y3 = vertices[i+1] - p_old = vertices[i] - - # Create Vectors - v1x = (x2 - x1) * 1.0 - v1y = (y2 - y1) * 1.0 - - v2x = (x3 - x2) * 1.0 - v2y = (y3 - y2) * 1.0 - - # Calculate angle - a = ((v1x * v2x) + (v1y * v2y)) - b = sqrt((v1x*v1x) + (v1y*v1y)) - c = sqrt((v2x*v2x) + (v2y*v2y)) - - # No Division by 0 :) - if (b*c) == 0.0: continue - - # Get the current degrees - # We have a bug here sometimes... - try: - angle = degrees(acos(a / (b*c))) - except: - # cos=1.0 - print "cos=", a/(b*c) - continue - - # Check if inside tolerance - if fabs(angle) > tolerance: - p_new.append(vertices[i]) - # print "x", 180-angle, is_left(vertices[i-1], vertices[i], vertices[i+1]) - - # Check if convex: - if dir == None: - dir = is_left(vertices[i-1], vertices[i], vertices[i+1]) - else: - if dir != is_left(vertices[i-1], vertices[i], vertices[i+1]): - is_convex = False - - # We also want to append the last point :) - p_new.append(v_last) - - # Returns: (1) New Pointlist, (2) Soft reduced pointlist (reduce_poly()) - return p_new, is_convex - - """ OLD FUNCTION: """ - # Wipe all points too close to each other - vxx = vertices = reduce_poly(vertices, minlen) - - # Create Output List - p_new = [] - p_new.append(vertices[0]) - - # Set the starting vertice - p_old = vertices[0] - alpha_old = None - - # For each vector, compare the angle difference to the last one - for i in range(1, len(vertices)): - x1, y1 = p_old - x2, y2 = vertices[i] - p_old = (x2, y2) - - # Make Vector - vx, vy = (x2-x1, y2-y1) - - # Vector length - l = sqrt((vx*vx) + (vy*vy)) - - # normalize - vx /= l - vy /= l - - # Get Angle - if fabs(vx) < 0.2: - alpha = 90 - else: - alpha = degrees(atan(vy * 1.0) / (vx*1.0)) - - if alpha_old == None: - alpha_old = alpha - continue - - # Get difference to previous angle - alpha_diff = fabs(alpha - alpha_old) - alpha_old = alpha - - # If the new vector differs from the old one, we add the old point - # to the output list, as the line changed it's way :) - if alpha_diff > tolerance: - #print ">",alpha_diff, "\t", vx, vy, l - p_new.append(vertices[i-1]) - - # We also want to append the last point :) - p_new.append(vertices[-1]) - - # Returns: (1) New Pointlist, (2) Soft reduced pointlist (reduce_poly()) - return p_new, vxx - - -# The following functions is_left, reduce_poly and convex_hull are -# from the pymunk project (http://code.google.com/p/pymunk/) -def is_left(p0, p1, p2): - """Test if p2 is left, on or right of the (infinite) line (p0,p1). - - :return: > 0 for p2 left of the line through p0 and p1 - = 0 for p2 on the line - < 0 for p2 right of the line - """ - sorting = (p1[0] - p0[0])*(p2[1]-p0[1]) - (p2[0]-p0[0])*(p1[1]-p0[1]) - if sorting > 0: return 1 - elif sorting < 0: return -1 - else: return 0 - -def is_convex(points): - """Test if a polygon (list of (x,y)) is strictly convex or not. - - :return: True if the polygon is convex, False otherwise - """ - #assert len(points) > 2, "not enough points to form a polygon" - - p0 = points[0] - p1 = points[1] - p2 = points[2] - - xc, yc = 0, 0 - is_same_winding = is_left(p0, p1, p2) - for p2 in points[2:] + [p0] + [p1]: - if is_same_winding != is_left(p0, p1, p2): - return False - a = p1[0] - p0[0], p1[1] - p0[1] # p1-p0 - b = p2[0] - p1[0], p2[1] - p1[1] # p2-p1 - if sign(a[0]) != sign(b[0]): xc +=1 - if sign(a[1]) != sign(b[1]): yc +=1 - p0, p1 = p1, p2 - - return xc <= 2 and yc <= 2 - -def sign(x): - if x < 0: return -1 - else: return 1 - - -def reduce_poly(points, tolerance=50): - """Remove close points to simplify a polyline - tolerance is the min distance between two points squared. - - :return: The reduced polygon as a list of (x,y) - """ - curr_p = points[0] - reduced_ps = [points[0]] - - for p in points[1:]: - x1, y1 = curr_p - x2, y2 = p - dx = fabs(x2 - x1) - dy = fabs(y2 - y1) - l = sqrt((dx*dx) + (dy*dy)) - if l > tolerance: - curr_p = p - reduced_ps.append(p) - - return reduced_ps - -def convex_hull(points): - """Create a convex hull from a list of points. - This function uses the Graham Scan Algorithm. - - :return: Convex hull as a list of (x,y) - """ - ### Find lowest rightmost point - p0 = points[0] - for p in points[1:]: - if p[1] < p0[1]: - p0 = p - elif p[1] == p0[1] and p[0] > p0[0]: - p0 = p - points.remove(p0) - - ### Sort the points angularly about p0 as center - f = partial(is_left, p0) - points.sort(cmp = f) - points.reverse() - points.insert(0, p0) - - ### Find the hull points - hull = [p0, points[1]] - - for p in points[2:]: - - pt1 = hull[-1] - pt2 = hull[-2] - l = is_left(pt2, pt1, p) - if l > 0: - hull.append(p) - else: - while l <= 0 and len(hull) > 2: - hull.pop() - pt1 = hull[-1] - pt2 = hull[-2] - l = is_left(pt2, pt1, p) - hull.append(p) - return hull - +""" +This file is part of the 'Elements' Project +Elements is a 2D Physics API for Python (supporting Box2D2) + +Copyright (C) 2008, The Elements Team, + +Home: http://elements.linuxuser.at +IRC: #elements on irc.freenode.org + +Code: http://www.assembla.com/wiki/show/elements + svn co http://svn2.assembla.com/svn/elements + +License: GPLv3 | See LICENSE for the full text +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" +from functools import partial + +from math import fabs +from math import sqrt +from math import atan +from math import atan2 +from math import degrees +from math import acos + +from locals import * +from elements import box2d + +def ComputeCentroid(pd): + count = pd.vertexCount + + if count < 3: + return False + + c = box2d.b2Vec2(0, 0) + area = 0.0 + + # pRef is the reference point for forming triangles. + # It's location doesn't change the result (except for rounding error). + pRef = box2d.b2Vec2(0.0, 0.0) + + inv3 = 1.0 / 3.0 + + for i in range(count): + # Triangle vertices. + p1 = pRef + p2 = pd.getVertex(i) + if i + 1 < count: + p3 = pd.getVertex(i+1) + else: p3 = pd.getVertex(0) + + e1 = p2 - p1 + e2 = p3 - p1 + + D = box2d.b2Cross(e1, e2) + + triangleArea = 0.5 * D + area += triangleArea + + # Area weighted centroid + c += triangleArea * inv3 * (p1 + p2 + p3) + + # Centroid + # if area < FLT_EPSILON: + #raise ValueError, "ComputeCentroid: area < FLT_EPSILON" + + return c / area + +def checkDef(pd): + """Check the polygon definition for invalid vertices, etc. + + Return: True if valid, False if invalid + """ + +# if pd.vertexCount < 3 or pd.vertexCount > box2d.b2_maxPolygonVertices: + #raise ValueError, "Invalid vertexCount" + + threshold = FLT_EPSILON * FLT_EPSILON + verts = pd.getVertices_b2Vec2() + normals = [] + v0 = verts[0] + for i in range(pd.vertexCount): + if i == pd.vertexCount-1: + v1 = verts[0] + else: v1 = verts[i+1] + edge=v1 - v0 +# if edge.LengthSquared() < threshold: +# raise ValueError, "edge.LengthSquared < FLT_EPSILON**2" + normals.append( box2d.b2Cross(edge, 1.0) ) + normals[-1].Normalize() + v0=v1 + + centroid = ComputeCentroid(pd) + + d=box2d.b2Vec2() + for i in range(pd.vertexCount): + i1 = i - 1 + if i1 < 0: i1 = pd.vertexCount - 1 + i2 = i + n1 = normals[i1] + n2 = normals[i2] + v = verts[i] - centroid + + d.x = box2d.b2Dot(n1, v) - box2d.b2_toiSlop + d.y = box2d.b2Dot(n2, v) - box2d.b2_toiSlop + + # Shifting the edge inward by b2_toiSlop should + # not cause the plane to pass the centroid. + + # Your shape has a radius/extent less than b2_toiSlop. +# if d.x < 0.0 or d.y <= 0.0: +# raise ValueError, "Your shape has a radius/extent less than b2_toiSlop." + + A = box2d.b2Mat22() + A.col1.x = n1.x; A.col2.x = n1.y + A.col1.y = n2.x; A.col2.y = n2.y + #coreVertices[i] = A.Solve(d) + m_centroid + + return True + +def calc_center(points): + """ Calculate the center of a polygon + + Return: The center (x,y) + """ + tot_x, tot_y = 0,0 + for p in points: + tot_x += p[0] + tot_y += p[1] + n = len(points) + return (tot_x/n, tot_y/n) + +def poly_center_vertices(pointlist): + """ Rearranges vectors around the center + + Return: pointlist ([(x, y), ...]) + """ + poly_points_center = [] + center = cx, cy = calc_center(pointlist) + + for p in pointlist: + x = p[0] - cx + y = cy - p[1] + poly_points_center.append((x, y)) + + return poly_points_center + +def is_line(vertices, tolerance=25.0): + """ Check if passed vertices are a line. Done by comparing + the angles of all vectors and check tolerance. + + Parameters: + vertices ... a list of vertices (x, y) + tolerance .. how many degrees should be allowed max to be a line + + Returns: True if line, False if no line + """ + if len(vertices) <= 2: + return True + + # Step 1: Points -> Vectors + p_old = vertices[0] + alphas = [] + + + for p in vertices[1:]: + x1, y1 = p_old + x2, y2 = p + p_old = p + + # Create Vector + vx, vy = (x2-x1, y2-y1) + + # Check Length + l = sqrt((vx*vx) + (vy*vy)) + if l == 0.0: continue + + # Normalize vector + vx /= l + vy /= l + + # Append angle + if fabs(vx) < 0.2: alpha = 90.0 + else: alpha = degrees(atan(vy / vx)) + + alphas.append(fabs(alpha)) + + # Sort angles + alphas.sort() + + # Get maximum difference + alpha_diff = fabs(alphas[-1] - alphas[0]) + print "alpha difference:", alpha_diff + + if alpha_diff < tolerance: + return True + else: + return False + +def reduce_poly_by_angle(vertices, tolerance=10.0, minlen=20): + """ This function reduces a poly by the angles of the vectors (detect lines) + If the angle difference from one vector to the last > tolerance: use last point + If the angle is quite the same, it's on the line + + Parameters: + vertices ... a list of vertices (x, y) + tolerance .. how many degrees should be allowed max + + Returns: (1) New Pointlist, (2) Soft reduced pointlist (reduce_poly()) + """ + v_last = vertices[-1] + vertices = vxx = reduce_poly(vertices, minlen) + + p_new = [] + p_new.append(vertices[0]) + + dir = None + is_convex = True + + for i in xrange(len(vertices)-1): + if i == 0: + p_old = vertices[i] + continue + + x1, y1 = p_old + x2, y2 = vertices[i] + x3, y3 = vertices[i+1] + p_old = vertices[i] + + # Create Vectors + v1x = (x2 - x1) * 1.0 + v1y = (y2 - y1) * 1.0 + + v2x = (x3 - x2) * 1.0 + v2y = (y3 - y2) * 1.0 + + # Calculate angle + a = ((v1x * v2x) + (v1y * v2y)) + b = sqrt((v1x*v1x) + (v1y*v1y)) + c = sqrt((v2x*v2x) + (v2y*v2y)) + + # No Division by 0 :) + if (b*c) == 0.0: continue + + # Get the current degrees + # We have a bug here sometimes... + try: + angle = degrees(acos(a / (b*c))) + except: + # cos=1.0 + print "cos=", a/(b*c) + continue + + # Check if inside tolerance + if fabs(angle) > tolerance: + p_new.append(vertices[i]) + # print "x", 180-angle, is_left(vertices[i-1], vertices[i], vertices[i+1]) + + # Check if convex: + if dir == None: + dir = is_left(vertices[i-1], vertices[i], vertices[i+1]) + else: + if dir != is_left(vertices[i-1], vertices[i], vertices[i+1]): + is_convex = False + + # We also want to append the last point :) + p_new.append(v_last) + + # Returns: (1) New Pointlist, (2) Soft reduced pointlist (reduce_poly()) + return p_new, is_convex + + """ OLD FUNCTION: """ + # Wipe all points too close to each other + vxx = vertices = reduce_poly(vertices, minlen) + + # Create Output List + p_new = [] + p_new.append(vertices[0]) + + # Set the starting vertice + p_old = vertices[0] + alpha_old = None + + # For each vector, compare the angle difference to the last one + for i in range(1, len(vertices)): + x1, y1 = p_old + x2, y2 = vertices[i] + p_old = (x2, y2) + + # Make Vector + vx, vy = (x2-x1, y2-y1) + + # Vector length + l = sqrt((vx*vx) + (vy*vy)) + + # normalize + vx /= l + vy /= l + + # Get Angle + if fabs(vx) < 0.2: + alpha = 90 + else: + alpha = degrees(atan(vy * 1.0) / (vx*1.0)) + + if alpha_old == None: + alpha_old = alpha + continue + + # Get difference to previous angle + alpha_diff = fabs(alpha - alpha_old) + alpha_old = alpha + + # If the new vector differs from the old one, we add the old point + # to the output list, as the line changed it's way :) + if alpha_diff > tolerance: + #print ">",alpha_diff, "\t", vx, vy, l + p_new.append(vertices[i-1]) + + # We also want to append the last point :) + p_new.append(vertices[-1]) + + # Returns: (1) New Pointlist, (2) Soft reduced pointlist (reduce_poly()) + return p_new, vxx + + +# The following functions is_left, reduce_poly and convex_hull are +# from the pymunk project (http://code.google.com/p/pymunk/) +def is_left(p0, p1, p2): + """Test if p2 is left, on or right of the (infinite) line (p0,p1). + + :return: > 0 for p2 left of the line through p0 and p1 + = 0 for p2 on the line + < 0 for p2 right of the line + """ + sorting = (p1[0] - p0[0])*(p2[1]-p0[1]) - (p2[0]-p0[0])*(p1[1]-p0[1]) + if sorting > 0: return 1 + elif sorting < 0: return -1 + else: return 0 + +def is_convex(points): + """Test if a polygon (list of (x,y)) is strictly convex or not. + + :return: True if the polygon is convex, False otherwise + """ + #assert len(points) > 2, "not enough points to form a polygon" + + p0 = points[0] + p1 = points[1] + p2 = points[2] + + xc, yc = 0, 0 + is_same_winding = is_left(p0, p1, p2) + for p2 in points[2:] + [p0] + [p1]: + if is_same_winding != is_left(p0, p1, p2): + return False + a = p1[0] - p0[0], p1[1] - p0[1] # p1-p0 + b = p2[0] - p1[0], p2[1] - p1[1] # p2-p1 + if sign(a[0]) != sign(b[0]): xc +=1 + if sign(a[1]) != sign(b[1]): yc +=1 + p0, p1 = p1, p2 + + return xc <= 2 and yc <= 2 + +def sign(x): + if x < 0: return -1 + else: return 1 + + +def reduce_poly(points, tolerance=50): + """Remove close points to simplify a polyline + tolerance is the min distance between two points squared. + + :return: The reduced polygon as a list of (x,y) + """ + curr_p = points[0] + reduced_ps = [points[0]] + + for p in points[1:]: + x1, y1 = curr_p + x2, y2 = p + dx = fabs(x2 - x1) + dy = fabs(y2 - y1) + l = sqrt((dx*dx) + (dy*dy)) + if l > tolerance: + curr_p = p + reduced_ps.append(p) + + return reduced_ps + +def convex_hull(points): + """Create a convex hull from a list of points. + This function uses the Graham Scan Algorithm. + + :return: Convex hull as a list of (x,y) + """ + ### Find lowest rightmost point + p0 = points[0] + for p in points[1:]: + if p[1] < p0[1]: + p0 = p + elif p[1] == p0[1] and p[0] > p0[0]: + p0 = p + points.remove(p0) + + ### Sort the points angularly about p0 as center + f = partial(is_left, p0) + points.sort(cmp = f) + points.reverse() + points.insert(0, p0) + + ### Find the hull points + hull = [p0, points[1]] + + for p in points[2:]: + + pt1 = hull[-1] + pt2 = hull[-2] + l = is_left(pt2, pt1, p) + if l > 0: + hull.append(p) + else: + while l <= 0 and len(hull) > 2: + hull.pop() + pt1 = hull[-1] + pt2 = hull[-2] + l = is_left(pt2, pt1, p) + hull.append(p) + return hull + -- cgit v0.9.1