FancyCar to follow path

Hi,

I’m trying to move FancyCar along a path giving it force applied and steering value.

I’ve problems with steering value.

Can you help me?





[java]

/*

  • Copyright © 2009-2010 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 it.F1Viewer3D.test;



    import com.jme3.bullet.BulletAppState;

    import com.jme3.app.SimpleApplication;

    import com.jme3.asset.TextureKey;

    import com.jme3.bounding.BoundingBox;

    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.nodes.PhysicsNode;

    import com.jme3.bullet.objects.VehicleWheel;

    import com.jme3.bullet.util.CollisionShapeFactory;

    import com.jme3.font.BitmapText;

    import com.jme3.input.ChaseCamera;

    import com.jme3.input.KeyInput;

    import com.jme3.input.controls.ActionListener;

    import com.jme3.input.controls.KeyTrigger;

    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.Vector2f;

    import com.jme3.math.Vector3f;

    import com.jme3.renderer.queue.RenderQueue.ShadowMode;

    import com.jme3.scene.Geometry;

    import com.jme3.scene.Mesh;

    import com.jme3.scene.Node;

    import com.jme3.scene.Spatial;

    import com.jme3.scene.debug.Arrow;

    import com.jme3.scene.shape.Box;

    import com.jme3.shadow.BasicShadowRenderer;

    import com.jme3.texture.Texture;

    import com.jme3.texture.Texture.WrapMode;



    public class TestCarPath 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;

    private float wheelRadius;

    private float steeringValue = 0;

    private float accelerationValue = 0;

    private Node carNode;

    private Vector3f[] cameraPoints;

    private ChaseCamera chaseCam;

    private int current;

    private boolean started=false;

    private boolean first=true;

    private Vector3f currentPoint;

    private BitmapText hintText;



    public static void main(String[] args) {

    TestCarPath app = new TestCarPath();

    app.start();

    }



    private void setupKeys() {

    inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_H));

    inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_K));

    inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_U));

    inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_J));

    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);

    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);

    }

    cam.setFrustumFar(5000f);

    flyCam.setMoveSpeed(10);

    loadHintText();

    setupKeys();

    setupFloor();

    //setupPathLinear();

    setupPath();

    buildPlayer();



    DirectionalLight dl = new DirectionalLight();

    dl.setDirection(new Vector3f(-0.5f, -1f, -0.3f).normalizeLocal());

    rootNode.addLight(dl);



    dl = new DirectionalLight();

    dl.setDirection(new Vector3f(0.5f, -0.1f, 0.3f).normalizeLocal());

    rootNode.addLight(dl);

    }



    private PhysicsSpace getPhysicsSpace() {

    return bulletAppState.getPhysicsSpace();

    }



    public void setupFloor() {

    Material 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);



    Box floor = new Box(new Vector3f(0,-6,0), 5000, 2f, 5000);

    floor.scaleTextureCoordinates(new Vector2f(112.0f, 112.0f));

    Geometry floorGeom = new Geometry("Floor", floor);

    floorGeom.setMaterial(mat);

    //floorGeom.setLocalTranslation(new Vector3f(0f, 0f, 0f));



    floorGeom.addControl(new RigidBodyControl(new MeshCollisionShape(floorGeom.getMesh()), 0));

    rootNode.attachChild(floorGeom);

    getPhysicsSpace().add(floorGeom);

    }

    private void setupPath(){

    int k=0;

    int step=36;

    cameraPoints = new Vector3f[step
    10];

    Material mat = new Material(assetManager, "Common/MatDefs/Misc/ColoredTextured.j3md");

    TextureKey tkey = new TextureKey("Textures/ColoredTex/Monkey.png", true);

    tkey.setGenerateMips(true);

    Texture tex = assetManager.loadTexture(tkey);

    tex.setMinFilter(Texture.MinFilter.Trilinear);

    mat.setTexture("ColorMap", tex);

    float radius=200f;

    for(float i=0;i<FastMath.PI;i+=FastMath.PI/step){



    cameraPoints[k] = new Vector3f(radiusFastMath.cos(i),-2,radiusFastMath.sin(i));

    Box b1 = new Box(Vector3f.ZERO, .2f, .2f, .2f);

    Geometry geom1 = new Geometry("Box"+k, b1);

    Vector3f v =new Vector3f(cameraPoints[k]);

    v.y=3;

    geom1.setLocalTranslation(v);



    if(k>0){

    geom1.addControl(new RigidBodyControl(new MeshCollisionShape(geom1.getMesh()), 0));

    rootNode.attachChild(geom1);

    getPhysicsSpace().add(geom1);

    }







    // getPhysicsSpace().add(geom1);



    // geom1.getLocalTranslation().y=60;

    geom1.setMaterial(mat);

    // cameraPoints.setY(100);

    k++;



    }

    }

    private void setupPathLinear(){

    int k=0;

    int step=36;

    cameraPoints = new Vector3f[step];

    Material mat = new Material(assetManager, "Common/MatDefs/Misc/ColoredTextured.j3md");

    TextureKey tkey = new TextureKey("Textures/ColoredTex/Monkey.png", true);

    tkey.setGenerateMips(true);

    Texture tex = assetManager.loadTexture(tkey);

    tex.setMinFilter(Texture.MinFilter.Trilinear);

    mat.setTexture("ColorMap", tex);

    float radius=20f;

    for(float i=0;i<step;i++){



    cameraPoints[k] = new Vector3f(radius*i,-2,0);

    Box b1 = new Box(Vector3f.ZERO, .2f, .2f, .2f);

    Geometry geom1 = new Geometry("Box"+k, b1);

    Vector3f v =new Vector3f(cameraPoints[k]);

    v.y=3;

    geom1.setLocalTranslation(v);



    if(k>0){

    geom1.addControl(new RigidBodyControl(new MeshCollisionShape(geom1.getMesh()), 0));

    rootNode.attachChild(geom1);

    getPhysicsSpace().add(geom1);

    }







    // getPhysicsSpace().add(geom1);



    // geom1.getLocalTranslation().y=60;

    geom1.setMaterial(mat);

    // cameraPoints.setY(100);

    k++;



    }

    }

    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);

    carNode.addControl(player);



    //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.attachDebugShape(assetManager);

    player.getWheel(2).setFrictionSlip(4);

    player.getWheel(3).setFrictionSlip(4);



    rootNode.attachChild(carNode);

    flyCam.setEnabled(false);



    // Enable a chase cam

    chaseCam = new ChaseCamera(cam, carNode,inputManager);

    chaseCam.setInvertYaxis(false);



    chaseCam.setSmoothMotion(true);



    getPhysicsSpace().add(player);

    player.setPhysicsLocation(cameraPoints[0]);

    Matrix3f ROT_MATRIX = new Matrix3f();

    float angle=cameraPoints[1].angleBetween(cameraPoints[0]);

    ROT_MATRIX.fromAngleAxis(angle, new Vector3f(0,1,0));

    hintText.setText(" angle=" + angle);



    player.setPhysicsRotation(ROT_MATRIX);

    }



    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);

    } 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 {

    }

    }

    }

    public void loadHintText() {

    hintText = new BitmapText(guiFont, false);

    hintText.setSize(guiFont.getCharSet().getRenderedSize());

    hintText.setLocalTranslation(0, getCamera().getHeight(), 0);

    hintText.setText("Hit T to switch to wireframe");

    guiNode.attachChild(hintText);

    }



    @Override

    public void simpleUpdate(float tpf) {

    // cam.lookAt(carNode.getWorldTranslation(), Vector3f.UNIT_Y);

    // cam.lookAt(cameraPoints[0], Vector3f.UNIT_Y);

    if(current>=cameraPoints.length)return;

    Vector3f pos1 = cameraPoints[current].clone();

    pos1.y = 0;

    Vector3f pos2 = carNode.getLocalTranslation().clone();

    pos2.y = 0;

    float distance = pos1.distanceSquared(pos2);

    if (first) {

    first = false;

    timer.reset();

    }

    System.out.println("distance=" + distance + ",goal=" + pos1 + "actual="
  • pos2);

    if (!started && timer.getTimeInSeconds() >= 4) {

    started = true;



    // vehicleNode.updateGeometricState();

    System.out.println("startdistance=" + distance + ",goal="
  • cameraPoints[current] + "actual="
  • carNode.getLocalTranslation());

    } else if (started) {



    if (current < cameraPoints.length-1) {

    // vehicle.getPhysicsLocation().y=12.25f;



    Vector3f force = pos1.subtract(pos2).normalize();

    // if(force.xforce.x<.4f)force.x=0;

    // if(force.z
    force.z<.4f)force.z=0;

    force.y = 0;

    float angle=cameraPoints[current+1].angleBetween(force);



    Matrix3f ROT_MATRIX = new Matrix3f();

    ROT_MATRIX.fromAngleAxis(angle, new Vector3f(0,1,0));

    hintText.setText("force=" + force + " current=" + current
  • " distance=" + distance+ " steer=" + angle/FastMath.PI);

    // vehicle.applyImpulse(force.mult(20f),Vector3f.ZERO);

    player.applyCentralForce(force.mult(1000f));

    //player.setLinearVelocity(force.mult(5f));

    //player.setPhysicsRotation(ROT_MATRIX);

    player.steer(-angle/FastMath.PI);

    // vehicle.accelerate(1000);

    if (distance < 300) {

    current++;



    putArrow(pos1, force.clone().mult(10),

    ColorRGBA.Red);

    // putArrow(Vector3f.ZERO, new Vector3f(0,force.y,0),

    // ColorRGBA.Green);

    // putArrow(Vector3f.ZERO, new Vector3f(0,0,force.z),

    // ColorRGBA.Blue);



    }



    }

    currentPoint = new Vector3f(player.getPhysicsLocation());



    }

    }



    public Geometry putShape(Mesh shape, ColorRGBA color){

    Geometry g = new Geometry("shape", shape);

    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

    mat.getAdditionalRenderState().setWireframe(true);

    mat.setColor("Color", color);

    g.setMaterial(mat);

    rootNode.attachChild(g);

    return g;

    }



    public void putArrow(Vector3f pos, Vector3f dir, ColorRGBA color){

    Arrow arrow = new Arrow(dir);

    arrow.setLineWidth(4); // make arrow thicker

    putShape(arrow, color).setLocalTranslation(pos);

    }

    }



    [/java]

For steering what I can tell you is that it is in radians. If I am not mistaken (I am suffering with the math) 1 radian = 57.2957795 degrees. Positive values and negative values move to one side or the other. For example if moving upwards and you want to go to the left it is positive and if to the right then negative.

Do you have the path programmed out? If so, code a method that gets if the vehicle is within, say, 10 units from the desired point, then change it’s current destination to the next point and get whether or not it’s to the left or right and accordingly apply steering.

Example:

[java]

Vector3f currentDest = /put some data here/

public boolean getIsAtPoint() {

Vector2f vehicleXZ = new Vector2f(

vehicle.getPhysicsLocation().getX(),

vehicle.getPhysicsLocation.getZ());

Vector2f curLoc = new Vector2f

currentDest.getX(),

currentDest.getZ());

float tolerance = 10f;

if(((vehicleXZ.getX() > curLoc.getX() - tolerance) ||

(vehicleXZ.getX() < curLoc.getX() + tolerance)) &&

((vehicleXZ.getY() < curLoc.getY() + tolerance) ||

(vehicleXZ.getY() > curLoc.getY() - tolerance)) {

return true

} else {

return false;

}

}



later in simpleinitapp or update od whatever…



if(getIsAtPoint()) {

//code to turn and stuff

}

[/java]



That’s the best I can do off the top of my head, and there’s probbly plenty of optimization to be done on it, but it should work!

I agree with you @vinexgames , just try getting it to one point at a time without worrying about if it follows an exact path. And disable all collisions except with the ground etc.



A different way would be to use an exact path but make a “pseudo vehicle control” that moves and rotates wheels etc, but doesn’t use physics. That’s how I am gonna move AI vehicles myself, because it would be very hard to move the raycast vehicle “as expected” using full physics simulation… Unless using a perfectly flat ground and very low speeds maybe.



It does look a lot better with the damping and everything tho, of course…