My example of a Physics Vehicle

I've been doing some tweeking to the TestVehicle class and finally came up with a solution for a suspension system that works well. I'm posting the source code for the physics part here, so anyone can test it and share some ideas.



My car is made of one main class for the Car

  • Physics node for the chassis
  • Two suspension systems
  • Methods for accelerating, braking (releasing gas pedal actually), steering and unsteering



    One class for the suspension system:
  • Two physics nodes for representing the suspension anchors
  • Two joints, with one translational axis (Y), for representing the suspension course
  • Two Wheels attached
  • helper methods for the actions



    One class for representing the Wheel
  • One physics node (capsule) for representing the rubber wheel (material)
  • One joint with the suspension, with two rotational axis, for accelerating (sppining) the wheel, and steering it
  • helper methods for the actions



    Just add one instance of this class to your rootNode (you'll need a PhysicsSpace of course) and create input actions for using the car methods for accelerating and steering.



    One can do some tweeking with the parameters to get different driving feelings. Here is the source code (without includes) for these three car classes:



    The car class



    public class Car extends Node{

    private static final long serialVersionUID = 1L;

    private DynamicPhysicsNode chassis;

    private Suspension rearSuspension, frontSuspension;



    public Car(PhysicsSpace pSpace) {

    buildChassis(pSpace);

    buildSuspension(pSpace,chassis);

    }



    private void buildSuspension(PhysicsSpace pSpace, DynamicPhysicsNode chassis) {

    rearSuspension = new Suspension(pSpace, chassis, new Vector3f(-7,0,0));

    this.attachChild(rearSuspension);

    frontSuspension = new Suspension(pSpace, chassis, new Vector3f(7,0,0));

    this.attachChild(frontSuspension);

    }



    private void buildChassis(PhysicsSpace pSpace) {

    chassis = pSpace.createDynamicNode();

    chassis.createBox("chassis");

    chassis.generatePhysicsGeometry();

                    chassis.setMaterial( Material.CONCRETE );

                    chassis.setLocalScale(new Vector3f(15f,0.1f,3f));

    chassis.setMass(900);

    this.attachChild(chassis);

    }



    public void accelerate(float amount) {

    rearSuspension.accelerate(amount);

    frontSuspension.accelerate(amount);

    }



    public void brake(){

    rearSuspension.brake();

    frontSuspension.brake();

    }



    public void steer(float direction) {

    frontSuspension.steer(direction);

    }



    public void unsteer(){

    frontSuspension.unsteer();

    }



    public DynamicPhysicsNode getChassis() {

    return chassis;

    }

    }



    The suspension class



    public class Suspension extends Node {



    private static final long serialVersionUID = 1L;

    private Wheel leftWheel, rightWheel;

    private DynamicPhysicsNode leftAnchor, rightAnchor;



    public Suspension(PhysicsSpace pSpace, DynamicPhysicsNode chassis, Vector3f position) {

    leftAnchor = buildAnchor(pSpace, chassis, position.add(new Vector3f(0,-1,3)));

    rightAnchor = buildAnchor(pSpace, chassis, position.add(new Vector3f(0,-1,-3)));

    leftWheel = new Wheel(leftAnchor, new Vector3f(0,0,2));

    this.attachChild(leftWheel);

    rightWheel = new Wheel(rightAnchor, new Vector3f(0,0,-2));

    this.attachChild(rightWheel);

    }



    private DynamicPhysicsNode buildAnchor(PhysicsSpace pSpace, DynamicPhysicsNode chassis, Vector3f translation) {

    DynamicPhysicsNode anchor = pSpace.createDynamicNode();

    anchor.setLocalTranslation(chassis.getLocalTranslation().add(translation));

    anchor.createBox("anchor");

    anchor.setLocalScale(0.1f);

    anchor.setMass(50);

    anchor.setMaterial(Material.IRON);

    this.attachChild(anchor);

    Joint j = pSpace.createJoint();

    j.attach(anchor, chassis);

    TranslationalJointAxis axis = j.createTranslationalAxis();

    axis.setPositionMaximum(0.3f);

    axis.setPositionMinimum(-0.3f);

    axis.setAvailableAcceleration(15);

    axis.setDesiredVelocity(0);

    axis.setDirection(new Vector3f(0,1,0));

    return anchor;

    }



    public void accelerate(float direction) {

    leftWheel.accelerate(direction);

    rightWheel.accelerate(direction);

    }



    public void brake() {

    leftWheel.brake();

    rightWheel.brake();

    }



    public void steer(float direction) {

    leftWheel.steer(direction);

    rightWheel.steer(direction);

    }



    public void unsteer() {

    leftWheel.unsteer();

    rightWheel.unsteer();

    }

    }



    …and Wheel class:



    public class Wheel extends Node {



            private static final long serialVersionUID = 1L;

    private DynamicPhysicsNode capsule;

    private RotationalJointAxis throttleAxis, steerAxis;



    public Wheel(DynamicPhysicsNode anchor, Vector3f position) {

    capsule = anchor.getSpace().createDynamicNode();

    capsule.setLocalTranslation(anchor.getLocalTranslation().add(position));

    capsule.createCapsule("capsule");

    capsule.generatePhysicsGeometry();

    capsule.setMaterial(Material.RUBBER);

    capsule.setMass(30);



    Joint j = anchor.getSpace().createJoint();

    j.attach(anchor, capsule);

    j.setAnchor(capsule.getLocalTranslation().subtract(anchor.getLocalTranslation()));



    steerAxis = j.createRotationalAxis();

    steerAxis.setDirection(new Vector3f(0,1,0));

    steerAxis.setAvailableAcceleration(1000);



    throttleAxis = j.createRotationalAxis();

    throttleAxis.setDirection(new Vector3f(0,0,1));

    throttleAxis.setRelativeToSecondObject(true);

    throttleAxis.setAvailableAcceleration(2000);



    unsteer();

    this.attachChild(capsule);

    }



    public void accelerate(float direction){

    throttleAxis.setDesiredVelocity(direction);

    }



    public void brake(){

    throttleAxis.setDesiredVelocity(0);

    }



    public void steer(float direction){

    steerAxis.setDesiredVelocity(direction);

    steerAxis.setPositionMaximum(0.5f);

    steerAxis.setPositionMinimum(-0.5f);

    }



    public void unsteer(){

    steerAxis.setDesiredVelocity(0);

    steerAxis.setPositionMaximum(0);

    steerAxis.setPositionMinimum(0);

    }

    }



    The input action classes used for controlling the wheels:



    public class SteerAction implements InputActionInterface {



    Car car;

    float direction;



    public SteerAction(Car car, float direction) {

    this.car = car;

    this.direction = direction;

    }



    public void performAction(InputActionEvent e) {

    if ( e.getTriggerPressed() ) {

                car.steer(direction);

            }

            else {

                car.unsteer();

            }

    }



    }



    public class TractionAction implements InputActionInterface {



    Car car;

    float amount;



    public TractionAction(Car car, float amount) {

    this.car = car;

    this.amount = amount;

    }



    public void performAction(InputActionEvent e) {

    if ( e.getTriggerPressed() ) {

                car.accelerate(amount);

            }

            else {

                car.brake();

            }

    }



    }



    Use this code to bind the actions to the input controller:



                    input.addAction(new TractionAction(car, 300),InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_UP, InputHandler.AXIS_NONE, false);

    input.addAction(new TractionAction(car, -300),InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_DOWN, InputHandler.AXIS_NONE, false);

    input.addAction(new SteerAction(car, -400),InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_LEFT, InputHandler.AXIS_NONE, false);

    input.addAction(new SteerAction(car, 400),InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_RIGHT, InputHandler.AXIS_NONE, false);



    remember that "car" is your instance of the Car class.



    I'd really apreciate any suggestion for this aproach.

Thanks for the code l ! I'll test it soon. I guess supsension was the solution … :lol:





Kine 

Thanks for the code! Just tested it on a 40x40x2 Box as the floor but the Car falls through after hitting the floor once…?



Any help appreciated :slight_smile:

I

Yeah ! It works fine !!

I obtained better results with Spheres instead of Capsules when turning at high speed, and had problem too at high  speed with my circuit when using triangles.





Thank you !



Kine —> Next cap: bullets & homing missiles programming …  :slight_smile:

Kine,



I also changed to sphere instead of capsules. Made a couple of ajustments as well. One you should consider is putting some velocity to the translation axis in the suspension Joint… So the car will bem a bit lifted and the general feeling is better.



I put this code here to create the axis (the only difference is the available acelleration and the velocity):



                TranslationalJointAxis axis = j.createTranslationalAxis();

axis.setPositionMaximum(0.5f);

axis.setPositionMinimum(-0.5f);

axis.setAvailableAcceleration(3500);

axis.setDesiredVelocity(-500);

axis.setDirection(new Vector3f(0,1,0));



Did you ever had some problems with one of the wheels getting into the terrain (and then strange things can happen because of the unusual forces and contacts)?



I don't know if this is related to slow framerates. I tried triangule acurate collisions and had no better luck.



Thanks in advance.



PS. I'll also start the coding for bullets, rockets and related stuff.

Irrisor,



Can you give me some hint here? I read in another post about a minigolf game project that was suffering the same kind of problem, I gess. They said that when the ball was fast enougth it could run through some walls or terrain. Then they tried making the ball bigger. You said in a post you've heard that ODE has some issues with triangle-triangle collision.



I'll try putting the physics terrain mesh (originated from a terrainblock <- from a ImageBasedHeightMap) WITH triangle accuracy - generatePhysicsGeometry(true) - and the wheels spheres WITHOUT triangle accuracy - generatePhysicsGeometry() only.



Is that the correct way of doing it?

No luck with that. I think the problem is related to the issue reported in some posts about problems with the collision detection implemented in ODE/ODEJava. I'm a bit sad and unhappy that I didn't find a solution yet.



See this post for a reference: http://www.jmonkeyengine.com/jmeforum/index.php?topic=2031.0



JME and JMEPhysics are great tools and the foruns are even better. I'll keep tryong though. Next effort will be testing different wheel sizes vs. terrain TriMesh triangle sizes to see if I have any luck.



Irrisor,



You mentioned (in response to a post) about the implementation of collision detection with a HeightField - http://www.jmonkeyengine.com/jmeforum/index.php?topic=3099.msg27635#msg27635 - Any adavance? Want some help in doing that? I'm preparing for a PhD in computer science and will keep on game/physics programming for quite a while.

I did not do/hear anything about using height field with ODEJava / jME Physics, sorry. But any efforts are appreciated :wink:



Using sphere or capsule on trimesh terrain should be fine.



With ODE's collision detection there are usually two issues:

  1. Number of contact points per step is too low -> some contacts are discarded. I increased the number of contacts to 50 in OdePhysicsSpace (Odejava.setMaxContactGeomsPerNearcallback( 50 );). This really should be sufficient. If it's not you can test if it helps to raise it. But this costs a lot of performance. If it helps to raise the number use a terrain with bigger triangles instead.
  2. Objects are too fast. If your objects move more then the acceptable penetration depth in one physics step you should increase the number of steps per second (lower the step size). You can try something like this in your app:


        if ( physicsSpace instanceof OdePhysicsSpace ) {
            OdePhysicsSpace space = (OdePhysicsSpace) physicsSpace;
            space.setStepSize( 0.005f );
            space.setUpdateRate( 200 );
        }


Be aware that applied forces should be per step and depend on the step size.

If nothing helps, try with a box instead of terrain first...

Thanks a lot Irrisor,



I'd already tried the bigger triangles and it really worked well. I'll try to raise the max contacts:


(Odejava.setMaxContactGeomsPerNearcallback( 50 )


I'm aware of the interdependencies of step size, gravity, forces and etc. I'll keep you informed of the possible implementation of the heightfield conlision detection... First I'll study JMEPhysics/ODEJava a bit more.

[]'s

I've also got problems with my car falling though the ground. I've noticed that there is a very strong force that actually makes the car "pop" though the ground if I use this getPhysicsSpace().createJoint().attach(dynamicCarNode); to try to hold my car in mid air then its as if my car test is working fine.



What can that force be that pulls my car though the floor if I don't use that joint to keep my car on the ground?

joh said:

Thanks for the code! Just tested it on a 40x40x2 Box as the floor but the Car falls through after hitting the floor once...?

Any help appreciated :)


Ah, it works now. No idea what I did wrong, but I put up another test and it works smoothly :) Thanks!

I guess there is no point in just saying that it doesn’t work (I need to produce some code for you to check where my problem is) and so here it is. Notice that the dynamicCarNode.getLocalTranslation().y is -9 when the car is still on the ground (just before it pops though the ground) and the ground is about 0



Thanks for your help :wink:

I'll have a look at your code now and answer ASAP.

The problem is that you created a dynamicPhysicsNode and attached my car implementation to it. You don't need to create any dynamicNode at all… They are all defined and created inside the car, suspension and wheel classes.



change the createCar method to this:



private void createCar() {

car = new Car(getPhysicsSpace(), new Vector3f(0,10,0)); // note the Vector3f as parameter. It's the car initial position, 10 units over the floor.

rootNode.attachChild(car);

}



This is the new Car constructor (I've been tweeking this classes a bit, if you want I can send the whole new source as well):



public Car(PhysicsSpace pSpace, Vector3f position) {

buildChassis(pSpace, position);

buildSuspension(pSpace);

}



…and the new buildChassis internal method:



private void buildChassis(PhysicsSpace pSpace, Vector3f position) {

chassis = pSpace.createDynamicNode();

chassis.setLocalTranslation(position);

chassis.createBox("chassis");

chassis.generatePhysicsGeometry();

        chassis.setMaterial( Material.GHOST );

        //chassis.setLocalScale(new Vector3f(12f,0.1f,3f));

chassis.setMass(750);

chassis.setCenterOfMass(new Vector3f(0,-3,0));

this.attachChild(chassis);

}



Remember to remove the System.out.println line from your simpleUpdate method… There's no need at all to thar DynamicPhysicsNode in your test.



If it doesn't work I'll send you the complete sources.

this solves my problem thanks a lot  :smiley:

Hi Perrick ! First I want to thank you, all you tips helped me a lot. I'm a begginer in Java &  Game develloping too. I've been learning a lot with JMonkey. It's great !!



So I encouter the same problem with number of triangles per mesh and number of points impact …  my car "hitting the ground".



I tried to increase the number of PhysicSpace refresh and found how to increase the number of collision points per triangle too. It works fine but really annoys my CPU.



If I'm right increasing number of collisions as said by Renanse can be combined with decreasing number of triangles to avoid loose of computing speed…

I can't find how to modify the number of triangles you talked about … Can you bring me some help again ?



Another simple question: Will you use DynamicPhysicsNode for your rocket and bullets stuff ? Number of physics Object for a  8-player game would

be >50 Don't you think there is enough coding functions for spatials to make it possible without phys ? Perhaps CPU time would be saved… I precise I don't apply gravity to my projectiles … You've probably readed  the JME Physic implementation more than me, so do you think it should save some CPU time (I ask this question even if my Object programming view tell me the contrary… )



I suppose you pre-charge your 3d models rockets and other… Do you charge one once you've fired one or all at the beginning as adviced by our Frog… ?



PS: My game FPS is beginning to get very low with all these rockets stuff !! Oops I shoudn't have used a rescaled Zepplin 3d model for my rockets …  :smiley:



See you !!



Kine

I guess I have to pre-charge the 3ds model of all projectiles from a file to the memory and just take a copy of these each time I  fire…  ://

I should think twice about game coding before posting  :-o

In french "charge" means "load ".


So does load mean charge?  :stuck_out_tongue:



Do infantry commanders on the battlefield say "LOAD!!!"…oh wait, french…I guess that would never come up. :wink: