java3dgame-physicsjmonkeyengine

How to fix the movement of 3D object?


I got a 3D spaceship that moves forward and backward in the 3D scene correctly but the rightward and leftward movements are not right and what the A and D buttons do seem to vary with the camera position. This is the listener code which works for buttons W (forward) and S (backward) while buttons A and D don't do exactly what they should.

enter image description here

When I start the 3D space scene the steering of the spaceship is working and the buttons A and D move the spaceship left and right but after a changed the camera and rotated the view, the buttons A and D are still opposite directions but not to the left and right but depend on the camera's position.

    public void onAnalog(String name, float value, float tpf) {
    // computing the normalized direction of the cam to move the node
    int movement = 80000;
    int rotation = 1;
    direction.set(cam.getDirection()).normalizeLocal();
    if (name.equals("moveForward")) {
        direction.multLocal(movement * tpf);
        ufoNode.move(direction);
    }
    if (name.equals("moveBackward")) {
        direction.multLocal(-movement * tpf);
        ufoNode.move(direction);
    }
    if (name.equals("moveRight")) {
        direction.crossLocal(Vector3f.UNIT_Y).multLocal(movement * tpf);
        ufoNode.move(direction);
    }
    if (name.equals("moveLeft")) {
        direction.crossLocal(Vector3f.UNIT_Y).multLocal(-movement * tpf);
        ufoNode.move(direction);
    }
    if (name.equals("rotateRight") && rotate) {
        ufoNode.rotate(0, 1 * tpf, 0);
    }
    if (name.equals("rotateLeft") && rotate) {
        ufoNode.rotate(0, -1 * tpf, 0);
    }
    if (name.equals("rotateUp") && rotate) {
        ufoNode.rotate(0, 0, -1 * tpf);
    }
    if (name.equals("rotateDown") && rotate) {
        ufoNode.rotate(0, 0, 1 * tpf);
    }
}

Can you help me and tell me what should be done to fix the right and left movements? The entire code is

public class SpaceStation extends SimpleApplication implements AnalogListener,
        ActionListener {

    private PlanetAppState planetAppState;
    private Geometry mark;
    private Node ufoNode;       
    private Node spaceStationNode;  
    private Node jumpgateNode;
    private Node jumpgateNode2;     
    private BetterCharacterControl ufoControl;
    CameraNode camNode;
    boolean rotate = false;
    Vector3f direction = new Vector3f();
    private BulletAppState bulletAppState;

    public static void main(String[] args) {
        AppSettings settings = new AppSettings(true);
        settings.setResolution(1024, 768);
        SpaceStation app = new SpaceStation();
        app.setSettings(settings);
        // app.showSettings = true;
        app.start();
    }

    @Override
    public void simpleInitApp() {
        // Only show severe errors in log
        java.util.logging.Logger.getLogger("com.jme3").setLevel(
                java.util.logging.Level.SEVERE);

        bulletAppState = new BulletAppState();
        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
        stateManager.attach(bulletAppState);
        bulletAppState.setDebugEnabled(false);

        DirectionalLight sun = new DirectionalLight();
        sun.setDirection(new Vector3f(-.1f, 0f, -1f));
        sun.setColor(new ColorRGBA(0.75f, 0.75f, 0.75f, 1.0f));
        rootNode.addLight(sun);

        // Add sky
        Node sceneNode = new Node("Scene");
        sceneNode.attachChild(Utility.createSkyBox(this.getAssetManager(),
                "Textures/blue-glow-1024.dds"));
        rootNode.attachChild(sceneNode);

        // Create collision test mark
        Sphere sphere = new Sphere(30, 30, 5f);
        mark = new Geometry("mark", sphere);
        Material mark_mat = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        mark_mat.setColor("Color", ColorRGBA.Red);
        mark.setMaterial(mark_mat);

        // Add planet app state
        planetAppState = new PlanetAppState(rootNode, sun);
        stateManager.attach(planetAppState);

        // Add planet
        FractalDataSource planetDataSource = new FractalDataSource(4);
        planetDataSource.setHeightScale(900f);
        Planet planet = Utility.createEarthLikePlanet(getAssetManager(),
                293710.0f, null, planetDataSource);
        planetAppState.addPlanet(planet);
        rootNode.attachChild(planet);

        // Add moon
        FractalDataSource moonDataSource = new FractalDataSource(5);
        moonDataSource.setHeightScale(300f);
        Planet moon = Utility.createMoonLikePlanet(getAssetManager(), 50000,
                moonDataSource);
        planetAppState.addPlanet(moon);
        rootNode.attachChild(moon);
        moon.setLocalTranslation(new Vector3f(10f, 10f, 505000f));//-950000f, 0f, 0f);
        // add saucer
        ufoNode = (Node) assetManager.loadModel("usaucer_v01.j3o");
        ufoNode.setLocalScale(100f);
        ufoNode.setLocalTranslation((new Vector3f(1000f, -1000f, 328000f)));
        jumpgateNode = (Node) assetManager.loadModel("JumpGate.j3o");
        jumpgateNode.setLocalScale(10000f);
        jumpgateNode.setLocalTranslation((new Vector3f(10f, 10f, 708000f)));

        spaceStationNode = (Node) assetManager.loadModel("SpaceStation.j3o");
        spaceStationNode.setLocalScale(4000f);
        spaceStationNode.setLocalTranslation((new Vector3f(10000f, -10f, 425000f)));    

        jumpgateNode2 = (Node) assetManager.loadModel("JumpGate.j3o");
        jumpgateNode2.setLocalScale(10000f);
        jumpgateNode2.setLocalTranslation((new Vector3f(10f, 10f, 798300f)));

        /* This quaternion stores a 180 degree rolling rotation */
        // Quaternion roll180 = new Quaternion();
        // roll180.fromAngleAxis(FastMath.PI, new Vector3f(0, 0, 1));
        /* The rotation is applied: The object rolls by 180 degrees. */
        // ufoNode.setLocalRotation(roll180);
        rootNode.attachChild(jumpgateNode);
        rootNode.attachChild(jumpgateNode2);
        rootNode.attachChild(spaceStationNode);     

        // creating the camera Node
        camNode = new CameraNode("CamNode", cam);
        // Setting the direction to Spatial to camera, this means the camera
        // will copy the movements of the Node
        camNode.setControlDir(ControlDirection.SpatialToCamera);
        // attaching the camNode to the teaNode
        ufoNode.attachChild(camNode);
        // setting the local translation of the cam node to move it away a bit
        camNode.setLocalTranslation(new Vector3f(-40, 0, 0));
        // setting the camNode to look at the teaNode
        camNode.lookAt(ufoNode.getLocalTranslation(), Vector3f.UNIT_Y);
        // disable the default 1st-person flyCam (don't forget this!!)
        ufoControl = new BetterCharacterControl(100000f, 80000f, 5000f);// (2, 4, 0.5f);
        // radius (meters), height (meters), gravity (mass)
        //ufoNode.addControl(ufoControl);
        //rootNode.attachChild(ninjaNode);
        //bulletAppState.getPhysicsSpace().add(ufoControl);
        //getPhysicsSpace().add(ufoControl);
        rootNode.attachChild(ufoNode);
        flyCam.setEnabled(false);
        registerInput();
    }

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

    public void registerInput() {
        inputManager.addMapping("moveForward", new KeyTrigger(keyInput.KEY_UP),
                new KeyTrigger(keyInput.KEY_W));
        inputManager.addMapping("moveBackward", new KeyTrigger(
                keyInput.KEY_DOWN), new KeyTrigger(keyInput.KEY_S));
        inputManager.addMapping("moveRight",
                new KeyTrigger(keyInput.KEY_RIGHT), new KeyTrigger(
                        keyInput.KEY_D));
        inputManager.addMapping("moveLeft", new KeyTrigger(keyInput.KEY_LEFT),
                new KeyTrigger(keyInput.KEY_A));
        inputManager.addMapping("toggleRotate", new MouseButtonTrigger(
                MouseInput.BUTTON_LEFT));
        inputManager.addMapping("rotateRight", new MouseAxisTrigger(
                MouseInput.AXIS_X, true));
        inputManager.addMapping("rotateLeft", new MouseAxisTrigger(
                MouseInput.AXIS_X, false));
        inputManager.addMapping("rotateUp", new MouseAxisTrigger(
                MouseInput.AXIS_Y, true));
        inputManager.addMapping("rotateDown", new MouseAxisTrigger(
                MouseInput.AXIS_Y, false));
        inputManager.addListener(this, "moveForward", "moveBackward",
                "moveRight", "moveLeft");
        inputManager.addListener(this, "rotateRight", "rotateLeft", "rotateUp",
                "rotateDown", "toggleRotate");
        // Toggle mouse cursor
        inputManager.addMapping("TOGGLE_CURSOR", new MouseButtonTrigger(
                MouseInput.BUTTON_LEFT), new KeyTrigger(KeyInput.KEY_SPACE));
        inputManager.addListener(actionListener, "TOGGLE_CURSOR");
        // Toggle wireframe
        inputManager.addMapping("TOGGLE_WIREFRAME", new KeyTrigger(
                KeyInput.KEY_T));
        inputManager.addListener(actionListener, "TOGGLE_WIREFRAME");
        // Collision test
        inputManager.addMapping("COLLISION_TEST", new MouseButtonTrigger(
                MouseInput.BUTTON_RIGHT));
        inputManager.addListener(actionListener, "COLLISION_TEST");
    }

    public void onAnalog(String name, float value, float tpf) {
        // computing the normalized direction of the cam to move the node
        int movement = 80000;
        int rotation = 1;
        direction.set(cam.getDirection()).normalizeLocal();
        if (name.equals("moveForward")) {
            direction.multLocal(movement * tpf);
            ufoNode.move(direction);
        }
        if (name.equals("moveBackward")) {
            direction.multLocal(-movement * tpf);
            ufoNode.move(direction);
        }
        if (name.equals("moveRight")) {
            direction.crossLocal(Vector3f.UNIT_Y).multLocal(movement * tpf);
            ufoNode.move(direction);
        }
        if (name.equals("moveLeft")) {
            direction.crossLocal(Vector3f.UNIT_Y).multLocal(-movement * tpf);
            ufoNode.move(direction);
        }
        if (name.equals("rotateRight") && rotate) {
            ufoNode.rotate(0, 1 * tpf, 0);
        }
        if (name.equals("rotateLeft") && rotate) {
            ufoNode.rotate(0, -1 * tpf, 0);
        }
        if (name.equals("rotateUp") && rotate) {
            ufoNode.rotate(0, 0, -1 * tpf);
        }
        if (name.equals("rotateDown") && rotate) {
            ufoNode.rotate(0, 0, 1 * tpf);
        }
    }

    public void onAction(String name, boolean keyPressed, float tpf) {
        // toggling rotation on or off
        if (name.equals("toggleRotate") && keyPressed) {
            rotate = true;
            inputManager.setCursorVisible(false);
        }
        if (name.equals("toggleRotate") && !keyPressed) {
            rotate = false;
            inputManager.setCursorVisible(true);
        }
        if (name.equals("TOGGLE_CURSOR") && !keyPressed) {
            if (inputManager.isCursorVisible()) {
                inputManager.setCursorVisible(false);
            } else {
                inputManager.setCursorVisible(true);
            }
        }
        if (name.equals("TOGGLE_WIREFRAME") && !keyPressed) {
            for (Planet planet : planetAppState.getPlanets()) {
                planet.toogleWireframe();
            }
        }
        if (name.equals("COLLISION_TEST") && !keyPressed) {
            CollisionResults results = new CollisionResults();
            Ray ray = new Ray(cam.getLocation(), cam.getDirection());
            // Test collision with closest planet's terrain only
            planetAppState.getNearestPlanet().getTerrainNode()
                    .collideWith(ray, results);
            System.out.println("----- Collisions? " + results.size() + "-----");
            for (int i = 0; i < results.size(); i++) {
                // For each hit, we know distance, impact point, name of
                // geometry.
                float dist = results.getCollision(i).getDistance();
                Vector3f pt = results.getCollision(i).getContactPoint();
                String hit = results.getCollision(i).getGeometry().getName();
                System.out.println("* Collision #" + i);
                System.out.println("  You shot " + hit + " at " + pt + ", "
                        + dist + " wu away.");
            }
            if (results.size() > 0) {
                // The closest collision point is what was truly hit:
                CollisionResult closest = results.getClosestCollision();
                // Let's interact - we mark the hit with a red dot.
                mark.setLocalTranslation(closest.getContactPoint());
                rootNode.attachChild(mark);
            } else {
                // No hits? Then remove the red mark.
                rootNode.detachChild(mark);
            }
        }
    }

    private ActionListener actionListener = new ActionListener() {
        public void onAction(String name, boolean pressed, float tpf) {
            if (name.equals("TOGGLE_CURSOR") && !pressed) {
                if (inputManager.isCursorVisible()) {
                    inputManager.setCursorVisible(false);
                } else {
                    inputManager.setCursorVisible(true);
                }
            }
            if (name.equals("TOGGLE_WIREFRAME") && !pressed) {
                for (Planet planet : planetAppState.getPlanets()) {
                    planet.toogleWireframe();
                }
            }
            if (name.equals("COLLISION_TEST") && !pressed) {
                CollisionResults results = new CollisionResults();
                Ray ray = new Ray(cam.getLocation(), cam.getDirection());
                // Test collision with closest planet's terrain only
                planetAppState.getNearestPlanet().getTerrainNode()
                        .collideWith(ray, results);
                System.out.println("----- Collisions? " + results.size()
                        + "-----");
                for (int i = 0; i < results.size(); i++) {
                    // For each hit, we know distance, impact point, name of
                    // geometry.
                    float dist = results.getCollision(i).getDistance();
                    Vector3f pt = results.getCollision(i).getContactPoint();
                    String hit = results.getCollision(i).getGeometry()
                            .getName();
                    System.out.println("* Collision #" + i);
                    System.out.println("  You shot " + hit + " at " + pt + ", "
                            + dist + " wu away.");
                }
                if (results.size() > 0) {
                    // The closest collision point is what was truly hit:
                    CollisionResult closest = results.getClosestCollision();
                    // Let's interact - we mark the hit with a red dot.
                    mark.setLocalTranslation(closest.getContactPoint());
                    rootNode.attachChild(mark);
                } else {
                    // No hits? Then remove the red mark.
                    rootNode.detachChild(mark);
                }
            }
        }
    };
}

Solution

  • Going off your last comment, I am posting this as an answer (although I'm not exactly sure what to use as a cross vector.

    When retrieving the cross vector, we are looking to get a perpendicular to the straight line out the front of the craft, and the vertical line that is perpendicular to the straight line, going vertically through the center of the craft.

    I assume that direction is our forward-direction vector, in which case (regardless of view) we want to cross this with the vertical line that goes through the center of the craft. The crossLocal of these two vectors would be a perpendicular line to both, either going out of the left or right of the craft (regardless of camera or craft orientation).

    for my code fix, I will assume craftSkewer is an imaginary skewer that runs through the center of the craft, vertically.

    direction.crossLocal(craftSkewer.UNIT_Y).multLocal(movement * tpf);
    

    I think the reason this works initially is due to the UNIT_Y returning 0 - But after moving craft or camera, it is recalculated incorrectly?