Bug camera

Hi, it’s always me! I finally managed to develop several AppStates for my game (I have 3 one for the menu, one during the game, and one for the game paused), when I pause the game and then I resume everything fine, but if I switch from I play the menu and then back to the game, the camera gets buggy … I don’t know how to explain it, so I’m attaching a video on youtube, (jmonkeyBug camera - YouTube).
I think it’s the fault of the fact that I manually control the rotation of the camera by creating a class for it.
Camera class:

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package Object;

import com.jme3.input.CameraInput;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.math.Matrix3f;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;

/**
 *
 * @author gattolfo
 */
public class FpsCam implements AnalogListener, ActionListener{

    
     final private static String[] mappings = new String[]{
        CameraInput.FLYCAM_LEFT,
        CameraInput.FLYCAM_RIGHT,
        CameraInput.FLYCAM_UP,
        CameraInput.FLYCAM_DOWN
    };
     //telecamera che controllo con questa classe
     protected Camera cam;
     //vettore che inizializza la cam
     protected Vector3f initialUpVec;
     //velocità di rotazione
     protected float rotationSpeed = 1f;
     
     protected boolean invertY = false;
     
     InputManager inputManager;
     
     public boolean canUpdate;
     public FpsCam(Camera cam, InputManager inputManager){
         this.cam = cam;
         
         initialUpVec = cam.getUp();
         System.out.print(cam.getUp());
         this.inputManager = inputManager;
         canUpdate = true;
         registerInput();
         
     }
    @Override
    public void onAnalog(String name, float value, float tpf) {
        if (name.equals(CameraInput.FLYCAM_LEFT)) {
            rotateCamera(value, initialUpVec);
        } else if (name.equals(CameraInput.FLYCAM_RIGHT)) {
            rotateCamera(-value, initialUpVec);
        } else if (name.equals(CameraInput.FLYCAM_UP)) {
            rotateCamera(-value * (invertY ? -1 : 1), cam.getLeft());
        } else if (name.equals(CameraInput.FLYCAM_DOWN)) {
            rotateCamera(value * (invertY ? -1 : 1), cam.getLeft());
        } 
    }

    @Override
    public void onAction(String action, boolean arg1, float arg2) {
        
    }
    
    //INPUT MANAGER
    private void registerInput(){
        inputManager.addMapping(CameraInput.FLYCAM_LEFT, new MouseAxisTrigger(MouseInput.AXIS_X, true),
                new KeyTrigger(KeyInput.KEY_LEFT));

        inputManager.addMapping(CameraInput.FLYCAM_RIGHT, new MouseAxisTrigger(MouseInput.AXIS_X, false),
                new KeyTrigger(KeyInput.KEY_RIGHT));

        inputManager.addMapping(CameraInput.FLYCAM_UP, new MouseAxisTrigger(MouseInput.AXIS_Y, false),
                new KeyTrigger(KeyInput.KEY_UP));

        inputManager.addMapping(CameraInput.FLYCAM_DOWN, new MouseAxisTrigger(MouseInput.AXIS_Y, true),
                new KeyTrigger(KeyInput.KEY_DOWN));
        
        inputManager.addListener(this, mappings);
        
    }
    
    
    
    protected void rotateCamera(float value, Vector3f axis) {

        if(!canUpdate)
            return;
        
        Matrix3f mat = new Matrix3f();
        mat.fromAngleNormalAxis(rotationSpeed * value, axis);
        
        Vector3f up = cam.getUp();
        Vector3f left = cam.getLeft();
        Vector3f dir = cam.getDirection();
        
        
        mat.mult(up, up);
        mat.mult(left, left);
        mat.mult(dir, dir);

        
        
        Quaternion q = new Quaternion();
        q.fromAxes(left, up, dir);
        q.normalizeLocal();

        if(up.getY()<0.3f || up.getY()<0f)
            return;
            
        cam.setAxes(q);
        
        
        //System.out.println(cam.getDirection());
        
    }
    
    
    
    
    //setter e getter
    
    public float getRotationSpeed() {
        return rotationSpeed;
    }

    public void setRotationSpeed(float rotationSpeed) {
        this.rotationSpeed = rotationSpeed;
    }
    
    public void setLocalPosition(Vector3f pos){
        cam.setLocation(pos);
    }

    public Camera getCam() {
        return cam;
    }

    public Vector3f getDirection() {
        return cam.getDirection();
    }

    Vector3f getLeft() {
        return cam.getLeft();
    }
    
    
    
}

I wonder if there is a way to reset the camera, I tried to pass camera.clone () so as not to change the main camera, but I don’t think I understand how the cameras work because I still see the main camera

Caould you send your application

You control the camera in kind of a strange way but your application is acting like constant vectors are getting messed up. Hard to say.

Try changing these lines:

To:

        Vector3f up = cam.getUp().clone();
        Vector3f left = cam.getLeft().clone();
        Vector3f dir = cam.getDirection().clone();

To see if that fixes it.

Whatever the case, your camera’s concept of what “up” is is getting messed up.

You can see other examples of camera/object movement with something like:

1 Like

Thanks for the answer, but unfortunately changing that piece of code didn’t do much …
I looked at the code you recommended, and it is very interesting and made me understand more about AppState too! But I would like to understand what gets the camcorder high, also to understand more about the Camera.

Which is that I don’t understand what I’m wrong … being that my camera control is very similar to jm3’s FlyByCamera

The devil is in the differences.

Edit: or if you switch back to fly cam and still have the issue then you know the problem isn’t the camera control at all and something else in the application.

When you jump, how do you decide what direction to jump?

when I jump I use the betterCharacterControl.jump () function;
and to manage the direction of the CharacterControl I use the one recommended by the guide
(in my Player class)

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package Object;

import com.jme3.asset.AssetManager;
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;

/**
 *
 * @author gattolfo
 */
public class BetterPlayer extends Node implements ActionListener, AnalogListener{
    
    BetterCharacterControl pControl;
    //movement
    float walkSpeed = 5f;
    private boolean up=false, right = false, down = false, left = false, run = false;
    private float strafeSpeed = 4f;
    
    FpsCam cam;
    InputManager inputManager;
    
    
    private int life =100;
    private int shield = 100;
    
    
    private Vector3f walkDirection = new Vector3f();
    private Vector3f camDir = new Vector3f();
    private Vector3f camLeft = new Vector3f();
    
    Vector3f startPos = new Vector3f(0f,0f,0f);
    public BetterPlayer(FpsCam cam,InputManager inputManager,AssetManager assetManager){
       //super("box",new Box(0.1f,0.1f,0.1f));
       

       this.setLocalTranslation(startPos);

       pControl = new BetterCharacterControl(0.5f,2f,1f); 
       this.cam = cam;
       this.inputManager = inputManager;
       pControl.setGravity(new Vector3f(0f,1f,0f));
       pControl.setJumpForce(new Vector3f(0,5f,0)); 
       pControl.warp(startPos);
       
       initKeys();
       this.addControl(pControl);
       
    }
    
    /**
     *
     * @param tpf
     */
    public void update(float tpf){
        if(pControl.isOnGround()){
           pControl.getRigidBody().setFriction(1f);
        }else{
           pControl.getRigidBody().setFriction(0f);
        }
        
        if(run)
            walkSpeed = 10f;
        else
            walkSpeed = 5f;
        walkDirection.set(0f, 0f, 0f);
        camDir.set(cam.getDirection()).multLocal(walkSpeed, 0.0f, walkSpeed);
        
        camLeft.set(cam.getLeft()).multLocal(strafeSpeed);
        
        cam.setLocalPosition(new Vector3f(this.getLocalTranslation().x ,this.getLocalTranslation().y + 1.5f,this.getLocalTranslation().z));
        
        direction(tpf);
        
    }
    
    private void direction(float tpf){
            if (left) {
                walkDirection.addLocal(camLeft);
            }
            if (right) {
                walkDirection.addLocal(camLeft.negate());
            }
            if (up) {
                walkDirection.addLocal(camDir);
            }
            if (down) {
                walkDirection.addLocal(camDir.negate());
            }
            pControl.setWalkDirection(walkDirection);
    }


    private void initKeys(){
        inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_W));
        inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_S));
        inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_A));
        inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_D));
        inputManager.addMapping("jump", new KeyTrigger(KeyInput.KEY_SPACE));
        inputManager.addMapping("run", new KeyTrigger(KeyInput.KEY_LSHIFT));
        
        
        inputManager.addListener(this, "up");
        inputManager.addListener(this, "down");
        inputManager.addListener(this, "left");
        inputManager.addListener(this, "jump");
        inputManager.addListener(this, "right");
        inputManager.addListener(this, "run");
        
    }
   

    @Override
    public void onAnalog(String action, float arg1, float arg2) {

    }
    
    @Override
    public void onAction(String binding, boolean isPressed, float arg2) {
         if (binding.equals("left")) {
          left = isPressed;
        } else if (binding.equals("right")) {
          right= isPressed;
        } else if (binding.equals("up")) {
          up = isPressed;
        } else if (binding.equals("down")) {
          down = isPressed;
        } else if (binding.equals("jump")) {
          if (isPressed) { pControl.jump(); }
        } else if (binding.equals("run")){
            run = isPressed;
        }
    }

    public BetterCharacterControl getpControl() {
        return pControl;
    }
    
    
    
}



The class is not extended to an AppState, the update method I call it in the Update method of an Appstate that I use to control all the things connected to the player

EDIT:
The problem with FlybyCamera is that the camera rotates 360 degrees and I don’t remember why I couldn’t lock it …

I think you could still be experiencing the trouble Paul mentioned with modifying constant vectors that should not be changed

As he mentioned, it is important to clone certain vectors before you do any math with them to avoid modifying an important vector that is being used by another class.

But you can also run into the same problem by using the multLocal() method in places where you instead need to use just the mult(), since the vectior.multLocal() method modifies the vector your calling the method on, while the mult() method returns a new vector that is safe to modify.

Edit: based on your video, my guess is that the problem results from your code that runs when you initiate or cleanup your appStates, since the problem only happens when you go back to the main menu and then start another game, but doesn’t appear to happen mid-game. So it could maybe help if you post the init and cleanup code that gets run between pressing the “back to menu” button and reloading the game.

It could also be related to improper cleanup in your FpsCam class, depending on whether you recreate a new FpsCam class when the player goes back and forth from the game to the main menu, or if you reuse the same FpsCam object for the entirety of the app’s runtime.

What if I create a new camera every time I start GameState? I try instead to switch the camera itself cam.clone (), but obviously I keep seeing the vision of the main camera, I try to make renderManager.setCamera (myCamera) but nothing happens … I think I need to understand better how it works the rendering of each camera on jm3

It really has nothing to do with that. You are experiencing a problem that no one else experiences.

By “modifying a constant”, we mean that Vector3f.UNIT_Y gets modified. This would then mess up any code that is also trying to use Vector3f.UNIT_Y for (0,1,0)

Put together a simple single class test case that illustrates the problem and post that. I suspect that if you try to make a simple test case then it will work fine and you can figure out what the difference is. And if not then you can post that test case and we can spot the issue for you.

2 Likes

okay, i managed to solve, (i don’t know if that’s the correct method), but as you said yourself Vector3f.UNIT_Y is (0,1,0) and when i first create my custom camera i actually take that vector (being that I don’t edit it before) but the following times I take the one from the camera that I edit to make it look around.

I simply thought of initializing the vector on which I then do the calculations to Vector3f.UNIT_Y and now it works perfectly

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package Object;

import com.jme3.input.CameraInput;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.math.Matrix3f;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;

/**
 *
 * @author gattolfo
 */
public class FpsCam implements AnalogListener, ActionListener{

    
     final private static String[] mappings = new String[]{
        CameraInput.FLYCAM_LEFT,
        CameraInput.FLYCAM_RIGHT,
        CameraInput.FLYCAM_UP,
        CameraInput.FLYCAM_DOWN
    };
     //telecamera che controllo con questa classe
     protected Camera cam;
     //vettore che inizializza la cam
     protected Vector3f initialUpVec;
     //velocità di rotazione
     protected float rotationSpeed = 1f;
     
     protected boolean invertY = false;
     
     InputManager inputManager;
     
     public boolean canUpdate;
     
     
     public FpsCam(Camera cam, InputManager inputManager, RenderManager renderManager){
         this.cam = cam;
         initialUpVec = Vector3f.UNIT_Y;
         System.out.print("vettore up:    "+cam.getUp());
         this.inputManager = inputManager;
         canUpdate = true;
         registerInput();
         
     }
    @Override
    public void onAnalog(String name, float value, float tpf) {
        if (name.equals(CameraInput.FLYCAM_LEFT)) {
            rotateCamera(value, initialUpVec);
        } else if (name.equals(CameraInput.FLYCAM_RIGHT)) {
            rotateCamera(-value, initialUpVec);
        } else if (name.equals(CameraInput.FLYCAM_UP)) {
            rotateCamera(-value * (invertY ? -1 : 1), cam.getLeft());
        } else if (name.equals(CameraInput.FLYCAM_DOWN)) {
            rotateCamera(value * (invertY ? -1 : 1), cam.getLeft());
        } 
    }

    @Override
    public void onAction(String action, boolean arg1, float arg2) {
        
    }
    
    //INPUT MANAGER
    private void registerInput(){
        inputManager.addMapping(CameraInput.FLYCAM_LEFT, new MouseAxisTrigger(MouseInput.AXIS_X, true),
                new KeyTrigger(KeyInput.KEY_LEFT));

        inputManager.addMapping(CameraInput.FLYCAM_RIGHT, new MouseAxisTrigger(MouseInput.AXIS_X, false),
                new KeyTrigger(KeyInput.KEY_RIGHT));

        inputManager.addMapping(CameraInput.FLYCAM_UP, new MouseAxisTrigger(MouseInput.AXIS_Y, false),
                new KeyTrigger(KeyInput.KEY_UP));

        inputManager.addMapping(CameraInput.FLYCAM_DOWN, new MouseAxisTrigger(MouseInput.AXIS_Y, true),
                new KeyTrigger(KeyInput.KEY_DOWN));
        
        inputManager.addListener(this, mappings);
        
    }
    
    
    
    protected void rotateCamera(float value, Vector3f axis) {

        if(!canUpdate)
            return;
        
        System.out.println("vettore up:    "+cam.getUp());
        
        Matrix3f mat = new Matrix3f();
        mat.fromAngleNormalAxis(rotationSpeed * value, axis);
        
        Vector3f up = cam.getUp().clone();
        Vector3f left = cam.getLeft().clone();
        Vector3f dir = cam.getDirection().clone();
        
//        up = mat.multLocal(up);
//        left = mat.multLocal(left);
//        dir = mat.multLocal(dir);
        mat.mult(up, up);
        mat.mult(left, left);
        mat.mult(dir, dir);

        
        
        Quaternion q = new Quaternion();
        q.fromAxes(left, up, dir);
        q.normalizeLocal();
        //q.norm();

        if(up.getY()<0.3f || up.getY()<0f)
            return;
            
        cam.setAxes(q);

        
        
        //System.out.println(cam.getDirection());
        
    }
    
    
    
    
    //setter e getter
    
    public float getRotationSpeed() {
        return rotationSpeed;
    }

    public void setRotationSpeed(float rotationSpeed) {
        this.rotationSpeed = rotationSpeed;
    }
    
    public void setLocalPosition(Vector3f pos){
        cam.setLocation(pos);
    }

    public Camera getCam() {
        return cam;
    }

    public Vector3f getDirection() {
        return cam.getDirection();
    }

    Vector3f getLeft() {
        return cam.getLeft();
    }
    
    
    
}

1 Like