PyNewton is an object oriented wrapper built on top of the Newton Game Dynamics engine, and it provides an easy way to access the physics simulation capabilities of the underlying library. The Python binding has not been done directly over Newton, but over a C++ object oriented library built on that purpose (named Newton Wrapper for obvious reasons), which can also be used as a high level component in your programs. To be able to access this library, the Python interface was created using SWIG, an interface generation utility. It made the process quite fast once I understood the way it worked, and figured out how to handle some issues that are not handled automatically. Most of Newton abstraction have been given its own class representation, and some extra classes have been created to allow the implementation of callbacks. There are already abstractions for World, Body, Collision Geometries, Joints, Callbacks, and some basic utility classes like vectors and matrices.
Binaries and source files can be downloaded from here
Right now there are no standard way to this package (I tried using Distutils, but I had some problems integrating SWIG). For Windows I'm providing a simple package which contains the compiled module. For the rest of the platforms in which Newton is available I'm providing a source code distribution.
The build files provided (Makefile and make.bat) have hardcoded Python 2.4 names, but it should work fine with previous versions.
In Windows you can unpack the binary distribution, or you can compile it using the provided make.bat. You must make sure you have SWIG and MINGW in the path, and you must have the following things too:
Anyway, it's easy to change those values in the .bat file. I'll try to come up with a better solution for building it on Windows, but since I use Linux I haven't invested too much time on it.
To build on Linux, you can use the Makefile (it does not install the package), and you need to have the following things:
First of all, you need to import the package using:
from PyNewton import * #Importing all the classes to the root namespace import PyNewton #Importing the PyNewton namespace
The first element which you will create is the world in which objects are going to interact. It can be created executing:
world = World()
The world object manages the bodies, joints and materials existing in the current simulation, and provides a way to advance a time step in it in the following way:
world.step(0.01) #The parameter is the time step to advance
Some extra properties can be configured for the world object, using the setWorldSize function or the properties minimumFrameRate, solverModel and frictionModel.
With a world instance it is possible to create body objects, which will be the basis of the simulation. A body can be created in the following way:
body = world.createBody(collisionGeometry, mass) #automatically generated name body = world.createBody(name, collisionGeometry, mass) #user provided name
The created body is automatically added to the world, and can be later retrieved by its name. The collisionGeometry and mass parameters are mandatory. mass is a real number representing, well, the mass of the object (it should be positive, of course). collisionGeometry provides the shape of the body that is about to be created. It must be a subclass of CollisionGeometry, and can be a primitive shape provided by Newton, or a user defined geometry. Some basic geometries provided are:
These are some samples of bodies creation:
body = world.createBody(StaticPlaneCollisionGeometry((0, 0, 0), (0, 1, 0)), 10) body = world.createBody(BoxCollisionGeometry(1, 1, 1), 10) body = world.createBody(BoxCollisionGeometry(1, 1, 1), 10) body = world.createBody(SphereCollisionGeometry(0.5, 0.5, 0.5), 20)
Bodies expose attributes which can be accesed as regular Python properties:
#Read & write properties body.position = Vector3(10, 10, 10) #Can also use (10, 10, 10) directly body.orientation = Quaternion(3.14, 10, 10, 10) body.material = world.getMaterial("test") body.userData = MyUserData()
#Read only properties print body.world print body.name print body.mass print body.collisionGeometry
A body can have forces, torques and accelerations (just a handy method to apply a force relative to the mass) individualy through Body methods, and all bodies in the world can have applied the same forces through World methods.
body.addForce((10, 0, 0)) #Applies a force of 10N in the x-positive direction (it returns the generated name of the function) body.addForce("test", (10, 0, 0)) #Applies a force of 10N in the x-positive direction (using the given name) body.removeForce("test") #Removes the recently added force world.addGlobalForce((10, 0, 0)) #Applies a force of 10N in the x-positive direction to all the bodies in the world (it returns the generated name of the function) world.addGlobalForce("test", (10, 0, 0)) #Applies a force of 10N in the x-positive direction to all the bodies in the world (using the given name) world.removeGlobalForce("test") #Removes the recently added force from all the bodies in the world
You can have listeners to use instantaneous forces, torques and accelerations if you are not interested in applying the same amount all the time (quite usual in fact).
class MyForceTorqueListener(ForceTorqueListener): def processForceTorqueEvent(self, body): body.addInstantaneousForce((10, 0, 0)) listener = MyForceTorqueListener() body.addForceTorqueListener(listener)
A body can have a material assigned to it, which is important for proper collision handling. The responsible of managing materials is the world, which provides the following methods:
#Material creation material = world.createMaterial() #Generated name material = world.createMaterial(materialName) #Provided name material = world.getMaterial(materialName) materialPair = world.getMaterialPair(materialName, materialName)
A material is simply a declaration and contains no individual properties. The place to provide that information is in a material pair, which is the place where is defined the way that two colliding materials should react. getMaterialPair returns a valid reference only if the two material names provided already exist. A material pair can have the following attributes customized:
materialPair.defaultCollidable = MaterialPair.CL_NON_COLLIDABLE #or CL_COLLIDABLE materialPair.continuousCollisionMode = MaterialPair.CM_NON_CONTINUOUS #or CM_CONTINUOUS materialPair.setDefaultFriction(0.6, 0.5) #Static, kinetic materialPair.defaultElasticity = 0.1 materialPair.defaultSoftness = 0.2 materialPair.userData = MyUserData()
Each material pair can have listeners for contact related events. To be able to handle them, you must subclass ContactListener:
class MyContactListener(ContactListener): def __init__(self): ContactListener.__init__(self) #DO NOT forget this... SWIG will crash otherwise self.countBegin = 0 self.countEnd = 0 self.countProcess = 0 def processContactBeginEvent(self, materialPairContact, bodyA, bodyB): self.countBegin += 1 print "Begin: " + str(self.countBegin) return MaterialPair.ACCEPT #You can also REJECT processing of this contact def processContactEndEvent(self, materialPairContact): self.countEnd += 1 print "End: " + str(self.countEnd) def processContactProcessEvent(self, materialPairContact, contact): self.countProcess += 1 print "Process: " + str(self.countProcess) return MaterialPair.ACCEPT
You are not forced to override the three member methods, just the ones you want to handle. The listeners can be added in the following way:
listener = ContactListener() materialPair.addContactBeginListener(listener) materialPair.addContactEndListener(listener) materialPair.addContactProcessListener(listener)
Bodies can be restricted in their movement through the use of joints. Joints can be applied to one or more bodies, but they are usually enforced between two of them. Newton provides a set of primitive joints, and a way to create used defined joints. Both of them have been implemented in PyNewton. Right now, these primitive joints are provided:
Next is a couple of basic examples of their use:
ballSocket = BallSocketJoint(world.getBody("sphere1"), world.getBody("sphere2"), Vector3(0, 7, 0)) world.addJoint(ballSocket) #Extremely important... If the reference to the created joint is lost, it will be deleted. You can keep it in other place, though upVector = UpVectorJoint(world.getBody("box1"), Vector3(0, 0, 10)) world.addJoint(upVector)