Playing around with the OpenDynamicsEngine (ODE) I found it a bit confusing and difficult to cope with both, the physical simulation as well as with the visualization. All relevant data is already included in the physical part, so why cope with both?
Under sourceforge.net/projects/ode-viz you can download a simple wraper for python, that uses pyvtk to visualize a simulation, just pass the “world” and the “space” to ODE_Visualization and let it handle the rest …
The easiest way to install this library is by using pip:
$ pip install odeViz
all dependencies should be installed automatically. For manually installation see section Download …
You can skip this part, if you just want a quick visualization, but if you want to extend your visualization with further VTK-functionalities … have a look on it.
The used classes can be devided into two parts, a visualization-part and a object-part, as depicted in the uml diagram below (created with umbrello).
VTK_Visualization and the inherited methods are used to handle and define window specifics, like size, title, background-color, etc. ODE-Visualization takes in as parameters, the world, the space, and is responsible to run the simulation. During initialization, the space is scanned for primitive objects, such as boxes, spheres, etc. ODE-Visualization creates a VTK object (encapsulated within a ODE_Object) for every primitive entity. These objects can be seen as glue-objects, each one is bound to a single object/geometry within the space, knowing its position, rotation, and size within the virtual space. By calling update(), the vtk-objects are simply set onto the same position/orientation in the visualization, as the ode-object in the simulation. ODE-Visualization only responsible to maintain a list of ODE_Object and to update the vtk-simulation in a appropriate manner.
VTK_Visualization is furthermore an ancestor of threading.Thread, and thus responsible to start and run the thread for the simulation. You simply have to overwrite method execute with all required stuff, as explained in more detail within the examples…
From XML to VTK
This example is included into the project, which can be simply executed by calling:
Within this example, our world an all included objects are defined within a single XML file. Simple capsules on different heights and a plane on the ground. By using xode, it is possible to read in this xml-file and to retrieve all required data for the simulation.
import ode import xode.parser import libxml2 import odeViz.ode_visualization as ode_viz class my_sim(ode_viz.ODE_Visualization): def __init__(self, world, space, dt): ode_viz.ODE_Visualization.__init__(self, world, space, dt) self.contactgroup = ode.JointGroup() def execute(self, caller, event): self.space.collide((self.world,self.contactgroup), self.near_callback) self.world.step(self.dt) self.contactgroup.empty() self.update() # do not forget ... def near_callback(self, args, geom1, geom2): # Check if the objects do collide contacts = ode.collide(geom1, geom2) # Create contact joints self.world,self.contactgroup = args for c in contacts: c.setBounce(0.2) c.setMu(5000) j = ode.ContactJoint(self.world, self.contactgroup, c) j.attach(geom1.getBody(), geom2.getBody()) # parsing the xml file xml = libxml2.parseFile("test.xml") xml.xincludeProcess() p = xode.parser.Parser() root = p.parseString(str(xml)) world = root.namedChild('world').getODEObject() space = root.namedChild('space').getODEObject() # setting some parameters world.setGravity( (-0.001,-9.81,-0.001) ) world.setERP(0.8) world.setCFM(1E-10) # pass all requred parameters world, space, and time step dt viz = my_sim(world, [space], 0.005) # start the simulation viz.start()
Pass the space and the world, as presented in line 44 to the visualization. Within the constructor it is possible to change the camera parameters, window properties, etc. Have a now a look to the execute method, this is the method which is called from within the working thread. It is everything that is required for the simulation… It check if there occurred some collisions, and calculates the the next step of the simulation, with delta time dt, which was also defined during the initialization.
Watch the resulting simulation …
Changing Tutorial 3
But if you want to add or remove new objects during runtime or change their colors, etc., this has to be done manually. To explain this, I chose the third step of the pyode tutorial from the project site: pyode.sourceforge.net/tutorials/tutorial3.html
Take a look at their example then onto the changed one, presented below:
# pyODE example 3: Collision detection import random, time import ode from math import * import odeViz.ode_visualization as ode_viz # geometric utility functions def scalp (vec, scal): vec *= scal vec *= scal vec *= scal def length (vec): return sqrt (vec**2 + vec**2 + vec**2) # create_box def create_box(world, space, density, lx, ly, lz): """Create a box body and its corresponding geom.""" # Create body body = ode.Body(world) M = ode.Mass() M.setBox(density, lx, ly, lz) body.setMass(M) # Set parameters for drawing the body body.shape = "box" body.boxsize = (lx, ly, lz) # Create a box geom for collision detection geom = ode.GeomBox(space, lengths=body.boxsize) geom.setBody(body) return body, geom # drop_object def drop_object(): """Drop an object into the scene.""" global viz global bodies, geom, counter, objcount body, geom = create_box(world, space, 1000, 1.0,0.2,0.2) body.setPosition( (random.gauss(0,0.1),2,random.gauss(0,0.1)) ) theta = random.uniform(0,2*pi) ct = cos (theta) st = sin (theta) body.setRotation([ct, 0., -st, 0., 1., 0., st, 0., ct]) bodies.append(body) geoms.append(geom) counter=0 objcount+=1 viz.addGeom(geom) viz.GetProperty(geom).SetColor(random.uniform(0,1), random.uniform(0,1), random.uniform(0,1)) # explosion def explosion(): """Simulate an explosion. Every object is pushed away from the origin. The force is dependent on the objects distance from the origin. """ global bodies for b in bodies: l=b.getPosition () d = length (l) a = max(0, 40000*(1.0-0.2*d*d)) l = [l / 4, l, l /4] scalp (l, a / length (l)) b.addForce(l) # pull def pull(): """Pull the objects back to the origin. Every object will be pulled back to the origin. Every couple of frames there'll be a thrust upwards so that the objects won't stick to the ground all the time. """ global bodies, counter for b in bodies: l=list (b.getPosition ()) scalp (l, -1000 / length (l)) b.addForce(l) if counter%60==0: b.addForce((0,10000,0)) # Collision callback def near_callback(args, geom1, geom2): """Callback function for the collide() method. This function checks if the given geoms do collide and creates contact joints if they do. """ # Check if the objects do collide contacts = ode.collide(geom1, geom2) # Create contact joints world,contactgroup = args for c in contacts: c.setBounce(0.2) c.setMu(5000) j = ode.ContactJoint(world, contactgroup, c) j.attach(geom1.getBody(), geom2.getBody()) ###################################################################### print "start" # Create a world object world = ode.World() world.setGravity( (0,-9.81,0) ) world.setERP(0.8) world.setCFM(1E-5) # Create a space object space = ode.Space() # Create a plane geom which prevent the objects from falling forever floor = ode.GeomPlane(space, (0,1,0), 0) # A list with ODE bodies bodies =  # The geoms for each of the bodies geoms =  # A joint group for the contact joints that are generated whenever # two bodies collide contactgroup = ode.JointGroup() # Some variables used inside the simulation loop fps = 75 dt = 1.0/fps running = True state = 0 counter = 0 objcount = 0 lasttime = time.time() # idle callback def _idlefunc (): global counter, state, lasttime t = dt - (time.time() - lasttime) if (t > 0): time.sleep(t) counter += 1 if state==0: if counter==20: drop_object() if objcount==30: state=1 counter=0 # State 1: Explosion and pulling back the objects elif state==1: if counter==100: explosion() if counter>300: pull() if counter==500: counter=20 # Simulate n = 2 for i in range(n): # Detect collisions and create contact joints space.collide((world,contactgroup), near_callback) # Simulation step world.step(dt/n) # Remove all contact joints contactgroup.empty() lasttime = time.time() class my_sim(ode_viz.ODE_Visualization): def __init__(self, world, space, dt): ode_viz.ODE_Visualization.__init__(self, world, space, dt) def execute(self, caller, event): _idlefunc() self.update() viz = my_sim(world, [space], dt) drop_object() viz.start()
We simply removed all the OpenGL stuff and added a new visualization class, in the same way as it was used in the previous example. The original update function is here called from within the execute method. And if you take a look into function drop(), which is responsible for the creation of objects. Every new object is here directly added to the visualization, and their color-properties are changed in the vtk manner (to random colors).
Watch the result in movie below …
download site: http://sourceforge.net/projects/ode-viz/
direct download: http://sourceforge.net/projects/ode-viz/files/latest/download
svn co https://ode-viz.svn.sourceforge.net/svnroot/ode-viz ode-viz