Physics: When exactly are Physics Nodes updated?

Hello!



I’ll keep it quick since my posts tend to get long-winded.


  1. I have a physicsCharacterNode running around, player controlled at the moment. I have other ‘stuff’ that follows it around so they’re attached to the node. Now, some of this ‘stuff’ requires me to know the distance to the floor. Without an updateGeometricState before doing this calculation, the distance will be incorrect. Is it “okay” to call this for this calculation? Later on I’ll likely have more of these nodes running around having to do the same thing, so I figure I’ll probably be calling updateGeometricState… A LOT of extra times… (I recall Momoko saying that extra calls to updateGeometricState are considered a ‘bug’ in my code, so it has me worried)


  2. I’d asked before if the move method of physicsCharacterNode was fps independent, but when I cap the fps of the program to 60fps, the amount moved changes dramatically. Also, if I change the accuracy on the node, this also changes it drastically. Before I move the node, I’ve already done the calculations to make the move itself fps independent. What am I doing wrong?


  3. Overall, I’m struggling with a lot of problems dealing with when the physicsCharacterNode is updated. Is it before, after or everywhere in relationship to the main application update loop?



    I’m away from my main machine atm, so if more details are required, I’ll post them as well.



    Thanks! :slight_smile:

The physics is framerate independent, you dont have to do any calculations, it always runs at the accuracy fps value. So the walkDirection vector of the character does not need to be adapted to the tpf. You should not call updategeometricstate on single nodes at all, only on the rootnode, see the wiki article about updategeometric. The physics is kind of special as it involves another update loop, the bullet physics update loop. The update order is as follows normally: update() is called and you do your modifications. Then updateGeometricState() is called by the opengl thread. This is where physics and jme information is synced, because physics needs the world information. Then the physics update call is done, so effectively the effect of physics on the jme spatials is one frame “late”. This allows multithreading the physics in parallel to the rendering process w/o breaking the update order.

Hope this helps,

Normen

Hello again!



Indeed, this information helps a bunch. I played around with a static walkDirection of something really low and played with the render fps and the physics accuracy fps, and I observed what you explained.


  1. Knowing this, in my situation, would I be better off having a node that handles the model and attached “stuff” ™, just translate to the position of the physicsCharacterNode when it is moved? I would probably go back to the issue I had long, long ago where the model lags behind the physics object, but I have a feeling the player would not notice this because they cannot see the debugWireframe of the Physics. Not to mention, I’d have an up to date relative position for the floor without having to call updateGeometricState. - Though, I recall the physicsCharacterNode being a bit more unruly (jittery, unpredictable) doing it this way. I think that’s the main reason I decided to just attach everything to the physicsCharacterNode.


  2. Is the accuracy guaranteed? Or does it get slower just as the render fps would get slower due to scene complexity? I assume this is somehow compensated for in walkDirection?


  3. Speaking of accuracy. What would a finer grain accuracy buy you? For example 1/120 accuracy instead of 1/60 (default)?



    Thanks again! :smiley:
  1. The PhysicsCharacterNode is a Node, so you can simply attach a model as a child and it will move with the character node. Only thing you will have to do yourself is rotate the model in the walkDirection as character nodes do not rotate.

    I also added a rayTest method to the physics space now, but raycasting works a little different in bullet, you will have to implement a callback listener (see javadoc), this info will of course be in sync with physics. Still, mixing of geometry picking and physics should not be too much of a problem normally.


  2. The physics engine should be “deterministic” meaning that when the actual framerate drops below the accuracy (1/60tpf or 60fps by default) the physics will compute two steps to compensate for that. Because the physics engine keeps its own timing the walkDirection should get you along the same amount of units in delta t when its not changed, independent of framerate.


  3. Finer accuracy mainly brings you more realistic physics at high velocities. For example a fast object could simply move through a wall because its so fast that at 1/60tpf it would be before the wall in one frame and behind it in the next. Although for this special case you can enable continuous collision detecion (CCD).



    Cheers,

    Normen

Hello again!



I haven’t made progress enough to use the rayTest yet, though I did look at the source and it looks nice. Definitely going to try to get the floor distance calculation using that. This morning, I’ve been experimenting with my frame independence issues using the walkDirection by making a smaller program to play with settings. Two questions and one observation so far, code below. =p


  1. Before, I was calculating the walkDirection by doing “modelNode.getLocalRotation().getRotationColumn(2).mult(velocity * tpf)”. Now, I’m still using the time per frame to handle the amount the model turns when the j or l keys are depressed, but I’ve removed the tpf from the walkDirection as below. Instead, I’m using a unit vector in the rotation direction multiplied by just the desired velocity. It seems like it’s not going crazy with this, but I haven’t implemented drift yet to smooth out the movement. Is this the way to go though as far as using moveDirection?


  2. Setting a higher accuracy makes the simulation smoother as well I’m guessing? I noticed some very, very slight jitter/lurching with it at 1/60, but it seems to disappear at 1/120.


  3. Observation: This was something I’d noticed a long time ago but I figured I was probably "doing it wrong"™ =p If you run the code below, move the camera to where you can see the box good, and then hold the i and l (or j) to move forward and turn, observe that after about 20-30 seconds, the simulation starts to sputter with the framerate capped at 60. Usually, I’m running jME3 at the full 60 or 59 fps with VSync on. I’ve had this issue with my main program as well, where after a bit of moving around and testing with nothing more complex than this simple example, the fps drops to about 40-50 and really feels like it’s sputtering. No idea if it’s doing this with VSync off, as it would be really difficult to tell.



    Thank you again for taking the time to answer my questions. :smiley:



    import com.jme3.app.SimpleBulletApplication;

    import com.jme3.bullet.collision.shapes.BoxCollisionShape;

    import com.jme3.bullet.collision.shapes.SphereCollisionShape;

    import com.jme3.bullet.nodes.PhysicsCharacterNode;

    import com.jme3.bullet.nodes.PhysicsNode;

    import com.jme3.input.KeyInput;

    import com.jme3.input.controls.AnalogListener;

    import com.jme3.input.controls.KeyTrigger;

    import com.jme3.material.Material;

    import com.jme3.math.ColorRGBA;

    import com.jme3.math.Vector3f;

    import com.jme3.scene.Geometry;

    import com.jme3.scene.shape.Box;

    import com.jme3.system.AppSettings;



    public class PhysicsPlayground extends SimpleBulletApplication {



    private static AppSettings settings;

    private Player player;



    public static void main(String[] args) {

    PhysicsPlayground app = new PhysicsPlayground();

    settings = new AppSettings(true);

    settings.setVSync(true);

    settings.setTitle(“Physics Playground (9/28/2010)”);

    app.setSettings(settings);

    app.start();

    }



    @Override

    public void simpleInitApp() {

    flyCam.setMoveSpeed(50);



    initWorld();

    initPlayer();

    initKeys();



    // getPhysicsSpace().setAccuracy(1f/120f);

    }



    private void initKeys() {

    inputManager.addMapping(“forward”, new KeyTrigger(KeyInput.KEY_I));

    inputManager.addMapping(“backpedal”, new KeyTrigger(KeyInput.KEY_K));

    inputManager.addMapping(“turnleft”, new KeyTrigger(KeyInput.KEY_J));

    inputManager.addMapping(“turnright”, new KeyTrigger(KeyInput.KEY_L));



    inputManager.addListener(playerMovementListener, new String[]{“forward”, “backpedal”, “turnleft”, “turnright”});

    }



    private AnalogListener playerMovementListener = new AnalogListener() {

    public void onAnalog(String action, float value, float tpf) {

    if(action.equals(“forward”)) {

    player.goForward();

    }

    else if(action.equals(“backpedal”)) {

    player.goBackward();

    }

    else if(action.equals(“turnleft”)) {

    player.turn(tpf, 1);

    }

    else if(action.equals(“turnright”)) {

    player.turn(tpf, -1);

    }

    }

    };



    private void initWorld() {

    Box worldMesh = new Box(Vector3f.ZERO, 100f, 1, 100f);

    Geometry worldModel = new Geometry(“The World”, worldMesh);

    Material worldMat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

    worldMat.setColor(“m_Color”, ColorRGBA.Yellow);

    worldModel.setMaterial(worldMat);



    PhysicsNode worldPN = new PhysicsNode(new BoxCollisionShape(new Vector3f(100f, 1, 100f)), 0);

    worldPN.attachChild(worldModel);

    worldPN.setLocalTranslation(new Vector3f(0, -5, 0));



    Material wireMat = new Material(assetManager, “Common/MatDefs/Misc/WireColor.j3md”);

    wireMat.setColor(“m_Color”, ColorRGBA.Blue);

    worldPN.attachDebugShape(wireMat);



    rootNode.attachChild(worldPN);

    rootNode.updateGeometricState();

    getPhysicsSpace().add(worldPN);

    }



    private void initPlayer() {

    Box playerMesh = new Box(Vector3f.ZERO, 1, 0.5f, 1);

    Geometry playerModel = new Geometry(“Player Geometry”, playerMesh);

    Material playerMat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

    playerMat.setColor(“m_Color”, ColorRGBA.Green);

    playerModel.setMaterial(playerMat);



    playerModel.setLocalTranslation(0, -0.5f, 0);

    player = new Player(playerModel);



    rootNode.attachChild(player);

    rootNode.updateGeometricState();

    getPhysicsSpace().add(player);

    }



    public class Player extends PhysicsCharacterNode {



    private Geometry playerModel;

    private float velocity;

    private Vector3f move;



    public Player(Geometry model) {

    super(new SphereCollisionShape(1.0f), 0.5f);

    this.playerModel = model;

    this.attachChild(playerModel);

    move = new Vector3f(0, 0, 0);



    Material wireMat = new Material(assetManager, “Common/MatDefs/Misc/WireColor.j3md”);

    wireMat.setColor(“m_Color”, ColorRGBA.Blue);

    this.attachDebugShape(wireMat);



    setFallSpeed(10);

    setGravity(10);

    setJumpSpeed(30);





    }



    public void goForward() {

    Vector3f rotation = getCurrentModelRotation();

    move = rotation.mult(.25f);

    }



    public void goBackward() {

    Vector3f rotation = getCurrentModelRotation();

    move = rotation.negate().mult(.25f);

    }



    public void turn(float tpf, int direction) {

    if(onGround()) {

    playerModel.rotate(0, direction * 2.2f * tpf, 0);

    }

    }



    private Vector3f getCurrentModelRotation() {

    return playerModel.getLocalRotation().getRotationColumn(2).normalize();

    }



    @Override

    public void updateLogicalState(float tpf) {

    super.updateLogicalState(tpf);



    velocity = move.length();

    System.out.println("Magnitude/V : " + velocity);



    setWalkDirection(move);



    move = Vector3f.ZERO;

    }

    }

    }

  1. I’d rather take the input value, then compute a walkDirection and rotation for the model and set that instead of setting it to the model first and then reading it again to apply it to the physics.
  2. As said it mostly depends on the velocities, it could have multiple reasons it remedied the “jittering” you were experiencing, also there could be multiple ways to remedy.
  3. As said, if your framerate drops below 60 fps the physics calculate 2 steps per frame to compensate, this might be one reason why you get unsteady results, because its switching back and forth between one and two steps.



    Cheers,

    Normen

Hello once again!


  1. I’ve read and read and read your answer here, but I just don’t understand how I could do it that way. How would I know which direction to face the walkDirection without checking the model for it’s rotation?


  2. Gotcha.


  3. Ah yeah, you’re right. I bumped up the frame cap to 120 fps, and it’s fine at all accuracy levels. Maybe I’ll just leave it there.



    Thanks again! :slight_smile:

Well I dont know how you apply the rotation to the model in the first place. If you just directly increase the rotation of the model on a keypress you could also do so to some Quaternion instead and apply that to the model but its almost the same if you dont use the world rotation value of the model spatial. In any case the rotation of the model is just a rotation, so you dont depend on the model in the first place.



Cheers,

Normen

Well before I was just using playerModel.rotate(x,y,z); Whenever the player decides they want to turn their character. Then I’d just get the rotation when I wanted to move forward or backwards.



Took me… An hour I think to realize that I needed to multiply the quaternions to add an angle… Oyie. So confusing. I know I took Linear Algebra a long time ago but man it sure isn’t with me right now =p



So I’m guessing you meant something like this for the inner class then? Using a stored quaternion in the class to handle everything?



public class Player extends PhysicsCharacterNode {



private Geometry playerModel;

private float velocity;

private Vector3f move;

private Quaternion rotation;



public Player(Geometry model) {

super(new SphereCollisionShape(1.0f), 0.5f);

this.playerModel = model;

this.attachChild(playerModel);



move = new Vector3f(0, 0, 0);

rotation = new Quaternion();



Material wireMat = new Material(assetManager, “Common/MatDefs/Misc/WireColor.j3md”);

wireMat.setColor(“m_Color”, ColorRGBA.Blue);

this.attachDebugShape(wireMat);



setFallSpeed(10);

setGravity(10);

setJumpSpeed(30);





}



public void goForward() {

move = rotation.getRotationColumn(2).mult(.25f);

}



public void goBackward() {

move = rotation.getRotationColumn(2).negateLocal().mult(.25f);

}



public void turn(float tpf, int direction) {

if(onGround()) {

rotation.multLocal(new Quaternion().fromAngles(0, direction * 2.2f * tpf, 0));

rotation.normalize();

playerModel.setLocalRotation(rotation);

}

}



private Vector3f getCurrentModelRotation() {

return playerModel.getLocalRotation().getRotationColumn(2);

}



@Override

public void updateLogicalState(float tpf) {

super.updateLogicalState(tpf);



velocity = move.length();

System.out.println("Magnitude/V : " + velocity + " Rotate : " + rotation.toString());



setWalkDirection(move);



move = Vector3f.ZERO;

}

}

Yeah, something like that, as said it wont matter too much for your small example and its perfectly fine (as in no problem) to do it like you did but you should not feel dependent on the model for the rotation if you know what I mean :wink:



Happy coding,

Normen

Alright! I got the movement straightened out with drift and everything. So I’ve gotten back to the original problem with the “stuff” needing the floor coordinates below the Player Object. I thought after I looked at the classes and the javadoc I understood what needed to be done. But I haven’t had any success doing it the way I thought =p



public void updateLogicalState(float tpf) {

super.updateLogicalState(tpf);



drift(tpf);



move = rotation.getRotationColumn(2).mult(velocity);



setWalkDirection(move);

move = Vector3f.ZERO;



getPhysicsSpace().rayTest(new Vector3f(0, 10, 0), new Vector3f(0, 100, 0), rayListener);

}



PhysicsRayResultListener rayListener = new PhysicsRayResultListener() {

public void rayCollision(PhysicsCollisionObject arg0, Vector3f arg1,

float arg2, boolean arg3) {

System.out.println(arg0);

System.out.println(arg1);

System.out.println(arg2);

System.out.println(arg3);

System.out.println(“wuuut?”);

}

};




The above is what I’ve attempted so far inside the Player inner-class. It’s positioned at 0,10,0 just for the initial go round’, that will change. I have an odd feeling I’m supposed to add the listener to something but I didn’t see a way to add the listener to the PhysicsSpace. Either that I’m going about this completely wrong XD



Also, how is the impact point extracted from this? The only vector in there appears to be a normal?



Thanks! :slight_smile:

Your vector doesnt point downwards but upwards. (0,10,0) → (0,100,0)

But generally it might be there are still problems with this feature as its just been implemented. The listener is added with the rayTest() method.

Cheers,

Normen

Ahah. Silly mistake eh? Anyways, yeah I’m not sure if I can gleam anything useful from what it returns.



I figured that I needed to use it as:



getPhysicsSpace().rayTest(player.getWorldTranslation(), player.getWorldTranslation().addLocal(0, -100, 0), rayListener);



But it doesn’t seem to return any results. Neither does replacing it with the getLocalTranslation().



I do get something weird back if I use:



getPhysicsSpace().rayTest(player.getWorldTranslation(), player.getLocalTranslation().addLocal(0, -100, 0), rayListener);



But that screams wrong anyway. What it returns doesn’t look correct:



Floor (PhysicsNode)

(1.2732969E-11, 1.0, -3.8146973E-6)

0.009997465

true

wuuut?



This is with the player a bit down the Z- axis.



Will this method return the contact point or distance somehow? Or is that what that indeed is?



Thanks again!

In any instance you should not do this: player.getXXXTranslation().addLocal() :o

This will corrupt the location of the model, as you locally add the other vector to the models location vector.



The first value should be your collision location, yes. Using getWorldXXX() requires doing a rootNode.updateGeometricState() to get correct values. You should probably directly use the physics location of the node via getPhysicsLocation(). This is always in world coordinates and can be read from the physics thread. This value represents the location of the actual bullet physics object which is applied to the spatial when updateGeometricState() is called on the rootNode.



Cheers,

Normen

Ah. Yeah, that sounds bad =p



PhysicsCharacterNode doesn’t have a getPhysicsLocation() method though… I assume it could be brought over from PhysicsNode?



Still, the other problem remains, how do I get the contact point on the surface of the object that the ray hits? The returned values are the collision object, the hit norm, the hit fraction (whatever that is? :D), and a boolean to tell you if the norm is returned in world space.



Thanks!

It should ba a location vector, not a normal, might be its named wrong.

The vector it’s returning back is REALLY strange though. The floor object comes back alright as the object it should be, but the vector’s y never changes even when the player jumps. Instead, the x and z of the returned vector go bonkers.



Sitting at 0,0,0 -

Floor (PhysicsNode)

(0.0, 1.0, 0.0)

0.01

true



Sitting at (12.799015, -3.0000002, 23.343159) -

Floor (PhysicsNode)

(1.1444092E-5, 1.0, 1.1444092E-5)

0.009981246

true



Jumping straight up at (12.799015, -1.9027795, 23.343159) -

Floor (PhysicsNode)

(5.45679E-6, 1.0, 5.45679E-6)

0.02096326

true



The system outs are just arg0 - arg3 output to the console. So that is collisionObject, norm, fraction, and normInWorldSpace respectively.

As a followup, I’ve tried numerous ways to go about using the rayTest, and it reacts the same in every test I can come up with.



I just broke down and did a rayTest from 15,0,15 to 15,-100,15 that should be piercing a box that is at 0, -5, 0 with dimensions 100, 1, 100 - and it reports the correct object, but the vector3f in the listener is always (Close to Zero, 1.0, Close to Zero). I can’t even wager a guess as to what the float does, but it’s far too small to be the correct distance.



Back on the moving example, it always reports the correct object below the player, but I don’t think I can gleam the actual point on the collision object that the ray pierced.



At this point I’m at a loss as to what to try next. It’s great that I can at least get the right object for picking, but I can’t seem to get anything more than that.

Completely stumped. Posting everything I’ve written so far… It’s sorta a beast 8O



One other funny thing I noticed. Why is it sometimes when the player is under the box I have planted in the air, if the player jumps and basically “headbutts” the box, it’s as if the player sticks to it and floats down slowly rather than bonking it and bouncing back to the ground. Seems to happen most often when jumping directly upwards with no forward/backward motion.



PhysicsPlayground.java

[java]

public class PhysicsPlayground extends SimpleBulletApplication {



private static PhysicsPlayground application;

private static AppSettings settings;

private static PhysicsSpace mainPS;

private PhysicsPlayer player;



public static void main(String[] args) {

application = new PhysicsPlayground();

settings = new AppSettings(true);

settings.setFrameRate(120);

settings.setTitle(“Physics Playground (10/1/2010)”);

application.setSettings(settings);

application.start();

}



public static PhysicsPlayground getApplication() {

return application;

}



public static PhysicsSpace getMainPS() {

return mainPS;

}



@Override

public void simpleInitApp() {

flyCam.setMoveSpeed(50);



mainPS = getPhysicsSpace();



initWorld();

initPlayer();

initKeys();



getPhysicsSpace().setAccuracy(1f/120f);

}



private void initKeys() {

inputManager.addMapping(“forward”, new KeyTrigger(KeyInput.KEY_I));

inputManager.addMapping(“backpedal”, new KeyTrigger(KeyInput.KEY_K));

inputManager.addMapping(“turnleft”, new KeyTrigger(KeyInput.KEY_J));

inputManager.addMapping(“turnright”, new KeyTrigger(KeyInput.KEY_L));

inputManager.addMapping(“jump”, new KeyTrigger(KeyInput.KEY_M));

inputManager.addMapping(“croutch”, new KeyTrigger(KeyInput.KEY_O));

inputManager.addMapping(“run”, new KeyTrigger(KeyInput.KEY_LSHIFT));



inputManager.addListener(playerMovementListener, new String[]{“forward”, “backpedal”, “turnleft”, “turnright”});

inputManager.addListener(playerActionListener, new String[]{“jump”, “croutch”, “run”});

}



private AnalogListener playerMovementListener = new AnalogListener() {

public void onAnalog(String action, float value, float tpf) {

if(action.equals(“forward”)) {

player.forward();

}

else if(action.equals(“backpedal”)) {

player.backpedal();

}

else if(action.equals(“turnleft”)) {

player.turn(tpf, 1);

}

else if(action.equals(“turnright”)) {

player.turn(tpf, -1);

}

}

};



private ActionListener playerActionListener = new ActionListener() {

public void onAction(String name, boolean keyPressed, float tpf) {

if(name.equals(“jump”) && keyPressed) {

player.jump();

Sphere s = new Sphere(16, 16, .2f);

Geometry sg = new Geometry(“Jumped!”, s);

Material sm = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

sm.setColor(“m_Color”, ColorRGBA.Red);

sg.setMaterial(sm);



sg.setLocalTranslation(new Vector3f(player.getLocalTranslation()));

rootNode.attachChild(sg);

}

if(name.equals(“croutch”) && keyPressed) {

player.croutch();

}

if(name.equals(“run”)) {

if(keyPressed) {

player.setRunning(true);

}

else if(!keyPressed) {

player.setRunning(false);

}

}

}

};



private void initWorld() {

Box worldMesh = new Box(Vector3f.ZERO, 100f, 1, 100f);

Geometry worldModel = new Geometry(“The World”, worldMesh);

Material worldMat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

worldMat.setColor(“m_Color”, ColorRGBA.Yellow);

worldModel.setMaterial(worldMat);



PhysicsNode worldPN = new PhysicsNode(new BoxCollisionShape(new Vector3f(100f, 1, 100f)), 0);

worldPN.attachChild(worldModel);

worldPN.setLocalTranslation(new Vector3f(0, -5, 0));

worldPN.setName(“Floor”);



Material wireMat = new Material(assetManager, “Common/MatDefs/Misc/WireColor.j3md”);

wireMat.setColor(“m_Color”, ColorRGBA.Blue);

worldPN.attachDebugShape(wireMat);



rootNode.attachChild(worldPN);

rootNode.updateGeometricState();

getPhysicsSpace().add(worldPN);



Box obstacleMesh = new Box(Vector3f.ZERO, 1, 1, 1);

Geometry obstacleModel = new Geometry(“Obstacle”, obstacleMesh);

Material obstacleMat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

obstacleMat.setColor(“m_Color”, ColorRGBA.Red);

obstacleModel.setMaterial(obstacleMat);



PhysicsNode obstaclePN = new PhysicsNode(new BoxCollisionShape(new Vector3f(1, 1, 1)), 0);

obstaclePN.attachChild(obstacleModel);

obstaclePN.setLocalTranslation(new Vector3f(-2, -3.5f, 0));



obstaclePN.attachDebugShape(wireMat);



rootNode.attachChild(obstaclePN);

rootNode.updateGeometricState();

getPhysicsSpace().add(obstaclePN);



Box ceilingMesh = new Box(Vector3f.ZERO, 3, 0.5f, 3);

Geometry ceilingModel = new Geometry(“Obstacle”, ceilingMesh);

Material ceilingMat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

ceilingMat.setColor(“m_Color”, ColorRGBA.Red);

ceilingModel.setMaterial(ceilingMat);



PhysicsNode ceilingPN = new PhysicsNode(new BoxCollisionShape(new Vector3f(3, 0.5f, 3)), 0);

ceilingPN.attachChild(ceilingModel);

ceilingPN.setLocalTranslation(new Vector3f(6, -2f, 6));



ceilingPN.attachDebugShape(wireMat);



rootNode.attachChild(ceilingPN);

rootNode.updateGeometricState();

getPhysicsSpace().add(ceilingPN);

}



private void initPlayer() {

Box playerMesh = new Box(Vector3f.ZERO, 0.5f, 0.25f, 0.5f);

Geometry playerModel = new Geometry(“Player Geometry”, playerMesh);

Material playerMat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

playerMat.setColor(“m_Color”, ColorRGBA.Green);

playerModel.setMaterial(playerMat);



playerModel.setLocalTranslation(0, -0.25f, 0);

SphereCollisionShape c = new SphereCollisionShape(playerMesh.xExtent);

player = new PhysicsPlayer(playerModel, c);



rootNode.attachChild(player);

rootNode.updateGeometricState();

getPhysicsSpace().add(player);

}

}

[/java]



And the Player - PlayerPhysics.java

[java]

public class PhysicsPlayer extends PhysicsCharacterNode {



Geometry debugSphere;



public static enum PlayerState {

IDLE, WALKING, BACKPEDALING, RUNNING, CROUTCHED, CRAWLING, CRAWLINGBACKWARDS, JUMPING, FALLING

}



/** Geometry /

private Geometry playerModel;



/
Movement Parameters (Initialized to Zero) /

private Vector3f move = new Vector3f(0, 0, 0);

private Quaternion rotation = new Quaternion();

private boolean croutched = false;

private boolean running = false;

private PlayerState playerState;



/
Movement Characteristics (Defaults) **/

private float velocity = 0;

private float acceleration = .025f; // Decel too?

private float maxForewardWalkVelocity = .25f;

private float maxForewardRunVelocity = .4f;

private float maxBackpedalVelocity = .125f;

private float runningFalloff = 0;



public PhysicsPlayer(Geometry model, SphereCollisionShape collisionShape) {

super(collisionShape, 0.5f);

this.playerModel = model;

this.attachChild(playerModel);



Material wireMat = new Material(PhysicsPlayground.getApplication().getAssetManager(),

“Common/MatDefs/Misc/WireColor.j3md”);

wireMat.setColor(“m_Color”, ColorRGBA.Blue);

this.attachDebugShape(wireMat);



setFallSpeed(10);

setGravity(10);

setJumpSpeed(5);



playerState = PlayerState.IDLE;



Sphere sphere = new Sphere(16, 16, .25f);

debugSphere = new Geometry(“Blah”, sphere);

Material smat = new Material(PhysicsPlayground.getApplication().getAssetManager(),

“Common/MatDefs/Misc/SolidColor.j3md”);

smat.setColor(“m_Color”, ColorRGBA.Orange);

debugSphere.setMaterial(smat);

attachChild(debugSphere);

}



public void forward() {

if(onGround()) {

velocity += acceleration;

if(isRunning()) {

runningFalloff = velocity;

}

if(isCroutched()) { // Different V for croutched speed?

if(velocity > maxForewardWalkVelocity /2f) {

velocity = maxForewardWalkVelocity /2f;

}

}

else {

if(!isRunning()) {

if(runningFalloff > maxForewardWalkVelocity) {

velocity = runningFalloff;

}

else if(velocity > maxForewardWalkVelocity) {

velocity = maxForewardWalkVelocity;

}

}

else if(isRunning() && velocity > maxForewardRunVelocity) {

velocity = maxForewardRunVelocity;

}

}

}

}



public void backpedal() {

if(onGround()) { // Always half of foreward? Also, reverse croutch speed?

velocity -= acceleration /2f;

if(velocity < -maxBackpedalVelocity) {

velocity = -maxBackpedalVelocity;

}

}

}



public void turn(float tpf, int direction) {

if(onGround()) {

rotation.multLocal(new Quaternion().fromAngles(0, direction * 2.2f * tpf, 0));

rotation.normalize();

playerModel.setLocalRotation(rotation);

}

}



public void croutch() {

if(onGround() && velocity < maxForewardWalkVelocity / 1.25f) { // TODO : Should it check V? Only croutch/decroutch at V < amount

// if(onGround()) {

// Vector3f preCroutchLoc = new Vector3f(this.getLocalTranslation());



// Check ceiling distance/clearance

// TODO: MAKE IT A METHOD THAT RETURNS A BOOOOOOOOOL

Ray clearanceRay = new Ray(this.getLocalTranslation(), Vector3f.UNIT_Y);

CollisionResults cr = new CollisionResults();

// PhysicsPlayground.getApplication()…getParent().updateGeometricState();

PhysicsPlayground.getApplication().getRootNode().collideWith(clearanceRay, cr);



Vector3f dist = new Vector3f(Vector3f.ZERO);

if(cr.size() > 0) {

dist = cr.getFarthestCollision().getContactPoint();

System.out.println(cr.getFarthestCollision().getGeometry().getName());

}

System.out.println("Dist: " + dist);

System.out.println("LT : " + this.getLocalTranslation());



SphereCollisionShape c = (SphereCollisionShape) this.getCollisionShape();

PhysicsPlayground.getApplication().getRootNode().detachChild(this);

PhysicsPlayground.getMainPS().remove(this);



if(!isCroutched()) { // Croutching

setCroutched(true);

c = new SphereCollisionShape(c.getRadius() * .5f);

}

else { // De-Croutching/Un-Croutching (lawl!)

setCroutched(false);

c = new SphereCollisionShape(c.getRadius() / .5f);

}

this.setCollisionShape©;

PhysicsPlayground.getApplication().getRootNode().attachChild(this);

// rootNode.updateGeometricState(); // Something with the rayTest needs this? Oh because it needs WorldTransform…

PhysicsPlayground.getMainPS().add(this);



// this.setLocalTranslation(preCroutchLoc);



Material wireMat = new Material(PhysicsPlayground.getApplication().getAssetManager(),

“Common/MatDefs/Misc/WireColor.j3md”);

wireMat.setColor(“m_Color”, ColorRGBA.Blue);

this.attachDebugShape(wireMat);

}

}



// .75 is like weight

// .3 is like forward air drag

public void drift(float tpf) {

float drifty = .75f * tpf;

if(!onGround()) {

drifty = .75f * .3f * tpf;

}

if(velocity < -FastMath.FLT_EPSILON) {

velocity += drifty;

if(velocity > 0) {

velocity = 0;

}

}

else if(velocity > FastMath.FLT_EPSILON) {

if(runningFalloff > maxForewardWalkVelocity) {

runningFalloff -= drifty;

}

velocity -= drifty;

if(velocity < 0) {

velocity = 0;

}

}

}



public boolean isCroutched() {

return croutched;

}



public void setCroutched(boolean croutched) {

this.croutched = croutched;

}



public boolean isRunning() {

return running;

}



public void setRunning(boolean running) {

this.running = running;

}



public PlayerState getPlayerState() {

return playerState;

}



@Override

public void updateLogicalState(float tpf) {

super.updateLogicalState(tpf);



drift(tpf);



move = rotation.getRotationColumn(2).mult(velocity);

// System.out.println("Magnitude/V : " + velocity + " Rotate : " + rotation.toString());

// System.out.println("RunningFall : " + runningFalloff);



setWalkDirection(move);

move = Vector3f.ZERO;



if(velocity == 0) {

if(!isCroutched()) playerState = PlayerState.IDLE;

else playerState = PlayerState.CROUTCHED;

}

else {

if(!isCroutched()) {

if(velocity > 0) {

if(velocity > maxForewardWalkVelocity) playerState = PlayerState.RUNNING;

if(velocity < maxForewardWalkVelocity) playerState = PlayerState.WALKING;

}

else {

playerState = PlayerState.BACKPEDALING;

}

}

else {

if(velocity > 0) playerState = PlayerState.CRAWLING;

if(velocity < 0) playerState = PlayerState.CRAWLINGBACKWARDS;

}

}

// System.out.println("State : " + playerState);



System.out.println(this.getLocalTranslation());

// System.out.println(this.getLocalTranslation().addLocal(0, -100, 0));

Vector3f temp = new Vector3f(this.getLocalTranslation());

temp.y -= 100;

Vector3f tempS = new Vector3f(this.getLocalTranslation());

tempS.y -= .5f;

// PhysicsPlayground.getApplication().getRootNode().attachChild(debugSphere);

// debugSphere.setLocalTranslation(this.getLocalTranslation());

// debugSphere.getLocalTranslation().y = -2;

PhysicsPlayground.getMainPS().rayTest(this.getLocalTranslation(), temp, rayListener);

}



PhysicsRayResultListener rayListener = new PhysicsRayResultListener() {

public void rayCollision(PhysicsCollisionObject arg0, Vector3f arg1,

float arg2, boolean arg3) {

// if(debugSphere.getParent() != null) {

// debugSphere.getParent().detachChildNamed(“Blah”);

// }

// attachChild(debugSphere);

debugSphere.setLocalTranslation(arg1.negate());

System.out.println(arg0);

System.out.println(arg1);

System.out.println(arg2);

System.out.println(arg3);

}

};

}

[/java]

Maybe you try making a character out of a normal physics node, switching it to kinematic mode when its being controlled or somethung similar. As said, the CharacterNode in bullet is a very simple simulation.

Cheers,

Normen