[Solved] need advice for changing MD5Joints by Hand

I have played a bit with Ragdoll, now I want to parse the RagDoll on a MD5Mesh.



Parsing out of the Joints a Ragdoll works quite fine. But now I dont know how to Calculate correctly the changes back to ne md5. I think the original Translation should stay, and I have only to manipulate the Orientation. Does'nt work so good, look here

http://www.youtube.com/watch?v=TE4SkSTTg3U



to the Code: there ist the "body" which ich the DynamicNode of the actual Bone, an there ist "parentBone.body" which belongs to the parentJoint  :wink:



if(parentBone!=null){
   Quaternion quat = new Quaternion();
   parentBone.body.getWorldRotation().inverse().mult(body.getWorldRotation(),quat);

   
   if(orgRotation==null){
      orgRotation=new Quaternion(md5Joint.getOrientation());
   }
   Quaternion neu = new emp.math.Quaternion();
   orgRotation.mult(orientation, neu);
   neu.normalize();
         
   md5Joint.updateTransform(md5Joint.getTranslation(), neu);
}



have any Ideas?

I came a bit further, have set the Oriention to defaultValue, and set the Translation from PhysicJoints.



Now the Positions are goot, but the errors (see in the Video) come from the wrong Rotation. when I change the Rotation I would have to recalculate the translations. And dont know how.



Link:

juhu, I have it. It was no MD5 Problem, I just had written stupid Code.  :wink:



PhysicBone.java


package emp.physics.ragdoll.test;

import com.jme.animation.Bone;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.TriMesh;
import com.jme.scene.shape.Sphere;
import com.jmex.physics.DynamicPhysicsNode;
import com.jmex.physics.Joint;
import com.jmex.physics.PhysicsSpace;
import com.jmex.physics.RotationalJointAxis;
import com.jmex.physics.material.Material;

public class PhysicBone extends Node {

   private DynamicPhysicsNode   parentBody;
   private DynamicPhysicsNode   body;
   private Bone   realBone;
   private Vector3f   meshScale;
   private Spatial   mesh;
   
   public PhysicBone(Bone md, Spatial mesh, PhysicsSpace space) {
      this(md, mesh.getParent(),null, mesh.getLocalScale(), space);
      this.mesh = mesh;
      setCullHint(CullHint.Always);
   }
   private PhysicBone(Bone md, Node parent, PhysicBone myParent, Vector3f meshScale, PhysicsSpace space) {
      super(md.getName());
      parent.attachChild(this);
      
      this.meshScale = meshScale;
      realBone = md;
      
      Vector3f myPos = md.getWorldTranslation().clone();
      Vector3f parentPos = md.getParent().getWorldTranslation();
      
      Quaternion myRot = md.getWorldRotation().clone();
   
      float height = myPos.distance(parentPos)/2f;
      String real = realBone.getName();
      if(height>0&&!real.contains("Footsteps")&&!real.contains("Pelvis")){
         TriMesh capsule = new Sphere(name, 3, 3, height>.75f?.75f:height);
         body = space.createDynamicNode();
      
         if(myParent==null){
            body.setMaterial(Material.GHOST);
         }else{
            body.attachChild(capsule);
            body.setMaterial(Material.WOOD);
            body.generatePhysicsGeometry();
            body.computeMass();
         }         
         
         
         body.setLocalTranslation(myPos);
         body.setLocalRotation(myRot);
         attachChild(body);
         
         if(myParent!=null&&myParent.body!=null){
            Vector3f ncor = new Vector3f(0,0,0);
            if(Material.GHOST.equals(myParent.body.getMaterial())){
               join( myParent.body,body, ncor,getLookAtDirection(myRot), 0,0);
            }else{
               join( myParent.body,body, ncor,getLookAtDirection(myRot), -.1f, .1f);
            }
            parentBody = myParent.body;
         }      
      }else{
         body=myParent.body;
         parentBody=myParent.parentBody;
      }
      
      if(md.getName().contains("Hand")){
         return;
      }
      for (int i = 0; i < md.getQuantity(); i++) {
         Spatial child = md.getChild(i);
         if(child instanceof Bone){
            new PhysicBone((Bone) child,this, this,meshScale, space);
         }
      }
   }
   
   @Override
   public void updateGeometricState(float time, boolean initiator) {
      if(parentBody!=null){
         Vector3f store = new Vector3f();
         parentBody.worldToLocal(body.getLocalTranslation(), store);
         store.divideLocal(meshScale);
         realBone.setLocalTranslation(store);
         
         Quaternion res = new Quaternion();
         parentBody.getLocalRotation().inverse().mult(body.getLocalRotation(), res);
         realBone.setLocalRotation(res);
      }else{
         realBone.setLocalTranslation(new Vector3f());
         realBone.setLocalRotation(new Quaternion());
      }
      if(mesh!=null){
         mesh.setLocalTranslation(new Vector3f(body.getLocalTranslation()));
         mesh.setLocalRotation(new Quaternion(body.getLocalRotation()));
      }
      super.updateGeometricState(time, initiator);
   }
   
   public static Joint join(DynamicPhysicsNode node1, DynamicPhysicsNode node2, com.jme.math.Vector3f anchor,
         com.jme.math.Vector3f direction, float min, float max) {
      
      Joint jmeJoint = node1.getSpace().createJoint();      
      RotationalJointAxis jmeAxis = jmeJoint.createRotationalAxis();
      
      jmeAxis.setPositionMinimum(min);
      jmeAxis.setPositionMaximum(max);

      jmeJoint.attach(node1, node2);
      jmeJoint.setAnchor(anchor);
      jmeAxis.setDirection(direction);
      return jmeJoint;
   }
    public static Vector3f getLookAtDirection(Quaternion quaternion) {
         Vector3f[] neu =new Vector3f[3];
         quaternion.toAxes(neu);
         return new Vector3f(neu[1].crossLocal(neu[0]).normalizeLocal().negateLocal());
      }
}



TestCase:



package emp.physics.ragdoll.test;

import java.io.File;
import java.net.URISyntaxException;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme.app.BaseGame;
import com.jme.bounding.BoundingBox;
import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.shape.Box;
import com.jme.util.resource.ResourceLocatorTool;
import com.jme.util.resource.SimpleResourceLocator;
import com.jmex.physics.StaticPhysicsNode;
import com.jmex.physics.material.Material;
import com.jmex.physics.util.SimplePhysicsGame;
import com.model.md5.ModelNode;
import com.model.md5.importer.MD5Importer;

import emp.physics.ragdoll.MD5Bone;

public class RagDollParseTest extends SimplePhysicsGame {
   
   @Override
   protected void simpleInitGame() {
      pause = true;
      initGround();
      parseRhino(rootNode);            
      
      rootNode.updateGeometricState(0, true);
      rootNode.updateRenderState();
   }
   
   public PhysicBone parseRhino(Node rootNode) {
      PhysicBone character;
      
       try {
         ResourceLocatorTool.addResourceLocator(
                  ResourceLocatorTool.TYPE_TEXTURE,
                  new SimpleResourceLocator(ClassLoader.getSystemClassLoader().getResource("emp/resources/level_test/gegner/")));
      }
      catch (URISyntaxException e) {
         e.printStackTrace();
      }

      ModelNode rhino = loadOnlyMd5Model("emp/resources/level_test/gegner/rhino_rig");

      rhino.getControllers().clear();
      MD5Bone md = new MD5Bone(rhino);
               
      rhino.attachChild(md);
      rhino.setLocalScale(.02f);
      
      rhino.updateGeometricState(0, true);
      rootNode.attachChild(rhino);
      character = new PhysicBone(md, rhino, getPhysicsSpace());
      return character;
   }
   public static ModelNode loadOnlyMd5Model(String path) {
      MD5Importer im = MD5Importer.getInstance();
      ModelNode node;
      File   but = new File("src/"+path+".md5mesh");
      
      try {
         im.loadMesh(but.toURI().toURL(), "m5s");
      }
      catch (Exception e1) {
         e1.printStackTrace();
      }
      node = im.getModelNode();
      im.cleanup();
      return node;
   }
   
   private void initGround(){
      StaticPhysicsNode staticNode = getPhysicsSpace().createStaticNode();
      Spatial floorBox = new Box("Terrain", new Vector3f(0, 0, 0), 100, 1f, 100);
      floorBox.setModelBound(new BoundingBox());
      floorBox.updateModelBound();
      staticNode.attachChild(floorBox);      

      staticNode.getLocalTranslation().set(0, -14, 0);
      staticNode.updateGeometricState(0, false);
      staticNode.generatePhysicsGeometry();
      staticNode.setMaterial(Material.GRANITE);
      rootNode.attachChild(staticNode);
   }
   
   public static void main(String[] args) {
      Logger.getLogger("").setLevel(Level.WARNING);
      BaseGame game = new RagDollParseTest();
      game.setConfigShowMode(ConfigShowMode.AlwaysShow);
      game.start();
   }
}




MD5Bone: It an adapter for md5 to standard JME Bone



package emp.physics.ragdoll;

import com.jme.animation.Bone;
import com.jme.math.Matrix3f;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.model.md5.ModelNode;
import com.model.md5.resource.mesh.Joint;

public class MD5Bone extends Bone {
   private Joint   org;
   
   private MD5Bone(Joint originalBone){
      super(originalBone.getName());
      org=originalBone;
   }

   public MD5Bone(ModelNode node) {
      Joint[] js = node.getJoints();
      int l = js.length;
      MD5Bone[] our = new MD5Bone[l];
      for (int i = 0; i < l; i++) {
         int parent = js[i].getParent();
         if(parent!=-1)
            our[i] = new MD5Bone(js[i]);
         else{
            skinRoot=true;
            org=js[i];
            setName(org.getName()+"-node");
            our[i] = this;
         }
      }
      for (int i = 0; i < our.length; i++) {
         int parent = js[i].getParent();
         if(parent!=-1)
            our[parent].attachChild(our[i]);
      }
   }

   @Override
   public void updateGeometricState(float time, boolean initiator) {
      super.setLocalTranslation(org.getTranslation());
      super.setLocalRotation(org.getOrientation());   
      super.updateGeometricState(time, initiator);
   }
   

   @Override
   public void setLocalRotation(Matrix3f rotation) {
      Quaternion rot = new Quaternion();
      rot.fromRotationMatrix(rotation);
      setLocalRotation(rot);
   }

   @Override
   public void setLocalRotation(Quaternion rotation) {
      org.updateTransform(localTranslation, rotation);
   }

   @Override
   public void setLocalTranslation(float x, float y, float z) {
      setLocalTranslation(new Vector3f(x,y,z));
   }

   @Override
   public void setLocalTranslation(Vector3f localTranslation) {
      org.updateTransform(localTranslation, localRotation);
   }
}



ahh, and a video:

http://www.youtube.com/watch?v=CgTf-HdsVpE

hope It works fine

Hey thats really cool!

Virtual Rhinos have feelings too you know.

jeah, I'll take cover when I see one  :slight_smile:

Can you share the rhino rag doll?

It looks so cute XD

Could you please make the sourcecode for your rhino rag doll public? I'm curious how certain problems are solved, this would be a great help for me! 

The only thing you need is a jointed md5 model with bones/armatures (you can make it with Blender), no?

Well, I'm kinda a newbie in JME, I have a MD5 model, however I don't know how to set up a scene like this one with the rhino. I haven't found any simple example yet… Do you know about anything simple, where a MD5 model is used? I'm afraid complex games like RADAKAN or JavaCRPG are beyond my comprehension  :expressionless:

there is no simple example, as .:emp…HellG:. has developed this on his own.

The basic approach is to have a MD5 model, and a joint structure representing the original MD5 bones in physic-space. Then u are able to use the bone positions/rotations from the physics, instead of using the precalculated animations stored in the MD5 model.



But HellG might tell you more about this.

I'm Sorry,



I tried to use the PhysicBone with the marine from the md5Importer. But there is the head extra, and I dont think its a StandardRig, there the ParentBone ist the Pelvis.



I cant upload the model for everyone, my designer goes insane,

And I have only this one :mrgreen:



Here is the newer Version of the Source. Now I have only TriangleMeshes in ODE, with a nice TriangleDebugView (DynamicMesh). But Ode is very instabil…



You have to make some methods of the md5Importer public,

ahh jes and this version just works with md5Meshes!



PhysicBone:


package emp.physics.ragdoll.test;

import java.util.ArrayList;
import java.util.List;

import com.jme.animation.Bone;
import com.jme.math.Quaternion;
import com.jme.math.Triangle;
import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jmex.physics.DynamicPhysicsNode;
import com.jmex.physics.Joint;
import com.jmex.physics.PhysicsSpace;
import com.jmex.physics.RotationalJointAxis;
import com.jmex.physics.material.Material;
import com.model.md5.ModelNode;
import com.model.md5.resource.mesh.Mesh;
import com.model.md5.resource.mesh.primitive.Weight;

import emp.physics.ragdoll.DynamicMesh;

public class PhysicBone extends Node {

   private DynamicPhysicsNode   parentBody;
   private DynamicPhysicsNode   body;
   private Bone   realBone;
   private Vector3f   meshScale;
   private Spatial   mesh;
   
   public PhysicBone(Bone md, Spatial mesh, PhysicsSpace space) {
      this(md, mesh.getParent(),null, mesh.getLocalScale(), space, (ModelNode) mesh);
      this.mesh = mesh;
      setCullHint(CullHint.Always);
   }
   private PhysicBone(Bone md, Node parent, PhysicBone myParent, Vector3f meshScale, PhysicsSpace space, ModelNode source) {
      super(md.getName());
      parent.attachChild(this);
      
      this.meshScale = meshScale;
      realBone = md;
      
      Vector3f myPos = md.getWorldTranslation().clone();
      Vector3f parentPos = md.getParent().getWorldTranslation();
      
      Quaternion myRot = md.getWorldRotation().clone();
   
      float height = myPos.distance(parentPos)/2f;
      String real = realBone.getName();
      if(height>0&&!real.contains("Footsteps")&&!real.contains("Pelvis")){
         body = space.createDynamicNode();
      
         if(myParent==null){
            body.setMaterial(Material.GHOST);
         }else{
//            body.createSphere("s").setLocalScale(.05f);
            body.setMaterial(Material.WOOD);
            attachSkin(source);      
         }         
         
         
         body.setLocalTranslation(myPos);
         body.setLocalRotation(myRot);
         attachChild(body);
         
         if(myParent!=null&&myParent.body!=null){
            Vector3f ncor = new Vector3f(0,0,0);
            if(Material.GHOST.equals(myParent.body.getMaterial())){
               join( myParent.body,body, ncor,getLookAtDirection(myRot), 0,0);
            }else{
               join( myParent.body,body, ncor,getLookAtDirection(myRot), -.1f, .1f);
            }
            parentBody = myParent.body;
         }      
      }else{
         body=myParent.body;
         parentBody=myParent.parentBody;
      }
      
      if(md.getName().contains("Hand")){
         return;
      }
      for (int i = 0; i < md.getQuantity(); i++) {
         Spatial child = md.getChild(i);
         if(child instanceof Bone){
            new PhysicBone((Bone) child,this, this,meshScale, space, source);
         }
      }
   }
   private void attachSkin(ModelNode modelnode) {
      Mesh m = modelnode.getMesh(0);
      int index = -1;
      com.model.md5.resource.mesh.Joint[] allJ = modelnode.getJoints();
      for (int i = 0; i < allJ.length; i++) {
         if(allJ[i].getName().equals(name)){
            index = i;
            break;
         }
      }
      List<Triangle> tris = new ArrayList<Triangle>();
      for (int i = 0; i < m.getTriangleCount(); i++) {
         int[] storage = new int[3];
         m.getTriangle(i, storage);
         if(checkTriangle(storage, m, index)){
            Vector3f[] vertices = new Vector3f[3];
            m.getTriangle(i, vertices);
            for (int j = 0; j < vertices.length; j++) {
               Vector3f bPos = vertices[j];
               modelnode.localToWorld(bPos, bPos);
               bPos.addLocal(realBone.getWorldTranslation().negate());               
               Quaternion rot = realBone.getWorldRotation().inverse().mult(body.getLocalRotation());
               rot.mult(bPos,bPos);
            }
            Triangle triangle = new Triangle(vertices[0], vertices[1], vertices[2]);
            tris.add(triangle);            
         }         
      }
      
      int size = tris.size();
      if(size>0){
         new DynamicMesh(name, body, tris);
      }
   }
   private boolean checkTriangle(int[] vStorage, Mesh m, int jIndex){
      int b = 0;
      for (int j = 0; j < vStorage.length; j++) {
         if(checkVertex(vStorage[j], m, jIndex)){
            b++;
         }
      }
      if(b=:3){
         return true;
      }
      return false;
   }
   private boolean checkVertex(int vIndex, Mesh m, int jIndex){
      int[] wall = m.getVertex(vIndex).getWeightIndices();
      float max = 0;
      Weight wMax = null;
      for (int i = 0; i < wall.length; i++) {
         Weight w = m.getWeight(wall[i]);
         float v = w.getWeightValue();
         if(v>max){
            wMax = w;
            max = v;
         }
      }
      if(wMax.getJointIndex()==jIndex){
         return true;
      }
      return false;
   }
   @Override
   public void updateGeometricState(float time, boolean initiator) {
      if(parentBody!=null){
         Vector3f store = new Vector3f();
         parentBody.worldToLocal(body.getLocalTranslation(), store);
         store.divideLocal(meshScale);
         realBone.setLocalTranslation(store);
         
         Quaternion res = new Quaternion();
         parentBody.getLocalRotation().inverse().mult(body.getLocalRotation(), res);
         realBone.setLocalRotation(res);
      }else{
         realBone.setLocalTranslation(new Vector3f());
         realBone.setLocalRotation(new Quaternion());
      }
      if(mesh!=null){
         mesh.setLocalTranslation(new Vector3f(body.getLocalTranslation()));
         mesh.setLocalRotation(new Quaternion(body.getLocalRotation()));
      }
      super.updateGeometricState(time, initiator);
   }
   
   public static Joint join(DynamicPhysicsNode node1, DynamicPhysicsNode node2, com.jme.math.Vector3f anchor,
         com.jme.math.Vector3f direction, float min, float max) {
      
      Joint jmeJoint = node1.getSpace().createJoint();      
      RotationalJointAxis jmeAxis = jmeJoint.createRotationalAxis();
      
      jmeAxis.setPositionMinimum(min);
      jmeAxis.setPositionMaximum(max);

      jmeJoint.attach(node1, node2);
      jmeJoint.setAnchor(anchor);
      jmeAxis.setDirection(direction);
      return jmeJoint;
   }
    public static Vector3f getLookAtDirection(Quaternion quaternion) {
         Vector3f[] neu =new Vector3f[3];
         quaternion.toAxes(neu);
         return new Vector3f(neu[1].crossLocal(neu[0]).normalizeLocal().negateLocal());
      }
}



DynamicMesh


package emp.physics.ragdoll;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.List;

import com.jme.bounding.BoundingBox;
import com.jme.math.Triangle;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.TriMesh;
import com.jme.util.geom.BufferUtils;
import com.jmex.physics.DynamicPhysicsNode;
import com.jmex.physics.PhysicsCollisionGeometry;
import com.jmex.physics.PhysicsDebugger;
import com.jmex.physics.PhysicsNode;
import com.jmex.physics.geometry.PhysicsMesh;

public class DynamicMesh extends PhysicsCollisionGeometry {
   private TriMesh   triMesh;
   private PhysicsMesh   ph;

   public DynamicMesh(String name, PhysicsNode physicsNode, List<Triangle> triangles){
      super(physicsNode);
      setName(name);
      int size = triangles.size();
   
      FloatBuffer vertices = BufferUtils.createFloatBuffer(size*9);
      FloatBuffer normals = BufferUtils.createFloatBuffer(size*9);
      IntBuffer indices = BufferUtils.createIntBuffer(size*9);
      for (int i = 0; i < size; i++) {
         Triangle t = triangles.get(i);
         for (int j = 0; j < 3; j++) {
            BufferUtils.setInBuffer(t.get(j), vertices, i*3+j);
            indices.put(i*3+j);
            BufferUtils.setInBuffer(Vector3f.UNIT_Y, normals, i*3+j);
         }
      }
      
      triMesh = new TriMesh(name, vertices,normals,null,null,indices );
      triMesh.setCullHint(CullHint.Always);
      ph = physicsNode.createMesh(name);
      ph.copyFrom(triMesh);
      PhysicsDebugger.setupDebugGeom(triMesh);
      physicsNode.attachChild(this);
      
      if(!physicsNode.isStatic()){
         DynamicPhysicsNode dyn = (DynamicPhysicsNode)physicsNode;
         float mass = dyn.getMass();
         mass+=dyn.getMaterial().getDensity()*getVolume();
         dyn.setMass(mass);
      }
   }

   @Override
   protected void drawDebugShape(PhysicsNode physicsNode, Renderer renderer) {
      triMesh.setLocalTranslation(ph.getWorldTranslation());
      triMesh.setLocalRotation(ph.getWorldRotation());
      triMesh.setLocalScale(ph.getWorldScale());
        PhysicsDebugger.drawDebugShape( triMesh, triMesh.getWorldTranslation(), this, renderer, 1 );

   }

   @Override
   public float getVolume() {
      BoundingBox b = new BoundingBox();
      b.computeFromPoints(triMesh.getVertexBuffer());
      System.out.println("volumen: "+b.getVolume());
      return b.getVolume();
   }
}

Please, could you use the Marine model from the MD5Importer? Just for torso, no need for two models… Since I don't know how to set up a scene with some model and physics, it would be really really nice to see a working demo 

Thanks a lot.

:slight_smile: I got it, I just had to the take the first child of the RootBone



TestCase:


package emp.physics.ragdoll.test;

import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme.animation.Bone;
import com.jme.app.BaseGame;
import com.jme.bounding.BoundingBox;
import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.shape.Box;
import com.jmex.physics.StaticPhysicsNode;
import com.jmex.physics.material.Material;
import com.jmex.physics.util.SimplePhysicsGame;
import com.model.md5.ModelNode;
import com.model.md5.importer.MD5Importer;

import emp.physics.ragdoll.MD5Bone;

public class RagDollParseTest extends SimplePhysicsGame {
   
   @Override
   protected void simpleInitGame() {
      pause = true;
      initGround();
      parseRhino(rootNode);            
      
      rootNode.updateGeometricState(0, true);
      rootNode.updateRenderState();
   }
   
   public PhysicBone parseRhino(Node rootNode) {
      PhysicBone character = null;
      

      ModelNode rhino = loadOnlyMd5Model("/test/model/md5/data/marine");

      rhino.getControllers().clear();
      MD5Bone md = new MD5Bone(rhino);
               
      rhino.attachChild(md);
      rhino.setLocalScale(.333f);
      
      rhino.updateGeometricState(0, true);
      rootNode.attachChild(rhino);
      character = new PhysicBone((Bone) md.getChild(0), rhino, getPhysicsSpace());
      return character;
   }
   public static ModelNode loadOnlyMd5Model(String path) {
      MD5Importer im = MD5Importer.getInstance();
      ModelNode node;      
      try {
         im.loadMesh(MD5Importer.class.getResource(path+".md5mesh"), "m5s");
      }
      catch (Exception e1) {
         e1.printStackTrace();
      }
      node = im.getModelNode();
      im.cleanup();
      return node;
   }
   
   private void initGround(){
      StaticPhysicsNode staticNode = getPhysicsSpace().createStaticNode();
      Spatial floorBox = new Box("Terrain", new Vector3f(0, 0, 0), 100, 1f, 100);
      floorBox.setModelBound(new BoundingBox());
      floorBox.updateModelBound();
      staticNode.attachChild(floorBox);      

      staticNode.getLocalTranslation().set(0, -14, 0);
      staticNode.updateGeometricState(0, false);
      staticNode.generatePhysicsGeometry();
      staticNode.setMaterial(Material.GRANITE);
      rootNode.attachChild(staticNode);
   }
   
   public static void main(String[] args) {
      Logger.getLogger("").setLevel(Level.WARNING);
      BaseGame game = new RagDollParseTest();
      game.setConfigShowMode(ConfigShowMode.AlwaysShow);
      game.start();
   }
}



PhysicBone:


package emp.physics.ragdoll.test;

import java.util.ArrayList;
import java.util.List;

import com.jme.animation.Bone;
import com.jme.math.Quaternion;
import com.jme.math.Triangle;
import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jmex.physics.DynamicPhysicsNode;
import com.jmex.physics.Joint;
import com.jmex.physics.PhysicsSpace;
import com.jmex.physics.RotationalJointAxis;
import com.jmex.physics.material.Material;
import com.model.md5.ModelNode;
import com.model.md5.resource.mesh.Mesh;
import com.model.md5.resource.mesh.primitive.Weight;

import emp.physics.ragdoll.DynamicMesh;

public class PhysicBone extends Node {

   private DynamicPhysicsNode   parentBody;
   private DynamicPhysicsNode   body;
   private Bone   realBone;
   private Vector3f   meshScale;
   private Spatial   mesh;
   
   public PhysicBone(Bone md, Spatial mesh, PhysicsSpace space) {
      this(md, mesh.getParent(),null, mesh.getLocalScale(), space, (ModelNode) mesh);
      this.mesh = mesh;
      setCullHint(CullHint.Always);
   }
   private PhysicBone(Bone md, Node parent, PhysicBone myParent, Vector3f meshScale, PhysicsSpace space, ModelNode source) {
      super(md.getName());
      parent.attachChild(this);
      
      this.meshScale = meshScale;
      realBone = md;
      
      Vector3f myPos = md.getWorldTranslation().clone();
      Vector3f parentPos = md.getParent().getWorldTranslation();
      
      Quaternion myRot = md.getWorldRotation().clone();
   
      float height = myPos.distance(parentPos)/2f;
      if(height>0){
         body = space.createDynamicNode();
      
         if(myParent==null){
            body.setMaterial(Material.GHOST);
         }else{
//            TODO: choose yourself, if you need this line
//            body.createSphere("s").setLocalScale(.05f);
            body.setMaterial(Material.WOOD);

//            TODO: and this too...
            attachSkin(source);      
         }         
         
         
         body.setLocalTranslation(myPos);
         body.setLocalRotation(myRot);
         attachChild(body);
         
         if(myParent!=null&&myParent.body!=null){
            Vector3f ncor = new Vector3f(0,0,0);
            if(Material.GHOST.equals(myParent.body.getMaterial())){
               join( myParent.body,body, ncor,getLookAtDirection(myRot), 0,0);
            }else{
               join( myParent.body,body, ncor,getLookAtDirection(myRot), -.1f, .1f);
            }
            parentBody = myParent.body;
         }      
      }else{
         body=myParent.body;
         parentBody=myParent.parentBody;
      }
      
      if(md.getName().contains("Hand")){
         return;
      }
      for (int i = 0; i < md.getQuantity(); i++) {
         Spatial child = md.getChild(i);
         if(child instanceof Bone){
            new PhysicBone((Bone) child,this, this,meshScale, space, source);
         }
      }
   }
   private void attachSkin(ModelNode modelnode) {
      Mesh m = modelnode.getMesh(0);
      int index = -1;
      com.model.md5.resource.mesh.Joint[] allJ = modelnode.getJoints();
      for (int i = 0; i < allJ.length; i++) {
         if(allJ[i].getName().equals(name)){
            index = i;
            break;
         }
      }
      List<Triangle> tris = new ArrayList<Triangle>();
      for (int i = 0; i < m.getTriangleCount(); i++) {
         int[] storage = new int[3];
         m.getTriangle(i, storage);
         if(checkTriangle(storage, m, index)){
            Vector3f[] vertices = new Vector3f[3];
            m.getTriangle(i, vertices);
            for (int j = 0; j < vertices.length; j++) {
               Vector3f bPos = vertices[j];
               modelnode.localToWorld(bPos, bPos);
               bPos.addLocal(realBone.getWorldTranslation().negate());               
               Quaternion rot = realBone.getWorldRotation().inverse().mult(body.getLocalRotation());
               rot.mult(bPos,bPos);
            }
            Triangle triangle = new Triangle(vertices[0], vertices[1], vertices[2]);
            tris.add(triangle);            
         }         
      }
      
      int size = tris.size();
      if(size>0){
         new DynamicMesh(name, body, tris);
      }
   }
   private boolean checkTriangle(int[] vStorage, Mesh m, int jIndex){
      int b = 0;
      for (int j = 0; j < vStorage.length; j++) {
         if(checkVertex(vStorage[j], m, jIndex)){
            b++;
         }
      }
      if(b=:3){
         return true;
      }
      return false;
   }
   private boolean checkVertex(int vIndex, Mesh m, int jIndex){
      int[] wall = m.getVertex(vIndex).getWeightIndices();
      float max = 0;
      Weight wMax = null;
      for (int i = 0; i < wall.length; i++) {
         Weight w = m.getWeight(wall[i]);
         float v = w.getWeightValue();
         if(v>max){
            wMax = w;
            max = v;
         }
      }
      if(wMax.getJointIndex()==jIndex){
         return true;
      }
      return false;
   }
   @Override
   public void updateGeometricState(float time, boolean initiator) {
      if(parentBody!=null){
         Vector3f store = new Vector3f();
         parentBody.worldToLocal(body.getLocalTranslation(), store);
         store.divideLocal(meshScale);
         realBone.setLocalTranslation(store);
         
         Quaternion res = new Quaternion();
         parentBody.getLocalRotation().inverse().mult(body.getLocalRotation(), res);
         realBone.setLocalRotation(res);
      }else{
         realBone.setLocalTranslation(new Vector3f());
         realBone.setLocalRotation(new Quaternion());
      }
      if(mesh!=null){
         mesh.setLocalTranslation(new Vector3f(body.getLocalTranslation()));
         mesh.setLocalRotation(new Quaternion(body.getLocalRotation()));
      }
      super.updateGeometricState(time, initiator);
   }
   
   public static Joint join(DynamicPhysicsNode node1, DynamicPhysicsNode node2, com.jme.math.Vector3f anchor,
         com.jme.math.Vector3f direction, float min, float max) {
      
      Joint jmeJoint = node1.getSpace().createJoint();      
      RotationalJointAxis jmeAxis = jmeJoint.createRotationalAxis();
      
      jmeAxis.setPositionMinimum(min);
      jmeAxis.setPositionMaximum(max);

      jmeJoint.attach(node1, node2);
      jmeJoint.setAnchor(anchor);
      jmeAxis.setDirection(direction);
      return jmeJoint;
   }
    public static Vector3f getLookAtDirection(Quaternion quaternion) {
         Vector3f[] neu =new Vector3f[3];
         quaternion.toAxes(neu);
         return new Vector3f(neu[1].crossLocal(neu[0]).normalizeLocal().negateLocal());
      }
}



Then you need the DynamicMesh for debugging with the PhysicsDebugger

and the Md5Bone for porting the MD5Joints

http://www.youtube.com/watch?v=rlqPZK6uDeA
and a small Video

Have Fun!

Thanks for the code, but I am still unable to make my demo work… Could you tell me which version of MD5Importer you are using? If I use the MD5Importer for jME1.0, there are some methods missing in class com.model.md5.resource.mesh.Mesh - for example getTriangleCount(). These methods are implemented in MD5Importer for jME2.0 (and from the youtube video I can see you are using jME2.0), but if I use the jME2.0 Importer, thousands of errors appear… Did you make some kind of a mix from those two versions of MD5Importer? Or am I completely wrong? :slight_smile:

I have changed some stuff, but my Version is more than 2month old,



I see, there is now a lot different, i will check out it at the weekend, and make an actual version

Now it should work





Good to debug:


package emp.physics.ragdoll;

import com.jme.animation.Bone;
import com.jme.math.Matrix3f;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.model.md5.interfaces.IMD5Node;
import com.model.md5.interfaces.mesh.IJoint;

public class MD5Bone extends Bone {
   private IJoint   org;
   
   private MD5Bone(IJoint originalBone){
      super(originalBone.getName());
      org=originalBone;
   }

   public MD5Bone(IMD5Node node) {
      IJoint[] js = node.getJoints();
      int l = js.length;
      MD5Bone[] our = new MD5Bone[l];
      for (int i = 0; i < l; i++) {
         IJoint parent = js[i].getParent();
         if(parent!=null)
            our[i] = new MD5Bone(js[i]);
         else{
            skinRoot=true;
            org=js[i];
            setName(org.getName()+"-node");
            our[i] = this;
         }
      }
      for (int i = 0; i < our.length; i++) {
         IJoint parent = js[i].getParent();
         if(parent!=null){
            our[parent.getIndex()].attachChild(our[i]);
         }
      }
   }

   @Override
   public void updateGeometricState(float time, boolean initiator) {
      super.setLocalTranslation(org.getTranslation());
      super.setLocalRotation(org.getOrientation());   
      super.updateGeometricState(time, initiator);
   }
   

   @Override
   public void setLocalRotation(Matrix3f rotation) {
      Quaternion rot = new Quaternion();
      rot.fromRotationMatrix(rotation);
      setLocalRotation(rot);
   }

   @Override
   public void setLocalRotation(Quaternion rotation) {
      org.updateTransform(localTranslation, rotation);
   }

   @Override
   public void setLocalTranslation(float x, float y, float z) {
      setLocalTranslation(new Vector3f(x,y,z));
   }

   @Override
   public void setLocalTranslation(Vector3f localTranslation) {
      org.updateTransform(localTranslation, localRotation);
   }

   public final IJoint getOrg() {
      return org;
   }
}



More DebugStuff


package emp.physics.ragdoll;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.List;

import com.jme.bounding.BoundingBox;
import com.jme.math.Triangle;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.TriMesh;
import com.jme.util.geom.BufferUtils;
import com.jmex.physics.DynamicPhysicsNode;
import com.jmex.physics.PhysicsCollisionGeometry;
import com.jmex.physics.PhysicsDebugger;
import com.jmex.physics.PhysicsNode;
import com.jmex.physics.geometry.PhysicsMesh;

public class DynamicMesh extends PhysicsCollisionGeometry {
   private TriMesh   triMesh;
   private PhysicsMesh   ph;

   public DynamicMesh(String name, PhysicsNode physicsNode, List<Triangle> triangles){
      super(physicsNode);
      setName(name);
      int size = triangles.size();
   
      FloatBuffer vertices = BufferUtils.createFloatBuffer(size*9);
      FloatBuffer normals = BufferUtils.createFloatBuffer(size*9);
      IntBuffer indices = BufferUtils.createIntBuffer(size*9);
      for (int i = 0; i < size; i++) {
         Triangle t = triangles.get(i);
         for (int j = 0; j < 3; j++) {
            BufferUtils.setInBuffer(t.get(j), vertices, i*3+j);
            indices.put(i*3+j);
            BufferUtils.setInBuffer(Vector3f.UNIT_Y, normals, i*3+j);
         }
      }
      
      triMesh = new TriMesh(name, vertices,normals,null,null,indices );
      triMesh.setCullHint(CullHint.Always);
      ph = physicsNode.createMesh(name);
      ph.copyFrom(triMesh);
      PhysicsDebugger.setupDebugGeom(triMesh);
      physicsNode.attachChild(this);
      
      if(!physicsNode.isStatic()){
         DynamicPhysicsNode dyn = (DynamicPhysicsNode)physicsNode;
         float mass = dyn.getMass();
         mass+=dyn.getMaterial().getDensity()*getVolume();
         dyn.setMass(mass);
      }
   }

   @Override
   protected void drawDebugShape(PhysicsNode physicsNode, Renderer renderer) {
      triMesh.setLocalTranslation(ph.getWorldTranslation());
      triMesh.setLocalRotation(ph.getWorldRotation());
      triMesh.setLocalScale(ph.getWorldScale());
        PhysicsDebugger.drawDebugShape( triMesh, triMesh.getWorldTranslation(), this, renderer, 1 );

   }

   @Override
   public float getVolume() {
      BoundingBox b = new BoundingBox();
      b.computeFromPoints(triMesh.getVertexBuffer());
      return b.getVolume();
   }
}



the PhysicStuff


package emp.physics.ragdoll.test;

import java.util.ArrayList;
import java.util.List;

import com.jme.animation.Bone;
import com.jme.math.Quaternion;
import com.jme.math.Triangle;
import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jmex.physics.DynamicPhysicsNode;
import com.jmex.physics.Joint;
import com.jmex.physics.PhysicsSpace;
import com.jmex.physics.RotationalJointAxis;
import com.jmex.physics.material.Material;
import com.model.md5.MD5Node;
import com.model.md5.interfaces.mesh.IJoint;
import com.model.md5.interfaces.mesh.primitive.IWeight;
import com.model.md5.resource.mesh.Mesh;
import com.model.md5.resource.mesh.primitive.Vertex;

import emp.physics.ragdoll.DynamicMesh;

public class PhysicBone extends Node {

   private DynamicPhysicsNode   parentBody;
   private DynamicPhysicsNode   body;
   private Bone   realBone;
   private Vector3f   meshScale;
   private MD5Node   mesh;
   
   public PhysicBone(Bone md, MD5Node mesh, PhysicsSpace space) {
      this(md, mesh.getParent(),null, mesh.getLocalScale(), space, mesh);
      this.mesh = mesh;
   }
   private PhysicBone(Bone md, Node parent, PhysicBone myParent, Vector3f meshScale, PhysicsSpace space, MD5Node source) {
      super(md.getName());
      parent.attachChild(this);
      
      this.meshScale = meshScale;
      realBone = md;
      
      Vector3f myPos = md.getWorldTranslation().clone();
      Vector3f parentPos = md.getParent().getWorldTranslation();
      
      Quaternion myRot = md.getWorldRotation().clone();
   
      float height = myPos.distanceSquared(parentPos);
      if(height>0){
         body = space.createDynamicNode();
//         TODO: choose yourself, if you need this line
//         body.createSphere("s").setLocalScale(.05f);
         body.setMaterial(Material.WOOD);
         attachSkin(source);      
         body.setLocalTranslation(myPos);
         body.setLocalRotation(myRot);
         attachChild(body);
         
         if(myParent!=null&&myParent.body!=null){
            Vector3f ncor = new Vector3f(0,0,0);
            join( myParent.body,body, ncor,getLookAtDirection(myRot), -.1f, .1f);
            parentBody = myParent.body;
         }      
      }else{
         body=myParent.body;
         parentBody=myParent.parentBody;
      }
      
      if(md.getName().contains("hand")){
         return;
      }
      for (int i = 0; i < md.getQuantity(); i++) {
         Spatial child = md.getChild(i);
         if(child instanceof Bone){
            new PhysicBone((Bone) child,this, this,meshScale, space, source);
         }
      }
   }
   private void attachSkin(MD5Node modelnode) {
      Mesh m = (Mesh) modelnode.getMesh(0);
      int index = -1;
      IJoint[] allJ = modelnode.getJoints();
      for (int i = 0; i < allJ.length; i++) {
         if(allJ[i].getName().equals(name)){
            index = i;
            break;
         }
      }
      List<Triangle> tris = new ArrayList<Triangle>();
      for (int i = 0; i < m.getTriangleCount(); i++) {
         int[] storage = new int[3];
         m.getTriangle(i, storage);
         if(checkTriangle(storage, m, index)){
            Vector3f[] vertices = new Vector3f[3];
            m.getTriangle(i, vertices);
            for (int j = 0; j < vertices.length; j++) {
               Vector3f bPos = vertices[j];
               modelnode.localToWorld(bPos, bPos);
               bPos.addLocal(realBone.getWorldTranslation().negate());               
               Quaternion rot = realBone.getWorldRotation().inverse().mult(body.getLocalRotation());
               rot.mult(bPos,bPos);
            }
            Triangle triangle = new Triangle(vertices[0], vertices[1], vertices[2]);
            tris.add(triangle);            
         }         
      }
      
      int size = tris.size();
      if(size>0){
         new DynamicMesh(name, body, tris);
      }
   }
   private boolean checkTriangle(int[] vStorage, Mesh m, int jIndex){
      int b = 0;
      for (int j = 0; j < vStorage.length; j++) {
         if(checkVertex(vStorage[j], m, jIndex)){
            b++;
         }
      }
      if(b=:3){
         return true;
      }
      return false;
   }
   private boolean checkVertex(int vIndex, Mesh m, int jIndex){
      IWeight[] wall = ((Vertex)m.getVertex(vIndex)).getWeightIndices();
      float max = 0;
      IWeight wMax = null;
      for (int i = 0; i < wall.length; i++) {
         float v = wall[i].getWeightValue();
         if(v>max){
            wMax = wall[i];
            max = v;
         }
      }
      if(wMax.getJoint().getIndex()==jIndex){
         return true;
      }
      return false;
   }
   @Override
   public void updateGeometricState(float time, boolean initiator) {
      if(parentBody!=null){
         Vector3f store = new Vector3f();
         parentBody.worldToLocal(body.getLocalTranslation(), store);
         store.divideLocal(meshScale);
         realBone.setLocalTranslation(store);
         
         Quaternion res = new Quaternion();
         parentBody.getLocalRotation().inverse().mult(body.getLocalRotation(), res);
         realBone.setLocalRotation(res);
      }else{
         realBone.setLocalTranslation(Vector3f.ZERO);
         realBone.setLocalRotation(new Quaternion());
      }
      if(mesh!=null){
         mesh.setLocalTranslation(new Vector3f(body.getLocalTranslation()));
         mesh.setLocalRotation(new Quaternion(body.getLocalRotation()));
         mesh.flagUpdate();
      }
      super.updateGeometricState(time, initiator);
   }
   
   public static Joint join(DynamicPhysicsNode node1, DynamicPhysicsNode node2, Vector3f anchor, Vector3f direction, float min, float max) {
      
      Joint jmeJoint = node1.getSpace().createJoint();      
      RotationalJointAxis jmeAxis = jmeJoint.createRotationalAxis();
      
      jmeAxis.setPositionMinimum(min);
      jmeAxis.setPositionMaximum(max);

      jmeJoint.attach(node1, node2);
      jmeJoint.setAnchor(anchor);
      jmeAxis.setDirection(direction);
      return jmeJoint;
   }
    public static Vector3f getLookAtDirection(Quaternion quaternion) {
         Vector3f[] neu =new Vector3f[3];
         quaternion.toAxes(neu);
         return new Vector3f(neu[1].crossLocal(neu[0]).normalizeLocal().negateLocal());
      }
}



and the TestClass


package emp.physics.ragdoll.test;

import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme.animation.Bone;
import com.jme.app.BaseGame;
import com.jme.bounding.BoundingBox;
import com.jme.input.InputHandler;
import com.jme.input.KeyInput;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.shape.Box;
import com.jme.util.BoneDebugger;
import com.jmex.physics.StaticPhysicsNode;
import com.jmex.physics.material.Material;
import com.jmex.physics.util.SimplePhysicsGame;
import com.model.md5.MD5Node;
import com.model.md5.importer.MD5Importer;

import emp.physics.ragdoll.MD5Bone;

public class RagDollParseTest extends SimplePhysicsGame {
   
   private boolean   showBones;

   @Override
   protected void simpleInitGame() {
      pause = true;
      initGround();
      parseRhino(rootNode);            
      
      rootNode.updateGeometricState(0, true);
      rootNode.updateRenderState();
      
      input.addAction( new InputAction() {
            public void performAction( InputActionEvent evt ) {
                if ( evt.getTriggerPressed() ) {
                    showBones = !showBones;
                }
            }
        }, InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_SPACE, InputHandler.AXIS_NONE, false );
   }
   
   
   
   @Override
   protected void doDebug(Renderer r) {
      super.doDebug(r);
      if(showBones){
         BoneDebugger.drawBones(rootNode, r, true);
      }
   }



   public PhysicBone parseRhino(Node rootNode) {
      PhysicBone character = null;
      

      MD5Node rhino = loadOnlyMd5Model("/test/model/md5/data/marine");

      rhino.getControllers().clear();
      MD5Bone md = new MD5Bone(rhino);
               
      rhino.attachChild(md);
      rhino.setLocalScale(.2f);
      
      rhino.updateGeometricState(0, true);
      rootNode.attachChild(rhino);
      character = new PhysicBone((Bone) md.getChild(0), rhino, getPhysicsSpace());
      return character;
   }
   public static MD5Node loadOnlyMd5Model(String path) {
      MD5Importer im = MD5Importer.getInstance();
      MD5Node node;      
      try {
         im.loadMesh(MD5Importer.class.getResource(path+".md5mesh"), "m5s");
      }
      catch (Exception e1) {
         e1.printStackTrace();
      }
      node = (MD5Node) im.getModelNode();
      im.cleanup();
      return node;
   }
   
   private void initGround(){
      StaticPhysicsNode staticNode = getPhysicsSpace().createStaticNode();
      Spatial floorBox = new Box("Terrain", new Vector3f(0, 0, 0), 100, 1f, 100);
      floorBox.setModelBound(new BoundingBox());
      floorBox.updateModelBound();
      staticNode.attachChild(floorBox);      

      staticNode.getLocalTranslation().set(0, -14, 0);
      staticNode.updateGeometricState(0, false);
      staticNode.generatePhysicsGeometry();
      staticNode.setMaterial(Material.GRANITE);
      rootNode.attachChild(staticNode);
   }
   
   public static void main(String[] args) {
      Logger.getLogger("").setLevel(Level.WARNING);
      BaseGame game = new RagDollParseTest();
      game.setConfigShowMode(ConfigShowMode.AlwaysShow);
      game.start();
   }
}



now I use the actualy Version from the MD5Importer for JME2.0 from their SVN.

In the class "Vertex" from the Importer you have to add the fuction:


public IWeight[] getWeightIndices() {
   return weights;
}


Thanks a lot! Now it really works  8) Once again, thanks.

Hi,



I have one more stupid question. I don't know where to put the marine.tga texture, so that JME finds it and uses it.



I'm getting these warnings:



19.3.2009 13:03:16 com.jme.util.resource.ResourceLocatorTool locateResource

WARNING: Unable to locate: models/characters/male_npc/marine/marine

19.3.2009 13:03:16 com.jme.util.TextureManager loadTexture

WARNING: Could not load image…  URL was null. defaultTexture used.





but I'm not quite sure where the relative path beggins.



While playing with TestMesh.java (MD5Importer 2.0), I found out that marine.tga can be either in the folder "texture" relatively to marine.md5mesh, or it must be in the "texture/models/characters/male_npc/marine/" folder relatively to marine.md5mesh.



(I don't know, what forces the "texture" folder in the path - there is only "/models/characters/male_npc/marine/" in marine.md5mesh, as it's written in the warning few lines above.)



But for your project, none of those paths works… I know from the earlier video, that your Rhino model used textures, could you please tell me what path was written in your rhino.md5mesh and where the texture was placed in your project?



Thanks in advance.

Make sure you set up the Texture manager to find your Texture:


   
   try {
         MultiFormatResourceLocator locator = new MultiFormatResourceLocator(this.getClass().getClassLoader().getResource("com/myproject/data/textures/"),
               new String[]{".tga", ".bmp", ".png", ".jpg", ".texture", ".jme"});
         ResourceLocatorTool.addResourceLocator(ResourceLocatorTool.TYPE_TEXTURE, locator);
      } catch (URISyntaxException e) {
         e.printStackTrace();
      }