Several First-Person camera pitfalls (speed and angle) [jME3]

@pspeed said: It might also be getting buggy because of the strange way you are using the up vector. It should be 0,1,0 every time. As written, it's not even a proper direction vector so you might be corrupting your view projection matrix over time.

Thanks for the advice, setting the up vector as you said fixed the buggyness at higher inclinations… Tested inclination up to 0.99.
I guess that should have been obvious. Of course the up vector should only have a Y component. ~_~
Vectors still perplex me. It’s hard to think about something in a coordinate system that doesn’t have position.

@UMS-kid said: Thanks for the advice, setting the up vector as you said fixed the buggyness at higher inclinations.. Tested inclination up to 0.99. I guess that should have been obvious. Of course the up vector should only have a Y component. ~_~ Vectors still perplex me. It's hard to think about something in a coordinate system that doesn't have position.

…reminds me of when I walked into a coworkers office and he was holding red, green, and blue white board markers in each hand positioned as orthogonal axes. He was trying to visualize rotations from one reference frame to another and was using left hand as his starting reference frame and right hand as the result. Clever… but still funny to walk in on someone waving their fists full of whiteboard markers in the air.

Cheap coordinate axes for the win: :smiley:

2 Likes

Thank you to both of you. Both your combined answers with my previous method resolved this issue in my project for good now… can no longer go upside down! :stuck_out_tongue: The up vector is VERY important tough… else it was really screwing up the matrix when I was shaking the mouse violently :stuck_out_tongue: All billboards were no longer straight up. I just use the Vector3f.UNIT_Y constant instead of new Vector3f(0,1,0) as the up vector tough.

[java]
// Declare this in the very top of the application:
private Quaternion old_cam_direction = new Quaternion();

// Then in the simpleUpdate() function where you constrain your player movements, put this and that’s it…

            // Prevent upside down camera angles by reverting
            // to previous frame's camera rotation if it goes too far...
            if(cam.getDirection().getY() > 0.97f){
                cam.setRotation(old_cam_direction);
                cam.lookAtDirection(new Vector3f(cam.getDirection().getX(),0.97f,cam.getDirection().getZ()).normalizeLocal(), Vector3f.UNIT_Y);
            }
            else if(cam.getDirection().getY() < -0.97f){
                cam.setRotation(old_cam_direction);
                cam.lookAtDirection(new Vector3f(cam.getDirection().getX(),-0.97f,cam.getDirection().getZ()).normalizeLocal(), Vector3f.UNIT_Y);
            }
            else{
                old_cam_direction = cam_node.getLocalRotation();
            }

[/java]

EDIT: .normalizeLocal()

Note: the vector you are passing to lookAtDirection() in the first two cases is slightly unnormalized. You may want to normalizeLocal() it as you pass it in.

That’s true, but what harm could a 0.03f offset concretely cause? I’m interested in understanding the effect this could have: is it going to accumulate those offsets from frame to frame or can it break something in the camera matrix?

@.Ben. said: That's true, but what harm could a 0.03f offset concretely cause? I'm interested in understanding the effect this could have: is it going to accumulate those offsets from frame to frame or can it break something in the camera matrix?

It creates an “almost valid” quaternion that will return “almost valid” vectors when transformed. Better just to normalize it and not worry about it.

if my mouse move too fast, the problem still happens, but it will work in most cases I think. btw, have you improved it? but… we are trying to fix the result and not the origin of the problem this way.

@wezrule

this worked perfectly with mouse at full speed

@override
protected void rotateCamera(float value, Vector3f axis){
...
mat.mult(up,up);
if (firstPersonConstraint && up.getY() < 0) {
  return;
}

but I overriden SimpleApplication.initialize()
and thru reflex modified private FlyCamAppState.flyCam to my extended flybycamera class with that simple patch, and… I dont know if doing this is actually good, despite fun… hehe

Extending is so poweful, I guess it’s even simpler than all that approximate sh%& I did on my own lol GJ :stuck_out_tongue:

I just think that, @Ben1 your approach or mine are better than copying and modifiying the full class, because they may change/improve things and if we have a modified copy it will be troubling to update it too…

but instead, if we just extend and apply small fixes, or work after like you tried, is better, has less chances of we missing something later

I would like to link this thread, seems to have other ideas also:

if someone wants to try it, here is the trick, it is for SimpleApplication extended Main class

public static class FpsFlyByCamera extends FlyByCamera{
	private FpsFlyByCamera(Camera cam) {super(cam);}
	
	@Override
protected void rotateCamera(float value, Vector3f axis){
		Matrix3f mat = new Matrix3f();
		mat.fromAngleNormalAxis(rotationSpeed * value, axis);
		Vector3f up = cam.getUp().clone();
		mat.mult(up, up);
		if (up.getY() < 0) {
			return;
		}		
		
		super.rotateCamera(value, axis);
}
}

@Override
public void initialize() {
	super.initialize();
	if(super.inputManager != null){
		if(super.stateManager.getState(FlyCamAppState.class) != null){
			super.flyCam = new FpsFlyByCamera(cam);
			super.flyCam.setMoveSpeed(1f);
			
			try { //trick
				FlyCamAppState cs = super.stateManager.getState(FlyCamAppState.class);
				Field fld = FlyCamAppState.class.getDeclaredField("flyCam");
				fld.setAccessible(true);
				fld.set(cs, super.flyCam);
			} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
				Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
			}
		}
	}
}

You only have to do that if you actually use the flyCam field… which is a bad idea in general because it was a mistake to have made these fields protected in the first place.

A more forward-looking solution is just to use your own AppState instead, either based on FlyCamAppState (and then just ignore the setCamera() call) or create your own app state and instantiate your fly cam subclass there.

Someday, SimpleApplication will be refactored and deprecated and your current solution will fail… but an app state based solution would continue to work.

I am still using SimpleApplication because it is pretty ready for a quick start, and is working pretty well I must say.

But later, my intent is to extend Application like SimpleApplication does, then I think all that will be better to work with.

Well, extending Application directly will certainly break in the future. People read “SimpleApplication” and think “Oh, that’s just for simple things” but it’s not. It’s the class that everyone should extend to make a game. Part of the refactoring will fix this naming to avoid confusion… and Application will be deprecated completely when that happens. We will at least provide a migration path for SimpleApplication but if you are going against all advice and extending Application directly then you will be on your own.

There is really no good reason to avoid using SimpleApplication. That’s the entire point of AppStates in the first place. And if you aren’t using AppStates then you are really just doing it wrong.

1 Like

oh… but then, the current naming is non-intuitive,
til now I believed SImpleApplication was intended only to make it easy to create the JME Tests! and we should extend Application, now I see clearly at their javadoc that the right is to use SimpleApplication… thx on saying that… good I didnt even begin :slight_smile:

Btw, I suggest SimpleApplication be renamed to ApplicationBase.

Oh I understand now, so I can create a FlyCamAppState and still use SImpleApplication, I didnt see that at first, I will try code it now and see what happens! that hack I did is just fun…

[SOLVED, see at the end]

wow… I am having a hard time trying to figure out how to use my flycamappstate…

EDIT: my application will not initialize properly (the terrain wont show up and other things) if I do this:

@Override
public void initialize() {
	super.stateManager.attach(new FpsFlyCamAppState());
	super.initialize();
}

or this

@Override
public void initialize() {
    super.initialize();
    super.stateManager.detach(super.stateManager.getState(FlyCamAppState.class));
    super.stateManager.attach(new FpsFlyCamAppState());
}

EDIT: ok, I think to do it is currently impossible, I am in a loop trying to create my flycamappstate and not being able to, see:

the only possible place to add it seems here:

public Main() {
	super( new StatsAppState(), new FpsFlyCamAppState(), new DebugKeysAppState() );
}

I get several nullpointer exceptions because the order in which things are executed:

@Override
public void initialize() {
  // I cant initialize fpsFlyCam here because Camera is not instantiated yet...
  super.initialize(); //this creates Camera but also calls my Main.simpleInitApp() that access SimpleApplication.flyCam and crashes with NPE
}

the point is, I should be able to initialize flycam stuff at simpleInitApp(), but I cant if I have my own flycamappstate :frowning:

EDIT: ok, I am not using override on initialize() anymore; flycamappstate is only auto initialized after simpleInitApp() and before simpleUpdate(), so I moved my flycam init code to simpleUpdate() (with a boolean to run it on in the first frame), but I think there remained some conflict or some problem about, the game will not initialize properly :(, I will see what more I can find…

EDIT: SOLVED! the problem here was that, if I put a breakpoint and it happens on the first frame, the applicaion will not initialize properly, the terrain wont load and so on, everything ok now thx!

You can use app.enqueue instead of your boolean.

This the pattern of my latests projects :


AppSettings settings = new AppSettings(true);
// setup settings
SimpleApplication app = new SimpleApplication(){
  @Override
  public void simpleInitApp() {
  }
};
app.setSettings(settings);
app.start();
//Setup Camera
app.enqueue(() -> {
  app.getStateManager().detach(app.getStateManager().getState(FlyCamAppState.class));
  app.getStateManager().attach(new FpsFlyCamAppState());
  return null;
});


in context :

oh it has a queue!? cool!

but sometime ago I created my own queue as a Runnable and control how and where it runs :), may be I can throw some stuff on this queue too

I see that is lambda xp right? I havent moved to eclipse yet :frowning: hehe

btw I solved it, the breakpoint on the first update frame was breaking the application (timing?) I think

The syntax for jdk7, is something like

 app.enqueue(new Callable<Void>(){
  public Void call() {
    ///...
    return null;
  }
});

enqueued Callable are dequeued and called just before simpleUpdate (each Frame).

1 Like