CameraNode

For my game I had to handle the camera as a Node:

So here the code, to share.



Handles Camera as a Node:

(just like the CameraNode in jme2)

:smiley:

I don't know wich package makes the most sense to put this in, maybe com.jme3.scene?




import com.jme3.renderer.Camera;
import com.jme3.scene.Node;

/**
 *
 * @author tim
 */
public class CameraNode extends Node {
    private final Camera cam;
    private boolean enabled=true;

    public CameraNode(String name, Camera cam) {
        super(name);
        this.cam = cam;
    }

    public CameraNode(Camera cam) {
        this("defName", cam);
    }

    @Override
    public void updateGeometricState() {
        super.updateGeometricState();
       
        if (enabled) {
            // Update Camera:
            cam.setLocation(getWorldTranslation());
            cam.setRotation(getWorldRotation());
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final CameraNode other = (CameraNode) obj;
        if (this.cam != other.cam && (this.cam == null || !this.cam.equals(other.cam)))
            return false;
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 59 * hash + (this.cam != null ? this.cam.hashCode() : 0);
        return hash;
    }
   
    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

}

Nice, can you make it implement Savable and add read() and write() methods?

and add more java doc :slight_smile:

normen said:

Nice, can you make it implement Savable and add read() and write() methods?


Javadoc just added, but i'm not quite sure how to save it:
How can you save a Camera?
I mean, save it in a way, so that it is the camera used by an Application?
And.. should field camera be reassignable? I think it shouldn't. Because then it can be used in HashSets/Maps better.
And you don't really need to set the Camera, do you?
tim8dev said:

Javadoc just added, but i'm not quite sure how to save it:
How can you save a Camera?
I mean, save it in a way, so that it is the camera used by an Application?
And.. should field camera be reassignable? I think it shouldn't. Because then it can be used in HashSets/Maps better.
And you don't really need to set the Camera, do you?

Yeah, was thinking of that myself when I hit "post" just wanted to see what you make out of it ;)
Its true, it cannot be saved properly right now.. But it has to be saveable to not make the whole tree unsaveable, an easy way to re-add the cam after loading should suffice I think.

Just a small idea, maybe this should be implemented as a Control? All functionality will still be there.

Momoko_Fan said:

Just a small idea, maybe this should be implemented as a Control? All functionality will still be there.

I see the reason in doing a Node, you might want to attach it to something else like a car or character.

Yeah but you can create a node and attach the control to it and it will be exactly the same.

Momoko_Fan said:

Yeah but you can create a node and attach the control to it and it will be exactly the same.


Yes, it would behave exactly the same, but it would even reduce some overhead, because you don't create BoundingVolumes etc. on the CameraNode level.

Well, but if you want to add a Control you have to expand the ControlType enum by another Type, don't you?
I may be wrong, but when I wanted to attach a InputController to my Node, I couldn't implement it as a Control, because there were no proper ControlType... :(
tim8dev said:

Yes, it would behave exactly the same, but it would even reduce some overhead, because you don't create BoundingVolumes etc. on the CameraNode level.

Well, but if you want to add a Control you have to expand the ControlType enum by another Type, don't you?
I may be wrong, but when I wanted to attach a InputController to my Node, I couldn't implement it as a Control, because there were no proper ControlType... :(

Afaik right now the Controllers are registered via classes, not via some ControlType anymore.

Yes it's no longer there. I realized it is a big limitation to customization and removed it, so it is class based now.

Oh! I've just updated my jME3, so I should take a look into it :smiley:



I've already noticed some changes in the Assetmanager ;-]



But if it's class based, it means I can now implement my InputController as a Control :slight_smile:

And I'll implement the Camera as a Control, too.

Thanks ;-]

So… here it is: the CameraControl :smiley:

You can set the ControlDirection now!


import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
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;

/**
 * This Control maintains a reference to a Camera,
 * wich will be synced with the position (worldTranslation)
 * of the current spatial.
 * @author tim
 */
public class CameraControl extends AbstractControl {
    public static enum ControlDirection {
        /**
         * Means, that the Camera's transform is "copied"
         * to the Tranform of the Spatial.
         */
        CameraToSpatial,
        /**
         * Means, that the Spatial's transform is "copied"
         * to the Tranform of the Camera.
         */
        SpatialToCamera;
    }
    
    private Camera camera;
    private ControlDirection controlDir = ControlDirection.CameraToSpatial;

    /**
     * Constructor used for Serialization.
     */
    public CameraControl() {}

    /**
     * @param camera The Camera to be synced.
     */
    public CameraControl(Camera camera) {
        this.camera = camera;
    }

    public CameraControl(Spatial spatial, Camera camera, ControlDirection controlDir) {
        super(spatial);
        this.camera = camera;
        this.controlDir = controlDir;
    }
    
    /**
     * @param spatial The spatial to be synced.
     * @param camera The Camera to be synced.
     */
    public CameraControl(Spatial spatial, Camera camera) {
        super(spatial);
        this.camera = camera;
    }

    public Camera getCamera() {
        return camera;
    }

    public void setCamera(Camera camera) {
        this.camera = camera;
    }

    public ControlDirection getControlDir() {
        return controlDir;
    }

    public void setControlDir(ControlDirection controlDir) {
        this.controlDir = controlDir;
    }

    // fields used, when inversing ControlDirection:

    @Override
    protected void controlUpdate(float tpf) {
        if(spatial != null && camera != null) {
            switch(controlDir) {
                case SpatialToCamera:
                    camera.setLocation(spatial.getWorldTranslation());
                    camera.setRotation(spatial.getWorldRotation());
                    break;
                case CameraToSpatial:
                    // set the localtransform, so that the worldtransform would be equal to the camera's transform.
                    // Location:
                    Vector3f vecDiff = camera.getLocation().subtract(spatial.getWorldTranslation());
                    spatial.getLocalTranslation().addLocal(vecDiff);

                    // Rotation:
                    Quaternion worldDiff = camera.getRotation().subtract(spatial.getWorldRotation());
                    spatial.getLocalRotation().addLocal(worldDiff);
                    break;
            }
        }
    }

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {
        // nothing to do
    }

    @Override
    public Control cloneForSpatial(Spatial newSpatial) {
        final CameraControl control = new CameraControl(newSpatial, camera, controlDir);
        control.setEnabled(isEnabled());
        return control;
    }

    private static final String CONTROL_DIR_NAME = "controlDir";
    @Override
    public void read(JmeImporter im) throws IOException {
        super.read(im);
        im.getCapsule(this).readEnum(CONTROL_DIR_NAME,
                ControlDirection.class, ControlDirection.SpatialToCamera);
    }

    @Override
    public void write(JmeExporter ex) throws IOException {
        super.write(ex);
        ex.getCapsule(this).write(controlDir, CONTROL_DIR_NAME,
                ControlDirection.SpatialToCamera);
    }

}



And here: a shortcut to use Camera as a Node:
the.. CameraNode! :D


import com.blemme30.control.CameraControl.ControlDirection;
import com.jme3.renderer.Camera;
import com.jme3.scene.Node;

/**
 * This Node is a shorthand for using a CameraControl.
 *
 * @author Tim8Dev
 */
public class CameraNode extends Node {
    private CameraControl camControl;

    /**
     * for IO purpose
     */
    public CameraNode() {}
    public CameraNode(Camera camera) {
        this("defCamNodeName", camera);
    }
    public CameraNode(CameraControl control) {
        this("defCamNodeName", control);
    }
    public CameraNode(String name, Camera camera){
        this(name, new CameraControl(camera));
    }
    public CameraNode(String name, CameraControl control) {
        super(name);
        addControl(control);
        camControl = control;
        camControl.setSpatial(this);
    }

    public void setEnabled(boolean enabled) {
        camControl.setEnabled(enabled);
    }

    public boolean isEnabled() {
        return camControl.isEnabled();
    }

    public void setControlDir(ControlDirection controlDir) {
        camControl.setControlDir(controlDir);
    }

    public void setCamera(Camera camera) {
        camControl.setCamera(camera);
    }

    public ControlDirection getControlDir() {
        return camControl.getControlDir();
    }

    public Camera getCamera() {
        return camControl.getCamera();
    }
}



It can be easily modified to control an Light, instead of a Camera!

Looks fine, although note that the getWorld*** calls will return bad data in the controlUpdate method, since it is called before updateGeometricState().

I am guessing we might need something like controlPreUpdate, controlPostUpdate?

Hi Guys,

Thanks you to have share this CameraNode feature. Is it normal I don’t see thoses classes in JME3 releases ?

Yeah I was going to add those but at that time the site was offline.

This should be in the latest SVN.

Looks fine, although note that the getWorld*** calls will return bad data in the controlUpdate method, since it is called before updateGeometricState().



Wait, i thought the partial update stuff is working? At least it seems so.

You might be right, I haven’t actually tried this class yet.

Partial update should be working, so using getWorld****() calls should be fine.

Hi



Looking at the code of CameraControl, I am wondering why’s that this is working.



I mean, I thought I finally cracked the use of quaternions (reading books and online materials), but then I see Quaternion.add(Quaternion) and Quaternion.substract(Quaternion) used here and can’t explain it to myself. I would say there need to be some quaternions multiplication (division / inversion) involved since that’s the way to combine rotations - or at least that’s what I thought.



So … anyone willing to explain why is this working with straight addition / substraction ? Do they really handle differences of quaternions - how come none of articles dedicated to rotations in 3d space / games I’ve read tells this ?!



Thx

Bump



sorry reviving this, but I don’t feel comfortable with moving further in my protoyping since there’s still this issue that proves I can’t count on my understanding of things. Pls, help me in this. Someone. Thx