Implementing a moveTo() Method

Hello, I’m still learning JME and feel dissatisfied with what I’ve accomplished after releasing my first JME test game: https://play.google.com/store/apps/details?id=com.mourningdovesoft.baseballsack

The moveTo() and rotateTo() methods that I implemented in the above game were outright bad. I still haven’t found a good way of searching these forums either, google find site: is failing me. My latest attempt at a moveTo() method is shown below. At the moment the trouble I’m having is that the object isn’t arriving at its destination, more like half way. Am I going about this the right way and only missing a few things or is this completely wrong? Any links to forum posts about implementations for such a method would be greatly appreciated. Thanks in advance.


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Android_Template;

import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import java.io.IOException;

/**
 *
 * @author practicing01
 */
public class PhysicsSceneObjectControl extends AbstractControl implements SceneObjectInterface {
    //Any local variables should be encapsulated by getters/setters so they
    //appear in the SDK properties window and can be edited.
    //Right-click a local variable to encapsulate it with getters and setters.

    public BetterCharacterControl betterCharacterControl;
    private float radius, height, mass, moveToTravelTime, moveToElapsedTime, moveToTPFOffset, moveToSpeed;
    private Vector3f forwardDir, leftDir, walkDirection, viewDirection, moveToDir, moveToLoc, moveToDest;
    private boolean isMoving = false, moveToStopOnTime;

    public PhysicsSceneObjectControl(float radius, float height, float mass) {
        walkDirection = new Vector3f(Vector3f.ZERO);
        viewDirection = new Vector3f(0f, 0f, 1f);
        this.radius = radius;
        this.height = height;
        this.mass = mass;
        betterCharacterControl = new BetterCharacterControl(radius, height, mass);
        Main.bulletAppState.getPhysicsSpace().add(betterCharacterControl);
    }

    public void attachControls() {
        spatial.addControl(betterCharacterControl);
    }

    public void setGravity(Vector3f gravity) {
        betterCharacterControl.setGravity(gravity);
    }

    public void setPhysicsDamping(float damping) {
        betterCharacterControl.setPhysicsDamping(damping);
    }

    @Override
    public void onMoveToComplete() {
    }

    @Override
    public void moveTo(Vector3f dest, float speed, boolean stopOnCompletion) {
        moveToSpeed = speed;
        moveToDest = dest;
        moveToLoc = spatial.getWorldTranslation();
        moveToDir = dest.subtract(moveToLoc);
        moveToDir.normalizeLocal();
        moveToTravelTime = moveToDest.distance(moveToLoc) / speed;
        moveToElapsedTime = -1f;
        moveToStopOnTime = stopOnCompletion;
        isMoving = true;
        viewDirection = moveToDir;
    }

    @Override
    protected void controlUpdate(float tpf) {
        //TODO: add code that controls Spatial,
        //e.g. spatial.rotate(tpf,tpf,tpf)

        if (isMoving == true) {

            if (moveToElapsedTime == -1f) {
                moveToTPFOffset = tpf;
            }
            moveToElapsedTime += tpf;

            if (moveToElapsedTime - moveToTPFOffset >= moveToTravelTime) {
                isMoving = false;
                if (moveToStopOnTime == true) {
                    betterCharacterControl.setWalkDirection(Vector3f.ZERO);
                }
                onMoveToComplete();
                return;
            }

            forwardDir = spatial.getWorldRotation().mult(Vector3f.UNIT_Z);
            leftDir = spatial.getWorldRotation().mult(Vector3f.UNIT_X);

            walkDirection.set(0f, 0f, 0f);
            walkDirection.addLocal(forwardDir.mult(moveToSpeed));
            betterCharacterControl.setWalkDirection(walkDirection);
            betterCharacterControl.setViewDirection(viewDirection);

            System.out.println(moveToElapsedTime + " " + moveToTravelTime + " " + moveToTPFOffset);
        }

    }

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {
        //Only needed for rendering-related operations,
        //not called when spatial is culled.
    }

    @Override
    public Control cloneForSpatial(Spatial spatial) {
        PhysicsSceneObjectControl control = new PhysicsSceneObjectControl(radius, height, mass);
        //TODO: copy parameters to new Control
        return control;
    }

    @Override
    public void read(JmeImporter im) throws IOException {
        super.read(im);
        InputCapsule in = im.getCapsule(this);
        //TODO: load properties of this Control, e.g.
        //this.value = in.readFloat("name", defaultValue);
    }

    @Override
    public void write(JmeExporter ex) throws IOException {
        super.write(ex);
        OutputCapsule out = ex.getCapsule(this);
        //TODO: save properties of this Control, e.g.
        //out.write(this.value, "name", defaultValue);
    }
}

I think using timings for movement based on speed can lead to problems unless the world is entirely deterministic.
It’s better to check the distance between the current position and moveToLoc in the controlUpdate method and stop moving when close enough to moveToLoc.

I think these two are working, I need to test some more. Many thanks to the forum posters with similar problems and this example: https://code.google.com/p/jme-simple-examples/source/browse/JMESimpleExamples/src/com/intermediate/MoveNodeOnSurface.java


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Android_Template;

import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.PhysicsTickListener;
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import java.io.IOException;

/**
 *
 * @author practicing01
 */
public class BCCSceneObjectControl extends AbstractControl implements SceneObjectInterface, PhysicsTickListener {
    //Any local variables should be encapsulated by getters/setters so they
    //appear in the SDK properties window and can be edited.
    //Right-click a local variable to encapsulate it with getters and setters.

    public BetterCharacterControl betterCharacterControl;
    private float radius, height, mass, moveToTravelTime, moveToElapsedTime, moveToSpeed;
    private Vector3f forwardDir, leftDir, walkDirection, viewDirection, moveToDir, moveToLoc, moveToDest;
    private boolean isMoving = false, moveToStopOnTime;

    public BCCSceneObjectControl(float radius, float height, float mass) {
        walkDirection = new Vector3f(Vector3f.ZERO);
        viewDirection = new Vector3f(0f, 0f, 1f);
        this.radius = radius;
        this.height = height;
        this.mass = mass;
        betterCharacterControl = new BetterCharacterControl(radius, height, mass);
        Main.bulletAppState.getPhysicsSpace().add(betterCharacterControl);
    }

    public void attachControls() {
        spatial.addControl(betterCharacterControl);
    }

    public void setGravity(Vector3f gravity) {
        betterCharacterControl.setGravity(gravity);
    }

    public void setPhysicsDamping(float damping) {
        betterCharacterControl.setPhysicsDamping(damping);
    }

    @Override
    public void onMoveToComplete() {
    }

    @Override
    public void moveTo(Vector3f dest, float speed, boolean stopOnCompletion) {
        moveToSpeed = speed;
        moveToDest = dest;
        moveToLoc = spatial.getWorldTranslation();
        moveToDir = dest.subtract(moveToLoc);
        moveToDir.normalizeLocal();
        moveToTravelTime = moveToDest.distance(moveToLoc) / speed;
        moveToElapsedTime = 0;
        moveToStopOnTime = stopOnCompletion;
        isMoving = true;
        viewDirection = moveToDir;
    }

    @Override
    protected void controlUpdate(float tpf) {
        //TODO: add code that controls Spatial,
        //e.g. spatial.rotate(tpf,tpf,tpf)

    }

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {
        //Only needed for rendering-related operations,
        //not called when spatial is culled.
    }

    @Override
    public Control cloneForSpatial(Spatial spatial) {
        BCCSceneObjectControl control = new BCCSceneObjectControl(radius, height, mass);
        //TODO: copy parameters to new Control
        return control;
    }

    @Override
    public void read(JmeImporter im) throws IOException {
        super.read(im);
        InputCapsule in = im.getCapsule(this);
        //TODO: load properties of this Control, e.g.
        //this.value = in.readFloat("name", defaultValue);
    }

    @Override
    public void write(JmeExporter ex) throws IOException {
        super.write(ex);
        OutputCapsule out = ex.getCapsule(this);
        //TODO: save properties of this Control, e.g.
        //out.write(this.value, "name", defaultValue);
    }

    @Override
    public void prePhysicsTick(PhysicsSpace space, float tpf) {
        if (isMoving == true) {

            forwardDir = spatial.getWorldRotation().mult(Vector3f.UNIT_Z);
            leftDir = spatial.getWorldRotation().mult(Vector3f.UNIT_X);

            walkDirection.set(0f, 0f, 0f);
            walkDirection.addLocal(forwardDir.mult(moveToSpeed));
            betterCharacterControl.setWalkDirection(walkDirection);
            betterCharacterControl.setViewDirection(viewDirection);

            moveToElapsedTime += tpf;

            if (moveToElapsedTime >= moveToTravelTime) {
                isMoving = false;
                if (moveToStopOnTime == true) {
                    betterCharacterControl.setWalkDirection(Vector3f.ZERO);
                }
                onMoveToComplete();
            }

            //System.out.println(moveToElapsedTime + " " + moveToTravelTime + " ");
        }
    }

    @Override
    public void physicsTick(PhysicsSpace space, float tpf) {
    }
}

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Android_Template;

import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import java.io.IOException;
import java.util.concurrent.Callable;

/**
 *
 * @author practicing01
 */
public class SceneObjectControl extends AbstractControl implements SceneObjectInterface {
    //Any local variables should be encapsulated by getters/setters so they
    //appear in the SDK properties window and can be edited.
    //Right-click a local variable to encapsulate it with getters and setters.

    private float moveToTravelTime, moveToElapsedTime, moveToSpeed, inderp, remainingDist;
    private Vector3f moveToDir, moveToLoc, moveToDest;
    private boolean isMoving = false, moveToStopOnTime;

    @Override
    protected void controlUpdate(float tpf) {
        //TODO: add code that controls Spatial,
        //e.g. spatial.rotate(tpf,tpf,tpf);
        if (isMoving == true) {

            inderp = tpf * moveToSpeed;
            remainingDist = spatial.getLocalTranslation().distance(moveToDest);
            
            Main.app.enqueue(new Callable() {
                public Object call() throws Exception {
                    spatial.setLocalTranslation(spatial.getWorldTranslation().interpolate(moveToDest, inderp / remainingDist));
                    return null;
                }
            });
            
            moveToElapsedTime += tpf;

            if (moveToElapsedTime >= moveToTravelTime) {
                isMoving = false;
                if (moveToStopOnTime == true) {
                }
                onMoveToComplete();
            }

            //System.out.println(moveToElapsedTime + " " + moveToTravelTime + " ");
        }
    }

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {
        //Only needed for rendering-related operations,
        //not called when spatial is culled.
    }

    public Control cloneForSpatial(Spatial spatial) {
        SceneObjectControl control = new SceneObjectControl();
        //TODO: copy parameters to new Control
        return control;
    }

    @Override
    public void read(JmeImporter im) throws IOException {
        super.read(im);
        InputCapsule in = im.getCapsule(this);
        //TODO: load properties of this Control, e.g.
        //this.value = in.readFloat("name", defaultValue);
    }

    @Override
    public void write(JmeExporter ex) throws IOException {
        super.write(ex);
        OutputCapsule out = ex.getCapsule(this);
        //TODO: save properties of this Control, e.g.
        //out.write(this.value, "name", defaultValue);
    }

    @Override
    public void onMoveToComplete() {
    }

    @Override
    public void moveTo(Vector3f dest, float speed, boolean stopOnCompletion) {
        moveToSpeed = speed;
        moveToDest = dest;
        moveToLoc = spatial.getWorldTranslation();
        moveToDir = dest.subtract(moveToLoc);
        moveToDir.normalizeLocal();
        moveToTravelTime = moveToDest.distance(moveToLoc) / speed;
        moveToElapsedTime = 0;
        moveToStopOnTime = stopOnCompletion;
        isMoving = true;
    }
}