I know this sounds a little crazy but it seems the Camera.lookAt(Vector3f,Vector3f) method doesn't work if you're trying to look at spatial that's at the origin. Here is my very simple test case:
The camera will only face the sphere in my example if I set a local translation on the sphere (other than 0,0,0). You can see this by simply uncommenting the line:
//s.setLocalTranslation(1, 0, 0);
Any idea why this is happening? And is this normal?
confirmed. and it's not normal. it's because of the vector operations in lookAt().
Thank-you for the confirmation. So this problem has been around since the conception of Camera.lookAt() then? Should I attempt to find the problem myself or are there others responsible for doing that? Not sure how things work around here.
if you find a problem make a test case (a bug report in the issue tracker is also very useful).
if you can fix it, post the test case and the fix. usually the fix is then merged into jme.
if you can't fix it post the test case. usually someone (mos probably the devs) will come up with a fix which will be merged in jme.
i'm at work right now and my vector math is not the best, so i won't be able to fix it ;)
// check to see if we haven't really updated camera -- no need to call
// sets.
if ( newDirection.equals( direction ) ) {
return;
}
direction.set( newDirection );
up.set( worldUpVector );
left.set( up ).crossLocal( direction ).normalizeLocal();
up.set( direction ).crossLocal( left ).normalizeLocal();
onFrameChange();
}
In particular, the first line is assuming that the position of the camera is absolute (world coordinates) while doing the difference, because if it were local (and most likely local means it is centered in (0,0,0)) then attempting to look at the origin would result in a zero vector, and hence, an impossible to normalize one. That would cause the rest of the calls to also fail.
So I would ask: Is your camera position really different than (0,0,0)?
So I would ask: Is your camera position really different than (0,0,0)?
I don't know if you're asking me this question, but anyway, the camera position in my test case is (0,200,0) as you can see from the code. So it's not at the origin. If you run the test case you'll see that the location of the sphere is actually what makes the difference. That is, if the sphere is located at (0,0,0) then the lookAt will fail, otherwise it'll pass.
Yes, yes… Sorry about that, it was more like a rhetorical question for if it was local or world position… But I know what is the problem with the code (either yours or jME's).
The problem is that you are setting your camera exactly above the sphere in the case of lookAt(0,0,0) And unfortunately, the direction of the camera and the vector pointing up cannot be in the same line, otherwise the left direction cannot be dermined… The cross product is only defined for two vectors that are linearly independent.
To fix this, just move your camera a tiny bit in the Z direction like so:
cam.setLocation(new Vector3f(0f,200f,0.1f));
It should work like a charm. BTW, any developers around? I would suggest to change lookAt like so:
// check to see if we haven't really updated camera -- no need to call
// sets.
if ( newDirection.equals( direction ) ) {
return;
}
direction.set( newDirection );
up.set( worldUpVector );
left.set( up ).crossLocal( direction ).normalizeLocal();
if( left.equals( Vector3f.ZERO ) )
left.set( -1.0f, 0f, 0f );
up.set( direction ).crossLocal( left ).normalizeLocal();
onFrameChange();
}
Hmm, but does changing left like so work if you were in a -X = up situation? Perhaps detecting a collinear situation of direction to UP and shifting the lookat very slightly might be a better solution.
Perhaps detecting a collinear situation of direction to UP and shifting the lookat very slightly might be a better solution
Does this imply re-positioning the camera from it's intended position? I mean, from a usage point of view, if I want to place the camera at (0,200,0) then that's exactly where I want it to be, not approximately there. Is this too demanding?
I think the better solution is (and I think thsi is what renanse is saying) to shift the "look at" vector, NOT the camera. So your camera would be aimed slightly off from where you intended (I'm assuming this will be so slight as to be unnoticable under normal usage) but your camera itself would not shift. So when you request to look at 0,0,0 it would be shifted to something like .001,0,0 before the calculations if a colinear situation were detected (probably a bad example).
to shift the "look at" vector, NOT the camera. So your camera would be aimed slightly off from where you intended
So you're referring to the direction vector of the camera? So the direction won't be "exactly" where you want to look but "close enough"? I wonder, is this really a limitation of vector math?
Damn, you are absolutely right… I missed the fact that you set the up to whatever you want… Testing for collinearity is trivial, but I wanted to save the computation.
This brings another issue to the table: Which should be the left direction if up and direction are collinear? (IMHO) it should not matter, and we should be able to just pick one, and make the computation faster… Perturbing the direction a bit is exactly the same as arbitrarily setting that direction, except we have control over which orientation the camera will have.
Well, if up and direction are already collinear and normalized, then we can simply get a non-zero component of direction (x or y in this example), and then (y, -x, 0) is perpendicular to (x, y, z)… if x and y are zero, then (z, 0, -x) (or (0, z, -y) ) is perpendicular to (x, y, z)…
// check to see if we haven't really updated camera -- no need to call
// sets.
if ( newDirection.equals( direction ) ) {
return;
}
direction.set( newDirection );
up.set( worldUpVector );
left.set( up ).crossLocal( direction ).normalizeLocal();
if( left.equals( Vector3f.ZERO ) )
if( direction.x != 0 )
left.set( direction.y, -direction.x, 0f );
else
left.set( 0f, direction.z, -direction.y );
up.set( direction ).crossLocal( left ).normalizeLocal();
onFrameChange();
}
@Mr. Marbles, it is not too much to ask, it is just that if you want your camera in the same direction as your up is defined, then it is a special case.
The above code will always point to the direction in question and will be consistent within runs... It uses many if's though, and I would like to optimize it a little bit.
Gentleman Hal comes out of left field… :P Valid point, but I am hoping to refactor these fields with a programming tool when we get to that anyway, so as long as we are getting proper functionality at this point.
I'm not a Vector math expert, but I noticed in the proposed solution that only the left vector is being checked for Vector3f.ZERO, what about:
up.set( direction ).crossLocal( left ).normalizeLocal();
Is there any chance of this producing Vector3f.ZERO as well?
Well, I have included this case in the method... fortunately, this takes care of the last cross product which is no longer needed because up is already normalized.
// check to see if we haven't really updated camera -- no need to call
// sets.
if ( newDirection.equals( direction ) ) {
return;
}
direction.set( newDirection );