From 8ba9d4ae100be129b255230790149f488032fa3c Mon Sep 17 00:00:00 2001 From: Walter Bender Date: Fri, 07 Mar 2014 19:44:11 +0000 Subject: elements cleanup --- (limited to 'elements') diff --git a/elements/add_objects.py b/elements/add_objects.py index bfb7e0b..cec8212 100644 --- a/elements/add_objects.py +++ b/elements/add_objects.py @@ -8,7 +8,7 @@ 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 + 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 @@ -22,7 +22,7 @@ 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 . +along with this program. If not, see . """ from locals import * from elements import box2d @@ -34,64 +34,75 @@ 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, 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) - + + 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) + 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 + lengths to the internal meter system if neccessary + (if INPUT_PIXELS), then call self._add_ball(...) - 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 - + # 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: + if self.parent.input_unit == 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): + 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=(x, y) + bodyDef.position = (x, y) - userData = { 'color' : self.parent.get_color() } + userData = {'color': self.parent.get_color()} bodyDef.userData = userData # Create the Body @@ -99,7 +110,7 @@ class Add: density = 0 body = self.parent.world.CreateBody(bodyDef) - + self.parent.element_count += 1 # Add a shape to the Body @@ -110,12 +121,14 @@ class Add: circleDef.friction = friction body.CreateShape(circleDef) - body.SetMassFromShapes() - - return body + 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) + 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: @@ -127,69 +140,78 @@ class Add: 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 + # 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: + if self.parent.input_unit == 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) + 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() + 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_unit (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]): + if width < 5: + width = 5 + + if (pos1[0] < pos2[0]): x1, y1 = pos1 x2, y2 = pos2 else: x1, y1 = pos2 - x2, y2 = pos1 + 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)) + # 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: + if self.parent.input_unit == 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 + + 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 + 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) - 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): + 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=(x, y) + bodyDef.position = (x, y) - userData = { 'color' : self.parent.get_color() } + userData = {'color': self.parent.get_color()} bodyDef.userData = userData # Create the Body @@ -197,25 +219,28 @@ class Add: density = 0 body = self.parent.world.CreateBody(bodyDef) - + self.parent.element_count += 1 # Add a shape to the Body boxDef = box2d.b2PolygonDef() - - boxDef.SetAsBox(width, height, (0,0), angle) + + boxDef.SetAsBox(width, height, (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() + 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) @@ -223,34 +248,39 @@ class Add: 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 - + """ + # 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: + if self.parent.input_unit == 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)) + 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): + 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=(x, y) - - userData = { 'color' : self.parent.get_color() } + bodyDef.position = (x, y) + + userData = {'color': self.parent.get_color()} bodyDef.userData = userData # Create the Body @@ -258,7 +288,7 @@ class Add: density = 0 body = self.parent.world.CreateBody(bodyDef) - + self.parent.element_count += 1 # Add a shape to the Body @@ -271,36 +301,40 @@ class Add: 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 + + 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]: + 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 + + # 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: + if self.parent.input_unit == INPUT_PIXELS: # translate pixel -> meters x /= self.parent.ppm y /= self.parent.ppm - + # Let's add the body bodyDef = box2d.b2BodyDef() - bodyDef.position=(x, y) + bodyDef.position = (x, y) - userData = { 'color' : self.parent.get_color() } + userData = {'color': self.parent.get_color()} bodyDef.userData = userData # Create the Body @@ -308,12 +342,12 @@ class Add: 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.vertexCount = 4 # rectangle polyDef.density = density polyDef.restitution = restitution polyDef.friction = friction @@ -324,25 +358,25 @@ class Add: circleDef.restitution = restitution circleDef.friction = friction - # Set the scale factor + # Set the scale factor factor = 8.0 v2 = box2d.b2Vec2(*vertices[0]) for v in vertices[1:]: - v1 = v2.copy() - v2 = box2d.b2Vec2(*v) - - vdir = v2-v1 # (v2x-v1x, v2y-v1y) + v1 = v2.copy() + v2 = box2d.b2Vec2(*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) + vn = box2d.b2Vec2(-vdir.y * factor, vdir.x * factor) - v = [ v1+vn, v1-vn, v2-vn, v2+vn ] + 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 - polyDef.setVertices( [vi / self.parent.ppm for vi in v] ) + # Create a line (rect) for each part of the polygon, + # and attach it to the body + polyDef.setVertices([vi / self.parent.ppm for vi in v]) try: polyDef.checkValues() @@ -359,20 +393,21 @@ class Add: break circleDef.localPosition = v2 / self.parent.ppm - body.CreateShape(circleDef) - + body.CreateShape(circleDef) + # Now, all shapes have been attached - body.SetMassFromShapes() - + 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): + 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) + vertices, is_convex = tools_poly.reduce_poly_by_angle(vertices) #print "->", is_convex # If start and endpoints are close to each other, close polygon @@ -380,7 +415,7 @@ class Add: x2, y2 = vertices[-1] dx = x2 - x1 dy = y2 - y1 - l = sqrt((dx*dx)+(dy*dy)) + l = sqrt((dx * dx) + (dy * dy)) if l < 50: vertices[-1] = vertices[0] @@ -392,19 +427,22 @@ class Add: # 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 + return self.convexPoly(vertices, dynamic, density, restitution, + friction), vertices else: # print "concave" - return self.concavePoly(vertices, dynamic, density, restitution, friction), vertices - + 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(...) + 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 @@ -412,31 +450,34 @@ class Add: 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 + # 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 + 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) + + # 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_orig_reduced = vertices vertices = tools_poly.poly_center_vertices(vertices) vertices = tools_poly.convex_hull(vertices) - if len(vertices) < 3: - return - + 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) + x, y = tools_poly.calc_center(vertices_orig_reduced) + return self.poly((x, y), vertices, dynamic, density, restitution, + friction) def to_b2vec(self, pt): # Convert vector to a b2vect @@ -454,33 +495,33 @@ class Add: b1, b2, p1, p2, flag = args p1 = self.to_b2vec(p1) - p2 = self.to_b2vec(p2) - + p2 = self.to_b2vec(p2) + jointDef = box2d.b2DistanceJointDef() jointDef.Initialize(b1, b2, p1, p2) jointDef.collideConnected = flag - - self.parent.world.CreateJoint(jointDef) + + self.parent.world.CreateJoint(jointDef) elif len(args) == 4: # Distance Joint b1, b2, p1, p2 = args p1 = self.to_b2vec(p1) - p2 = self.to_b2vec(p2) - + p2 = self.to_b2vec(p2) + jointDef = box2d.b2DistanceJointDef() jointDef.Initialize(b1, b2, p1, p2) jointDef.collideConnected = True - - self.parent.world.CreateJoint(jointDef) - + + self.parent.world.CreateJoint(jointDef) + elif len(args) == 3: # Revolute Joint between two bodies (unimplemented) pass elif len(args) == 2: - # Revolute Joint to the Background, at point + # Revolute Joint to the Background, at point b1 = self.parent.world.GetGroundBody() b2 = args[0] p1 = self.to_b2vec(args[1]) @@ -494,10 +535,10 @@ class Add: 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 motor(self, body, pt, torque=900, speed=-10): @@ -530,9 +571,8 @@ class Add: else: self.parent.mouseJoint = self.parent.world.CreateJoint(mj) body.WakeUp() - + def remove_mouseJoint(self): if self.parent.mouseJoint: self.parent.world.DestroyJoint(self.parent.mouseJoint) self.parent.mouseJoint = None - diff --git a/elements/callbacks.py b/elements/callbacks.py index 01e9545..d3aae96 100644 --- a/elements/callbacks.py +++ b/elements/callbacks.py @@ -8,7 +8,7 @@ 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 + 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 @@ -22,70 +22,78 @@ 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 . +along with this program. If not, see . """ from locals import * from elements import box2d + class CallbackHandler: - # List of contact callbacks and shapes to start them - sorted by type for quicker access - # Callbacks are saved as callbacks[callback_type][[function, parameters], ...] - callbacks = {} - + # List of contact callbacks and shapes to start them - sorted by + # type for quicker access Callbacks are saved as + # callbacks[callback_type][[function, parameters], ...] + callbacks = {} + def __init__(self, parent): self.parent = parent - - # init callback dict to avoid those slow try + + # init callback dict to avoid those slow try # (especially for self.get, as it is called *often*) for i in xrange(10): self.callbacks[i] = [] - + def add(self, callback_type, callback_handler, *args): """ Users can add callbacks for certain (or all) collisions - + Parameters: - callback_type ......... CALLBACK_CONTACT (nothing else for now) + callback_type ......... CALLBACK_CONTACT + (nothing else for now) callback_handler ...... a callback function - args (optional) ....... a list of parameters which can be used with callbacks.get - + args (optional) ....... a list of parameters which can be + used with callbacks.get + Return: callback_id ... used to remove a callback later (int) """ # Create contact listener if required - if callback_type in [CALLBACK_CONTACT_ADD, CALLBACK_CONTACT_PERSIST, CALLBACK_CONTACT_REMOVE]: - if self.parent.listener == None: + if callback_type in [CALLBACK_CONTACT_ADD, + CALLBACK_CONTACT_PERSIST, + CALLBACK_CONTACT_REMOVE]: + if self.parent.listener is None: self.parent.listener = kContactListener(self.get) - self.parent.world.SetContactListener( self.parent.listener ) + self.parent.world.SetContactListener(self.parent.listener) print "* ContactListener added" - + # Get callback dict for this callback_type c = self.callbacks[callback_type] - + # Append to the Callback Dictionary c.append([callback_handler, args]) self.callbacks[callback_type] = c - + # Return Callback ID # ID = callback_type.callback_index (1...n) return "%i.%i" % (callback_type, len(c)) - + def get(self, callback_type): return self.callbacks[callback_type] - + def start(self, callback_type, *args): callbacks = self.get(callback_type) for c in callbacks: callback, params = c callback() + class kContactListener(box2d.b2ContactListener): - def __init__(self, get_callbacks): + + def __init__(self, get_callbacks): # Init the Box2D b2ContactListener box2d.b2ContactListener.__init__(self) # Function to get the current callbacks self.get_callbacks = get_callbacks - + def check_contact(self, contact_type, point): # Checks if a callback should be started with this contact point contacts = self.get_callbacks(contact_type) @@ -94,9 +102,9 @@ class kContactListener(box2d.b2ContactListener): for c in contacts: callback, bodylist = c if len(bodylist) == 0: - # Without bodylist it's a universal callback (for all bodies) + # Without bodylist it's a universal callback (for all bodies) callback(point) - + else: # This is a callback with specified bodies # See if this contact involves one of the specified @@ -104,19 +112,18 @@ class kContactListener(box2d.b2ContactListener): b2 = str(point.shape2.GetBody()) for s in bodylist: s = str(s) - if b1 == s or b2 == s: + if b1 == s or b2 == s: # Yes, that's the one :) callback(point) - + def Add(self, point): """Called when a contact point is created""" self.check_contact(CALLBACK_CONTACT_ADD, point) - + def Persist(self, point): """Called when a contact point persists for more than a time step""" self.check_contact(CALLBACK_CONTACT_PERSIST, point) - + def Remove(self, point): """Called when a contact point is removed""" self.check_contact(CALLBACK_CONTACT_REMOVE, point) - diff --git a/elements/camera.py b/elements/camera.py index c45b27d..7fdf975 100644 --- a/elements/camera.py +++ b/elements/camera.py @@ -8,7 +8,7 @@ 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 + 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 @@ -22,28 +22,31 @@ 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 . +along with this program. If not, see . """ from locals import * + class Camera: """ The Camera class. We will see :) Please also see: http://www.assembla.com/spaces/elements/tickets/31 - + This class currently handles: - Scaling factor - Screen Offset from the World Coordinate System - + Inputs from the user have to be checked for them. - Places to check for it: elements.py, drawing.py, add_objects.py - """ - scale_factor = 1.0 # All coords to the renderer are multiplied with the scale factor in elements.draw() - track_body = None # Body which means to be tracked. Offset is set at each elements.draw() - + # All coords to the renderer are multiplied with the scale factor + # in elements.draw() + scale_factor = 1.0 + # Body which means to be tracked. Offset is set at each elements.draw() + track_body = None + def __init__(self, parent): self.parent = parent - + def track(self, body): """ Start tracking a specific body """ @@ -53,17 +56,17 @@ class Camera: """ Stop tracking a body """ self.track_body = None - + def center(self, pos, screenCoord=True, stopTrack=True): """ Centers the camera at given screen coordinates -- in pixel Typical call: world.camera.center(event.pos) - + Problem: Works ONLY WITH screenCoord now! - """ + """ x, y = pos x -= self.parent.display_width / 2 - y -= self.parent.display_height / 2 + y -= self.parent.display_height / 2 if screenCoord: x /= self.scale_factor @@ -71,25 +74,30 @@ class Camera: # Set the offset self.inc_offset((x, y), screenCoord, stopTrack) - + def set_offset(self, offset, screenCoord=True, stopTrack=True): - """ Set an offset from the screen to the world cs + """ Set an offset from the screen to the world cs -- in screen (or world) coordinates and in pixel """ # Stop tracking of an object - if stopTrack: self.track_stop() + if stopTrack: + self.track_stop() # If screenCoords, we have to bring them to the world cs - if screenCoord: x, y = self.parent.to_world(offset) - else: x, y = offset + if screenCoord: + x, y = self.parent.to_world(offset) + else: + x, y = offset self._set_offset((x/self.parent.ppm, y/self.parent.ppm)) def inc_offset(self, offset, screenCoord=True, stopTrack=True): - """ Increment an offset from the screen to the world cs -- in world coordinates and in pixel + """ Increment an offset from the screen to the world cs -- in world + coordinates and in pixel """ # Stop tracking of an object - if stopTrack: self.track_stop() + if stopTrack: + self.track_stop() # Get Current Offset x, y = self.parent.screen_offset_pixel @@ -97,28 +105,30 @@ class Camera: # Bring the directions into the world coordinate system if screenCoord: - if self.parent.inputAxis_x_left: dx *= -1 - if self.parent.inputAxis_y_down: dy *= -1 - + if self.parent.inputAxis_x_left: + dx *= -1 + if self.parent.inputAxis_y_down: + dy *= -1 + # Set New Offset - self._set_offset(((x+dx)/self.parent.ppm, (y+dy)/self.parent.ppm)) - + self._set_offset(((x + dx) / self.parent.ppm, + (y + dy) / self.parent.ppm)) + def _set_offset(self, offset): """ Set the screen offset to the world coordinate system (using meters and the world coordinate system's orientation) """ - x, y = offset + x, y = offset self.parent.screen_offset = (x, y) - self.parent.screen_offset_pixel = (x*self.parent.ppm, y*self.parent.ppm) - + self.parent.screen_offset_pixel = (x * self.parent.ppm, + y * self.parent.ppm) + def set_scale_factor(self, factor=1.0): """ Zoom factor for the renderer 1.0 = 1:1 (original) """ self.scale_factor = factor - + def inc_scale_factor(self, factor=0.0): """ Increases the zooms for the renderer a given factor """ self.scale_factor += factor - - \ No newline at end of file diff --git a/elements/drawing.py b/elements/drawing.py index b4722fb..c79b0f3 100644 --- a/elements/drawing.py +++ b/elements/drawing.py @@ -8,7 +8,7 @@ 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 + 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 @@ -22,16 +22,15 @@ 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 . +along with this program. If not, see . """ from math import pi from math import cos from math import sin -from math import sqrt import tools -# Functions of a rendering class +# Functions of a rendering class # mandatory: # __init__ # start_drawing @@ -47,32 +46,33 @@ import tools # for cairo: # draw_text # for opengl: -# +# # IMPORTANT # The drawing functions get the coordinates in their screen coordinate system # no need for translations anymore :) + class draw_pygame(object): """ This class handles the drawing with pygame, which is really simple since we only need draw_ellipse and draw_polygon. """ lineWidth = 0 - + def __init__(self): """ Load pygame.draw and pygame.Rect, and reference it for the drawing methods - + Parameters: surface .... pygame surface (default: None) - lineWidth .. + lineWidth .. Return: Class draw_pygame() """ - print "* Pygame selected as renderer" + print "* Pygame selected as renderer" from pygame import draw from pygame import Rect - + self.draw = draw self.Rect = Rect @@ -93,43 +93,43 @@ class draw_pygame(object): def start_drawing(self): pass - + def after_drawing(self): pass def draw_circle(self, clr, pt, radius, angle): """ Draw a circle - + Parameters: pt ........ (x, y) clr ....... color in rgb ((r), (g), (b)) - radius .... circle radius + radius .... circle radius angle ..... rotation in radians - + Return: - """ x, y = pt - + x1 = x - radius y1 = y - radius - - rect = self.Rect( [x1, y1, 2*radius, 2*radius] ) + + rect = self.Rect([x1, y1, 2 * radius, 2 * radius]) self.draw.ellipse(self.surface, clr, rect, self.lineWidth) - + # draw the orientation vector if radius > 10: rx = cos(angle) * radius ry = -sin(angle) * radius - - self.draw.line(self.surface, (255,255,255), pt, (x+rx, y+ry)) + + self.draw.line(self.surface, (255, 255, 255), pt, (x + rx, y + ry)) def draw_polygon(self, clr, points): """ Draw a polygon - + Parameters: clr ....... color in rgb ((r), (g), (b)) points .... polygon points in normal (x,y) positions - + Return: - """ self.draw.polygon(self.surface, clr, points, self.lineWidth) @@ -137,33 +137,34 @@ class draw_pygame(object): def draw_lines(self, clr, closed, points, width=None): """ Draw a polygon - + Parameters: clr ....... color in rgb ((r), (g), (b)) points .... polygon points in normal (x,y) positions - + Return: - """ - if width == None: + if width is None: lw = self.lineWidth - else: + else: lw = width - + self.draw.lines(self.surface, clr, closed, points, lw) + class draw_cairo(object): """ This class handles the drawing with cairo, which is really simple since we only need draw_ellipse and draw_polygon. """ window = None - da = None + da = None circle_surface = None - box_surface = None + box_surface = None def __init__(self, drawMethod="filled"): """ Load cairo.draw and cairo.Rect, and reference it for the drawing methods - + Return: Class draw_cairo() """ print "* Cairo selected as renderer" @@ -172,12 +173,12 @@ class draw_cairo(object): self.set_drawing_method(drawMethod) #self.draw_box = self.draw_box_image - def set_lineWidth(self, lw): # unused - self.lineWidth = lw + def set_lineWidth(self, lw): # unused + self.lineWidth = lw def set_drawing_area(self, da): """ Set the area for Cairo to draw to - + da ...... drawing area (gtk.DrawingArea) Return: - @@ -193,20 +194,22 @@ class draw_cairo(object): def start_drawing(self): self.width, self.height = self.window.get_size() - self.imagesurface = self.cairo.ImageSurface(self.cairo.FORMAT_ARGB32, self.width, self.height); + self.imagesurface = self.cairo.ImageSurface( + self.cairo.FORMAT_ARGB32, self.width, self.height) self.ctx = ctx = self.cairo.Context(self.imagesurface) - ctx.set_source_rgb(1, 1, 1) # background color + ctx.set_source_rgb(1, 1, 1) # background color ctx.paint() ctx.move_to(0, 0) - ctx.set_source_rgb(0, 0, 0) # defaults for the rest of the drawing + ctx.set_source_rgb(0, 0, 0) # defaults for the rest of the drawing ctx.set_line_width(1) ctx.set_tolerance(0.1) ctx.set_line_join(self.cairo.LINE_CAP_BUTT) - # LINE_CAP_BUTT, LINE_CAP_ROUND, LINE_CAP_SQUARE, LINE_JOIN_BEVEL, LINE_JOIN_MITER, LINE_JOIN_ROUND - + # LINE_CAP_BUTT, LINE_CAP_ROUND, LINE_CAP_SQUARE, + # LINE_JOIN_BEVEL, LINE_JOIN_MITER, LINE_JOIN_ROUND + #ctx.set_dash([20/4.0, 20/4.0], 0) def after_drawing(self): @@ -228,14 +231,14 @@ class draw_cairo(object): clr = tools.rgb2floats(clr) self.ctx.set_source_rgb(*clr) self.ctx.move_to(x, y) - self.ctx.arc(x, y, radius, 0, 2*3.1415) + self.ctx.arc(x, y, radius, 0, 2 * pi) self.ctx.fill() def draw_circle(): pass def draw_circle_image(self, clr, pt, radius, angle=0, sf=None): - if sf == None: + if sf is None: sf = self.circle_surface x, y = pt self.ctx.save() @@ -244,30 +247,32 @@ class draw_cairo(object): image_r = sf.get_width() / 2 scale = float(radius) / image_r self.ctx.scale(scale, scale) - self.ctx.translate(-0.5*sf.get_width(), -0.5*sf.get_height()) + self.ctx.translate(-0.5 * sf.get_width(), -0.5 * sf.get_height()) self.ctx.set_source_surface(sf) self.ctx.paint() self.ctx.restore() - def draw_image(self, source, pt, scale=1.0, rot=0, sourcepos=(0,0)): + def draw_image(self, source, pt, scale=1.0, rot=0, sourcepos=(0, 0)): self.ctx.save() self.ctx.rotate(rot) self.ctx.scale(scale, scale) destx, desty = self.ctx.device_to_user_distance(pt[0], pt[1]) - self.ctx.set_source_surface(source, destx-sourcepos[0], desty-sourcepos[1]) - self.ctx.rectangle(destx, desty, source.get_width(), source.get_height()) + self.ctx.set_source_surface(source, destx - sourcepos[0], + desty - sourcepos[1]) + self.ctx.rectangle(destx, desty, source.get_width(), + source.get_height()) self.ctx.fill() self.ctx.restore() - + def draw_polygon(self, clr, points): """ Draw a polygon - + Parameters: clr ....... color in rgb ((r), (g), (b)) points .... polygon points in normal (x,y) positions - + Return: - - """ + """ clr = tools.rgb2floats(clr) self.ctx.set_source_rgb(clr[0], clr[1], clr[2]) @@ -275,28 +280,31 @@ class draw_cairo(object): self.ctx.move_to(pt[0], pt[1]) for pt in points[1:]: self.ctx.line_to(pt[0], pt[1]) - + self.ctx.fill() - - def draw_text(self, text, center, clr=(0,0,0), size=12, fontname="Georgia"): + + def draw_text(self, text, center, clr=(0, 0, 0), size=12, + fontname="Georgia"): clr = tools.rgb2floats(clr) self.ctx.set_source_rgb(clr[0], clr[1], clr[2]) - self.ctx.select_font_face(fontname, self.cairo.FONT_SLANT_NORMAL, self.cairo.FONT_WEIGHT_NORMAL) + self.ctx.select_font_face(fontname, self.cairo.FONT_SLANT_NORMAL, + self.cairo.FONT_WEIGHT_NORMAL) self.ctx.set_font_size(size) x_bearing, y_bearing, width, height = self.ctx.text_extents(text)[:4] - self.ctx.move_to(center[0] + 0.5 - width / 2 - x_bearing, center[1] + 0.5 - height / 2 - y_bearing) + self.ctx.move_to(center[0] + 0.5 - width / 2 - x_bearing, + center[1] + 0.5 - height / 2 - y_bearing) self.ctx.show_text(text) def draw_lines(self, clr, closed, points): """ Draw a polygon - + Parameters: clr ....... color in rgb ((r), (g), (b)) closed .... whether or not to close the lines (as a polygon) points .... polygon points in normal (x,y) positions Return: - - """ + """ clr = tools.rgb2floats(clr) self.ctx.set_source_rgb(clr[0], clr[1], clr[2]) @@ -311,16 +319,18 @@ class draw_cairo(object): self.ctx.stroke() + class draw_opengl_pyglet(object): """ This class handles the drawing with pyglet """ lineWidth = 0 + def __init__(self): """ Load pyglet.gl, and reference it for the drawing methods - + Parameters: surface .... not used with pyglet - lineWidth .. + lineWidth .. """ print "* OpenGL_Pyglet selected as renderer" @@ -329,48 +339,49 @@ class draw_opengl_pyglet(object): def set_lineWidth(self, lw): self.lineWidth = lw - + def draw_circle(self, clr, pt, radius, a=0): clr = tools.rgb2floats(clr) - self.gl.glColor3f(clr[0], clr[1], clr[2]) + self.gl.glColor3f(clr[0], clr[1], clr[2]) x, y = pt - segs = 15 - coef = 2.0*pi/segs; - - self.gl.glBegin(self.gl.GL_LINE_LOOP) - for n in range(segs): - rads = n*coef - self.gl.glVertex2f(radius*cos(rads + a) + x, radius*sin(rads + a) + y) - self.gl.glVertex2f(x,y) - self.gl.glEnd() + segs = 15 + coef = 2.0 * pi / segs + + self.gl.glBegin(self.gl.GL_LINE_LOOP) + for n in range(segs): + rads = n*coef + self.gl.glVertex2f(radius * cos(rads + a) + x, + radius * sin(rads + a) + y) + self.gl.glVertex2f(x, y) + self.gl.glEnd() def draw_polygon(self, clr, points): clr = tools.rgb2floats(clr) - self.gl.glColor3f(clr[0], clr[1], clr[2]) - + self.gl.glColor3f(clr[0], clr[1], clr[2]) + self.gl.glBegin(self.gl.GL_LINES) p1 = points[0] for p in points[1:]: x1, y1 = p1 x2, y2 = p1 = p - + self.gl.glVertex2f(x1, y1) self.gl.glVertex2f(x2, y2) x1, y1 = points[0] - + self.gl.glVertex2f(x2, y2) self.gl.glVertex2f(x1, y1) self.gl.glEnd() - + def draw_lines(self, clr, closed, points): pass def start_drawing(self): pass - + def after_drawing(self): pass diff --git a/elements/elements.py b/elements/elements.py index 4033688..06d43cc 100644 --- a/elements/elements.py +++ b/elements/elements.py @@ -9,7 +9,7 @@ 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 + 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 @@ -23,9 +23,9 @@ 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 . +along with this program. If not, see . """ -__version__= '0.11' +__version__ = '0.11' __contact__ = '' # Load Box2D @@ -34,8 +34,8 @@ try: except: print 'Could not load the pybox2d library (Box2D).' print 'Please run "setup.py install" to install the dependencies.' - print - print 'Alternatively, recompile pybox2d for your system and python version.' + print + print 'Or recompile pybox2d for your system and python version.' print "See http://code.google.com/p/pybox2d" exit() @@ -53,90 +53,97 @@ import callbacks import camera # Main Class + + class Elements: """The class which handles all interaction with the box2d engine """ # Settings - run_physics =True # Can pause the simulation - element_count =0 # Element Count - renderer =None # Drawing class (from drawing.py) - input =INPUT_PIXELS # Default Input in Pixels! (can change to INPUT_METERS) - line_width =0 # Line Width in Pixels (0 for fill) - listener =None - - screen_offset = (0, 0) # Offset screen from world coordinate system (x, y) [meter5] - screen_offset_pixel = (0, 0) # Offset screen from world coordinate system (x, y) [pixel] - + run_physics = True # Can pause the simulation + element_count = 0 # Element Count + renderer = None # Drawing class (from drawing.py) + # Default Input in Pixels! (can change to INPUT_METERS) + input_unit = INPUT_PIXELS + line_width = 0 # Line Width in Pixels (0 for fill) + listener = None + + # Offset screen from world coordinate system (x, y) [meter5] + screen_offset = (0, 0) + # Offset screen from world coordinate system (x, y) [pixel] + screen_offset_pixel = (0, 0) + # The internal coordination system is y+=up, x+=right # But it's possible to change the input coords to something else, # they will then be translated on input - inputAxis_x_left = False # positive to the right by default - inputAxis_y_down = True # positive to up by default + inputAxis_x_left = False # positive to the right by default + inputAxis_y_down = True # positive to up by default mouseJoint = None - def __init__(self, screen_size, gravity=(0.0,-9.0), ppm=100.0, renderer='pygame'): + def __init__(self, screen_size, gravity=(0.0, -9.0), ppm=100.0, + renderer='pygame'): """ Init the world with boundaries and gravity, and init colors. - + Parameters: screen_size .. (w, h) -- screen size in pixels [int] gravity ...... (x, y) in m/s^2 [float] default: (0.0, -9.0) ppm .......... pixels per meter [float] default: 100.0 - renderer ..... which drawing method to use (str) default: 'pygame' + renderer ..... which drawing method to use (str) default: + 'pygame' Return: class Elements() """ self.set_screenSize(screen_size) self.set_drawingMethod(renderer) - + # Create Subclasses self.add = add_objects.Add(self) self.callbacks = callbacks.CallbackHandler(self) self.camera = camera.Camera(self) - + # Set Boundaries - self.worldAABB=box2d.b2AABB() + self.worldAABB = box2d.b2AABB() self.worldAABB.lowerBound = (-100.0, -100.0) self.worldAABB.upperBound = (100.0, 100.0) - + # Gravity + Bodies will sleep on outside self.gravity = gravity self.doSleep = True - + # Create the World self.world = box2d.b2World(self.worldAABB, self.gravity, self.doSleep) - # Init Colors + # Init Colors self.init_colors() - + # Set Pixels per Meter self.ppm = ppm - def set_inputUnit(self, input): + def set_inputUnit(self, input_unit): """ Change the input unit to either meter or pixels - + Parameters: input ... INPUT_METERS or INPUT_PIXELS - + Return: - """ - self.input = input - + self.input_unit = input_unit + def set_inputAxisOrigin(self, left=True, top=False): """ Change the origin of the input coordinate system axis - + Parameters: left ... True or False -- x = 0 is at the left? top .... True or False -- y = 0 is at the top? - + Return: - - """ + """ self.inputAxis_x_left = not left self.inputAxis_y_down = top def set_drawingMethod(self, m, *kw): """ Set a drawing method (from drawing.py) - + Parameters: m .... 'pygame' or 'cairo' *kw .. keywords to pass to the initializer of the drawing method @@ -144,86 +151,89 @@ class Elements: Return: True if ok, False if no method identifier m found """ try: - self.renderer = getattr(drawing, "draw_%s" % m) (*kw) + self.renderer = getattr(drawing, "draw_%s" % m)(*kw) return True except AttributeError: return False - + def set_screenSize(self, size): """ Set the current screen size - - Parameters: + + Parameters: size ... (int(width), int(height)) in pixels - + Return: - """ self.display_width, self.display_height = size def init_colors(self): """ Init self.colors with a fix set of hex colors - - Return: - + + Return: - """ self.fixed_color = None self.cur_color = 0 self.colors = [ - "#737934", "#729a55", "#040404", "#1d4e29", "#ae5004", "#615c57", - "#6795ce", "#203d61", "#8f932b" + "#737934", "#729a55", "#040404", "#1d4e29", "#ae5004", "#615c57", + "#6795ce", "#203d61", "#8f932b" ] shuffle(self.colors) def set_color(self, clr): - """ Set a fixed color for all future Elements (until reset_color() is called) - - Parameters: + """ Set a fixed color for all future Elements (until reset_color() + is called) + + Parameters: clr ... Hex '#123123' or RGB ((r), (g), (b)) - + Return: - """ self.fixed_color = clr - + def reset_color(self): """ All Elements from now on will be drawn in random colors - - Return: - + + Return: - """ self.fixed_color = None def get_color(self): - """ Get a color - either the fixed one or the next from self.colors - - Return: clr = ((R), (G), (B)) + """ Get a color - either the fixed one or the next from self.colors + + Return: clr = ((R), (G), (B)) """ - if self.fixed_color != None: + if self.fixed_color is not None: return self.fixed_color - - if self.cur_color == len(self.colors): + + if self.cur_color == len(self.colors): self.cur_color = 0 shuffle(self.colors) - + clr = self.colors[self.cur_color] if clr[0] == "#": clr = tools.hex2rgb(clr) - + self.cur_color += 1 return clr - + def update(self, fps=50.0, vel_iterations=10, pos_iterations=8): """ Update the physics, if not paused (self.run_physics) - + Parameters: fps ............. fps with which the physics engine shall work - vel_iterations .. velocity substeps per step for smoother simulation - pos_iterations .. position substeps per step for smoother simulation - + vel_iterations .. velocity substeps per step for smoother + simulation + pos_iterations .. position substeps per step for smoother + simulation + Return: - """ if self.run_physics: self.world.Step(1.0 / fps, vel_iterations, pos_iterations) def translate_coord(self, point): - """ Flips the coordinates in another coordinate system orientation, if necessary - (screen <> world coordinate system) + """ Flips the coordinates in another coordinate system orientation, + if necessary (screen <> world coordinate system) """ x, y = point @@ -232,46 +242,49 @@ class Elements: if self.inputAxis_y_down: y = self.display_height - y - + return (x, y) - + def translate_coords(self, pointlist): - """ Flips the coordinates in another coordinate system orientation, if necessary - (screen <> world coordinate system) - """ - p_out = [] + """Flips the coordinates in another coordinate system orientation, if + necessary (screen <> world coordinate system) + """ + p_out = [] for p in pointlist: p_out.append(self.translate_coord(p)) return p_out def to_world(self, pos): - """ Transfers a coordinate from the screen to the world coordinate system (pixels) + """ Transfers a coordinate from the screen to the world + coordinate system (pixels) + - Change to the right axis orientation - Include the offset: screen -- world coordinate system - - Include the scale factor (Screen coordinate system might have a scale factor) + - Include the scale factor (Screen coordinate system might have + a scale factor) """ dx, dy = self.screen_offset_pixel - + x = pos[0] / self.camera.scale_factor y = pos[1] / self.camera.scale_factor - + x, y = self.translate_coord((round(x), round(y))) - return (x+dx, y+dy) - + return(x + dx, y + dy) + def to_screen(self, pos): - """ Transfers a coordinate from the world to the screen coordinate system (pixels) - and by the screen offset + """Transfers a coordinate from the world to the screen coordinate + system (pixels) and by the screen offset """ dx, dy = self.screen_offset_pixel x = pos[0] - dx y = pos[1] - dy - + sx, sy = self.translate_coord((x, y)) return (sx * self.camera.scale_factor, sy * self.camera.scale_factor) - + def meter_to_screen(self, i): return i * self.ppm * self.camera.scale_factor - + def get_bodies_at_pos(self, search_point, include_static=False, area=0.01): """ Check if given point (screen coordinates) is inside any body. If yes, return all found bodies, if not found return False @@ -280,11 +293,11 @@ class Elements: sx /= self.ppm sy /= self.ppm - f = area/self.camera.scale_factor + f = area / self.camera.scale_factor - AABB=box2d.b2AABB() - AABB.lowerBound = (sx-f, sy-f) - AABB.upperBound = (sx+f, sy+f) + AABB = box2d.b2AABB() + AABB.lowerBound = (sx - f, sy - f) + AABB.upperBound = (sx + f, sy + f) amount, shapes = self.world.Query(AABB, 2) @@ -297,101 +310,110 @@ class Elements: if not include_static: if body.IsStatic() or body.GetMass() == 0.0: continue - + if s.TestPoint(body.GetXForm(), (sx, sy)): bodylist.append(body) return bodylist - + def draw(self): """ If a drawing method is specified, this function passes the objects to the module in pixels. - + Return: True if the objects were successfully drawn False if the renderer was not set or another error occurred """ self.callbacks.start(CALLBACK_DRAWING_START) - - # No need to run through the loop if there's no way to draw - if not self.renderer: + + # No need to run through the loop if there's no way to draw + if not self.renderer: return False if self.camera.track_body: # Get Body Center - p1 = self.camera.track_body.GetWorldCenter() - + p1 = self.camera.track_body.GetWorldCenter() + # Center the Camera There, False = Don't stop the tracking - self.camera.center(self.to_screen((p1.x*self.ppm, p1.y*self.ppm)), stopTrack=False) - + self.camera.center(self.to_screen((p1.x * self.ppm, + p1.y * self.ppm)), + stopTrack=False) + # Walk through all known elements self.renderer.start_drawing() - + for body in self.world.bodyList: xform = body.GetXForm() shape = body.GetShapeList() angle = body.GetAngle() - + if shape: userdata = body.GetUserData() - clr = userdata['color'] - - for shape in body.shapeList: + if 'color' in userdata: + clr = userdata['color'] + else: + clr = self.colors[0] + + for shape in body.shapeList: type = shape.GetType() - + if type == box2d.e_circleShape: position = box2d.b2Mul(xform, shape.GetLocalPosition()) - - pos = self.to_screen((position.x*self.ppm, position.y*self.ppm)) - self.renderer.draw_circle(clr, pos, self.meter_to_screen(shape.radius), angle) + + pos = self.to_screen((position.x * self.ppm, + position.y * self.ppm)) + + self.renderer.draw_circle( + clr, pos, self.meter_to_screen(shape.radius), angle) elif type == box2d.e_polygonShape: points = [] for v in shape.vertices: pt = box2d.b2Mul(xform, v) - x, y = self.to_screen((pt.x*self.ppm, pt.y*self.ppm)) + x, y = self.to_screen((pt.x * self.ppm, + pt.y * self.ppm)) points.append([x, y]) self.renderer.draw_polygon(clr, points) - + else: - print " unknown shape type:%d" % shape.GetType() - + print "unknown shape type:%d" % shape.GetType() for joint in self.world.jointList: p2 = joint.GetAnchor1() - p2 = self.to_screen((p2.x*self.ppm, p2.y*self.ppm)) - + p2 = self.to_screen((p2.x * self.ppm, p2.y * self.ppm)) + p1 = joint.GetAnchor2() - p1 = self.to_screen((p1.x*self.ppm, p1.y*self.ppm)) - + p1 = self.to_screen((p1.x * self.ppm, p1.y * self.ppm)) + if p1 == p2: - self.renderer.draw_circle((255,255,255), p1, 2, 0) + self.renderer.draw_circle((255, 255, 255), p1, 2, 0) else: - self.renderer.draw_lines((0,0,0), False, [p1, p2], 3) + self.renderer.draw_lines((0, 0, 0), False, [p1, p2], 3) self.callbacks.start(CALLBACK_DRAWING_END) self.renderer.after_drawing() - - return True + return True def mouse_move(self, pos): pos = self.to_world(pos) x, y = pos x /= self.ppm y /= self.ppm - + if self.mouseJoint: - self.mouseJoint.SetTarget((x,y)) + self.mouseJoint.SetTarget((x, y)) def pickle_save(self, fn, additional_vars={}): import cPickle as pickle self.add.remove_mouseJoint() - + if not additional_vars and hasattr(self, '_pickle_vars'): - additional_vars=dict((var, getattr(self, var)) for var in self._pickle_vars) + additional_vars = dict((var, getattr(self, var)) + for var in self._pickle_vars) - save_values = [self.world, box2d.pickle_fix(self.world, additional_vars, 'save')] + save_values = [self.world, box2d.pickle_fix(self.world, + additional_vars, 'save')] try: pickle.dump(save_values, open(fn, 'wb')) @@ -411,13 +433,13 @@ class Elements: try: world, variables = pickle.load(open(fn, 'rb')) world = world._pickle_finalize() - variables = box2d.pickle_fix(world, variables, 'load') + variables = box2d.pickle_fix(world, variables, 'load') except Exception, s: print 'Error while loading world: ', s return - + self.world = world - + if set_vars: # reset the additional saved variables: for var, value in variables.items(): @@ -461,7 +483,8 @@ class Elements: if shapename == "b2CircleShape": modelshape['type'] = 'circle' modelshape['radius'] = shape.radius - modelshape['localPosition'] = shape.localPosition.tuple() + modelshape['localPosition'] = \ + shape.localPosition.tuple() if shapename == "b2PolygonShape": modelshape['type'] = 'polygon' modelshape['vertices'] = shape.vertices @@ -528,19 +551,19 @@ class Elements: def json_load(self, path, serialized=False): import json - self.world.GetGroundBody().userData = {"saveid" : 0} + self.world.GetGroundBody().userData = {"saveid": 0} f = open(path, 'r') worldmodel = json.loads(f.read()) f.close() - #clean world + # clean world for joint in self.world.GetJointList(): self.world.DestroyJoint(joint) for body in self.world.GetBodyList(): if body != self.world.GetGroundBody(): self.world.DestroyBody(body) - #load bodys + # load bodies for body in worldmodel['bodylist']: bodyDef = box2d.b2BodyDef() bodyDef.position = body['position'] @@ -550,7 +573,7 @@ class Elements: #_logger.debug(newBody) newBody.angularVelocity = body['angularVelocity'] newBody.linearVelocity = body['linearVelocity'] - if body.has_key('shapes'): + if 'shapes' in body: for shape in body['shapes']: if shape['type'] == 'polygon': polyDef = box2d.b2PolygonDef() @@ -565,7 +588,7 @@ class Elements: circleDef.density = shape['density'] circleDef.restitution = shape['restitution'] circleDef.friction = shape['friction'] - circleDef.localPosition = shape['localPosition'] + circleDef.localPosition = shape['localPosition'] newBody.CreateShape(circleDef) newBody.SetMassFromShapes() @@ -577,7 +600,7 @@ class Elements: body2 = self.getBodyWithSaveId(joint['body2']) anch2 = joint['anchor2'] jointDef.collideConnected = joint['collideConnected'] - jointDef.Initialize(body1,body2,anch1,anch2) + jointDef.Initialize(body1, body2, anch1, anch2) jointDef.SetUserData(joint['userData']) self.world.CreateJoint(jointDef) if joint['type'] == 'revolute': @@ -585,7 +608,7 @@ class Elements: body1 = self.getBodyWithSaveId(joint['body1']) body2 = self.getBodyWithSaveId(joint['body2']) anchor = joint['anchor'] - jointDef.Initialize(body1,body2,anchor) + jointDef.Initialize(body1, body2, anchor) jointDef.SetUserData(joint['userData']) jointDef.enableMotor = joint['enableMotor'] jointDef.motorSpeed = joint['motorSpeed'] @@ -594,17 +617,17 @@ class Elements: self.additional_vars = {} addvars = {} - for (k,v) in worldmodel['additional_vars'].items(): + for (k, v) in worldmodel['additional_vars'].items(): addvars[k] = v if serialized and 'trackinfo' in addvars: trackinfo = addvars['trackinfo'] for key, info in trackinfo.iteritems(): if not info[3]: - addvars['trackinfo'][key][0] = \ - self.getBodyWithSaveId(info[0]) - addvars['trackinfo'][key][1] = \ - self.getBodyWithSaveId(info[1]) + addvars['trackinfo'][key][0] = \ + self.getBodyWithSaveId(info[0]) + addvars['trackinfo'][key][1] = \ + self.getBodyWithSaveId(info[1]) else: addvars['trackinfo'][key][0] = None addvars['trackinfo'][key][1] = None @@ -612,9 +635,9 @@ class Elements: self.additional_vars = addvars for body in self.world.GetBodyList(): - del body.userData['saveid'] #remove temporary data + del body.userData['saveid'] # remove temporary data - def getBodyWithSaveId(self,saveid): + def getBodyWithSaveId(self, saveid): for body in self.world.GetBodyList(): if body.userData['saveid'] == saveid: return body diff --git a/elements/locals.py b/elements/locals.py index 85528f7..352518a 100644 --- a/elements/locals.py +++ b/elements/locals.py @@ -8,7 +8,7 @@ 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 + 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 @@ -22,16 +22,16 @@ 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 . +along with this program. If not, see . """ INPUT_METERS = 0 INPUT_PIXELS = 1 CALLBACK_CONTACT_ADD = 0 CALLBACK_CONTACT_PERSIST = 1 -CALLBACK_CONTACT_REMOVE = 2 +CALLBACK_CONTACT_REMOVE = 2 -CALLBACK_DRAWING_START = 3 -CALLBACK_DRAWING_END = 4 +CALLBACK_DRAWING_START = 3 +CALLBACK_DRAWING_END = 4 FLT_EPSILON = 1.192092896e-07 diff --git a/elements/menu.py b/elements/menu.py index 1c8a417..3ea5d64 100644 --- a/elements/menu.py +++ b/elements/menu.py @@ -8,7 +8,7 @@ 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 + 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 @@ -22,7 +22,7 @@ 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 . +along with this program. If not, see . """ import pygame from pygame.locals import * @@ -32,24 +32,25 @@ import tools COLOR_HEX_BLUE1 = "6491a4" COLOR_HEX_BLUE2 = "9ec9ff" -class MenuItem: + +class MenuItem: # padding [px]: left, top, right, bottom padding = (5, 2, 5, 2) - + def empty(self, *args): pass - + def __init__(self, title, pos, userData, parent=None, callback=None): self.title = title self.userData = userData self.parent = parent self.childs = [] - + if self.parent: self.visible = False else: self.visible = True - + if callback: self.callback = callback else: @@ -57,143 +58,150 @@ class MenuItem: # Create Surface and Stuff :) self.font = pygame.font.Font(None, 32) - text = self.font.render(title, 1, (255,255,255)) + text = self.font.render(title, 1, (255, 255, 255)) - rx, ry, rw, rh = rect = text.get_rect() + rx, ry, rw, rh = text.get_rect() pl, pt, pr, pb = self.padding - - s1 = pygame.Surface((rw+pl+pr, rh+pt+pb)) + + s1 = pygame.Surface((rw + pl + pr, rh + pt + pb)) s1.fill(tools.hex2rgb(COLOR_HEX_BLUE1)) s1.blit(text, (pl, pt)) - s2 = pygame.Surface((rw+pl+pr, rh+pt+pb)) + s2 = pygame.Surface((rw + pl + pr, rh + pt + pb)) s2.fill(tools.hex2rgb(COLOR_HEX_BLUE2)) s2.blit(text, (pl, pt)) - + self.rect = s1.get_rect().move(pos) self.surface_inactive = s1 self.surface_active = s2 - + def pos_inside(self, pos): if not self.visible: return False - - x,y,w,h = self.rect + + x, y, w, h = self.rect px, py = pos - - if px > x and px < x+w and py > y and py < y+h: + + if px > x and px < x + w and py > y and py < y + h: return True else: return False - + + class MenuClass: """ Important: Never delete an Item, just overwrite it if deleting, else the menuitem id's get messed up """ # current active menu point it focus = False - + # each item is stored as MenuItem - items = [] + items = [] # where to start drawing start_at = (0, 0) - + # menubar properties - height = 0 # px - width = 0 # px (set in set_width) - setWidth = False # if width was set by hand (if not, increase width by adding stuff) - + height = 0 # px + width = 0 # px (set in set_width) + # if width was set by hand (if not, increase width by adding stuff) + setWidth = False + def __init__(self): self.draw_at = self.start_at - + def set_width(self, width): self.setWidth = True self.width = width - + def addItem(self, title, callback=None, userData='', parent=None): # Get position for the Item - if parent: draw_at = (0,0) - else: draw_at = self.draw_at - + if parent: + draw_at = (0, 0) + else: + draw_at = self.draw_at + # Create Items - M = MenuItem(title=title, pos=draw_at, userData=userData, parent=parent, callback=callback) + M = MenuItem(title=title, pos=draw_at, userData=userData, + parent=parent, callback=callback) self.items.append(M) - + # Set a new position - x,y,w,h = M.rect + x, y, w, h = M.rect x, y = self.draw_at - + if parent: # Set the info that the item has a child to the parent item - self.items[parent-1].childs.append(len(self.items)-1) + self.items[parent-1].childs.append(len(self.items) - 1) else: # New next drawing position - self.draw_at = (x+w, y) + self.draw_at = (x + w, y) # Adjust the width of the menu bar if not self.setWidth: - self.width = x+w + self.width = x + w # Adjust the height of the menu bar - if h > self.height: self.height = h + 2 + if h > self.height: + self.height = h + 2 # Return array id of this item return len(self.items) - + def click(self, pos): """ Checks a click for menuitems and starts the callback if found - - Return: True if a menu item was found or hit the MenuBar, and False if not + + Return: True if a menu item was found or hit the MenuBar, + and False if not """ focus_in = self.focus - + found = False for i in xrange(len(self.items)): item = self.items[i] if item.pos_inside(pos): found = True item.callback(item.title, item.userData) - + # Expand the menu if necessary if len(item.childs) > 0: - self.focus = i+1 - - # Close any opened menu windows if clicked somewhere else + self.focus = i + 1 + + # Close any opened menu windows if clicked somewhere else if self.focus == focus_in: self.focus = False - self.subwin_rect = (0,0,0,0) + self.subwin_rect = (0, 0, 0, 0) for item in self.items: if item.parent: item.visible = False - + # Check if click is inside menubar - x,y = pos + x, y = pos mx, my = self.start_at - + if found: return True - else: + else: return False - + def draw(self, surface): """ Draw the menu with pygame on a given surface """ s = pygame.Surface((self.width, self.height)) s.fill(tools.hex2rgb(COLOR_HEX_BLUE1)) - surface.blit(s, (0,0)) - + surface.blit(s, (0, 0)) + for i in xrange(len(self.items)): item = self.items[i] - if not item.parent: - x,y,w,h = item.rect - if self.focus == i+1: - surface.blit(item.surface_active, (x,y)) + if not item.parent: + x, y, w, h = item.rect + if self.focus == i + 1: + surface.blit(item.surface_active, (x, y)) else: - surface.blit(item.surface_inactive, (x,y)) + surface.blit(item.surface_inactive, (x, y)) # If a menu item is open, draw that if self.focus: @@ -205,33 +213,33 @@ class MenuClass: if j.parent == self.focus: i.append(j) x, y, w, h = j.rect - if w > width: width = w + if w > width: + width = w height += h if len(i) > 0: s = pygame.Surface((width, height)) s.fill(tools.hex2rgb(COLOR_HEX_BLUE1)) - # Parent Coordinates - px, py, pw, ph = self.items[self.focus-1].rect - + # Parent Coordinates + px, py, pw, ph = self.items[self.focus - 1].rect + # y Counter y = 0 - + for item in i: item.visible = True s.blit(item.surface_inactive, (0, y)) - ix, iy, iw, ih = item.rect + ix, iy, iw, ih = item.rect if (ix, iy) == (0, 0): - item.rect = item.rect.move((px, y+ph)) - ix, iy, iw, ih = item.rect + item.rect = item.rect.move((px, y + ph)) + ix, iy, iw, ih = item.rect if iw < width: - item.rect = (ix,iy,width,ih) + item.rect = (ix, iy, width, ih) y += ih - - surface.blit(s, (px,py+ph)) - self.subwin_rect = s.get_rect().move(px, py+ph) + surface.blit(s, (px, py + ph)) + self.subwin_rect = s.get_rect().move(px, py + ph) diff --git a/elements/tools.py b/elements/tools.py index 9851ff8..3c81eb2 100644 --- a/elements/tools.py +++ b/elements/tools.py @@ -8,7 +8,7 @@ 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 + 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 @@ -22,19 +22,24 @@ 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 . +along with this program. If not, see . """ # Some Hex Tools + + def hex2dec(hex): """ Convert and hex value in a decimal number - """ + """ return int(hex, 16) -def hex2rgb(hex): + +def hex2rgb(hex): """ Convert a hex color (#123abc) in RGB ((r), (g), (b)) """ - if hex[0:1] == '#': hex = hex[1:]; - return (hex2dec(hex[:2]), hex2dec(hex[2:4]), hex2dec(hex[4:6])) + if hex[0: 1] == '#': + hex = hex[1:] + return (hex2dec(hex[:2]), hex2dec(hex[2: 4]), hex2dec(hex[4: 6])) + def rgb2floats(rgb): """Convert a color in the RGB (0..255,0..255,0..255) format to the @@ -45,21 +50,21 @@ def rgb2floats(rgb): ret.append(float(c) / 255) return ret + def point_in_poly(point, poly): #print ">", point, poly x, y = point n = len(poly) inside = False - p1x,p1y = poly[0] - for i in range(n+1): - p2x,p2y = poly[i % n] - if y > min(p1y,p2y): - if y <= max(p1y,p2y): - if x <= max(p1x,p2x): + p1x, p1y = poly[0] + for i in range(n + 1): + p2x, p2y = poly[i % n] + if y > min(p1y, p2y): + if y <= max(p1y, p2y): + if x <= max(p1x, p2x): if p1y != p2y: - xinters = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x + xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x if p1x == p2x or x <= xinters: inside = not inside - p1x,p1y = p2x,p2y + p1x, p1y = p2x, p2y return inside - \ No newline at end of file diff --git a/elements/tools_poly.py b/elements/tools_poly.py index 5ca3986..a2bb425 100644 --- a/elements/tools_poly.py +++ b/elements/tools_poly.py @@ -8,7 +8,7 @@ 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 + 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 @@ -22,7 +22,7 @@ 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 . +along with this program. If not, see . """ from functools import partial @@ -33,167 +33,177 @@ from math import degrees from math import acos from locals import * -from elements import box2d + def calc_center(points): """ Calculate the center of a polygon - + Return: The center (x,y) """ - tot_x, tot_y = 0,0 + 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) - + 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) - + 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: + 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) - + # Create Vector + vx, vy = (x2 - x1, y2 - y1) + # Check Length - l = sqrt((vx*vx) + (vy*vy)) - if l == 0.0: continue + 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(atan2(vy,vx)) + if fabs(vx) < 0.2: + alpha = 90.0 + else: + alpha = degrees(atan2(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 - + """ 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 = [] p_new.append(vertices[0]) - - dir = None + + dir = None is_convex = True - - for i in xrange(len(vertices)-1): + + 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] + 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)) + b = sqrt((v1x * v1x) + (v1y * v1y)) + c = sqrt((v2x * v2x) + (v2y * v2y)) + + # No Division by 0 :) + if (b * c) == 0.0: + continue - # 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))) + angle = degrees(acos(a / (b * c))) except: # cos=1.0 - print "cos=", a/(b*c) + 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]) - + 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]) + if dir is 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]): + 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: """ + """ 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 @@ -205,110 +215,121 @@ def reduce_poly_by_angle(vertices, tolerance=10.0, minlen=20): p_old = (x2, y2) # Make Vector - vx, vy = (x2-x1, y2-y1) - + vx, vy = (x2 - x1, y2 - y1) + # Vector length - l = sqrt((vx*vx) + (vy*vy)) - + l = sqrt((vx * vx) + (vy * vy)) + # normalize vx /= l vy /= l - + # Get Angle if fabs(vx) < 0.2: alpha = 90 - else: - alpha = degrees(atan2(vy,vx)) + else: + alpha = degrees(atan2(vy, vx)) - if alpha_old == None: + if alpha_old is None: alpha_old = alpha continue - + # Get difference to previous angle - alpha_diff = fabs(alpha - alpha_old) + 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]) + 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 + + +# 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 - """ + 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] + + 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 - + 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)) + 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 @@ -319,21 +340,21 @@ def convex_hull(points): 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.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) + l = is_left(pt2, pt1, p) if l > 0: hull.append(p) else: @@ -343,5 +364,4 @@ def convex_hull(points): pt2 = hull[-2] l = is_left(pt2, pt1, p) hull.append(p) - return hull - + return hull -- cgit v0.9.1