pyODE attract and explode example

Return to Code Snippets

pyODE attract and explode example

Postby miel » Wed Jun 14, 2006 1:24 pm

renders a configurable amount of rgb cubes (or any other model) and applies basic physics to it (mass > gravity, collisions, bounce, rotation).

It works with GeomBox but that's easily replacable by a Geom*enterpyODEShapeHere kinda thing.

See for yourself:

Code: Select all

import direct.directbase.DirectStart
from pandac.PandaModules import *
from direct.interval.IntervalGlobal import *
from direct.gui.DirectGui import *
from direct.showbase.DirectObject import *

import sys, os, random, time
from math import *


import direct.ode as ode

# fall test as demonstrated in O So Many Tech Demo's

# Figure out what directory this program is in.
MYDIR=os.path.abspath(sys.path[0])
MYDIR=Filename.fromOsSpecific(MYDIR).getFullpath()

class ODEDemo(DirectObject):
    def __init__(self):
        #variables
        self.bodies = []
        self.solids = []
        self.scale = .5
        #constants
        self.world = ode.World()
        self.world.setGravity((0, 0, -9.81))
        self.world.setERP(0.8)
        self.world.setCFM(1E-2)
        self.space = ode.Space(type=1)
        self.contactgroup = ode.JointGroup()

        self.floor = ode.GeomPlane(self.space, (0, 0, 1), 0)
       
        self.geomFloor = loader.loadModel("misc/plane")
        self.geomFloor.reparentTo(render)
        self.geomFloor.setPos(0,0,0)
        self.geomFloor.setHpr(0, -90, 0)
        self.geomFloor.setScale(100)
       
        self.state = 0
        self.counter = 0
        self.lasttime = time.time()
       
       
        # and... GO!
       
        self.title = OnscreenText(text="Panda3D: PyODE integration",
                              style=1, fg=(1,1,1,1),
                              pos=(0.8,-0.95), scale = .07)
        self.wExp   = self.genLabelText("[ 1 ] : w key - BOOOM!", 0)
        self.sExp   = self.genLabelText("[ 2 ] : s key - magnetism!", 1)
   
   
        self.drawBodies()
       
        self.accept("w", self.explo)
       
        self.accept("s", self.pull)
       
        taskMgr.add(self.simulate, 'runSim')

    def genLabelText(self, text, i):
        return OnscreenText(text = text, pos = (-1.3, .95-.05*i), fg=(1,1,1,1), align = TextNode.ALeft, scale = .05)

    def scalp (self, vec, scal):
        vec[0] *= scal
        vec[1] *= scal
        vec[2] *= scal

    def length (self, vec):
        return sqrt (vec[0]**2 + vec[1]**2 + vec[2]**2)

    def drawBodies(self):
        randomizedMass = random.randint(500, 5000)
        body = ode.Body(self.world)
        M = ode.Mass()
        M.setBox(randomizedMass, self.scale / 3, self.scale / 3, self.scale / 3)
           
        body.setMass(M)
           
        geom = ode.GeomBox(self.space, lengths=(self.scale, self.scale, self.scale))
        geom.setBody(body)
       
        sphere = loader.loadModel("misc/rgbCube")
        sphere.reparentTo(render)
       
        body.setPosition( ( random.gauss(0,0.5),random.gauss(0,0.3), 3.0) )
       
        theta = random.uniform(0, 2*pi)
       
        ct = cos (theta)
        st = sin (theta)
       
        body.setRotation([ct, 0., -st, 0., 1., 0., st, 0., ct])
        self.bodies.append(body)
        self.solids.append(sphere)
       
       
    def explo(self):
        for b in range(len(self.bodies)):
            l=self.bodies[b].getPosition ()
            d = self.length (l)
            a = max(0, 10000*(1.0-0.2*d*d))
            l = [l[0] / 4, l[1], l[2] /4]
            self.scalp (l, a / self.length (l))
            self.bodies[b].addForce(l)
           
    def pull(self):
        for b in range(len(self.bodies)):
            l=list (self.bodies[b].getPosition ())
            self.scalp (l, -2000 / self.length (l))
            self.bodies[b].addForce(l)

    def near_callback(self, 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
        self.world, self.contactgroup = args
        for c in contacts:
            c.setBounce(0.05)
            c.setMu(5000)
            j = ode.ContactJoint(self.world, self.contactgroup, c)
            j.attach(geom1.getBody(), geom2.getBody())


    def simulate(self, task):
        dt = 1.0 / 50.0
       
        t = dt - (time.time() - self.lasttime)
       
        if (t>0):
            time.sleep(t)
            print t
        self.counter +=1
        if self.state == 0:
            if self.counter<25:
                self.drawBodies()
            if len(self.bodies)==25:
                self.state = 1
                self.counter = 0
        elif self.state == 1:
            if self.counter==100:
                for i in range(0, 2):
                    self.explo()
            if self.counter == 300:
                for i in range(0, 3):
                    self.pull()
            if self.counter == 500:
                self.counter = 20
       
        for b in range(0, len(self.bodies)):
            body = self.bodies[b]
            x, y, z = body.getPosition()
            R =body.getRotation()
           
            rot =  Mat4(R[0], R[3], R[6], 0.,
                        R[1], R[4], R[7], 0.,
                        R[2], R[5], R[8], 0.,
                        x, y, z, 1.0)
           
            self.solids[b].setMat(rot)
            self.solids[b].setScale(self.scale)


        self.space.collide((self.world,self.contactgroup), self.near_callback)
        self.world.step(dt)
        self.contactgroup.empty()
        base.camera.setPos(15,35,10)
        base.camera.lookAt(self.solids[0])
        return Task.cont
       
demo = ODEDemo()

run()




NEXT UP : Basic Ragdoll Physics
cheers,

Miel[/quote]
Last edited by miel on Fri Jun 16, 2006 11:01 am, edited 2 times in total.
miel
 
Posts: 45
Joined: Tue Jan 17, 2006 10:20 am

Postby MikeD » Wed Jun 14, 2006 11:40 pm

Appreciate your ODE samples -- but was wondering if you could post in such as way that the indentation isn't lost. It looks like you've used used the code tags, but that they're not working -- all code is flush left.

Would really like to try out sample and see code formatted correctly.

Thanks again,

Mike
MikeD
 
Posts: 8
Joined: Tue Nov 22, 2005 3:29 pm

Postby miel » Thu Jun 15, 2006 4:02 am

It's fixed. I always forget to enable BBCode...

cheers
miel
 
Posts: 45
Joined: Tue Jan 17, 2006 10:20 am

Postby MikeD » Thu Jun 15, 2006 8:36 pm

Thanks for formatting the code. Like the program!

One thing to note for others who may run it -- you have to adjust the camera in order to see the boxes; they're not in view when the program first starts.

Mike
MikeD
 
Posts: 8
Joined: Tue Nov 22, 2005 3:29 pm

Postby bigfoot29 » Fri Jun 16, 2006 10:48 am

Sorry for not-testing it till now, but doesn't pyODE require quite some sort of work till it finally works? I think I remember something like that...

I guess its a usefull piece of code - however, a short way of how to install pyODE would be nice. :D

Maybe someone can add that?


(Well as said. sorry for the incompetent question. *g*)


Regards, Bigfoot29
User avatar
bigfoot29
 
Posts: 617
Joined: Thu Jun 30, 2005 4:22 am
Location: Germany

Postby miel » Fri Jun 16, 2006 11:10 am

Hey all,

The camera stuff is working now. I updated the code (again) .

It requires some tricky stuff like the matrix calculations and the scaling. Plus, it only works for cube shaped object right now (although I did also test it with teapots and GeomSpheres and that worked just as well.

Currently i'm looking at ways to get the vertex data out of an egg file into pyODE's TriMeshData.

for the PyODE install instructions:

requirements:
# Python 2.4 installation
# pyODE
# Panda3D

-> install pyODE to your python path.
-> In a browser go to X:\PYTHONPATH\lib\site-packages
-> copy the file pyode.pyd and the directory xode to: X:\PANDAPATH\direct\src

-> in your scripts import direct.ode as ode

-> all done!




I tested it with 75 objects and it worked like a charm.
miel
 
Posts: 45
Joined: Tue Jan 17, 2006 10:20 am

Postby ynjh_jo » Fri Jul 14, 2006 12:56 am

( post erased due to irrelevant workaround )
Last edited by ynjh_jo on Sun Jan 07, 2007 1:30 am, edited 9 times in total.
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby ynjh_jo » Sat Jul 15, 2006 3:18 am

( post erased due to irrelevant workaround )
Last edited by ynjh_jo on Sun Jan 07, 2007 1:31 am, edited 9 times in total.
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby ynjh_jo » Mon Jul 17, 2006 12:03 am

( post erased due to irrelevant workaround )
Last edited by ynjh_jo on Sun Jan 07, 2007 1:35 am, edited 11 times in total.
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby ynjh_jo » Tue Jul 18, 2006 8:28 am

( post erased due to irrelevant workaround )
Last edited by ynjh_jo on Sun Jan 07, 2007 1:37 am, edited 1 time in total.
http://ynjh.panda3dprojects.com | http://ynjh.p3dp.com
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby ynjh_jo » Sat Jan 06, 2007 2:41 am

My bad, seems I've done a foolish workaround of something that apparently doesn't have to be done. I'll fix it and post it tonight.
http://ynjh.panda3dprojects.com | http://ynjh.p3dp.com
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby ynjh_jo » Sun Jan 07, 2007 1:48 am

It's possible to pass a multi-part object. You just need to pass a nodepath or a geomnode, and then vertices & faces of ALL geometry under this node will be stored to the list. We can use findAllMatches() to find all geomnode under a node. This works well for the teapot, which consists of 4 parts : body, handle, spout, and lid, with total 1350 vertices and 2256 faces. This approach helps much when we need to keep the separated parts updated, added, moved around, transformed in anyway without any need to keep them as a single mesh, or name them.

There isn't any built-in method for that.
1. The format of vertex index in Panda is not the format that ODE wants. It must be sequence of 3-sequences tuple (3 vertices build 1 triangle). So, we have to split them, give them partitions every 3 vertices.

2. This is more critical. Since a node we pass to ODE may consists of several geomNode, which each one may has it's own transformation, then we must preserve the transformation, or else the shape wouldn't be correct. So, we must bring each vertex under the given node into the parent's coordinate system, that's the simplest way.

The center of gravity is the objects's origin. You can set it since modeling phase and make sure to export the transformation (I believe most exporters do this as default).

This is the example :
[get the latest update on the following post]

Problems:
- poor collision between custom shape geometries, too much interpenetration
Last edited by ynjh_jo on Tue Jan 30, 2007 5:09 am, edited 4 times in total.
http://ynjh.panda3dprojects.com | http://ynjh.p3dp.com
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby ynjh_jo » Sun Jan 07, 2007 9:48 am

No, the face orientation is correct, since it's based on the visible geometry of the objects used here, isn't it ?
Yes, that was my first suspect too at the first time I saw it long time ago.
If you doubt it, try to set everything transparent : "render.setAlphaScale(.8)", and see how the balls and cubes get trapped inside the custom objects, if you flip the face. And the visualizer would never show up, too.
http://ynjh.panda3dprojects.com | http://ynjh.p3dp.com
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby ynjh_jo » Thu Jan 11, 2007 1:50 pm

Update :
- I use the quickStep for more stability, but less accuracy
- I built a container to avoid the objects from going to infinity
- I built contacts visualizer, to show the contact points and the normal at each point
Now the contacts are visible, so it must be easier to figure out what is exactly going on and decide what should we do next. I also add the bounce to each contact IF the colliding objects are trimeshes.
Oh, just an info :
ODE latest release on September 8, 2006 supports trimesh-plane collision
http://sourceforge.net/project/shownotes.php?group_id=24884&release_id=444534
http://ynjh.panda3dprojects.com | http://ynjh.p3dp.com
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby ynjh_jo » Sun Jan 14, 2007 3:35 pm

Update (no performance improvement) :
added 2 scenes, just for fun :
- a tower built of boxes, I've built a stable tower shape so that it can support itself against ODE's calculation inaccuracy. Try to break down the tower, just using a few explosions. The forces (pull & explode) aren't applied to the tower.
- a very simple spiral chain reaction. Try to hit the first piece by launching any of primitive objects over the red ramp.

[EDIT]
Enhanced the setTriMesh() a little, to accommodate the probability of :
- a geomnode consists of several geoms
- a vertex data shared by several geoms
Those are unusual for collision geometries, but it'd be better if the transfer method is more bullet-proof. Now, it's even possible to send the entire visible triangles of the standing panda model (panda.egg), where 6 geoms share only 3 vertex datas.
http://ynjh.panda3dprojects.com | http://ynjh.p3dp.com
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby ThomasEgi » Thu Jan 25, 2007 10:17 am

i just checked out the examples and they are working just great =)
pyode seems ways faster than the pandas internal collision detection (from what i can see) and features full physic simulation.
how about "integrating" it into panda to make usage easier?
User avatar
ThomasEgi
 
Posts: 2147
Joined: Fri Jul 28, 2006 10:43 am
Location: Germany,Koblenz

Postby ynjh_jo » Thu Jan 25, 2007 5:57 pm

Just a guide for doing ode simulation :
[1] create a body. Any types of simulation calculations is performed on a body, including position & orientation changes, and applied forces.
[2] create a geom. Geom is the shape on which the collision detection is performed. Dynamic objects have bodies, while static objects do not. Geom may be one of the basic ode primitives (box, sphere, cappedCylinder, etc.) or GeomTriMesh (polygonal custom shape, which is defined by passing vertices list and vertices index into ode).
[3] at runtime :
a. make the needed changes : change position, rotation, apply forces, etc.
b. run the simulation timestep
c. synchronize all visible geometries associated with the bodies

ThomasEgi wrote:how about "integrating" it into panda to make usage easier?

It sounds like a question for the devs.
If it's for me, then I have a question for you : how easy do you want it to be ?
Alright, I've simplified it for more, but it's just a quick & dirty one. I simply splitted the only one class into 2 classes : World and ODEsim. Class ODEsim consists of methods directly used for doing simulation (sim setting, collision detection & response, vertices passing, etc).

[EDIT]
enhanced setTriMesh :
- custom object's transformation is set before calling setTriMesh, so in setTriMesh, everything is correctly transformed. Thus, it's more "integrated" ??.
- more efficiency. No more space for unnecessary duplicated vertex data sent to ODE, compare the print out for yourself

[EDIT 3/18/07]
brought it to object oriented version


Here it is :
Code: Select all
from pandac.PandaModules import *
import direct.directbase.DirectStart
from direct.gui.DirectGui import OnscreenText
from direct.showbase.DirectObject import DirectObject
from direct.interval.IntervalGlobal import *
from direct.task import Task
import sys, random
from math import degrees, sin, cos, pi
from direct.showbase import PythonUtil as PU
import ode as ode

def genLabelText(text, i):
    return OnscreenText(text = text, pos = (-1.3, .95-.05*i), fg=(1,1,1,1), align = TextNode.ALeft, scale = .05, mayChange=1)


class ODEobject:
    ''' the base class of ODE boxes, spheres, and TriMeshes used here '''

    def storeProps(self, realObj, density, friction, noColl_ID=None):
        self.realObj=realObj
        self.density=density
        self.geom.friction=friction
        # randomize geom's collision ID, if not supplied
        if noColl_ID==None:
           if realObj:
              noColl_ID=id(realObj)
           else:
              noColl_ID=random.random()*random.random()*1000
        self.geom.noColl=noColl_ID

    def destroy(self):
        if hasattr(self,'body'):
           del self.body
        self.geom.getSpace().remove(self.geom)
        del self.geom
        if hasattr(self,'visualizer'):
           self.visualizer.removeNode()
        self.realObj.removeNode()

    def getOBB(self,collObj):
        ''' get the Oriented Bounding Box '''
        # save object's parent and transformation
        parent=collObj.getParent()
        trans=collObj.getTransform()
        # ODE need everything in world's coordinate space,
        # so bring the object directly under render, but keep the transformation
        collObj.wrtReparentTo(render)
        # get the tight bounds before any rotation
        collObj.setHpr(0,0,0)
        bounds=collObj.getTightBounds()
        # bring object to it's parent and restore it's transformation
        collObj.reparentTo(parent)
        collObj.setTransform(trans)
        # (max - min) bounds
        box=bounds[1]-bounds[0]
        return [box[0],box[1],box[2]]


class ODEbox(ODEobject):
    ''' An easy setup for creating ode.GeomBox (based on the tight bounding box),
        and it's body.

        If you have a geometry you'd like to use to define GeomBox size precisely,
        pass it as "collObj", or otherwise
        the "realObj" will be used to define GeomBox size.

        You can also set the object's mass density and friction coefficient (mu),
        set density=0 to set the object as a static object, or
        setting it larger than 0 will automatically set the object as a dynamic one.

        "noColl_ID" is an arbitrary number, use it when you need to build
        a larger collision structure from several geoms, which might overlap each other.
        Geoms with the same "noColl_ID" will NOT be tested for collision.
        In such case, don't pass your "realObj" to every geoms, or else
        your "realObj" transformation will be updated based on your every geoms.
        Pass it only when creating the main geom.
    '''
    def __init__(self, world, space, realObj=None, collObj=None,
                       density=0, friction=0, noColl_ID=None):
        if realObj==None:
           obj=collObj
        else:
           obj=realObj
           if collObj==None:
              collObj=realObj

        boundingBox=self.getOBB(collObj)

        self.geom = ode.GeomBox(space, lengths=boundingBox)
        if density:  # create body if the object is dynamic, otherwise don't
           self.body = ode.Body(world)
           M = ode.Mass()
           M.setBox(density, *boundingBox)
           self.body.setMass(M)
           self.geom.setBody(self.body)
        # synchronize ODE geom's transformation according to the real object's
        self.geom.setPosition(obj.getPos(render))
        self.geom.setQuaternion(obj.getQuat(render))
        # store object's properties
        self.storeProps(realObj, density, friction, noColl_ID)


class ODEsphere(ODEobject):
    ''' An easy setup for creating ode.GeomSphere (based on the tight bounding box),
        and it's body.

        If you have a geometry you'd like to use to define GeomSphere size precisely,
        pass it as "collObj", or otherwise
        the "realObj" will be used to define GeomSphere size.

        You can also set the object's mass density and friction coefficient (mu),
        set density=0 to set the object as a static object, or
        setting it larger than 0 will automatically set the object as a dynamic one.

        "noColl_ID" is an arbitrary number, use it when you need to build
        a larger collision structure from several geoms, which might overlap each other.
        Geoms with the same "noColl_ID" will NOT be tested for collision.
        In such case, don't pass your "realObj" to every geoms, or else
        your "realObj" transformation will be updated based on your every geoms.
        Pass it only when creating the main geom.
    '''
    def __init__(self, world, space, realObj=None, collObj=None,
                       density=0, friction=0, noColl_ID=None):
        if realObj==None:
           obj=collObj
        else:
           obj=realObj
           if collObj==None:
              collObj=realObj

        boundingBox=self.getOBB(collObj)
        radius=.5*max(*boundingBox)

        self.geom = ode.GeomSphere(space, radius)
        if density:  # create body if the object is dynamic, otherwise don't
           self.body = ode.Body(world)
           M = ode.Mass()
           M.setSphere(density, radius)
           self.body.setMass(M)
           self.geom.setBody(self.body)
        # synchronize ODE geom's transformation according to the real object's
        self.geom.setPosition(obj.getPos(render))
        self.geom.setQuaternion(obj.getQuat(render))
        # store object's properties
        self.storeProps(realObj, density, friction, noColl_ID)


class ODEtrimesh(ODEobject):
    ''' An easy setup for creating ode.GeomTriMesh and it's body.

        Pass any nodepath as "collObj", all geometries' polygons under that node will be sent to ODE,
        including the current transformation (position, rotation, scale), except shear.
        If not given, your "realObj" will be used.

        You can also set the object's mass density and friction coefficient (mu),
        set density=0 to set the object as a static object, or
        setting it larger than 0 will automatically set the object as a dynamic one.
    '''
    def __init__(self, world, space, realObj, collObj=None, density=0, friction=0, showCollObj=0, noColl_ID=None):
        if collObj==None:
           collObj=realObj
        # find any geomnode under the given node
        geomList=collObj.findAllMatches('**/+GeomNode').asList()
        if not geomList:
           geomList.append(collObj)

        # get the node's scale to preserve it
        meshScale=collObj.getScale(render)
        collNumVtx=0
        collnumface=0
        # dictionary to keep the overall vertex data length before appending the new data
        vDataStart={}
        # list for the vertices
        collVertices=[]
        # list for the vertices index
        collFaces=[]

        # append all vertex data into a complete list
        for collGeom in geomList:
            for gIdx in range(collGeom.node().getNumGeoms()):
                currentGeom=collGeom.node().getGeom(gIdx)
                collVtxData=currentGeom.getVertexData()
                numVtx=collVtxData.getNumRows()
                # append the current vertex data, IF it hasn't been added to the list,
                # otherwise, don't add it again, to avoid duplicated vertex data,
                # which may be shared by several geoms
                if not vDataStart.has_key(collVtxData):
                   # store the number of the collected vertices so far,
                   # to mark the index start for the next vertex data
                   vDataStart[collVtxData]=collNumVtx
                   # create vertex reader
                   collvtxreader=GeomVertexReader(collVtxData)
                   # set the start position for reading the vertices,
                   # begin reading at column 0, which is the vertex position
                   collvtxreader.setColumn(0)
                   # begin reading at vertex 0
                   collvtxreader.setRow(0)
                   for i in range(numVtx):
                       # respect each geomNode's transformation which may be exist
                       vtx=collObj.getRelativePoint(collGeom,collvtxreader.getData3f())
                       # synchronize TriMesh to the current scale of the collision mesh
                       vtx1=vtx[0]*meshScale[0]
                       vtx2=vtx[1]*meshScale[1]
                       vtx3=vtx[2]*meshScale[2]
                       collVertices.append((vtx1,vtx2,vtx3))
                   # add the current collected vertices count
                   collNumVtx+=numVtx

        # storing the vertices index
        for collGeom in geomList:
            for gIdx in range(collGeom.node().getNumGeoms()):
                geom=collGeom.node().getGeom(gIdx)
                collVtxData=geom.getVertexData()
                vDataBegin=vDataStart[collVtxData]
                for prim in range(geom.getNumPrimitives()):
                    # store the vertices index for each triangle
                    collFaceData=geom.getPrimitive(prim).decompose()
                    # get the triangle counts
                    numface=collFaceData.getNumFaces()
                    # get the start index of current primitive at geom's vertex data
                    s = collFaceData.getPrimitiveStart(prim)
                    for i in range(numface):
                        # refer to the vertex data length list created earlier
                        vtx1=vDataBegin+collFaceData.getVertex(s+i*3)
                        vtx2=vDataBegin+collFaceData.getVertex(s+i*3+1)
                        vtx3=vDataBegin+collFaceData.getVertex(s+i*3+2)
                        collFaces.append((vtx1,vtx2,vtx3))
                    collnumface+=numface

        meshdata=ode.TriMeshData()
        # create TriMesh data
        meshdata.build(collVertices,collFaces)
        # and pass it to ODE
        self.geom = ode.GeomTriMesh(meshdata,space)

        # create body if the object is dynamic, otherwise don't
        if density:
           M2 = ode.Mass()
           M2.setBox(density, 1,1,1)
           self.body = ode.Body(world)
           self.body.setMass(M2)
           self.body.setGravityMode(1)
           self.geom.setBody(self.body)
        # store object's properties
        self.storeProps(realObj, density, friction, noColl_ID)

        print '###############################'
        print collObj
        collObj.analyze()
        print '--- sent to ODE ---'
        print 'Vertices : ',collNumVtx
        print 'Faces    : ',collnumface
        print '###############################\n'

        # originally only to debug the transfer method
        self.visualize(collnumface)

        # synchronize TriMesh to the current transformation (position & rotation) of the collision mesh
        self.geom.setPosition(collObj.getPos(render))
        self.geom.setQuaternion(collObj.getQuat(render))

        # hide the collision geometry, if it's not the visible object
        if not showCollObj and collObj!=realObj:
           collObj.hide()

    def visualize(self,collnumface):
        ''' this method creates the actual geometry succesfully sent to ODE,
            originally only to debug the transfer method '''

        #(step.1) create GeomVertexData and add vertex information
        format=GeomVertexFormat.getV3()
        vdata=GeomVertexData('vertices', format, Geom.UHStatic)
        vertexWriter=GeomVertexWriter(vdata, 'vertex')
        tris=GeomTriangles(Geom.UHStatic)

        for i in range(collnumface):
          vtx1,vtx2,vtx3=self.geom.getTriangle(i)
          vertexWriter.addData3f(*vtx1)
          vertexWriter.addData3f(*vtx2)
          vertexWriter.addData3f(*vtx3)
          #(step.2) make primitives and assign vertices to them
          tris.addConsecutiveVertices(i*3,3)
          #indicates that we have finished adding vertices for the triangle
          tris.closePrimitive()

        #(step.3) make a Geom object to hold the primitives
        collmeshGeom=Geom(vdata)
        collmeshGeom.addPrimitive(tris)
        #(step.4) now put geom in a GeomNode
        collmeshGN=GeomNode('')
        collmeshGN.addGeom(collmeshGeom)

        self.visualizer = self.realObj.attachNewNode(collmeshGN)
        self.visualizer.setRenderModeWireframe(1)
        self.visualizer.setColor(1,1,1,1)
        scale=self.realObj.getScale()
        self.visualizer.setScale(1./scale[0],1./scale[1],1./scale[2])
        self.visualizer.setLightOff()
        # put the axis at object's origin (represents it's center of gravity)
        axis = loader.loadModelCopy('zup-axis')
        axis.reparentTo(self.visualizer)
        axis.setScale(.25)


class ODEsim:
    ''' this class consists of methods associated with
        ODE simulation setting & loop '''

    def initODE(self):
        self.ode_WORLD = ode.World()
        self.ode_WORLD.setGravity((0, 0, -9.81))
        self.ode_WORLD.setCFM(0)
#        self.ode_WORLD.setContactMaxCorrectingVel(1)
#        self.ode_WORLD.setERP(0.8)
#        self.ode_WORLD.setContactSurfaceLayer(.001)
        self.ode_SPACE = ode.Space(type=1)
        self.floor = ode.GeomPlane(self.ode_SPACE, (0, 0, 1),0)
        self.ode_CONTACTgroup = ode.JointGroup()
        self.ODEdt = 1.0 / 40.0

    def scalp (self, vec, scal):
        vec[0] *= scal
        vec[1] *= scal
        vec[2] *= scal

    def near_callback(self, 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.
        '''

        # don't check for collision IF both geoms build a larger collision geom,
        # because they probably overlap each other
        noColl1=101010
        noColl2=-noColl1
        if hasattr(geom1, 'noColl'):
           noColl1=geom1.noColl
        if hasattr(geom2, 'noColl'):
           noColl2=geom2.noColl
        if noColl1==noColl2:
           return

        # Check if the objects do collide
        contacts = ode.collide(geom1, geom2)
        if not len(contacts):
           return

        # averaging the friction coefficient of the two colliding objects
        friction1=friction2=0
        if hasattr(geom1, 'friction'):
           friction1=geom1.friction
        if hasattr(geom2, 'friction'):
           friction2=geom2.friction
        averageFrictionCoef=PU.average(friction1, friction2)
#        print 'AVG :',geom1, geom2, averageFrictionCoef

        # looking for TriMesh-anything collision
        geom1_is_TriMesh=type(geom1).__name__=='GeomTriMesh'
        geom2_is_TriMesh=type(geom2).__name__=='GeomTriMesh'
        bothTriMesh=geom1_is_TriMesh and geom2_is_TriMesh
        if bothTriMesh:
           g1Vel=g2Vel=Vec3(0,0,0)
           g1body=geom1.getBody()
           if g1body:
              g1Vel=Vec3(*g1body.getLinearVel())
           g2body=geom2.getBody()
           if g2body:
              g2Vel=Vec3(*g2body.getLinearVel())

           diff=40.0/(g1Vel-g2Vel).length()
           diff=min(20.0,diff)
           bouncePerContact=diff/len(contacts)
#           print diff,bouncePerContact

        if ( geom1_is_TriMesh or geom2_is_TriMesh ) and self.contactVis:
           LSpoint=LineSegs()
           LSpoint.setColor(1,1,0)
           LSpoint.setThickness(8)
           LSnormal=LineSegs()
           LSnormal.setColor(1,.5,.2)
           LSnormal.setThickness(2)
           TriMeshCollision=1
        else:
           TriMeshCollision=0

        # Create contact joints
        self.ode_WORLD, self.ode_CONTACTgroup = args
        for c in contacts:
            # if any TriMesh collision, collect the contact points
            if TriMeshCollision:
               pos, normal, depth, g1, g2 = c.getContactGeomParams()
               # create a single point, call moveTo only
               pos=Point3(pos[0],pos[1],pos[2])
               LSpoint.moveTo(pos)
               # create a line
               LSnormal.moveTo(pos)
               LSnormal.drawTo(pos+Point3(normal[0],normal[1],normal[2]))

            # add more bounce if both objects are TriMesh
            if bothTriMesh:
               c.setBounce(bouncePerContact)
            else:
               c.setBounce(.2)
            c.setMu(averageFrictionCoef)
            j = ode.ContactJoint(self.ode_WORLD, self.ode_CONTACTgroup, c)
            j.attach(geom1.getBody(), geom2.getBody())

        # render TriMesh contacts
        if TriMeshCollision:
           contactVisualizer=render.attachNewNode('')
           contactVisualizer.attachNewNode(LSpoint.create())
           contactVisualizer.attachNewNode(LSnormal.create())
           contactVisualizer.setLightOff()
           # remove the visualized TriMesh contacts, after the given time
           taskMgr.doMethodLater(.01,self.removeContactVis,'removeContact',[contactVisualizer])

    def removeContactVis(self,vis):
        vis.removeNode()

    def simulate(self):
        # synchronize the position and rotation of the visible object to it's body
        for o in self.simObjects:
            if o.density and o.realObj!=None:
               body = o.body
               o.realObj.setPos(render,*body.getPosition())
               o.realObj.setQuat(render,Quat(*body.getQuaternion()))

        self.ode_SPACE.collide((self.ode_WORLD,self.ode_CONTACTgroup), self.near_callback)
        self.ode_WORLD.quickStep(self.ODEdt)
        ''' you can switch to standard world step, for more accuracy,
        but on most cases, it causes simulation instability '''
        #self.ode_WORLD.step(self.ODEdt)
        self.ode_CONTACTgroup.empty()



class World(DirectObject,ODEsim):
    ''' ODE demo main class '''

    def __init__(self):
        '''
        self.ode_WORLD   and   self.ode_SPACE   are
        ode.World        and   ode.Space      , respectively
        '''

        # initialize class ODEsim, to prepare ODE simulation,
        # create world, space, and some simulation tuning) '''
        self.initODE()

        base.setFrameRateMeter(1)
        render.setTransparency(TransparencyAttrib.MDual)
        self.setupLights()

        # list for ODE simulation objects
        self.simObjects = []
        # base scale for the primitive objects
        self.scale = .2
        # TriMesh object visualizer state (on/off)
        self.trimeshVis=1
        # TriMesh contacts visualizer state (on/off)
        self.contactVis=0

        OnscreenText(text='Panda3D: PyODE integration',style=1, fg=(1,1,1,1),pos=(0.8,-0.95), scale = .07)
        genLabelText('the XYZ axis represents the center of gravity', 0)
        genLabelText('[ w ] : BOOOM!', 2)
        genLabelText('[ s ] : magnetism!', 3)

        self.trimeshVisText='[ NUM + ] : toggle TRIMESH VISUALIZER  '
        self.contactVisText='[ NUM - ] : toggle CONTACTS VISUALIZER  '
        self.onOff=['OFF','ON']

        self.trimeshVisLabel = genLabelText(self.trimeshVisText+'[%s]' %self.onOff[self.trimeshVis],4)
        self.contactVisLabel = genLabelText(self.contactVisText+'[%s]' %self.onOff[self.contactVis],5)

        genLabelText('[ 0 ] : scene 0', 7)
        genLabelText('[ 1 ] : scene 1', 8)
        genLabelText('[ 2 ] : scene 2', 9)

        self.keys = {'explode' : 0, 'pull': 0}

        self.accept('w', self.setKey, ['explode', 1])
        self.accept('w-up', self.setKey, ['explode', 0])
        self.accept('s', self.setKey, ['pull', 1])
        self.accept('s-up', self.setKey, ['pull', 0])
        self.accept('+', self.toggleVis)
        self.accept('-', self.toggleContactsVis)
        self.accept('0', self.startScene0)
        self.accept('1', self.startScene1)
        self.accept('2', self.startScene2)
        self.accept('escape', sys.exit)

        self.startScene1()
        taskMgr.add(self.gameLoop, 'runSim')

    def cameraLook(self, time,lookPoint=Point3(0,0,0)):
        base.cam.lookAt(lookPoint)

    def setKey(self, key, val):
        self.keys[key] = val

    def setupLights(self):
        ambientLight = AmbientLight( 'ambientLight' )
        ambientLight.setColor( Vec4(0.4, 0.4, 0.4, 1) )
        self.ambientLight=render.attachNewNode( ambientLight.upcastToPandaNode() )

        directionalLight = DirectionalLight( 'directionalLight1' )
        directionalLight.setDirection( Vec3( 1, -.5,-1 ) )
        directionalLight.setColor( Vec4( .6, .6, .6, 1 ) )
        self.directionalLight=render.attachNewNode( directionalLight.upcastToPandaNode() )

        render.setLight(self.ambientLight)
        render.setLight(self.directionalLight)

    def drawCage(self):
        self.cage=render.attachNewNode('')
        size=80
        height=10
        scale=Vec3(size,size,5)
        obj = loader.loadModelCopy('misc/rgbCube')
        obj.reparentTo(self.cage)
        obj.setPos(0,0,-scale[2]*.5)
        obj.setScale(scale)
        obj.setColor(0,0,0,1)
        self.simObjects.append( ODEbox( world=self.ode_WORLD, space=self.ode_SPACE,
                                        realObj=obj, density=0, friction=5) )

        scale=Vec3(1,size,height)
        for side in range(2):
            obj = loader.loadModelCopy('misc/rgbCube')
            obj.reparentTo(self.cage)
            obj.setPos(size*(side-.5),0,height*.5)
            obj.setScale(scale)
            obj.setTransparency(1)
            obj.setAlphaScale(.7)
            self.simObjects.append( ODEbox( world=self.ode_WORLD, space=self.ode_SPACE,
                                            realObj=obj, density=0, friction=5) )

        scale=Vec3(size,1,height)
        for side in range(2):
            obj = loader.loadModelCopy('misc/rgbCube')
            obj.reparentTo(self.cage)
            obj.setPos(0,size*(side-.5),height*.5)
            obj.setScale(scale)
            obj.setTransparency(1)
            obj.setAlphaScale(.7)
            self.simObjects.append( ODEbox( world=self.ode_WORLD, space=self.ode_SPACE,
                                            realObj=obj, density=0, friction=5) )

        print 'drawCage done...'


    def drawBodies(self,numBodies):
        for i in range(numBodies):
            dynamic=i%2
            if dynamic:
               scale=self.scale*1.2
            else:
               scale=self.scale

            theta = random.uniform(0, 2*pi)
            rX=random.gauss(0,2)
            rY=random.gauss(0,2)
            rX += 2.*rX/abs(rX)
            rY += 2.*rY/abs(rY)
            pos=Point3(rX,rY,20*dynamic)
            if random.randint(0,1):    # sphere shape model
               if dynamic:
                  model='smiley'
                  hpr=Vec3(theta,theta,theta)
               else:
                  model='jack'
                  hpr=Vec3(180,0,0)
                  scale*=2.5
               obj = loader.loadModelCopy(model)
               obj.reparentTo(render)
               obj.setPosHprScale(pos, hpr, Vec3(scale,scale,scale))
               self.simObjects.append( ODEsphere( world=self.ode_WORLD, space=self.ode_SPACE,
                                                  realObj=obj, density=250*dynamic, friction=1000) )
            else:                      # box shape model
               if dynamic:
                  hpr=Vec3(theta,theta,theta)
                  scale=Vec3(scale, scale, scale)
               else:
                  hpr=Vec3(0,0,0)
                  scale=Vec3(scale*3, scale, scale*10)
               obj = loader.loadModelCopy('misc/rgbCube')
               obj.reparentTo(render)
               obj.setPosHprScale(pos, hpr, scale)
               self.simObjects.append( ODEbox( world=self.ode_WORLD, space=self.ode_SPACE,
                                               realObj=obj, density=1200*dynamic, friction=1000) )

        print 'drawBodies done...'


    def drawTriMesh(self):
#         self.realObject5=loader.loadModelCopy('camera')
#         self.realObject5.reparentTo(render)
#         self.realObject5.setPos(-4,2,30)
#         self.realObject5.setScale(1.5)
#         self.simObjects.append( ODEtrimesh( world=self.ode_WORLD, space=self.ode_SPACE,
#                                             realObj=self.realObject5, density=40, friction=10 ) )


        self.realObject6=loader.loadModelCopy('misc/objectHandles')
        self.realObject6.reparentTo(render)
        self.realObject6.setPos(4,2,20)
        self.realObject6.setScale(1.4)
        self.simObjects.append( ODEtrimesh( world=self.ode_WORLD, space=self.ode_SPACE,
                                            realObj=self.realObject6, density=30, friction=20 ) )

        self.realObject7=loader.loadModelCopy('misc/Pointlight')
        self.realObject7.reparentTo(render)
        self.realObject7.setPos(-4,-1,25)
        self.realObject7.setScale(1.3)
        self.simObjects.append( ODEtrimesh( world=self.ode_WORLD, space=self.ode_SPACE,
                                            realObj=self.realObject7, density=30, friction=10 ) )

#         self.realObject8=loader.loadModelCopy('misc/Dirlight')
#         self.realObject8.reparentTo(render)
#         self.realObject8.setPos(-4,-5,20)
#         self.realObject8.setScale(.6)
#         self.simObjects.append( ODEtrimesh( world=self.ode_WORLD, space=self.ode_SPACE,
#                                             realObj=self.realObject8, density=30, friction=15 ) )

#         self.realObject9=loader.loadModelCopy('misc/Spotlight')
#         self.realObject9.reparentTo(render)
#         self.realObject9.setPos(4,3,25)
#         self.realObject9.setScale(.6)
#         self.simObjects.append( ODEtrimesh( world=self.ode_WORLD, space=self.ode_SPACE,
#                                             realObj=self.realObject9, density=50, friction=15 ) )

#         self.realObject10=loader.loadModelCopy('panda-model')
#         self.realObject10.reparentTo(render)
#         self.realObject10.setPos(-4,-4,25)
#         self.realObject10.setScale(.005)
#         self.simObjects.append( ODEtrimesh( world=self.ode_WORLD, space=self.ode_SPACE,
#                                             realObj=self.realObject10, density=50, friction=10 ) )

#         self.realObject11=loader.loadModelCopy('panda')
#         self.realObject11.reparentTo(render)
#         self.realObject11.setPos(5,-2,25)
#         self.realObject11.setScale(.3)
#         self.simObjects.append( ODEtrimesh( world=self.ode_WORLD, space=self.ode_SPACE,
#                                             realObj=self.realObject11, density=30, friction=2 ) )


    def buildBoxesTower(self):
        size=3
        height=1.2
        columnScale=(.55,.55,height)
        floorThick=.15
        floorScale=(size*1.12,size*1.12,floorThick)
        deviation=.28
        for floor in range(9):
            pos=( 0,0,(height+floorThick)*(floor+1)-floorThick*.5 )
            obj = loader.loadModelCopy('misc/rgbCube')
            obj.reparentTo(render)
            obj.setPos(*pos)
            obj.setScale(floorScale[0]-floor*deviation,floorScale[1]-floor*deviation,floorScale[2])
            obj.setLightOff()
            obj.setPythonTag('posZ',pos[2])
            # exclude object from receiving explode & pull forces
            obj.setTag('excluded','')
            self.simObjects.append( ODEbox( world=self.ode_WORLD, space=self.ode_SPACE,
                                            realObj=obj, density=75, friction=5000) )

            for column in range(4):
                pos=( (size-floor*deviation)*((column%2)-.5), .5*(size-floor*deviation)*(column-column%2-1),(height+floorThick)*floor+height*.5 )
                scale=Vec3(columnScale[0]-floor*.02,columnScale[1]-floor*.02,columnScale[2])
                obj = loader.loadModelCopy('misc/rgbCube')
                obj.reparentTo(render)
                obj.setPos(*pos)
                obj.setScale(scale)
                obj.setLightOff()
                obj.setPythonTag('posZ',pos[2])
                # exclude object from receiving explode & pull forces
                obj.setTag('excluded','')
                self.simObjects.append( ODEbox( world=self.ode_WORLD, space=self.ode_SPACE,
                                                realObj=obj, density=75, friction=5000) )

    def buildChainReaction(self):
        radius=10
        center=(-radius,16.1)
        height=3
        objGap=.27
        scale=Vec3(height*.5,.5,height)
        for i in range(30):
            rad=i*(objGap+i*i*.0002)
            x=cos(rad)*(radius-i*objGap) + center[0]
            y=sin(rad)*(radius-i*objGap) + center[1]
            obj = loader.loadModelCopy('misc/rgbCube')
            obj.reparentTo(render)
            obj.setPosHprScale(Point3( x, y, height*.5), Vec3(degrees(rad),0,0), scale)
            obj.setLightOff()
            # exclude object from receiving explode & pull forces
            obj.setTag('excluded','')
            self.simObjects.append( ODEbox( world=self.ode_WORLD, space=self.ode_SPACE,
                                            realObj=obj, density=30, friction=200) )

        for i in range(2):
            height=8
            pos=Point3(-2*(i-.5)*3,3.5,height*.5)
            scale=Vec3(25,.2,height)
            deg=2*(i-.5)*78
            obj = loader.loadModelCopy('misc/rgbCube')
            obj.reparentTo(render)
            obj.setPosHprScale(pos,Vec3(deg,0,0),scale)
            obj.setColor(1,1,1,.5)
            obj.setLightOff()
            # exclude object from receiving explode & pull forces
            obj.setTag('excluded','')
            self.simObjects.append( ODEbox( world=self.ode_WORLD, space=self.ode_SPACE,
                                            realObj=obj, density=0, friction=0) )

        ramp = loader.loadModelCopy('misc/rgbCube')
        ramp.reparentTo(render)
        ramp.setPosHprScale(0,12.8,.5, 0,210,90, .2,7,2.5)
        ramp.setLightOff()
        self.simObjects.append( ODEbox( world=self.ode_WORLD, space=self.ode_SPACE,
                                        realObj=ramp, density=0, friction=0) )

        self.arrow = loader.loadModelCopy('misc/Dirlight')
        self.arrow.reparentTo(ramp)
        self.arrow.setH(180)
        self.arrow.setScale(.2)
        arrowBW=LerpPosInterval(self.arrow,.4,Point3(.5,1.5,0),startPos=Point3(.5,1.2,0),blendType='easeOut')
        arrowFW=self.arrow.posInterval(.4,Point3(.5,1.2,0))
        self.arrowAnim = Sequence(
                           Parallel(arrowBW,
                                    LerpColorInterval(self.arrow,.4,Vec4(0,0,0,0),Vec4(0,0,0,1)) ),
                           Parallel(arrowFW,
                                    LerpColorInterval(self.arrow,.4,Vec4(0,0,0,1)) ) )
        self.arrowAnim.loop()

    def explo(self):
        for o in self.simObjects:
            if o.density and not o.realObj.hasTag('excluded'):
               l=o.body.getPosition ()
               d=Vec3(*l).length()
               if not d: d=1
               a = max(0, 2000*(1.0-0.2*d*d))
               l = [l[0] / 10, l[1]/10, l[2]/1.5]
               self.scalp (l, a / d)
               o.body.addForce(l)

    def pull(self):
        for o in self.simObjects:
            if o.density and not o.realObj.hasTag('excluded'):
               l=list (o.body.getPosition ())
               d=Vec3(*l).length()
               if not d: d=1
               self.scalp (l, -300 / d)
               o.body.addForce(l)

    def gameLoop(self, task):
        if self.keys['explode']:
           self.explo()
        if self.keys['pull']:
           self.pull()

        self.simulate()
        # check if each object is already collapsed (structurally failed),
        # degrade it's color if it does
        self.isCollapsed()

        return Task.cont

    def isCollapsed(self):
        if self.scene==1:
           for o in self.simObjects:
               if o.realObj.hasTag('excluded'):
                  z=o.realObj.getPythonTag('posZ')
                  if ( Vec3(*o.body.getLinearVel()).length() > z*.7 or
                       Vec3(*o.body.getAngularVel()).length() > z*.7 ):
                       o.realObj.setColorScale(.2,.2,.2,1)
                       o.realObj.clearTag('excluded')
        elif self.scene==2:
           numExclusion=0
           for o in self.simObjects:
               if o.realObj.hasTag('excluded'):
                  numExclusion+=1
                  if abs(o.realObj.getP()) > 60 :
                     o.realObj.setColorScale(.2,.2,.2,1)
                     o.realObj.clearTag('excluded')
           if numExclusion==2 and self.arrowAnim:
              self.arrowAnim.pause()
              self.arrowAnim=None
              self.arrow.removeNode()

    def toggleVis(self):
        self.trimeshVis=not self.trimeshVis
        self.trimeshVisLabel.setText(self.trimeshVisText+'[%s]' %self.onOff[self.trimeshVis])
        for o in self.simObjects:
            if hasattr(o,'visualizer'):
               if self.trimeshVis:
                  o.visualizer.show()
               else:
                  o.visualizer.hide()

    def toggleContactsVis(self):
        self.contactVis=not self.contactVis
        self.contactVisLabel.setText(self.contactVisText+'[%s]' %self.onOff[self.contactVis])


    def sceneSetup(self,numBodies=60):
        if self.ode_SPACE.getNumGeoms()>1:
           for o in self.simObjects:
               o.destroy()
           if self.cameraInterval:
              self.cameraInterval.pause()
              self.cameraInterval=None
           self.simObjects = []

        self.drawCage()
        self.drawBodies(numBodies)
        self.drawTriMesh()
        for o in self.simObjects:
            if not self.trimeshVis and hasattr(o,'visualizer'):
               o.visualizer.hide()

    def startScene0(self):
        self.scene=0
        self.sceneSetup(numBodies=60)
        base.mouseInterfaceNode.reset()
        self.cameraInterval=Parallel(
             LerpPosInterval(base.cam, 3, Point3(0,-20,8), startPos=Point3(0,0,90), blendType='easeOut'),
             LerpFunc(self.cameraLook, duration=3) )
        self.cameraInterval.start()

    def startScene1(self):
        self.scene=1
        self.sceneSetup(numBodies=40)
        self.buildBoxesTower()
        base.mouseInterfaceNode.reset()
        self.cameraInterval=Parallel(
             LerpPosInterval(base.cam, 3, Point3(0,-28,10), startPos=Point3(60,20,90), blendType='easeOut'),
             LerpFunc(self.cameraLook, duration=3, extraArgs=[Point3(0,10,4)]) )
        self.cameraInterval.start()

    def startScene2(self):
        self.scene=2
        self.sceneSetup(numBodies=40)
        self.buildChainReaction()
        base.mouseInterfaceNode.reset()
        self.cameraInterval=Parallel(
             LerpPosInterval(base.cam, 4, Point3(0,-40,30), startPos=Point3(60,20,90), blendType='easeOut'),
             LerpFunc(self.cameraLook, duration=4, extraArgs=[Point3(0,10,0)]) )
        self.cameraInterval.start()

World()
run()
Last edited by ynjh_jo on Wed Apr 11, 2007 6:28 am, edited 5 times in total.
http://ynjh.panda3dprojects.com | http://ynjh.p3dp.com
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby cqdemal » Wed Jan 31, 2007 12:47 am

I've been messing around with the code snippets in this thread and have found them very interesting. However, I am just a little n00b in Panda and ODE, so I can't really do much.

My question here is: Would it be possible to, say, apply force wherever I click? That'd need addForceAtPos, right? I haven't been successful with this at all. No matter what I do, the position of the mouse doesn't effect anything at all.
cqdemal
 
Posts: 1
Joined: Wed Jan 31, 2007 12:29 am

Postby ynjh_jo » Wed Feb 07, 2007 1:31 pm

Are you sure you added the forces on the correct bodies ? How do you keep the mesh-body link ? using list, or dictionary ? Or maybe the force is too small. You need bigger force to kick heavy mass.
http://ynjh.panda3dprojects.com | http://ynjh.p3dp.com
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Additional Script needed?....

Postby Liquid7800 » Fri Mar 16, 2007 8:48 am

Hello, excuse me if I am missing something in this matter, but when I try to run the topmost script above as is...(ie copy & paste) I get

global name Task not defined"). error...
however when I add:

Code: Select all
from direct.task import Task


Then it runs the simulation...

Anyway my question is if this is needed... then why is this snippet of code left out from all the PyODE examples posted....does this mean I have something wrong with PyODE or the author left it out? Thanks
User avatar
Liquid7800
 
Posts: 140
Joined: Mon Feb 19, 2007 1:22 am

Postby rdb » Fri Mar 16, 2007 10:12 am

Tasks are just functions that are executed each second.
You need to import the Task class to use these tasks.
So yes, I assume the author accidentally left it out ;)
rdb
 
Posts: 8575
Joined: Mon Dec 04, 2006 5:58 am
Location: Netherlands

Thanks for the reply

Postby Liquid7800 » Fri Mar 16, 2007 10:41 am

I appreciate the informed reply...I went and read the manual about tasks after I tried this code, so you really did clarify it for me even more...thanks!
User avatar
Liquid7800
 
Posts: 140
Joined: Mon Feb 19, 2007 1:22 am

Postby ynjh_jo » Sat Mar 17, 2007 12:12 pm

UPDATE:
it's OO now. :)

[EDIT 4/11/07]
50% faster, more sophisticated classes

Another example :
http://panda3d.org/phpbb2/viewtopic.php?t=2510
http://ynjh.panda3dprojects.com | http://ynjh.p3dp.com
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby Manux » Tue Aug 19, 2008 1:25 pm

Hum...

I get this crash error, when I try to run your codes:
Code: Select all
ODE INTERNAL ERROR 2: colliders array not initialized in dCollide()
Abort (core dumped)

Here's the full log of the second(OO) code:
Code: Select all
DirectStart: Starting the game.
Warning: DirectNotify: category 'Interval' already exists
Known pipe types:
  glxGraphicsPipe
(all display modules loaded.)
:audio(error):   LoadLibrary() failed, will use NullAudioManager
:audio(error):     No error.
:util(warning): Adjusting global clock's real time by 0.779431 seconds.
drawCage done...
drawBodies done...
###############################
render/objectHandles.egg
29 total nodes (including 0 instances); 0 LODNodes.
1 transforms; 0% of nodes have some render attribute.
3 Geoms, with 3 GeomVertexDatas and 1 GeomVertexFormats, appear on 3 GeomNodes.
75 vertices, 0 normals, 0 colors, 0 texture coordinates.
GeomVertexData arrays occupy 1K memory.
GeomPrimitive arrays occupy 1K memory.
144 triangles:
  0 of these are on 0 tristrips.
  144 of these are independent triangles.
0 textures, estimated minimum 0K texture memory required.
--- sent to ODE ---
Vertices :  75
Faces    :  144
###############################

###############################
render/Pointlight.egg
3 total nodes (including 0 instances); 0 LODNodes.
2 transforms; 0% of nodes have some render attribute.
1 Geoms, with 1 GeomVertexDatas and 1 GeomVertexFormats, appear on 1 GeomNodes.
134 vertices, 134 normals, 0 colors, 134 texture coordinates.
GeomVertexData arrays occupy 5K memory.
GeomPrimitive arrays occupy 1K memory.
96 triangles:
  96 of these are on 40 tristrips (2.4 average tris per strip).
  0 of these are independent triangles.
1 textures, estimated minimum 3K texture memory required.
--- sent to ODE ---
Vertices :  134
Faces    :  96
###############################

:util(warning): Adjusting global clock's real time by -0.59529 seconds.

ODE INTERNAL ERROR 2: colliders array not initialized in dCollide()
Abort (core dumped)


I wonder if this has to do with this code,which I highly doubt, or with my ODE compilation?
I remember compiling only with float precision...

Calling the ode.collide(geom1,geom2) seems to raise this error.
I've seen in google groups that, in C, calling dInitODE() seems to get this working.
I haven't found such a method in pyODE, maybe it calls it by default?
---

Manux
Manux
 
Posts: 45
Joined: Thu Jun 12, 2008 3:02 pm

Postby astelix » Thu Jan 15, 2009 6:35 am

I got issues too:

Code: Select all
python: /build/buildd/ode-0.9.dfsg/GIMPACT/src/gim_trimesh.cpp:183: void gim_trimesh_locks_work_data(GIM_TRIMESH*): Assertion `res==0' failed.
Aborted


it's a pity, 'cos this snippet sounds very 8)
My Rig:
P3D 1.7.0@WinXP & Kubuntu 10.04- Athlon 64 5200 X2 ~ Radeon 3200HD (integrated)
User avatar
astelix
 
Posts: 866
Joined: Mon Mar 27, 2006 4:36 pm
Location: Milano, ITA

Postby ynjh_jo » Fri Jan 16, 2009 12:38 pm

The error seems inside ODE or perhaps due to the way it's wrapped.
Have you tried ODE forum ?
http://ynjh.panda3dprojects.com | http://ynjh.p3dp.com
Intel P4Prescott 2.8GHz HT | ATI Radeon HD4670 1GB GDDR3
User avatar
ynjh_jo
 
Posts: 1795
Joined: Tue Apr 18, 2006 12:41 am
Location: Malang, Indonesia

Postby astelix » Fri Jan 16, 2009 6:49 pm

nope so far but could be possible I got ODE a little messed up since, even if looks working, the official panda sample don't runs perfectly cos the falling ragdoll puppet don't bounce over the box but penetrate into and some warnings were raised from console. Hope soon to dig into further.
My Rig:
P3D 1.7.0@WinXP & Kubuntu 10.04- Athlon 64 5200 X2 ~ Radeon 3200HD (integrated)
User avatar
astelix
 
Posts: 866
Joined: Mon Mar 27, 2006 4:36 pm
Location: Milano, ITA


Return to Code Snippets

Who is online

Users browsing this forum: No registered users and 0 guests