Adding model to ODE physics engine

Hey



I have lsuccesfully loaded a model and is wondering how I add it to the 3rd app ODE physics engine? I can only seem to find examples where there is used simple geometry. So I probably have to retrive the boundingbox from my model, but how?





My loading code



//load a model
       
       // Point to a URL of my model
       URL model=HelloModelLoading.class.getClassLoader().getResource("data/model/ship/shuttle.jme");
       
       URL textureloader=HelloModelLoading.class.getClassLoader().getResource("data/texture/ship/shuttle-1.png");
       Texture modelT = TextureManager.loadTexture(textureloader,Texture.MM_LINEAR,Texture.FM_LINEAR,true);
       
       // This byte array will hold my .jme file
       ByteArrayOutputStream BO=new ByteArrayOutputStream();
       // This will read the .jme format and convert it into a scene graph
       JmeBinaryReader jbr=new JmeBinaryReader();
       
       try {
           // Tell the binary reader to use bounding boxes instead of bounding spheres
           jbr.setProperty("bound","box");
           
           // Load the binary .jme format into a scene graph
           Node maggie=jbr.loadBinaryFormat(model.openStream());
           
           TextureState bgts = display.getRenderer().createTextureState();
           bgts.setTexture(modelT);
           bgts.setEnabled(true);
          // ms.
           maggie.setRenderState(bgts);
           // shrink this baby down some
           maggie.setLocalScale(10f);
           
         
           // Put her on the scene graph
           rootNode.attachChild(maggie);
           
           //PhysicsObject shipObj = new PhysicsObject(maggie.getWorldBound());
           
       }catch (IOException e) {   // Just in case anything happens
           System.out.println("Damn exceptions!" + e);
           e.printStackTrace();
           System.exit(0);
       }

[/code]

maggie.getChild(0).updateGeometricState(0, true);
Geometry bounds = (Geometry) node.getChild(0).getWorldBound();



... should do the trick.

If needed, you could also cast it into a Box or a BoundingBox, since you loaded it with the box property.

Thanks per. tried your suggestion couldent make it work. I am sure I am doing something wrong



I did this



maggie.getChild(0).updateGeometricState(0, true);

Geometry bounds = (Geometry) node.getChild(0).getWorldBound();



this



maggie.getChild(0).updateGeometricState(0, true);

Boxbounds = (Box) node.getChild(0).getWorldBound();





maggie.updateGeometricState(0, true);

Geometry bounds = (Geometry) node.getWorldBound();





Nothing mattered. The maggie node does not move as I would exspect with default gravity (drop like a rock)



 try {
           // Tell the binary reader to use bounding boxes instead of bounding spheres
           jbr.setProperty("bound","box");
     
           
           // Load the binary .jme format into a scene graph
           Node maggie=jbr.loadBinaryFormat(model.openStream());
           
           maggie.updateGeometricState(0, true);
           Box bounds = (Box) maggie.getWorldBound();
           
         
           TextureState bgts = display.getRenderer().createTextureState();
           bgts.setTexture(modelT);
           bgts.setEnabled(true);
           
           maggie.setRenderState(bgts);
           // shrink this baby down some
           maggie.setLocalScale(10f);
           
         
           // Put her on the scene graph
           rootNode.attachChild(maggie);
           
           PhysicsObject shipObj = new PhysicsObject(bounds,1);
           PhysicsWorld.getInstance().addObject(shipObj);
           
       }catch (IOException e) {   // Just in case anything happens
           System.out.println("Damn exceptions!" + e);
           e.printStackTrace();
           System.exit(0);
       }

Oh, right, you must make bounds "share" coordinates with maggie, like this:


bounds.setLocalTranslation(maggie.getLocalTranslation());
bounds.setLocalRotation(maggie.getLocalRotation());



This gives bounds a reference to maggies location and rotation, which results in that you don't have to do this every frame or anything.

Hope that helps.

yeah it did make sense but it did not make a difference…





alle of my code



package test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.URL;

import jmetest.TutorialGuide.HelloModelLoading;

import com.jme.app.SimpleGame;
import com.jme.image.Texture;
import com.jme.math.Vector3f;
import com.jme.scene.Geometry;
import com.jme.scene.Node;
import com.jme.scene.Skybox;
import com.jme.scene.model.XMLparser.JmeBinaryReader;
import com.jme.scene.shape.Box;
import com.jme.scene.state.TextureState;
import com.jme.util.TextureManager;
import com.jmex.physics.PhysicsObject;
import com.jmex.physics.PhysicsWorld;

/**
 * @author oliver
 *Feb 10, 2005
 */
public class SceneBox extends SimpleGame{
   
    Skybox skybox;

    public static void main(String[] args) {
        SceneBox app = new SceneBox();
        app.setDialogBehaviour(SceneBox.ALWAYS_SHOW_PROPS_DIALOG);
        app.start();
    }
   
   
    protected void simpleInitGame() {
       
        PhysicsWorld.create();
       
        // Here we tell the PhysicsWorld how many times per second we would like to
        // update it. It'll make the PhysicsWorlds internal timer govern the frequency
        // of update calls, thus obtaining frame rate independance. We set it to
        // 100 updates per second - the default is no restriction.
        PhysicsWorld.getInstance().setUpdateRate(100);
       
        // Here we tell the PhysicsWorld how much should change with each update.
        // A bigger value = faster animation. A step size of 2/UPS (updates/sec)
        // seem to give a rather nice simulation/result.
        PhysicsWorld.getInstance().setStepSize(2/100f);
       
       
        //Load skybox
        skybox = new Skybox("universe",300,300,300);
     
        URL sbloader = SceneBox.class.getClassLoader().getResource("data/galaxy/galax0.png");
        Texture westT = TextureManager.loadTexture(sbloader,Texture.MM_LINEAR,Texture.FM_LINEAR,true);
       
        sbloader = SceneBox.class.getClassLoader().getResource("data/galaxy/galax1.png");
        Texture northT = TextureManager.loadTexture(sbloader,Texture.MM_LINEAR,Texture.FM_LINEAR,true);
       
        sbloader = SceneBox.class.getClassLoader().getResource("data/galaxy/galax2.png");
        Texture eastT = TextureManager.loadTexture(sbloader,Texture.MM_LINEAR,Texture.FM_LINEAR,true);
       
        sbloader = SceneBox.class.getClassLoader().getResource("data/galaxy/galax3.png");
        Texture southT = TextureManager.loadTexture(sbloader,Texture.MM_LINEAR,Texture.FM_LINEAR,true);
       
        sbloader = SceneBox.class.getClassLoader().getResource("data/galaxy/galax4.png");
        Texture downT = TextureManager.loadTexture(sbloader,Texture.MM_LINEAR,Texture.FM_LINEAR,true);
       
        sbloader = SceneBox.class.getClassLoader().getResource("data/galaxy/galax5.png");
        Texture upT = TextureManager.loadTexture(sbloader,Texture.MM_LINEAR,Texture.FM_LINEAR,true);
       
       skybox.setTexture(Skybox.DOWN,downT);
       skybox.setTexture(Skybox.UP,upT);
       
       skybox.setTexture(Skybox.NORTH,northT);
       skybox.setTexture(Skybox.EAST,eastT);
       skybox.setTexture(Skybox.WEST,westT);
       skybox.setTexture(Skybox.SOUTH,southT);
       
       rootNode.attachChild(skybox);
       rootNode.setForceView(true);
       
       
       //load a model
       
       // Point to a URL of my model
       URL model=HelloModelLoading.class.getClassLoader().getResource("data/model/ship/shuttle.jme");
       
       URL textureloader=HelloModelLoading.class.getClassLoader().getResource("data/texture/ship/shuttle-1.png");
       Texture modelT = TextureManager.loadTexture(textureloader,Texture.MM_LINEAR,Texture.FM_LINEAR,true);
       
       // This byte array will hold my .jme file
       ByteArrayOutputStream BO=new ByteArrayOutputStream();
       // This will read the .jme format and convert it into a scene graph
       JmeBinaryReader jbr=new JmeBinaryReader();
       
       try {
           // Tell the binary reader to use bounding boxes instead of bounding spheres
           jbr.setProperty("bound","box");
     
           
           // Load the binary .jme format into a scene graph
           Node maggie=jbr.loadBinaryFormat(model.openStream());
           
           
           maggie.getChild(0).updateGeometricState(0, true);
         
           TextureState bgts = display.getRenderer().createTextureState();
           bgts.setTexture(modelT);
           bgts.setEnabled(true);
           maggie.setRenderState(bgts);
 
           maggie.setLocalScale(10f);
           
           Box bounds = (Box) maggie.getChild(0).getWorldBound();
           bounds.setLocalTranslation(maggie.getLocalTranslation());
           bounds.setLocalRotation(maggie.getLocalRotation());
           
         
           // Put her on the scene graph
           rootNode.attachChild(maggie);
           
           PhysicsObject shipObj = new PhysicsObject(bounds,10);
           PhysicsWorld.getInstance().addObject(shipObj);
           
       }catch (IOException e) {   // Just in case anything happens
           System.out.println("Damn exceptions!" + e);
           e.printStackTrace();
           System.exit(0);
       }
       
    // Creates the box that makes out the floor.
        Box floor = new Box("Floor", new Vector3f(0,-40,0), 50, 1, 50);
       
        // In order to add it to the PhysicsWorld we need to create a PhysicsObject
        // from it. Note that we don't pass a mass in the constructor. This makes it
        // static.
        PhysicsObject floorObj = new PhysicsObject(floor);
       
       PhysicsWorld.getInstance().addObject(floorObj);
       
        rootNode.attachChild(floor);
     
       
       
    }
   
    /**
     * Gets called on application ending.
     */
    protected void cleanup() {
        super.cleanup();
        // Before ending your application you should always clean up the PhysicsWorld.
        PhysicsWorld.getInstance().cleanup();
    }
   
   
    /**
     * Gets called every frame.
     */
    protected void simpleUpdate() {
        // We must call this method in order to make the simulation step forward. It doesn't
        // matter how many times per second we make the call, as long as we have a
        // FPS >= the updaterate we've given the PhysicsWorld. This is because of the
        // PhysicsWorlds internal timer, that govern the frequency of actual updates.
        PhysicsWorld.getInstance().update();
       
   
    }
   
 
}

Ok, let’s troubleshoot :slight_smile:



If you add this line: rootNode.attachChild(bounds);



Do you see a grey box falling down from maggie?

rootNode.attachChild(bounds);

// Put her on the scene graph

rootNode.attachChild(maggie);





This works now after I removed floor… both drop

if I do this it works… eg maggie falls and so does the box



maggie.attachChild(bounds);

Umhm… this is sort of weird, but anyhow, everything works for you now?



BTW: you can make the box invisible by calling setForceCall(true) (after attachment)… but I don’t quiet understand why it needs to be attached anyway…

It sorta works. It also drops through an object I made as it werent there…

Ok, a couple of things first. There is absolutely no need to obtain the "worldBounds". THe world bounds refer to nodes, and not actual model bounds. And world bounds change size and shape and you dont want that.





So your code should look like this:



maggie.setLocalScale(10f);             

// Put her on the scene graph
rootNode.attachChild(maggie);
           
PhysicsObject shipObj = new PhysicsObject(maggie,10);
PhysicsWorld.getInstance().addObject(shipObj);



That works fine over here. Ive even made a test case for you:


public class TestMaggie extends SimpleGame {
   
   protected void simpleUpdate() {
      PhysicsWorld.getInstance().update();
   }

   protected void simpleInitGame() {
      
      PhysicsWorld.create();
      PhysicsWorld.getInstance().setUpdateRate(100);
      PhysicsWorld.getInstance().setStepSize(3/100f);
      PhysicsWorld.getInstance().setGravity(new Vector3f(0, -9.81f, 0));
      
      ObjToJme converter = new ObjToJme();
      ByteArrayOutputStream BO = new ByteArrayOutputStream();
      // This will read the .jme format and convert it into a scene graph
      JmeBinaryReader jbr = new JmeBinaryReader();
      // url to model
      URL maggieURL = TestMaggie.class.getClassLoader().getResource("jmextest/data/maggie.obj");
      
      try {
         converter.convert(maggieURL.openStream(), BO);
         
         Node node = jbr.loadBinaryFormat(new ByteArrayInputStream(BO.toByteArray()));
         node.setLocalScale(0.1f);
         
         Geometry geo = (Geometry)node.getChild(0);
         PhysicsObject obj = new PhysicsObject(geo, 100, false);
         PhysicsWorld.getInstance().addObject(obj);
         
         rootNode.attachChild(node);
      } catch (IOException ioe) {
      }

   }

   public static void main(String[] args) {
      TestMaggie app = new TestMaggie();
      app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
      app.start();
   }
}



Just replace the "jmextest/data/maggie.obj" with whatever you want it to (probably your ship model) and you should be ok!

Hope this helps middy.

DP

it did help… using an obj model rather than my converted one (jme format)



I’ll experiement some more



thanks :))

it doesn’t matter whether its an obj format or a jme format. They both end up being made up of Nodes and Geometry. Oh and my code just converts an obj to jme and reads in the jme file! :slight_smile:



The PhysicsObject requires a geometry to be present as it can’t do physics on abstract objects (by abstract, i mean logically and not abstract java wise).



Also, i have just added a feature to auto determine whether the PhysicsWorld should use the bounding volume or use the actual geometry. I think this addition will get rid of this confusion of bounding volume or not. This (and some bug fixes) will be released along with the next release of the physicsSystem.



DP

Is the world bound really the wrong way to go here (tell me it is, cuz it’s a rather nasty solution in comparison :))? For the model I use in my game, I get a ClassCastException when trying to cast getChild(0) to Geometry. This is because it’s a Node which contains a couple of TriMeshes, and getWorldBound returns a BoundingVolume perfectly surrounding the whole thing. This was what led me to the world bound solution.



Maybe this works with the maggie model since it’s just one TriMesh (?).

Well after hours of testing I gave up on that part. I can make my model drop like it should but it wont react with a floor I made.



I also tried with your TestMaggie, it simply drops through.





/*
 * Created on Feb 11, 2005
 * This file is created for the Extorris game
 * and is copyrighted
 */
package test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;

import com.jme.app.SimpleGame;
import com.jme.math.Vector3f;
import com.jme.scene.Geometry;
import com.jme.scene.Node;
import com.jme.scene.model.XMLparser.JmeBinaryReader;
import com.jme.scene.model.XMLparser.Converters.ObjToJme;
import com.jme.scene.shape.Box;
import com.jmex.physics.PhysicsObject;
import com.jmex.physics.PhysicsWorld;

/**
 * @author oliver
 *Feb 11, 2005
 */
public class TestMaggie extends SimpleGame {
   
    protected void simpleUpdate() {
       PhysicsWorld.getInstance().update();
    }

    protected void simpleInitGame() {
       
       PhysicsWorld.create();
       PhysicsWorld.getInstance().setUpdateRate(100);
       PhysicsWorld.getInstance().setStepSize(3/100f);
       PhysicsWorld.getInstance().setGravity(new Vector3f(0, -0.81f, 0));
       
       ObjToJme converter = new ObjToJme();
       ByteArrayOutputStream BO = new ByteArrayOutputStream();
       // This will read the .jme format and convert it into a scene graph
       JmeBinaryReader jbr = new JmeBinaryReader();
       // url to model
       URL maggieURL = TestMaggie.class.getClassLoader().getResource("data/model/ship/shuttle.obj");
       
       try {
          converter.convert(maggieURL.openStream(), BO);
         
          Node node = jbr.loadBinaryFormat(new ByteArrayInputStream(BO.toByteArray()));
          node.setLocalScale(10f);
         
          Geometry geo = (Geometry)node.getChild(0);
          PhysicsObject obj = new PhysicsObject(geo, 100, false);
          PhysicsWorld.getInstance().addObject(obj);
         
          rootNode.attachChild(node);
       } catch (IOException ioe) {
       }
       
//     Creates the box that makes out the floor.
       Box floor = new Box("Floor", new Vector3f(), 50, 1, 50);   
       PhysicsObject floorObj = new PhysicsObject(floor);
       // We move it down 5 units, and away from the camera 10 units.
       floor.setLocalTranslation(new Vector3f(0, -100, 10));
       rootNode.attachChild(floor);
       

    }

    public static void main(String[] args) {
       TestMaggie app = new TestMaggie();
       app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
       app.start();
    }
 }

yes, i can see your problem.



Whats happening is that your creating the ode object. After that your changing its position. So ode doesn’t really know where the floor is.



       Box floor = new Box("Floor", new Vector3f(), 50, 1, 50);
       // We move it down 5 units, and away from the camera 10 units.
       floor.setLocalTranslation(new Vector3f(0, -100, 10));     
       PhysicsObject floorObj = new PhysicsObject(floor);
       rootNode.attachChild(floor);



You are also not adding the object to the PhysicsWorld. So after changing the above order, also do this:


PhysicsWorld.getInstance().addObject(floorObj);



And that should be it.

DP

You are right on the errors. It just didnt solve it, it still goes right through. perhaps you would like to test it with my object. Dont know if it makes a difference?








package test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;

import com.jme.app.SimpleGame;
import com.jme.math.Vector3f;
import com.jme.scene.Geometry;
import com.jme.scene.Node;
import com.jme.scene.model.XMLparser.JmeBinaryReader;
import com.jme.scene.model.XMLparser.Converters.ObjToJme;
import com.jme.scene.shape.Box;
import com.jmex.physics.PhysicsObject;
import com.jmex.physics.PhysicsWorld;

/**
 * @author oliver
 *Feb 11, 2005
 */
public class TestMaggie extends SimpleGame {
   
    protected void simpleUpdate() {
       PhysicsWorld.getInstance().update();
    }

    protected void simpleInitGame() {
       
       PhysicsWorld.create();
       PhysicsWorld.getInstance().setUpdateRate(100);
       PhysicsWorld.getInstance().setStepSize(3/100f);
       PhysicsWorld.getInstance().setGravity(new Vector3f(0, -0.81f, 0));
       
       ObjToJme converter = new ObjToJme();
       ByteArrayOutputStream BO = new ByteArrayOutputStream();
       // This will read the .jme format and convert it into a scene graph
       JmeBinaryReader jbr = new JmeBinaryReader();
       // url to model
       URL maggieURL = TestMaggie.class.getClassLoader().getResource("data/model/ship/shuttle.obj");
       
       try {
          converter.convert(maggieURL.openStream(), BO);
         
          Node node = jbr.loadBinaryFormat(new ByteArrayInputStream(BO.toByteArray()));
          node.setLocalScale(10f);
         
          Geometry geo = (Geometry)node.getChild(0);
          PhysicsObject obj = new PhysicsObject(geo, 100, false);
          PhysicsWorld.getInstance().addObject(obj);
         
          rootNode.attachChild(node);
     
       
//     Creates the box that makes out the floor.
       Box floor = new Box("Floor", new Vector3f(), 50, 1, 50);   
       
       // We move it down 5 units, and away from the camera 10 units.
       floor.setLocalTranslation(new Vector3f(0, -100, 10));
       PhysicsObject floorObj = new PhysicsObject(floor);
       PhysicsWorld.getInstance().addObject(floorObj);
       rootNode.attachChild(floor);
     
       
       } catch (IOException ioe) {
       }
    }

    public static void main(String[] args) {
       TestMaggie app = new TestMaggie();
       app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
       app.start();
    }
 }

Ok. Ive got the problem. The problem is on our side of things, but it can be solved using your code.



The problem is that once you have allocated a local scale to a "Node" it will render its children with that scale. That doesnt mean that their children are scaled 10 times bigger because their children are in local space. So really, the geo has a local scale of 1, not 10. For your model not go to through the floor, you need to change the ordering of things.



So the code should look like:



Node node = jbr.loadBinaryFormat(new ByteArrayInputStream(BO.toByteArray()));
         
          Geometry geo = (Geometry)node.getChild(0);
          geo.setLocalScale(10f);
          PhysicsObject obj = new PhysicsObject(geo, 100, false);
          PhysicsWorld.getInstance().addObject(obj);
         
          rootNode.attachChild(node);



And that should be it. I am finding that this scale stuff is becoming an issue and hopefully it will be solved in the next release.

DP

That’s good to know … I’m sure that would have affected me as well later on.



Thanks again DP!

ODE doesn’t really like our scene graph structure. ODE is linear, everything in an array and your sorted. Our scene graph structure is alot more complicated whereby each node having its own coordination/rotation/scaling space. So we as physics developers for jme we need to provide a way to merge the two. And as you can see, sometimes the seams begin to show :slight_smile: