0

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);
                }
            }
        }
    };
}
Code Enjoyer
  • 681
  • 4
  • 18
Niklas Rosencrantz
  • 25,640
  • 75
  • 229
  • 424
  • 1
    In your method: public void onAnalog(String name, float value, float tpf), would changing "cam" to "camNode" be correct? I don't see cam as accessible in your full code. – MaineCoder Jan 08 '14 at 13:17
  • @MaineCoder I think that `cam` might be inherited from the base class. There was no error about using it and it works from forward and backwards movement. You can look if you like at my project at sourceforge.net/projects/spaceworld/ – Niklas Rosencrantz Jan 08 '14 at 13:34
  • 1
    why are there crossLocal and multLocal for left and right, but only multLocal for forwards and backwards? – MaineCoder Jan 08 '14 at 15:58
  • 1
    I feel that .crossLocal(Vector3f.UNIT_Y) is causing the issue here as you are calling the UNIT_Y from the class Vector3f, rather than an instance of the class... – MaineCoder Jan 08 '14 at 16:05
  • @MaineCoder It works at the beginning but not after I rotate the camera. I probably should use the `crossLocal` but I don't know which Vector to use there. Do you have an idea which Vector should be the parameter to crossLocal? Since it works right at the beginning before I move the camera then I might need the Vector from the camera and than Vector is the same as the movement Vector for the UFO. – Niklas Rosencrantz Jan 08 '14 at 18:07

1 Answers1

1

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?

MaineCoder
  • 159
  • 1
  • 10
  • Yes. But how to compute `craftSkewer` ? I use the Node class from jmonkeyengine and there is no method that returns something like what I need here. – Niklas Rosencrantz Jan 08 '14 at 18:50
  • 1
    You will have to keep a vector in memory that is the vertical perpendicular to the straight direction and keep it updated for all movements. Or, calculate the vertical position based on the values of the forward position and rotation of the craft. I think keeping a vector in memory that is modified would be easiest with the current code. – MaineCoder Jan 08 '14 at 18:59
  • Ok. But how get the perpendicular to the straight forward direction to begin with? The Vector3F doc is http://www.jmonkeyengine.org/doc/com/jme/math/Vector3f.html And I find 2 methods `crossLocal` and `cross` and I don't understand the difference. I think there should be a method like `getPerpendicular` for a Vector3f and a way to get the `craftSkewer` directly but it's hard to do this. – Niklas Rosencrantz Jan 08 '14 at 19:12
  • 1
    Cross returns the cross, crossLocal modifies the passed object. – MaineCoder Jan 08 '14 at 19:13
  • Ok thanks. I think I'm looking for something with the notion of "local up" relative to the direction we got. If I got the `direction` I'm looking for something like `direction.localUp()` which I think is some rotation isn't it? If I rotate the variable `direction` 90 degrees and then take the cross from those 2 vectors? But I still don't know exactly how. – Niklas Rosencrantz Jan 08 '14 at 19:21
  • 1
    Getting closer with that method I think - I would play around with some different configs and see what works (let me know what the solution is haha). I only wish I had taken any classes in 3D design and vectors so that I would know the answer for sure... – MaineCoder Jan 08 '14 at 19:37
  • Thanks! This works: `direction.set(cam.getLeft()).multLocal(-speed * tpf);` – Niklas Rosencrantz Jan 08 '14 at 20:01