[SOLVED] How to use Custom Camera?

Hello Everyone

I’m looking for a way to change few things about how camera (FlyByCamera for now but it may change at some point) respond to controls.

For now I’m trying to disable completely WSAD movement and MOUSE rotation and force MOUSE to actually move point of view LEFT/RIGHT (former AD strafe) and TOP/BOTTOM (former QZ) thus forcing my camera to move only in 2 dimensions.

By searching around the forums here I got the impression that the best way to do it (especially with the possibility that I will change something else about my camera later) is copy-paste original FlyByCamera class, rename it (filename, classname, contructorname) and put between my files. Then I can change this class however I want (for example remap keys as I see fit).

But the question is: how do I run this camera in my main class? I did find command for disabling original FlyByCamera with:
flyCam.setEnabled(false);
used inside my SimpleInitApp Function

but now I need to make my application use my custom camera instead…

One idea that I used in my games is to create your own control class for a special node called Camera node

CameraNode cameraNode = new CameraNode("Main Camera", app.getCamera());
cameraNode.setControlDir(CameraControl.ControlDirection.SpatialToCamera);

GameCameraControl gameCameraControl = new GameCameraControl();
gameCameraControl.setInput(app.getInputManager());
cameraNode.addControl(gameCameraControl);

GameCameraControl allows me to control the cameraNode however I want and the camera will just be where cameraNode is.

You just create your new Camera object and connect it with the application cam (like the FlyByCamera constructor does). The FlyByCamera reacts on the user’s input and moves the application cam. I prefer using a control to control the camera btw… My way to do this is creating a camera spatial, and adding a CameraControl to it that sets the camera position and rotation to the spatial’s. But I am sure there are more ways to do it. For the start I think it is easier to write a completely own class, so you do exactly know what happens.

bloodwalker
Where Your code would go exactly? For testing purposes I just have one main application class with few custom models from blender loaded here and there, and several lightsources.

Smire
Code please.
I have:
TestCamera testcamera = new TestCamera(cam);
(TestCamera is exact duplicate of original FlyByCamera class with appropriate names changed of course).

To do it my way, just add a camera node to the rootNode. Write a CameraControl class (extends AbstractControl) and add it to the camera node. Give it the camera in the constructor or a init method.
The updateControl method would be something like

camera.setLocation(spatial.getWorldTranslation());
camera.setDirection(spatial.getWorldRotation());

Whenever you move or rotate the camera node with your own key bindings the camera gets the same location. You can turn this on and off by enabling or disabling the cameraControl.

ok, then you would just need these:

CameraNode cameraNode = new CameraNode("Main Camera", getCamera());
cameraNode.setControlDir(CameraControl.ControlDirection.SpatialToCamera);

CameraNode is within JME, so you can control it within the main app class

To use your own camera input system you will have to remove the existing one. Search for “remove flycamappstate”

Best way to handle camera input is with an app state. For whatever reason, things like FlyByCamera are not written this way to their detriment (possibly app states didn’t exist when it was written).

If it were me, I’d copy the relevant bits of FlyByCamera into an app state, ie: fold what FlyByCamAppState and FlyByCamera are doing into one app state class then modify it as you like. You can just pass it on the super() constructor of your application and then you won’t even have the default flycam app state messing you up.

bloodwalker
Ok Your camera node works but it has exact same settings as FlyByCamera. Now how do I change those settings? For example clear all key bindings and bind 4 mouse directions to strafe-left/strafe-right/move-up/move-down?

Smire
Does “write the camera control class” is the same as “copy-paste whole FlyByCamera class contents to new class”? Because what I need is changing few things not writing whole camera code from scratch… If the answer is Yes then I already have my custom class with FlyByCamera code (with few commented things to check if it works) but I don’t know how to make my application use it.

you first disable the flyCam before using the cameraNode. Sorry, forgot to mention that.

@necxelos

Here is my CameraControl from my current project (a RTS camera):

public final class CameraControl extends AbstractControl{
    
private Camera cam = null;
private InfiniteTerrain terrain = null;

public CameraControl(Camera camera){
    cam = camera;
}

/**
 * Resets the cam to its default position and rotation
 */
public void reset(){
    float h = 10;
    
    if(terrain != null)
        h = terrain.getHeight(new Vector2f(10, -5)) + 10;
    
    this.getSpatial().setLocalTranslation(10, h, -5);
    this.getSpatial().lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
}

public void setTerrain(InfiniteTerrain terrain){
    this.terrain = terrain;
}

@Override
protected void controlUpdate(float tpf) {
    cam.setRotation(this.getSpatial().getWorldRotation());
    cam.setLocation(this.getSpatial().getWorldTranslation());
}

@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}

}

bloodwalker
Ok, I figured this out (had FlyByCam disabling commented for a moment). Now for the “bind 4 mouse directions to strafe-left/strafe-right/move-up/move-down?” part…

Smire
Gimme a moment to test out Your stuff. You guys are so awesome (by replying so fast) that I’m unable to keep up :stuck_out_tongue:

you can bind it in a similar way as in the “Hello Input System” tutorial and setting up an Analog Listener in your code to move cameraNode

bloodwalker
That was the very thing I’m trying to avoid. Writing stuff from scratch, while I only need to change something in already existing solution.

pspeed
I’ll quote You here because that was the very solution I was looking for:

[quote=“pspeed, post:7, topic:33131, full:true”]
A tongue-in-cheek solution:
Step 1: Open FlyBycam.java
Step 2: Ctrl-A
Step 3: Ctrl-C
Step 4: Open a new file MyFlyCam.java
Step 5: Ctrl-V
Step 6: Change class name to MyFlyCam.java and constructor
Step 7: Change mappings as needed.[/quote]

Now my question is WHAT NEXT? How do I apply this new camera to my app? Hopefully it will be as simple as disabling original one.

And actually, I wouldn’t even do that. I’d combine FlyCamAppState and FlyByCamera into your own custom app state… then attach that instead of the FlyCamAppState.

…but I already said that.

To be completely honest unless You can show me some working code I have absolutely no idea what You mean by combining those 2 things. And JMonkey apart from being great tool is also one of the worse documented tools I ever seen so I can’t ever google “ready to go tutorial” using Your words :confused:

I’m completely new to doing anything in 3d Engine so please bear with me and give me some working code example. The best thing would be for me to compare Your 2 methods (pspeed and Smire because the one from bloodwalkers don’t need any further explanation) of doing the thing I mentioned before which is:

  • Having clean controls (meaning: every possible button on every possible device free to use and not bound to any action),
  • Having bound mouse axis (4 directions) to moving (not rotating) up/down/left/right,
private void initKeys() {
    inputManager.addMapping("Left",   new MouseAxisTrigger(MouseInput.AXIS_X, true));
    inputManager.addMapping("Right",  new MouseAxisTrigger(MouseInput.AXIS_X, false));

    inputManager.addListener(analogListener,"Left", "Right");
}

private AnalogListener analogListener = new AnalogListener() {
    public void onAnalog(String name, float value, float tpf) {
        if (name.equals("Right")) {
            Vector3f v = cameranode.getLocalTranslation();
            cameranode.setLocalTranslation(v.x + value*speed, v.y, v.z);
        }
        if (name.equals("Left")) {
            Vector3f v = cameranode.getLocalTranslation();
            cameranode.setLocalTranslation(v.x - value*speed, v.y, v.z);
        }
    }
};

public void simpleInitApp() {
    flyCam.setEnabled(false);
    initCrossHairs(); // a "+" in the middle of the screen to help aiming
    initKeys();       // load custom key mappings

    CameraNode cameranode = new CameraNode("Main Camera", getCamera());
    cameranode.setControlDir(CameraControl.ControlDirection.SpatialToCamera);
    rootNode.attachChild(cameranode);

bloodwalker
So it doesn’t work. My IDE says something about cameranode not being recognized in AnalogListener function.

Create a private variable for cameraNode in your main application class

private CameraNode cameranode;

then you can initialize it in simpleInitApp()

cameranode = new CameraNode("Main Camera", getCamera());

bloodwalker
Love You! It works! Something actually works for a change :slight_smile:

Now that stuff that is working brought another 2 questions:
a) I have normal mouse pointer and while I move it camera moves as well. What I need though is either:

  • A way to “grab screen and move it left and right while button is pushed and stays where it is when I release button”. Just like touch scrolling.
  • A way for this custom camera to act like FPS one (which would be this FlyByCamera) except for cleared keybindings/custom keybindings I just managed to make working.
    b) Why moving camera with mouse adjusts to mouse position instead of “how much it moved”? I mean when mouse pointer leaves the app-window in one side (this camera travelled a little in one direction) and I enter app-window from another side the camera position just switches back to normal (moves back in opposite direction).

pspeed, Smire
I would still be more then happy to check Your ways of doing the stuff as well - hopefully in a way that can be tested right out from the box :slight_smile:

I’ve posted my way in three or four different threads already… so I’m not super-psyched to do it again. As for writing code for you, I’d be glad to quote contractor rates.

A.1) to “grab a screen” just create a trigger for the left click button and a private isGrabbed variable

private boolean isGrabbed;
....
inputManager.addMapping("Grab", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
inputManager.addListener(this, "Grab");

public void onAction(String name, boolean isPressed, float tpf) {
        if(name.equals("Grab")){
            isGrabbed = isPressed;
        }
    }

then on your analog method:

private AnalogListener analogListener = new AnalogListener() {
    public void onAnalog(String name, float value, float tpf) {
        if (name.equals("Right") && isGrabbed) {
            Vector3f v = cameranode.getLocalTranslation();
            cameranode.setLocalTranslation(v.x + value*speed, v.y, v.z);
        }
        if (name.equals("Left") && isGrabbed) {
            Vector3f v = cameranode.getLocalTranslation();
            cameranode.setLocalTranslation(v.x - value*speed, v.y, v.z);
        }
    }
};

as for B, I cannot say for certain, there are other ways to get the position of the mouse on screen and use it to calculate how much it moved and then translate that to your code.

1 Like