Vehicle control and Terrain

Hello there,

I’m a relative newcomer to JMonkey , but I’ve been through the tutorials and have a reasonable idea of the principles, although the Math is a bit daunting.

Anyway, my issue is that I’m trying to modify the TestFancyCar module to drive over my own terrain and road, but whatever I do I can’t seem to position the vehicle over my terrain. setLocalTranslation and move commands seem ineffective on both the vehicle and my terrain. The car drives ok, but always starts from the same position, no matter where I set its initial position. If I omit the SetupFloor routine, the car just sinks in oblivion.

The code follows. I can supply the Terrain.j3o file if someone can tell me where to put it (nicely). This is a WIP at the moment and will be refined later, I just want to get the basic game going.

I thank you in advance for any advice.

/* * Copyright (c) 2009-2012 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package mygame;

import com.jme3.app.SettingsDialog;
import com.jme3.app.SimpleApplication;
import com.jme3.bounding.BoundingBox;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.collision.shapes.MeshCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.control.VehicleControl;
import com.jme3.bullet.objects.VehicleWheel;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix3f;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.CameraNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.CameraControl;
import com.jme3.scene.shape.Box;
import com.jme3.shadow.BasicShadowRenderer;
import com.jme3.system.AppSettings;
import com.jme3.texture.Texture.WrapMode;

public class TestFancyCar extends SimpleApplication implements ActionListener {

private BulletAppState bulletAppState;
private VehicleControl player;
private VehicleWheel fr, fl, br, bl;
private Node node_fr, node_fl, node_br, node_bl;
public float wheelRadius;
public float steeringValue = 0;
public float accelerationValue = 0;
public Node carNode;
public Material mat;
public Box floor;
public Geometry floorGeom;
private RigidBodyControl floorPhy;
public Spatial roadGeo;
public RigidBodyControl roadPhysics;
public CameraNode camNode;
 
public static void main(String[] args) {
    TestFancyCar app = new TestFancyCar();
    app.start();
}

private void setupKeys() {
    inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_LEFT));
    inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_RIGHT));
    inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_UP));
    inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_DOWN));
    inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE));
    inputManager.addMapping("Reset", new KeyTrigger(KeyInput.KEY_RETURN));
    inputManager.addListener(this, "Lefts");
    inputManager.addListener(this, "Rights");
    inputManager.addListener(this, "Ups");
    inputManager.addListener(this, "Downs");
    inputManager.addListener(this, "Space");
    inputManager.addListener(this, "Reset");
}

@Override
public void simpleInitApp() 
{

    bulletAppState = new BulletAppState();
    stateManager.attach(bulletAppState);

// bulletAppState.getPhysicsSpace().enableDebug(assetManager);
if (settings.getRenderer().startsWith(“LWJGL”)) {
// BasicShadowRenderer bsr = new BasicShadowRenderer(assetManager, 512);
// bsr.setDirection(new Vector3f(-0.5f, -0.3f, -0.3f).normalizeLocal());
// viewPort.addProcessor(bsr);
}
viewPort.setBackgroundColor(ColorRGBA.Blue);
cam.setFrustumFar(550f);
flyCam.setMoveSpeed(10);
setupKeys();

 //   PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace());
   
    setupFloor();
    
    LoadRoad();
    buildPlayer();
   
    carNode.move(new Vector3f(-200f,30f,-100f)); // does not seem to work
    
    
    DirectionalLight dl = new DirectionalLight();
    dl.setDirection(new Vector3f(0f, -1f, 0f).normalizeLocal());
    rootNode.addLight(dl);
    
    AmbientLight ambient = new AmbientLight();
    ambient.setColor(ColorRGBA.White.mult(5f));
    rootNode.addLight(ambient);
}

private PhysicsSpace getPhysicsSpace() {
    return bulletAppState.getPhysicsSpace();
}

//-------------------------------------------------
// Load the road
//-------------------------------------------------

public void LoadRoad()
{
  roadGeo =  assetManager.loadModel("Scenes/Terrain.j3o");
  roadPhysics = new RigidBodyControl(0f); // make the road solid
  roadGeo.addControl(roadPhysics);
  bulletAppState.getPhysicsSpace().add(roadPhysics);
  roadGeo.setLocalTranslation(new Vector3f(0,30,0)); // does not seem to have any effect
  rootNode.attachChild(roadGeo);

}

//------------------------------------------------
// Set up the floor
//------------------------------------------------

public void setupFloor() {
    mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m");
    mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat);
    mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat);
    mat.getTextureParam("ParallaxMap").getTextureValue().setWrap(WrapMode.Repeat);
    mat.setBoolean("UseMaterialColors", false); // with Lighting.j3md
    mat.setColor("Diffuse", ColorRGBA.White ); // with Lighting.j3md
    mat.setColor("Ambient", ColorRGBA.White ); // with Lighting.j3md


    floor = new Box(Vector3f.ZERO, 500, 1f, 500);
    floor.scaleTextureCoordinates(new Vector2f(112.0f, 112.0f));
    floorGeom = new Geometry("Floor", floor);
    floorGeom.setShadowMode(ShadowMode.Receive);
    floorGeom.setMaterial(mat);

   
    
    /* Make the floor physical with mass 0.0f! */
    
    floorPhy = new RigidBodyControl(0.0f);
    floorGeom.addControl(floorPhy);
    
   // floorGeom.setLocalTranslation(new Vector3f(0f,-20f,0f));
    floorGeom.setLocalTranslation(Vector3f.ZERO);
    floorGeom.move(0f,20f,0f); // Does not seem to have any effect
    bulletAppState.getPhysicsSpace().add(floorPhy);
    this.rootNode.attachChild(floorGeom);
    
}

//----------------------------------------------------------------

private Geometry findGeom(Spatial spatial, String name)
{
    if (spatial instanceof Node) {
        Node node = (Node) spatial;
        for (int i = 0; i < node.getQuantity(); i++) {
            Spatial child = node.getChild(i);
            Geometry result = findGeom(child, name);
            if (result != null) {
                return result;
            }
        }
    } else if (spatial instanceof Geometry) {
        if (spatial.getName().startsWith(name)) {
            return (Geometry) spatial;
        }
    }
    return null;
}

//--------------------------------------------------------------

private void buildPlayer()
{
    float stiffness = 120.0f;//200=f1 car
    float compValue = 0.2f; //(lower than damp!)
    float dampValue = 0.3f;
    final float mass = 400;

//Load model and get chassis Geometry
carNode = (Node) assetManager.loadModel(“Models/Ferrari/Car.scene”);
carNode.setShadowMode(ShadowMode.Cast);
Geometry chasis = findGeom(carNode, “Car”);
BoundingBox box = (BoundingBox) chasis.getModelBound();
//Create a hull collision shape for the chassis

    CollisionShape carHull = CollisionShapeFactory.createDynamicMeshShape(chasis);

//Create a vehicle control
player = new VehicleControl(carHull, mass);

//Setting default values for wheels
player.setSuspensionCompression(compValue * 2.0f * FastMath.sqrt(stiffness));
player.setSuspensionDamping(dampValue * 2.0f * FastMath.sqrt(stiffness));
player.setSuspensionStiffness(stiffness);
player.setMaxSuspensionForce(10000);
//Create four wheels and add them at their locations
//note that our fancy car actually goes backwards…
Vector3f wheelDirection = new Vector3f(0, -1, 0);
Vector3f wheelAxle = new Vector3f(-1, 0, 0);
Geometry wheel_fr = findGeom(carNode, “WheelFrontRight”);
wheel_fr.center();
box = (BoundingBox) wheel_fr.getModelBound();
wheelRadius = box.getYExtent();
float back_wheel_h = (wheelRadius * 1.7f) - 1f;
float front_wheel_h = (wheelRadius * 1.9f) - 1f;
player.addWheel(wheel_fr.getParent(), box.getCenter().add(0, -front_wheel_h, 0),
wheelDirection, wheelAxle, 0.2f, wheelRadius, true);
Geometry wheel_fl = findGeom(carNode, “WheelFrontLeft”);
wheel_fl.center();
box = (BoundingBox) wheel_fl.getModelBound();
player.addWheel(wheel_fl.getParent(), box.getCenter().add(0, -front_wheel_h, 0),
wheelDirection, wheelAxle, 0.2f, wheelRadius, true);
Geometry wheel_br = findGeom(carNode, “WheelBackRight”);
wheel_br.center();
box = (BoundingBox) wheel_br.getModelBound();
player.addWheel(wheel_br.getParent(), box.getCenter().add(0, -back_wheel_h, 0),
wheelDirection, wheelAxle, 0.2f, wheelRadius, false);
Geometry wheel_bl = findGeom(carNode, “WheelBackLeft”);
wheel_bl.center();
box = (BoundingBox) wheel_bl.getModelBound();
player.addWheel(wheel_bl.getParent(), box.getCenter().add(0, -back_wheel_h, 0),
wheelDirection, wheelAxle, 0.2f, wheelRadius, false);
player.getWheel(2).setFrictionSlip(4);
player.getWheel(3).setFrictionSlip(4);

    //-- Create a camera and attach it to the car

    carNode.addControl(player);
    rootNode.attachChild(carNode);
    bulletAppState.getPhysicsSpace().add(player);
}

//---------------------------------------------------------------------

public void onAction(String binding, boolean value, float tpf) {
    if (binding.equals("Lefts")) {
        if (value) {
            steeringValue += .5f;
        } else {
            steeringValue += -.5f;
        }
        player.steer(steeringValue);
    } else if (binding.equals("Rights")) {
        if (value) {
            steeringValue += -.5f;
        } else {
            steeringValue += .5f;
        }
        player.steer(steeringValue);
    } //note that our fancy car actually goes backwards..
    else if (binding.equals("Ups")) {
        if (value) {
            accelerationValue -= 800;
        } else {
            accelerationValue += 800;
        }
        player.accelerate(accelerationValue);
        player.setCollisionShape(CollisionShapeFactory.createDynamicMeshShape(findGeom(carNode, "Car")));
    } else if (binding.equals("Downs")) {
        if (value) {
            player.brake(40f);
        } else {
            player.brake(0f);
        }
    } else if (binding.equals("Reset")) {
        if (value) {
            System.out.println("Reset");
            player.setPhysicsLocation(Vector3f.ZERO);
            player.setPhysicsRotation(new Matrix3f());
            player.setLinearVelocity(Vector3f.ZERO);
            player.setAngularVelocity(Vector3f.ZERO);
            player.resetSuspension();
        } else {
        }
    }
}

@Override
public void simpleUpdate(float tpf) {
   // cam.lookAt(carNode.getWorldTranslation(), Vector3f.UNIT_Y);

   
    
    camNode = new CameraNode("CamNode", cam);
    camNode.setControlDir(CameraControl.ControlDirection.SpatialToCamera);
    camNode.setLocalTranslation(new Vector3f(0, 4, 0));
    Quaternion quat = new Quaternion();
    quat.lookAt(Vector3f.UNIT_Z.mult(-1), Vector3f.UNIT_Y);
    camNode.setLocalRotation(quat);
    carNode.attachChild(camNode);
    camNode.setEnabled(true);
    flyCam.setEnabled(false);

}

}

Your car is in a physical space, and the position of this car (carNode) is updated to match the position of the physic car. That why you can’t move your car by moving the carNode but you have to change instead the physic location.

1 Like

*on the physics control, not the node. it has a setphysicslocation(vector3f) method.

1 Like

Thanks gents, I have this working now.

On another issue, in scene composer I have loaded my terrain, and I want to return the position of the cursor (the vertical green line) of a point on my terrain as 3D coordinates. I can’t see a way to do this, is there one ?

Not that I’m aware of in the SDK. It can be done in-game with raycasting, though.

you could simply add a node and see what coords it appears at

I’m back again. I.m having trouble adding physics to my road. The road was created as an OBJ file, imported into blender, saved as a blender file and converted to j3o in the JMonkey IDE.

I have to load it as a spatial. My code is

 public void LoadRoad()
    {
    
      roadSpatial= assetManager.loadModel("Scenes/road.j3o");
    
      roadPhysics = new RigidBodyControl(0.0f); // make the road solid
      roadSpatial.addControl(roadPhysics);
    
      bulletAppState.getPhysicsSpace().add(roadPhysics);
      this.rootNode.attachChild(roadSpatial);
       
    }

Trouble is, if I do it this way, the physics does not work. All the other physics examples I’ve seen attach the physics to a geometry. Why doesn’t it work when attached to a spatial ?

If I use the following routine to find the geometry, and add the physics to that, the appearance of my road is altered to the point where it’s unusable.

 private Geometry findGeom(Spatial spatial, String name)
    {
        if (spatial instanceof Node) {
            Node node = (Node) spatial;
            for (int i = 0; i < node.getQuantity(); i++) {
                Spatial child = node.getChild(i);
                Geometry result = findGeom(child, name);
                if (result != null) {
                    return result;
                }
            }
        } else if (spatial instanceof Geometry) {
            if (spatial.getName().startsWith(name)) {
                return (Geometry) spatial;
            }
        }
        return null;
    }

   public void LoadRoad()
    {
      // roadGeo =  assetManager.loadModel("Scenes/Terrain.j3o");
        
              
      roadSpatial= assetManager.loadModel("Scenes/road.j3o");
      roadGeo = findGeom(roadSpatial,"road");
   
      roadPhysics = new RigidBodyControl(0.0f); // make the road solid
      roadGeo.addControl(roadPhysics);
    
      bulletAppState.getPhysicsSpace().add(roadPhysics);
      this.rootNode.attachChild(roadSpatial);
       
    }

Any ideas please ?