I managed to implement a lookAt matrix for a camera successfully, since there are many sources describing it.
I've been trying to translate this from camera lookAt to model lookAt. I cannot seem to get it to work, and I assume I have a slight misunderstanding as to how a matrix is constructed. I am under the assumption that I don't need to change the translation for a model to look at a point, since it's position should remain unchanged.
First of all, here is the relevant code. The lookAtRadians function should look at the point specified in the same frame of referance as it's translation (i.e. at - position = direction). However, there are some issues, which I'll show with screenshots. It does not check for direction.y() being 1.0f or -1.0f, but that is trivial.
void TransformMatrix3D::lookAtRadians(float atX, float atY, float atZ, float toZRadians)
{
Vector3D direction(atX - x(), atY - y(), atZ - z());
direction.normalize();
Vector3D up(0.0f, 1.0f, 0.0f);
Vector3D right(direction.crossProduct(up));
right.normalize();
up = direction.crossProduct(right);
mMatrix[0] = right.x();
mMatrix[4] = right.y();
mMatrix[8] = right.z();
mMatrix[1] = up.x();
mMatrix[5] = up.y();
mMatrix[9] = up.z();
mMatrix[2] = direction.x();
mMatrix[6] = direction.y();
mMatrix[10] = direction.z();
}
Here are the cross product and normalize functions, in case they are incorrect.
Vector3D Vector3D::crossProduct(const Vector3D& rightVector) const
{
const float NEW_X(y() * rightVector.z() - z() * rightVector.y());
const float NEW_Y(z() * rightVector.x() - x() * rightVector.z());
const float NEW_Z(x() * rightVector.y() - y() * rightVector.x());
return Vector3D(NEW_X, NEW_Y, NEW_Z);
}
void Vector3D::normalize()
{
float length(x() * x() + y() * y() + z() * z());
if(fabs(length) == 1.0f)
return;
length = 1.0f / sqrt(length);
moveTo(x() * length, y() * length, z() * length);
}
Here are some screenshots to describe my issue. The white sphere indicates the lookAt point.
I created a cube translated -10.0f down the Z axis (this will set mMatrix[12]
, mMatrix[13]
and mMatrix[14]
to 0.0f, 0.0f, -10.0f respectively. The rest of the matrix is identity. I have checked that this is the case) which I will use for demonstrating the problems.
Screenshot: No rotation
If I move the lookAt point along just the X and Y axes, the lookAt seems to work correctly.
Screenshot: X axis (Y rotation)
Screenshot: Y axis (X rotation)
However, when I combine the two (i.e. move the lookAt point so both X and Y aren't 0.0f), some Z rotation is applied, which shouldn't happen since UP x DIRECTION should always result in the RIGHT.y() being 0.0f. The Z rotation will be applied using toZRadians
(which is not yet implemented).
Screenshot: Added Z rotation
Also I've found that if I then move the lookAt point down the Y axis, the model still follows the lookAt point, but it actually rotates around the global X axis (or equivilant to that, at least).
Screenshot: Global X rotation
Now, when the lookAt point moves to -Z, the model has the correct Y rotation, but it's X rotation is inverted. I checked my vectors at this point and I found that UP.y() is negative, which shouldn't be possible (it can be 0.0f, but not negative) since DIRECTION and RIGHT should always wind the same way (i.e. clockwise from DIRECTION to RIGHT). The only way UP.y() could possibly be negative is if RIGHT is actually LEFT.
Screenshot: Inverted X rotation
The model still rotates around the global X axis as it did when the lookAt point was +Z.
Screenshot: Global X rotation (lookAt -Z)
As I mentioned, this is likely a misunderstanding on the way matrices work, but it could be something else. I've looked around for several days and I can only seem to find camera based lookAt functions. Any sources explaining the axes contained in the matrix have lead to the code presented in this post.
Ah, I found the problem. So simple I overlooked it.
My matrix:
mMatrix[0] = right.x();
mMatrix[4] = right.y();
mMatrix[8] = right.z();
mMatrix[1] = up.x();
mMatrix[5] = up.y();
mMatrix[9] = up.z();
mMatrix[2] = direction.x();
mMatrix[6] = direction.y();
mMatrix[10] = direction.z();
is defined column major in memory. It should be:
mMatrix[0] = right.x();
mMatrix[1] = right.y();
mMatrix[2] = right.z();
mMatrix[4] = up.x();
mMatrix[5] = up.y();
mMatrix[6] = up.z();
mMatrix[8] = direction.x();
mMatrix[9] = direction.y();
mMatrix[10] = direction.z();
And that works perfectly. Extremely silly mistake, I didn't even think to check that. This also explains the wierd inversions in the axes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With