Spatial to Camera

Perhaps this is a silly question, but I'm having a hard time figuring out how to make my camera synchronize rotation and aim the same direction my spatial is.  If I want to update my camera to follow directly behind a spatial as a simple following camera…just like camera node, but manually…how do I do that?  I'm really having difficulties trying to convert direction, up, left, location to my spatial's values.  I'm assuming someone like mojo or renanse could do this in one line of code, but Quaternion's and Vector3fs are a place I might always be lacking. :wink:

About to go to sleep here, but to get you started, the trick is to convert the spatial's world rotation quaternion into a 3x3 matrix and use various columns as left, forward and up.  See the key action classes for tips on how this works.

The rotation of the node doesn't specify where that spatial looks at (that depends on how that model is modelled):



BUT if we suppose that a given model looks at, say, (UNIT_Z * -1.0f), then you could do just:



Quaternion q = new Quaternion();
Vector3f observerRelativePosition = q.mult(Vector3f.UNIT_Z).normalize();
Vector3f observerUp = q.mult(Vector3f.UNIT_Y).normalize();
   
Camera camera = getCamera();
Vector3f target = spatial.getWorldTranslation();
Vector3f pos = target.add(spatial.getWorldRotation().mult(observerRelativePosition).normalize().mult(distance));
camera.setLocation(pos);
camera.lookAt(target, observerUp);
camera.update();



Are you trying to make your camera controller system? I've made a simple one too, because CameraNodes are always attached to one camera and are updated even if it is not needed.

Warning: this method is probably wrong and surely should be optimized.

Thanks, that's very helpful.  jjmontes, if you would like to post your custom code that might be very beneficial to me and anyone else interested in this.



Thanks again!

Ummm, my code for CameraControllers is really short. Also, I haven't solved some critical things like panning around objects…



Anyway, what I'm using is a CameraController. A CameraController is updated in my 'game loop'. So, I just need to update the selected CameraController each frame. If I want to change camera, I only have to set another CameraController.



Note that I'm assuming that all CameraControllers act on the actual renderer's camera. That's ok for me but you can change the getCamera method on this abstract class to provide control on different cameras.



CameraController.java



import com.jme.input.action.InputActionEvent;
import com.jme.renderer.Camera;
import com.jme.system.DisplaySystem;

/**
 * A camera attached to a Viewable entity.
 */
public abstract class CameraController {
   
   /**
    * Updates the camera using its controller
    */
   public abstract void update(float tpf);
   
   /**
    * Process mouse input
    */
   public abstract void processMouseInput(InputActionEvent evt);
   
        /**
         * The camera to update
         */
   protected Camera getCamera() {
      return DisplaySystem.getDisplaySystem().getRenderer().getCamera();
   }
   
}



My entities have a:

HashMap<String, CameraController> cameraControllers;

So I can reach their cameras easily. But I use the CameraController separately too.

Mi airplanes, in example, have a first person camera. I implemented this with a LockedCameraController. It just needs a spatial, a position relative to that spatial, and a view vector (I assume that the up vector is Vector3f.UNIT_Y). This is not very different to what a CameraNode would do:


import com.jme.input.action.InputActionEvent;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.scene.Spatial;

/**
 * A camera updater that follows orientation and position of a spatial.
 */
public class LockedController extends CameraController {
   
   protected Spatial spatial = null;
   
   protected Vector3f positionVector;
   
   protected Vector3f viewVector;
   
   public LockedController(Spatial spatial) {
      super();
      this.spatial = spatial;
   }

   /**
    * Process input
    */
   public void processMouseInput(InputActionEvent evt) {
      
   }
   
   /**
    * Updates the camera using this controller
    */
   public void update(float tpf) {
      
      Vector3f pos = spatial.getWorldTranslation().add(spatial.getWorldRotation().mult(positionVector));
      Vector3f target = pos.add(spatial.getWorldRotation().mult(viewVector));
      Vector3f up = spatial.getWorldRotation().mult(Vector3f.UNIT_Y);

      Camera camera = getCamera();
      camera.setLocation(pos);
      camera.lookAt(target, up);
      camera.update();
   }



   /**
    * @return the view
    */
   public Vector3f getViewVector() {
      return viewVector;
   }

   /**
    * @param view the view to set
    */
   public void setViewVector(Vector3f view) {
      this.viewVector = view;
   }

   /**
    * @return the positionVector
    */
   public Vector3f getPositionVector() {
      return positionVector;
   }

   /**
    * @param positionVector the positionVector to set
    */
   public void setPositionVector(Vector3f positionVector) {
      this.positionVector = positionVector;
   }
   
}



The cameras system is really simple. It is just a piece of code that updates the camera. The example above uses a Spatial as reference.

I have another couple controllers for external cameras (with zoom and panning) but panning around is not working correctly by now (damn Quaternions). Also, I'm letting the camera controllers process input events themselves, which I dislike.

It is not good code neither good design. But hope it answers your question.



Very helpful…thanks

jjmontes said:

The rotation of the node doesn't specify where that spatial looks at (that depends on how that model is modelled):

BUT if we suppose that a given model looks at, say, (UNIT_Z * -1.0f), then you could do just:


Quaternion q = new Quaternion();
Vector3f observerRelativePosition = q.mult(Vector3f.UNIT_Z).normalize();
Vector3f observerUp = q.mult(Vector3f.UNIT_Y).normalize();
   
Camera camera = getCamera();
Vector3f target = spatial.getWorldTranslation();
Vector3f pos = target.add(spatial.getWorldRotation().mult(observerRelativePosition).normalize().mult(distance));
camera.setLocation(pos);
camera.lookAt(target, observerUp);
camera.update();



Are you trying to make your camera controller system? I've made a simple one too, because CameraNodes are always attached to one camera and are updated even if it is not needed.

Warning: this method is probably wrong and surely should be optimized.


Okay, this is actually very helpful but when I hold the throttle forward or back button my spatial moves in TestSwingControlEditor and doesn't stay stationary as it should....it's very strange though because in actuality it only moves as I hold the button but with the throttle system it is still moving in the world when I let go of the button.

If you replace FollowingCameraPerspective with this:

public class FollowingCameraPerspective implements CameraPerspective {
   Quaternion q = new Quaternion();
   
   public FollowingCameraPerspective() {
   }
   
   public void update(Camera camera, Spatial spatial, float time) {
      Vector3f observerRelativePosition = q.mult(Vector3f.UNIT_Z).normalize();
      Vector3f observerUp = q.mult(Vector3f.UNIT_Y).normalize();

      Vector3f target = spatial.getWorldTranslation();
      Vector3f pos = target.add(spatial.getWorldRotation().mult(observerRelativePosition).normalize().mult(-15.0f));
      camera.setLocation(pos);
      camera.lookAt(target, observerUp);
      camera.update();
   }
}



You can see what I'm talking about....I'm still looking into this but not exactly sure why it's happening....