Walking & Jumping character with jME-physics

[quote author=[IR]Radek link=topic=10233.msg83238#msg83238 date=1240586866]

Yeah, that will be great if someone share it. I'd like to try it. BTW how this works with ThirdPersonController?

[/quote]



oh I must of deleted it when i edited it last time

In this code I would suggest getting the direction from the charNode itself I was going to create a simple enum for it but have been hung on trying to get scaling algorithm correct and add another method to gradually accelerate to a set speed and then decelerate to the original

 
package com.tps1.character;
 
import com.jme.bounding.BoundingBox;
import com.jme.input.InputHandler;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;
import com.jme.input.util.SyntheticButton;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.Node;

import com.jmex.physics.DynamicPhysicsNode;
import com.jmex.physics.Joint;
import com.jmex.physics.RotationalJointAxis;
import com.jmex.physics.contact.ContactInfo;
import com.jmex.physics.contact.MutableContactInfo;
import com.jmex.physics.geometry.PhysicsCapsule;
import com.jmex.physics.geometry.PhysicsSphere;

import com.jmex.physics.material.Material;

import com.tps1.GameState.gameSingleton;

public class characterMove {
   public enum Direction{
      FORWARD{ public Vector3f getDirection() { return Vector3f.UNIT_Z; }},
      BACKWARD{ public Vector3f getDirection() { return new Vector3f(0,0,-1); }},
      LEFT{ public Vector3f getDirection() { return Vector3f.UNIT_X; }},
      RIGHT{ public Vector3f getDirection() { return new Vector3f(-1,0,0); }},
      FORWARD_LEFT{ public Vector3f getDirection() { return new Vector3f(1,0,1); }},
      FORWARD_RIGHT{ public Vector3f getDirection() { return new Vector3f(-1,0,1); }},
      BACKWARD_LEFT{ public Vector3f getDirection() { return new Vector3f(1,0,-1); }},
      BACKWARD_RIGHT{ public Vector3f getDirection() { return new Vector3f(-1,0,-1); }};      
       public abstract Vector3f getDirection();         
   }
   
   private static final Material characterMaterial;
   private final Joint joint;
   private RotationalJointAxis rotAxis;
   private boolean offGround,allowFall, isMoving;
   private float Speed, scale;
    private final InputHandler contactDetect = new InputHandler();
   static {
           characterMaterial = new Material( "Character Material" );
           characterMaterial.setDensity( 1f );
           MutableContactInfo contactDetails = new MutableContactInfo();
           contactDetails.setBounce(0);
           contactDetails.setMu(1);
           contactDetails.setMuOrthogonal(1);
           contactDetails.setDampingCoefficient(10);
           characterMaterial.putContactHandlingDetails( Material.DEFAULT, contactDetails );
       }
   
   private final DynamicPhysicsNode feetNode, physNode;
   private final Node baseNode, charNode;
   /**
    * Initalizes the Ogre walking system
    * @param mainNode the Node that acts as the base to hold the branches of the system
    * @param modelNode the Node containing character Geometry
    * @param physicsSpace the active and dominate physicsSpace
    * @param input
    */
   public characterMove(playerGameState player) {
      Speed=player.getCharacterStates()[4];
      scale=CharacterStats.get().getWorldScale();
      physNode= gameSingleton.get().getPhysicsSpace().createDynamicNode();physNode.setName("physNode");;
      baseNode = player.getRootNode();
      charNode = player.getCharNode();
      feetNode=gameSingleton.get().getPhysicsSpace().createDynamicNode();feetNode.setName("feetNode");
      joint = gameSingleton.get().getPhysicsSpace().createJoint();
      offGround=false;
      isMoving=true;
      createPhysics();
   }
   /**
    * creates a basic movement system for characters
    */
   private void createPhysics(){
      BoundingBox bbox = (BoundingBox) charNode.getWorldBound();

      float cRadius = bbox.xExtent > bbox.zExtent ? bbox.zExtent : bbox.xExtent;
      float cHeight = bbox.yExtent *2f;
            
      PhysicsSphere feetGeom = feetNode.createSphere("Feet");
      feetGeom.setLocalScale(cRadius);
      feetNode.setMaterial(characterMaterial);
      feetNode.computeMass();
      charNode.setLocalTranslation(0, -cRadius/baseNode.getLocalScale().y, 0);
      baseNode.attachChild(feetNode);

      physNode.setLocalTranslation(0, 0, 0);
      physNode.setAffectedByGravity(false);
      PhysicsCapsule torsoGeom = physNode.createCapsule("Torso");
      torsoGeom.setLocalScale(new Vector3f(cRadius, cHeight-4*cRadius, cHeight/2.0f));
      torsoGeom.setLocalTranslation(0, cHeight - ((torsoGeom.getLocalScale().y)/2f+2f*cRadius), 0);
      Quaternion rot = new Quaternion();
      rot.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X);
      torsoGeom.setLocalRotation(rot);
      physNode.computeMass();
      physNode.attachChild(charNode);
      baseNode.attachChild(physNode);
      
      joint.setName("ROTATIONAL AXIS");
      // A single joint is created and the Direction Will change depending on the users needs:
      rotAxis =  joint.createRotationalAxis();
      rotAxis.setDirection(Vector3f.UNIT_Z);
      joint.attach(physNode, feetNode);      
      rotAxis.setRelativeToSecondObject( true );      
      rotAxis.setAvailableAcceleration(0f);rotAxis.setDesiredVelocity(0f);
      setUp();
      
      feetNode.setMass(0.7455306f);physNode.setMass(2.6873279f);
   }
   
   private Vector3f directioned= new Vector3f(0,0,0);
   
   public void update(float tpf){
      rotAxis.setDesiredVelocity( 0 );
      directioned.set(new Vector3f(0,0,0));
      if(!allowFall){preventFall();}else{resetFeetRotation();}      
      if(!isMoving){feetNode.clearDynamics();}      
      offGround = false;   
      contactDetect.update(tpf);
      physNode.rest();
      //System.out.println("Status of offGround is: "+offGround);
      }
   
   /*
    *In order to make sure the character does not fall over
    * we need to reset his vertical orientation once in a while
    */
   private void preventFall(){
      Quaternion rotation = physNode.getLocalRotation();
      Vector3f[] axes = new Vector3f[3];
      rotation.toAxes(axes);
      
      rotation.fromAxes(axes[0], Vector3f.UNIT_Y, axes[2]);
      physNode.setLocalRotation(rotation);
      physNode.setAngularVelocity(Vector3f.ZERO);
       physNode.updateWorldVectors();
   }
   
   /*
    * Countermeasures to make sure the feetNode remains stable
    */
   private void resetFeetRotation(){
      Quaternion rotation = feetNode.getLocalRotation();
      Vector3f[] axes = new Vector3f[3];
      rotation.toAxes(axes);
      
      rotation.fromAxes(Vector3f.UNIT_X, Vector3f.UNIT_Y, axes[2]);
      feetNode.setLocalRotation(rotation);
      
   }
   
   /* Detects when a Node collides with another Node */
   private void setUp(){
         SyntheticButton collButton = feetNode.getCollisionEventHandler();
           contactDetect.addAction( new InputAction() {              
               public void performAction( InputActionEvent evt ) {
                   ContactInfo contactInfo = (ContactInfo) evt.getTriggerData();
                   Vector3f vec = contactInfo.getContactNormal(null);
                  
                   if (vec.dot(Vector3f.UNIT_Y) > 1f){
                       offGround = true;
                   }
                   Vector3f vel = contactInfo.getContactVelocity(null);
                   if (vel.length() > 10){
                       System.out.println("POWERFUL CONTACT: "+vel.length());
                   }
               }              
           }, collButton, false );      
   }
   /**
    * Jump directly up. direction influenced from previous momentum
    * @param scale how heavy is the force up
    */   
   public void jump(){
      if(!offGround){feetNode.addForce(new Vector3f(0, 100f*scale, 0f));}
   }
   
   /**OverLoaded jump() method
    * Jump in a given direction
    * @param scale how heavy is the force up
    * @return
    */   
   public void jump(float x,int scale, float z){
      if(!offGround){feetNode.addForce(new Vector3f(x, 100f * scale, z));}
   }
   
   /**
    * moves uniform in a given direction
    * @param direction use the Direction enum to set a specific direction
    */
   public void move(Direction direction){
      Vector3f vec = new Vector3f(0,0,0);
      if(!offGround)
       if(direction.getDirection()!= vec){   
          try{
             rotAxis.setDirection(directioned.addLocal(direction.getDirection()));
             rotAxis.setAvailableAcceleration( 5*Speed );
            rotAxis.setDesiredVelocity( 5*Speed );}
          catch(Exception e){rotAxis.setDesiredVelocity(0f);   }   
          
                         
         }
       }
   
   
   /**
    * Accelerates uniformly until maxSpeed is met and then decelerates to desiredForce
    * @param desiredForce
    * @param acceleration
    * @param direction
    * @param maxSpeed
    * @return
    * @preCondition {@link characterMove#move(int scale, Vector3f direction)} is called
    */
   /*public void moveForward(int Speed, float acceleration, Vector3f direction, int maxSpeed){
      float run = 1+gameSingleton.get().timer.getTimeInSeconds();
      
           while(run>gameSingleton.get().timer.getTimeInSeconds())
              //{   move(maxSpeed,direction);
              rotAxis.setAvailableAcceleration(acceleration/(Speed*10));
           
           //}else{System.out.println("check");
                move(Speed,direction);
                //}
           
   }*/
   
   public RotationalJointAxis getRotationalAxis(){return rotAxis;}
   public boolean getOffGround(){return offGround;}
}

Is it a good idea to use this example for a FPS?

gouessej said:

Is it a good idea to use this example for a FPS?

Should work fine. Though the player's model should probably be set to invisible so it's not in the camera. Since this code does not use the model (other than to compute the height and radius) it would work as long as the model is there.

Wouldnt it be easier to have a look at, say, quake 3 or dooms movement code. (I remember oogling it for hours on earlier c++ games) and just using jme-physics for collision detection? Just a thought since I havent touched jme-physics yet.

I am pretty sure quake 3 and doom have their own light-weight physics system that they use for character collision. My code attempts to integrate character collision with a full-blown physics engine, and for my uses it worked well  :slight_smile:

If you want a more light-weight or quake3 like solution, consider using SimplePhysics which is made specifically for the purpose of character collision, there’s an example there that shows how the system works in an FPS like environment.

Thats an awesome idea, I'm so allergic to bloat that I often take away too much :slight_smile:

Bonechilla said:

[quote author=[IR]Radek link=topic=10233.msg83238#msg83238 date=1240586866]
Yeah, that will be great if someone share it. I'd like to try it. BTW how this works with ThirdPersonController?


oh I must of deleted it when i edited it last time
In this code I would suggest getting the direction from the charNode itself I was going to create a simple enum for it but have been hung on trying to get scaling algorithm correct and add another method to gradually accelerate to a set speed and then decelerate to the original

 
package com.tps1.character;
 
import com.jme.bounding.BoundingBox;
import com.jme.input.InputHandler;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;
import com.jme.input.util.SyntheticButton;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.Node;

import com.jmex.physics.DynamicPhysicsNode;
import com.jmex.physics.Joint;
import com.jmex.physics.RotationalJointAxis;
import com.jmex.physics.contact.ContactInfo;
import com.jmex.physics.contact.MutableContactInfo;
import com.jmex.physics.geometry.PhysicsCapsule;
import com.jmex.physics.geometry.PhysicsSphere;

import com.jmex.physics.material.Material;

import com.tps1.GameState.gameSingleton;

public class characterMove {
   public enum Direction{
      FORWARD{ public Vector3f getDirection() { return Vector3f.UNIT_Z; }},
      BACKWARD{ public Vector3f getDirection() { return new Vector3f(0,0,-1); }},
      LEFT{ public Vector3f getDirection() { return Vector3f.UNIT_X; }},
      RIGHT{ public Vector3f getDirection() { return new Vector3f(-1,0,0); }},
      FORWARD_LEFT{ public Vector3f getDirection() { return new Vector3f(1,0,1); }},
      FORWARD_RIGHT{ public Vector3f getDirection() { return new Vector3f(-1,0,1); }},
      BACKWARD_LEFT{ public Vector3f getDirection() { return new Vector3f(1,0,-1); }},
      BACKWARD_RIGHT{ public Vector3f getDirection() { return new Vector3f(-1,0,-1); }};      
       public abstract Vector3f getDirection();         
   }
   
   private static final Material characterMaterial;
   private final Joint joint;
   private RotationalJointAxis rotAxis;
   private boolean offGround,allowFall, isMoving;
   private float Speed, scale;
    private final InputHandler contactDetect = new InputHandler();
   static {
           characterMaterial = new Material( "Character Material" );
           characterMaterial.setDensity( 1f );
           MutableContactInfo contactDetails = new MutableContactInfo();
           contactDetails.setBounce(0);
           contactDetails.setMu(1);
           contactDetails.setMuOrthogonal(1);
           contactDetails.setDampingCoefficient(10);
           characterMaterial.putContactHandlingDetails( Material.DEFAULT, contactDetails );
       }
   
   private final DynamicPhysicsNode feetNode, physNode;
   private final Node baseNode, charNode;
   /**
    * Initalizes the Ogre walking system
    * @param mainNode the Node that acts as the base to hold the branches of the system
    * @param modelNode the Node containing character Geometry
    * @param physicsSpace the active and dominate physicsSpace
    * @param input
    */
   public characterMove(playerGameState player) {
      Speed=player.getCharacterStates()[4];
      scale=CharacterStats.get().getWorldScale();
      physNode= gameSingleton.get().getPhysicsSpace().createDynamicNode();physNode.setName("physNode");;
      baseNode = player.getRootNode();
      charNode = player.getCharNode();
      feetNode=gameSingleton.get().getPhysicsSpace().createDynamicNode();feetNode.setName("feetNode");
      joint = gameSingleton.get().getPhysicsSpace().createJoint();
      offGround=false;
      isMoving=true;
      createPhysics();
   }
   /**
    * creates a basic movement system for characters
    */
   private void createPhysics(){
      BoundingBox bbox = (BoundingBox) charNode.getWorldBound();

      float cRadius = bbox.xExtent > bbox.zExtent ? bbox.zExtent : bbox.xExtent;
      float cHeight = bbox.yExtent *2f;
            
      PhysicsSphere feetGeom = feetNode.createSphere("Feet");
      feetGeom.setLocalScale(cRadius);
      feetNode.setMaterial(characterMaterial);
      feetNode.computeMass();
      charNode.setLocalTranslation(0, -cRadius/baseNode.getLocalScale().y, 0);
      baseNode.attachChild(feetNode);

      physNode.setLocalTranslation(0, 0, 0);
      physNode.setAffectedByGravity(false);
      PhysicsCapsule torsoGeom = physNode.createCapsule("Torso");
      torsoGeom.setLocalScale(new Vector3f(cRadius, cHeight-4*cRadius, cHeight/2.0f));
      torsoGeom.setLocalTranslation(0, cHeight - ((torsoGeom.getLocalScale().y)/2f+2f*cRadius), 0);
      Quaternion rot = new Quaternion();
      rot.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X);
      torsoGeom.setLocalRotation(rot);
      physNode.computeMass();
      physNode.attachChild(charNode);
      baseNode.attachChild(physNode);
      
      joint.setName("ROTATIONAL AXIS");
      // A single joint is created and the Direction Will change depending on the users needs:
      rotAxis =  joint.createRotationalAxis();
      rotAxis.setDirection(Vector3f.UNIT_Z);
      joint.attach(physNode, feetNode);      
      rotAxis.setRelativeToSecondObject( true );      
      rotAxis.setAvailableAcceleration(0f);rotAxis.setDesiredVelocity(0f);
      setUp();
      
      feetNode.setMass(0.7455306f);physNode.setMass(2.6873279f);
   }
   
   private Vector3f directioned= new Vector3f(0,0,0);
   
   public void update(float tpf){
      rotAxis.setDesiredVelocity( 0 );
      directioned.set(new Vector3f(0,0,0));
      if(!allowFall){preventFall();}else{resetFeetRotation();}      
      if(!isMoving){feetNode.clearDynamics();}      
      offGround = false;   
      contactDetect.update(tpf);
      physNode.rest();
      //System.out.println("Status of offGround is: "+offGround);
      }
   
   /*
    *In order to make sure the character does not fall over
    * we need to reset his vertical orientation once in a while
    */
   private void preventFall(){
      Quaternion rotation = physNode.getLocalRotation();
      Vector3f[] axes = new Vector3f[3];
      rotation.toAxes(axes);
      
      rotation.fromAxes(axes[0], Vector3f.UNIT_Y, axes[2]);
      physNode.setLocalRotation(rotation);
      physNode.setAngularVelocity(Vector3f.ZERO);
       physNode.updateWorldVectors();
   }
   
   /*
    * Countermeasures to make sure the feetNode remains stable
    */
   private void resetFeetRotation(){
      Quaternion rotation = feetNode.getLocalRotation();
      Vector3f[] axes = new Vector3f[3];
      rotation.toAxes(axes);
      
      rotation.fromAxes(Vector3f.UNIT_X, Vector3f.UNIT_Y, axes[2]);
      feetNode.setLocalRotation(rotation);
      
   }
   
   /* Detects when a Node collides with another Node */
   private void setUp(){
         SyntheticButton collButton = feetNode.getCollisionEventHandler();
           contactDetect.addAction( new InputAction() {              
               public void performAction( InputActionEvent evt ) {
                   ContactInfo contactInfo = (ContactInfo) evt.getTriggerData();
                   Vector3f vec = contactInfo.getContactNormal(null);
                  
                   if (vec.dot(Vector3f.UNIT_Y) > 1f){
                       offGround = true;
                   }
                   Vector3f vel = contactInfo.getContactVelocity(null);
                   if (vel.length() > 10){
                       System.out.println("POWERFUL CONTACT: "+vel.length());
                   }
               }              
           }, collButton, false );      
   }
   /**
    * Jump directly up. direction influenced from previous momentum
    * @param scale how heavy is the force up
    */   
   public void jump(){
      if(!offGround){feetNode.addForce(new Vector3f(0, 100f*scale, 0f));}
   }
   
   /**OverLoaded jump() method
    * Jump in a given direction
    * @param scale how heavy is the force up
    * @return
    */   
   public void jump(float x,int scale, float z){
      if(!offGround){feetNode.addForce(new Vector3f(x, 100f * scale, z));}
   }
   
   /**
    * moves uniform in a given direction
    * @param direction use the Direction enum to set a specific direction
    */
   public void move(Direction direction){
      Vector3f vec = new Vector3f(0,0,0);
      if(!offGround)
       if(direction.getDirection()!= vec){   
          try{
             rotAxis.setDirection(directioned.addLocal(direction.getDirection()));
             rotAxis.setAvailableAcceleration( 5*Speed );
            rotAxis.setDesiredVelocity( 5*Speed );}
          catch(Exception e){rotAxis.setDesiredVelocity(0f);   }   
          
                         
         }
       }
   
   
   /**
    * Accelerates uniformly until maxSpeed is met and then decelerates to desiredForce
    * @param desiredForce
    * @param acceleration
    * @param direction
    * @param maxSpeed
    * @return
    * @preCondition {@link characterMove#move(int scale, Vector3f direction)} is called
    */
   /*public void moveForward(int Speed, float acceleration, Vector3f direction, int maxSpeed){
      float run = 1+gameSingleton.get().timer.getTimeInSeconds();
      
           while(run>gameSingleton.get().timer.getTimeInSeconds())
              //{   move(maxSpeed,direction);
              rotAxis.setAvailableAcceleration(acceleration/(Speed*10));
           
           //}else{System.out.println("check");
                move(Speed,direction);
                //}
           
   }*/
   
   public RotationalJointAxis getRotationalAxis(){return rotAxis;}
   public boolean getOffGround(){return offGround;}
}


[/quote]


if (vec.dot(Vector3f.UNIT_Y) > 1f){
   offGround = true;
}



Sorry if I'm mistaken, but since vec (the contact normal) and Vector3f.UNIT_Y are both unit vectors, this means that the dot product between them actually is the cosine of the angle between them. Cosines are never greater than one, so will this condition ever be true?

Hey,



I want to test this code, but I don't find the class of package com.tps1.GameState.



Somebody can me help, and say where I found this classes?



Thanks!