Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--DEVELOPING9
-rw-r--r--MANIFEST127
-rw-r--r--elements/__init__.py2
-rw-r--r--elements/add_objects.py538
-rw-r--r--elements/callbacks.py122
-rw-r--r--elements/camera.py124
-rw-r--r--elements/drawing.py376
-rw-r--r--elements/elements.py589
-rw-r--r--elements/locals.py37
-rw-r--r--elements/menu.py237
-rw-r--r--elements/tools.py65
-rw-r--r--elements/tools_poly.py347
-rw-r--r--lib/Elements-0.13-py2.5.eggbin60376 -> 0 bytes
14 files changed, 2438 insertions, 137 deletions
diff --git a/.gitignore b/.gitignore
index b6cb09a..37c737b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
*.pyc
+*~
locale
-.DS_Store
dist
diff --git a/DEVELOPING b/DEVELOPING
deleted file mode 100644
index 3369c8a..0000000
--- a/DEVELOPING
+++ /dev/null
@@ -1,9 +0,0 @@
-activity/
-activity.py
-elements/ - (upstream, but branched here) Simplification wrapper around pyBox2D (in a subdirectory here)
-helpers.py - mathematical helper functions
-icons/ - all graphics used in Physics (mostly svg menu icons)
-olpcgames/ - (upstream) The Pygame wrapper for the OLPC Sugar platform
-physics.py - contains screen setup, main loop, tool list
-setup.py - just runs the Sugar bundlebuilder
-tools.py - defines Tool class and all available tools (contexts for input/creation)
diff --git a/MANIFEST b/MANIFEST
deleted file mode 100644
index fdc2528..0000000
--- a/MANIFEST
+++ /dev/null
@@ -1,127 +0,0 @@
-activity.py
-COPYING
-DEVELOPING
-helpers.py
-physics.py
-setup.py
-standardcursor.png
-tools.py
-activity/activity-physics.svg
-activity/activity.info
-activity/application-x-physics-project.svg
-activity/mimetypes.xml
-icons/box.svg
-icons/circle.svg
-icons/destroy.svg
-icons/grab.svg
-icons/joint.svg
-icons/magicpen.svg
-icons/motor.svg
-icons/pin.svg
-icons/polygon.svg
-icons/roll.svg
-icons/triangle.svg
-lib/Box2D-2.0.2b1-py2.5-linux-i686.egg
-lib/Elements-0.13-py2.5.egg
-lib/pkg_resources.py
-olpcgames/__init__.py
-olpcgames/_cairoimage.py
-olpcgames/_gtkmain.py
-olpcgames/_version.py
-olpcgames/activity.py
-olpcgames/buildmanifest.py
-olpcgames/camera.py
-olpcgames/canvas.py
-olpcgames/copying
-olpcgames/dbusproxy.py
-olpcgames/eventwrap.py
-olpcgames/gtkEvent.py
-olpcgames/mesh.py
-olpcgames/pangofont.py
-olpcgames/pausescreen.py
-olpcgames/svg.py
-olpcgames/svgsprite.py
-olpcgames/textsprite.py
-olpcgames/util.py
-olpcgames/video.py
-olpcgames/data/__init__.py
-olpcgames/data/sleeping.svg
-olpcgames/data/sleeping_svg.py
-po/af.po
-po/am.po
-po/ar.po
-po/ay.po
-po/bg.po
-po/bi.po
-po/bn.po
-po/bn_IN.po
-po/ca.po
-po/cpp.po
-po/cs.po
-po/de.po
-po/dz.po
-po/el.po
-po/en.po
-po/es.po
-po/fa.po
-po/fa_AF.po
-po/ff.po
-po/fil.po
-po/fr.po
-po/gu.po
-po/ha.po
-po/he.po
-po/hi.po
-po/ht.po
-po/hu.po
-po/ig.po
-po/is.po
-po/it.po
-po/ja.po
-po/km.po
-po/ko.po
-po/kos.po
-po/mg.po
-po/mk.po
-po/ml.po
-po/mn.po
-po/mr.po
-po/ms.po
-po/mvo.po
-po/na.po
-po/nb.po
-po/ne.po
-po/nl.po
-po/pa.po
-po/pap.po
-po/Physics.pot
-po/pis.po
-po/pl.po
-po/ps.po
-po/pt.po
-po/pt_BR.po
-po/qu.po
-po/ro.po
-po/ru.po
-po/rw.po
-po/sd.po
-po/si.po
-po/sk.po
-po/sl.po
-po/sq.po
-po/sv.po
-po/sw.po
-po/ta.po
-po/te.po
-po/th.po
-po/tpi.po
-po/tr.po
-po/tvl.po
-po/tzo.po
-po/ug.po
-po/ur.po
-po/vi.po
-po/wa.po
-po/yo.po
-po/zh_CN.po
-po/zh_TW.po
diff --git a/elements/__init__.py b/elements/__init__.py
new file mode 100644
index 0000000..723c2cc
--- /dev/null
+++ b/elements/__init__.py
@@ -0,0 +1,2 @@
+__all__ = ['locals', 'menu']
+from elements import Elements
diff --git a/elements/add_objects.py b/elements/add_objects.py
new file mode 100644
index 0000000..49bf538
--- /dev/null
+++ b/elements/add_objects.py
@@ -0,0 +1,538 @@
+"""
+This file is part of the 'Elements' Project
+Elements is a 2D Physics API for Python (supporting Box2D2)
+
+Copyright (C) 2008, The Elements Team, <elements@linuxuser.at>
+
+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 <http://www.gnu.org/licenses/>.
+"""
+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, 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=(x, y)
+
+ 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=(x, y)
+
+ 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
+ boxDef = box2d.b2PolygonDef()
+
+ 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()
+
+ 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
+
+ # 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=(x, y)
+
+ 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.setVertices(vertices)
+ 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=(x, y)
+
+ 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(*vertices[0])
+ for v in vertices[1:]:
+ 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)
+
+ 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] )
+
+ try:
+ polyDef.checkValues()
+ except ValueError:
+ print "concavePoly: Created an invalid polygon!"
+ return None
+
+ 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):
+ # Convert vector to a b2vect
+ pt = self.parent.to_world(pt)
+ ptx, pty = pt
+ ptx /= self.parent.ppm
+ pty /= self.parent.ppm
+ pt = box2d.b2Vec2(ptx, pty)
+ return pt
+
+ def joint(self, *args):
+ print "* Add Joint:", args
+
+ if len(args) == 5:
+ # Tracking Joint
+ b1, b2, p1, p2, flag = args
+
+ p1 = self.to_b2vec(p1)
+ p2 = self.to_b2vec(p2)
+
+ jointDef = box2d.b2DistanceJointDef()
+ jointDef.Initialize(b1, b2, p1, p2)
+ jointDef.collideConnected = flag
+
+ 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)
+
+ jointDef = box2d.b2DistanceJointDef()
+ jointDef.Initialize(b1, b2, p1, p2)
+ jointDef.collideConnected = True
+
+ 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
+ b1 = self.parent.world.GetGroundBody()
+ b2 = args[0]
+ p1 = self.to_b2vec(args[1])
+
+ jointDef = box2d.b2RevoluteJointDef()
+ jointDef.Initialize(b1, b2, p1)
+ self.parent.world.CreateJoint(jointDef)
+
+ elif len(args) == 1:
+ # Revolute Joint to the Background, body center
+ 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):
+ # Revolute joint to the background with motor torque applied
+ b1 = self.parent.world.GetGroundBody()
+ pt = self.to_b2vec(pt)
+
+ jointDef = box2d.b2RevoluteJointDef()
+ jointDef.Initialize(b1, body, pt)
+ jointDef.maxMotorTorque = torque
+ jointDef.motorSpeed = speed
+ jointDef.enableMotor = True
+
+ self.parent.world.CreateJoint(jointDef)
+
+ def mouseJoint(self, body, pos, jointForce=100.0):
+ 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 = (x, y)
+ mj.maxForce = jointForce * body.GetMass()
+ if 'getAsType' in dir(box2d.b2Joint):
+ self.parent.mouseJoint = self.parent.world.CreateJoint(mj).getAsType()
+ 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
new file mode 100644
index 0000000..01e9545
--- /dev/null
+++ b/elements/callbacks.py
@@ -0,0 +1,122 @@
+"""
+This file is part of the 'Elements' Project
+Elements is a 2D Physics API for Python (supporting Box2D2)
+
+Copyright (C) 2008, The Elements Team, <elements@linuxuser.at>
+
+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 <http://www.gnu.org/licenses/>.
+"""
+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 = {}
+
+ def __init__(self, parent):
+ self.parent = parent
+
+ # 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_handler ...... a callback function
+ 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:
+ self.parent.listener = kContactListener(self.get)
+ 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):
+ # 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)
+
+ # Step through all callbacks for this type (eg ADD, PERSIST, REMOVE)
+ for c in contacts:
+ callback, bodylist = c
+ if len(bodylist) == 0:
+ # 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
+ b1 = str(point.shape1.GetBody())
+ b2 = str(point.shape2.GetBody())
+ for s in bodylist:
+ s = str(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
new file mode 100644
index 0000000..c45b27d
--- /dev/null
+++ b/elements/camera.py
@@ -0,0 +1,124 @@
+"""
+This file is part of the 'Elements' Project
+Elements is a 2D Physics API for Python (supporting Box2D2)
+
+Copyright (C) 2008, The Elements Team, <elements@linuxuser.at>
+
+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 <http://www.gnu.org/licenses/>.
+"""
+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()
+
+ def __init__(self, parent):
+ self.parent = parent
+
+ def track(self, body):
+ """ Start tracking a specific body
+ """
+ self.track_body = body
+
+ def track_stop(self):
+ """ 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
+
+ if screenCoord:
+ x /= self.scale_factor
+ y /= self.scale_factor
+
+ # 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
+ -- in screen (or world) coordinates and in pixel
+ """
+ # Stop tracking of an object
+ 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
+
+ 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
+ """
+ # Stop tracking of an object
+ if stopTrack: self.track_stop()
+
+ # Get Current Offset
+ x, y = self.parent.screen_offset_pixel
+ dx, dy = offset
+
+ # 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
+
+ # Set New Offset
+ 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
+ self.parent.screen_offset = (x, y)
+ 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
new file mode 100644
index 0000000..b4722fb
--- /dev/null
+++ b/elements/drawing.py
@@ -0,0 +1,376 @@
+"""
+This file is part of the 'Elements' Project
+Elements is a 2D Physics API for Python (supporting Box2D2)
+
+Copyright (C) 2008, The Elements Team, <elements@linuxuser.at>
+
+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 <http://www.gnu.org/licenses/>.
+"""
+from math import pi
+from math import cos
+from math import sin
+from math import sqrt
+
+import tools
+
+# Functions of a rendering class
+# mandatory:
+# __init__
+# start_drawing
+# after_drawing
+# draw_circle
+# draw_polygon
+# draw_lines
+# set_lineWidth
+#
+# renderer-specific mandatory functions:
+# for pygame:
+# set_surface
+# 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 ..
+
+ Return: Class draw_pygame()
+ """
+ print "* Pygame selected as renderer"
+ from pygame import draw
+ from pygame import Rect
+
+ self.draw = draw
+ self.Rect = Rect
+
+ def set_lineWidth(self, lw):
+ """
+ """
+ self.lineWidth = lw
+
+ def set_surface(self, surface):
+ """
+ """
+ self.surface = surface
+
+ def get_surface(self):
+ """
+ """
+ return self.surface
+
+ 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
+ angle ..... rotation in radians
+
+ Return: -
+ """
+ x, y = pt
+
+ x1 = x - radius
+ y1 = y - 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))
+
+ 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)
+ #self.draw.lines(self.surface, clr, True, points)
+
+ 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:
+ lw = self.lineWidth
+ 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
+ circle_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"
+ import cairo
+ self.cairo = cairo
+ self.set_drawing_method(drawMethod)
+ #self.draw_box = self.draw_box_image
+
+ 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: -
+ """
+ self.da = da
+ self.window = da.window
+ print "* Cairo renderer drawing area set"
+
+ def set_drawing_method(self, type):
+ """ type = filled, image """
+ self.draw_circle = getattr(self, "draw_circle_%s" % type)
+ #self.draw_box = getattr(self, "draw_box_%s" % type)
+
+ 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.ctx = ctx = self.cairo.Context(self.imagesurface)
+
+ 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_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
+
+ #ctx.set_dash([20/4.0, 20/4.0], 0)
+
+ def after_drawing(self):
+ dest_ctx = self.window.cairo_create()
+ dest_ctx.set_source_surface(self.imagesurface)
+ dest_ctx.paint()
+
+ def set_circle_image(self, filename):
+ self.circle_surface = self.cairo.ImageSurface.create_from_png(filename)
+ self.draw_circle = self.draw_circle_image
+
+# def set_box_image(self, filename):
+# self.box_surface = self.cairo.ImageSurface.create_from_png(filename)
+# self.draw_box = self.draw_box_image
+
+ def draw_circle_filled(self, clr, pt, radius, angle=0):
+ x, y = pt
+
+ 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.fill()
+
+ def draw_circle():
+ pass
+
+ def draw_circle_image(self, clr, pt, radius, angle=0, sf=None):
+ if sf == None:
+ sf = self.circle_surface
+ x, y = pt
+ self.ctx.save()
+ self.ctx.translate(x, y)
+ self.ctx.rotate(-angle)
+ 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.set_source_surface(sf)
+ self.ctx.paint()
+ self.ctx.restore()
+
+ 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.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])
+
+ pt = points[0]
+ 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"):
+ 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.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.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])
+
+ pt = points[0]
+ self.ctx.move_to(pt[0], pt[1])
+ for pt in points[1:]:
+ self.ctx.line_to(pt[0], pt[1])
+
+ if closed:
+ pt = points[0]
+ self.ctx.line_to(pt[0], pt[1])
+
+ 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 ..
+ """
+ print "* OpenGL_Pyglet selected as renderer"
+
+ from pyglet import gl
+ self.gl = gl
+
+ 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])
+
+ 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()
+
+ def draw_polygon(self, clr, points):
+ clr = tools.rgb2floats(clr)
+ 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
new file mode 100644
index 0000000..2b6fb71
--- /dev/null
+++ b/elements/elements.py
@@ -0,0 +1,589 @@
+#!/usr/bin/python
+"""
+This file is part of the 'Elements' Project
+Elements is a 2D Physics API for Python (supporting pybox2d)
+
+Copyright (C) 2008, The Elements Team, <elements@linuxuser.at>
+
+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 <http://www.gnu.org/licenses/>.
+"""
+__version__= '0.11'
+__contact__ = '<elements@linuxuser.at>'
+
+# Load Box2D
+try:
+ import Box2D as box2d
+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 "See http://code.google.com/p/pybox2d"
+ exit()
+
+# Standard Imports
+from random import shuffle
+
+# Load Elements Definitions
+from locals import *
+
+# Load Elements Modules
+import tools
+import drawing
+import add_objects
+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]
+
+ # 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
+
+ mouseJoint = None
+
+ 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'
+
+ 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.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
+ self.init_colors()
+
+ # Set Pixels per Meter
+ self.ppm = ppm
+
+ def set_inputUnit(self, input):
+ """ Change the input unit to either meter or pixels
+
+ Parameters:
+ input ... INPUT_METERS or INPUT_PIXELS
+
+ Return: -
+ """
+ self.input = input
+
+ 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
+
+ Return: True if ok, False if no method identifier m found
+ """
+ try:
+ 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:
+ 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: -
+ """
+ self.fixed_color = None
+ self.cur_color = 0
+ self.colors = [
+ "#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:
+ 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: -
+ """
+ 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))
+ """
+ if self.fixed_color != None:
+ return self.fixed_color
+
+ 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
+
+ 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)
+ """
+ x, y = point
+
+ if self.inputAxis_x_left:
+ x = self.display_width - x
+
+ 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 = []
+ 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)
+ - 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)
+ """
+ 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)
+
+ def to_screen(self, pos):
+ """ 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
+ """
+ sx, sy = self.to_world(search_point)
+ sx /= self.ppm
+ sy /= self.ppm
+
+ f = area/self.camera.scale_factor
+
+ AABB=box2d.b2AABB()
+ AABB.lowerBound = (sx-f, sy-f)
+ AABB.upperBound = (sx+f, sy+f)
+
+ amount, shapes = self.world.Query(AABB, 2)
+
+ if amount == 0:
+ return False
+ else:
+ bodylist = []
+ for s in shapes:
+ body = s.GetBody()
+ 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:
+ return False
+
+ if self.camera.track_body:
+ # Get Body Center
+ 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)
+
+ # 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:
+ 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)
+
+ 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))
+ points.append([x, y])
+
+ self.renderer.draw_polygon(clr, points)
+
+ else:
+ 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))
+
+ p1 = joint.GetAnchor2()
+ 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)
+ else:
+ self.renderer.draw_lines((0,0,0), False, [p1, p2], 3)
+
+ self.callbacks.start(CALLBACK_DRAWING_END)
+ self.renderer.after_drawing()
+
+ 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))
+
+ 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)
+
+ save_values = [self.world, box2d.pickle_fix(self.world, additional_vars, 'save')]
+
+ try:
+ pickle.dump(save_values, open(fn, 'wb'))
+ except Exception, s:
+ print 'Pickling failed: ', s
+ return
+
+ print 'Saved to %s' % fn
+
+ def pickle_load(self, fn, set_vars=True, additional_vars=[]):
+ """
+ Load the pickled world in file fn.
+ additional_vars is a dictionary to be populated with the
+ loaded variables.
+ """
+ import cPickle as pickle
+ try:
+ world, variables = pickle.load(open(fn, 'rb'))
+ world = world._pickle_finalize()
+ 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():
+ if hasattr(self, var):
+ setattr(self, var, value)
+ else:
+ print 'Unknown property %s=%s' % (var, value)
+
+ print 'Loaded from %s' % fn
+
+ return variables
+
+ def json_save(self, path, additional_vars = {}):
+ import json
+ worldmodel = {}
+
+ save_id_index = 1
+ self.world.GetGroundBody().userData = {"saveid" : 0}
+
+ bodylist = []
+ for body in self.world.GetBodyList():
+ if not body == self.world.GetGroundBody():
+ body.userData["saveid"] = save_id_index #set temporary data
+ save_id_index+=1
+ shapelist = body.GetShapeList()
+ modelbody = {}
+ modelbody['position'] = body.position.tuple()
+ modelbody['dynamic'] = body.IsDynamic()
+ modelbody['userData'] = body.userData
+ modelbody['angle'] = body.angle
+ modelbody['angularVelocity'] = body.angularVelocity
+ modelbody['linearVelocity'] = body.linearVelocity.tuple()
+ if shapelist and len(shapelist) > 0:
+ shapes = []
+ for shape in shapelist:
+ modelshape = {}
+ modelshape['density'] = shape.density
+ modelshape['restitution'] = shape.restitution
+ modelshape['friction'] = shape.friction
+ shapename = shape.__class__.__name__
+ if shapename == "b2CircleShape":
+ modelshape['type'] = 'circle'
+ modelshape['radius'] = shape.radius
+ modelshape['localPosition'] = shape.localPosition.tuple()
+ if shapename == "b2PolygonShape":
+ modelshape['type'] = 'polygon'
+ modelshape['vertices'] = shape.vertices
+ shapes.append(modelshape)
+ modelbody['shapes'] = shapes
+
+ bodylist.append(modelbody)
+
+ worldmodel['bodylist'] = bodylist
+
+ jointlist = []
+
+ for joint in self.world.GetJointList():
+ modeljoint = {}
+
+ if joint.__class__.__name__ == "b2RevoluteJoint":
+ modeljoint['type'] = 'revolute'
+ modeljoint['anchor'] = joint.GetAnchor1().tuple()
+ modeljoint['enableMotor'] = joint.enableMotor
+ modeljoint['motorSpeed'] = joint.motorSpeed
+ modeljoint['maxMotorTorque'] = joint.maxMotorTorque
+ elif joint.__class__.__name__ == "b2DistanceJoint":
+ modeljoint['type'] = 'distance'
+ modeljoint['anchor1'] = joint.GetAnchor1().tuple()
+ modeljoint['anchor2'] = joint.GetAnchor2().tuple()
+
+ modeljoint['body1'] = joint.body1.userData['saveid']
+ modeljoint['body2'] = joint.body2.userData['saveid']
+ modeljoint['collideConnected'] = joint.collideConnected
+ modeljoint['userData'] = joint.userData
+
+
+ jointlist.append(modeljoint)
+
+ worldmodel['jointlist'] = jointlist
+
+ controllerlist = []
+ worldmodel['controllerlist'] = controllerlist
+
+ worldmodel['additional_vars'] = additional_vars
+
+ f = open(path,'w')
+ f.write(json.dumps(worldmodel))
+ f.close()
+
+ for body in self.world.GetBodyList():
+ del body.userData['saveid'] #remove temporary data
+
+ def json_load(self, path, additional_vars = {}):
+ import json
+
+ self.world.GetGroundBody().userData = {"saveid" : 0}
+
+ f = open(path, 'r')
+ worldmodel = json.loads(f.read())
+ f.close()
+ #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
+ for body in worldmodel['bodylist']:
+ bodyDef = box2d.b2BodyDef()
+ bodyDef.position = body['position']
+ bodyDef.userData = body['userData']
+ bodyDef.angle = body['angle']
+ newBody = self.world.CreateBody(bodyDef)
+ #_logger.debug(newBody)
+ newBody.angularVelocity = body['angularVelocity']
+ newBody.linearVelocity = body['linearVelocity']
+ if body.has_key('shapes'):
+ for shape in body['shapes']:
+ if shape['type'] == 'polygon':
+ polyDef = box2d.b2PolygonDef()
+ polyDef.setVertices(shape['vertices'])
+ polyDef.density = shape['density']
+ polyDef.restitution = shape['restitution']
+ polyDef.friction = shape['friction']
+ newBody.CreateShape(polyDef)
+ if shape['type'] == 'circle':
+ circleDef = box2d.b2CircleDef()
+ circleDef.radius = shape['radius']
+ circleDef.density = shape['density']
+ circleDef.restitution = shape['restitution']
+ circleDef.friction = shape['friction']
+ circleDef.localPosition = shape['localPosition']
+ newBody.CreateShape(circleDef)
+ newBody.SetMassFromShapes()
+
+ for joint in worldmodel['jointlist']:
+ if joint['type'] == 'distance':
+ jointDef = box2d.b2DistanceJointDef()
+ body1 = self.getBodyWithSaveId(joint['body1'])
+ anch1 = joint['anchor1']
+ body2 = self.getBodyWithSaveId(joint['body2'])
+ anch2 = joint['anchor2']
+ jointDef.collideConnected = joint['collideConnected']
+ jointDef.Initialize(body1,body2,anch1,anch2)
+ jointDef.SetUserData(joint['userData'])
+ self.world.CreateJoint(jointDef)
+ if joint['type'] == 'revolute':
+ jointDef = box2d.b2RevoluteJointDef()
+ body1 = self.getBodyWithSaveId(joint['body1'])
+ body2 = self.getBodyWithSaveId(joint['body2'])
+ anchor = joint['anchor']
+ jointDef.Initialize(body1,body2,anchor)
+ jointDef.SetUserData(joint['userData'])
+ jointDef.enableMotor = joint['enableMotor']
+ jointDef.motorSpeed = joint['motorSpeed']
+ jointDef.maxMotorTorque = joint['maxMotorTorque']
+ self.world.CreateJoint(jointDef)
+
+ for (k,v) in worldmodel['additional_vars'].items():
+ additional_vars[k] = v
+
+ for body in self.world.GetBodyList():
+ del body.userData['saveid'] #remove temporary data
+
+ 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
new file mode 100644
index 0000000..85528f7
--- /dev/null
+++ b/elements/locals.py
@@ -0,0 +1,37 @@
+"""
+This file is part of the 'Elements' Project
+Elements is a 2D Physics API for Python (supporting Box2D2)
+
+Copyright (C) 2008, The Elements Team, <elements@linuxuser.at>
+
+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 <http://www.gnu.org/licenses/>.
+"""
+INPUT_METERS = 0
+INPUT_PIXELS = 1
+
+CALLBACK_CONTACT_ADD = 0
+CALLBACK_CONTACT_PERSIST = 1
+CALLBACK_CONTACT_REMOVE = 2
+
+CALLBACK_DRAWING_START = 3
+CALLBACK_DRAWING_END = 4
+
+FLT_EPSILON = 1.192092896e-07
diff --git a/elements/menu.py b/elements/menu.py
new file mode 100644
index 0000000..1c8a417
--- /dev/null
+++ b/elements/menu.py
@@ -0,0 +1,237 @@
+"""
+This file is part of the 'Elements' Project
+Elements is a 2D Physics API for Python (supporting Box2D2)
+
+Copyright (C) 2008, The Elements Team, <elements@linuxuser.at>
+
+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 <http://www.gnu.org/licenses/>.
+"""
+import pygame
+from pygame.locals import *
+
+import tools
+
+COLOR_HEX_BLUE1 = "6491a4"
+COLOR_HEX_BLUE2 = "9ec9ff"
+
+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:
+ self.callback = self.empty
+
+ # Create Surface and Stuff :)
+ self.font = pygame.font.Font(None, 32)
+ text = self.font.render(title, 1, (255,255,255))
+
+ rx, ry, rw, rh = rect = text.get_rect()
+ pl, pt, pr, pb = self.padding
+
+ 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.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
+ px, py = pos
+
+ 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 = []
+
+ # 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)
+
+ 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
+
+ # Create Items
+ 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 = 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)
+
+ else:
+ # New next drawing position
+ self.draw_at = (x+w, y)
+
+ # Adjust the width of the menu bar
+ if not self.setWidth:
+ self.width = x+w
+
+ # Adjust the height of the menu bar
+ 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
+ """
+ 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
+ if self.focus == focus_in:
+ self.focus = False
+ 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
+ mx, my = self.start_at
+
+ if found:
+ return True
+ 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))
+
+ 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))
+ else:
+ surface.blit(item.surface_inactive, (x,y))
+
+ # If a menu item is open, draw that
+ if self.focus:
+ width = 0
+ height = 0
+
+ i = []
+ for j in self.items:
+ if j.parent == self.focus:
+ i.append(j)
+ x, y, w, h = j.rect
+ 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
+
+ # y Counter
+ y = 0
+
+ for item in i:
+ item.visible = True
+ s.blit(item.surface_inactive, (0, y))
+
+ 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
+
+ if iw < width:
+ item.rect = (ix,iy,width,ih)
+
+ y += ih
+
+ 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
new file mode 100644
index 0000000..9851ff8
--- /dev/null
+++ b/elements/tools.py
@@ -0,0 +1,65 @@
+"""
+This file is part of the 'Elements' Project
+Elements is a 2D Physics API for Python (supporting Box2D2)
+
+Copyright (C) 2008, The Elements Team, <elements@linuxuser.at>
+
+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 <http://www.gnu.org/licenses/>.
+"""
+# Some Hex Tools
+def hex2dec(hex):
+ """ Convert and hex value in a decimal number
+ """
+ return int(hex, 16)
+
+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]))
+
+def rgb2floats(rgb):
+ """Convert a color in the RGB (0..255,0..255,0..255) format to the
+ (0..1, 0..1, 0..1) float format
+ """
+ ret = []
+ for c in 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):
+ if p1y != p2y:
+ xinters = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
+ if p1x == p2x or x <= xinters:
+ inside = not inside
+ p1x,p1y = p2x,p2y
+ return inside
+ \ No newline at end of file
diff --git a/elements/tools_poly.py b/elements/tools_poly.py
new file mode 100644
index 0000000..5ca3986
--- /dev/null
+++ b/elements/tools_poly.py
@@ -0,0 +1,347 @@
+"""
+This file is part of the 'Elements' Project
+Elements is a 2D Physics API for Python (supporting Box2D2)
+
+Copyright (C) 2008, The Elements Team, <elements@linuxuser.at>
+
+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 <http://www.gnu.org/licenses/>.
+"""
+from functools import partial
+
+from math import fabs
+from math import sqrt
+from math import atan2
+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
+ 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(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
+
+ 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(atan2(vy,vx))
+
+ 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
+
diff --git a/lib/Elements-0.13-py2.5.egg b/lib/Elements-0.13-py2.5.egg
deleted file mode 100644
index 93869b9..0000000
--- a/lib/Elements-0.13-py2.5.egg
+++ /dev/null
Binary files differ