   [C#] [XNA] Camera look at matrix creation is constantly changing camera's up vector.
  I'm currently setting the camera's view matrix to look at a target as follows:   public override void LookAt(Vector3 target)   {  zAxis = position - target;  zAxis.Normalize();  Vector3 up = yAxis;  if (Vector3.Cross(up, zAxis).LengthSquared() < 0.0001f)  { up = Vector3.UnitX;  }  yAxis = Vector3.Cross(up, zAxis);  yAxis.Normalize();  // No need to normalise xAxis as y and z axes are already normalised  xAxis = Vector3.Cross(yAxis, zAxis);  matrix_View.M11 = xAxis.X;  matrix_View.M21 = xAxis.Y;  matrix_View.M31 = xAxis.Z;  matrix_View.M12 = yAxis.X;  matrix_View.M22 = yAxis.Y;  matrix_View.M32 = yAxis.Z;  matrix_View.M13 = zAxis.X;  matrix_View.M23 = zAxis.Y;  matrix_View.M33 = zAxis.Z;  // Projects position onto new local axes after rotation  matrix_View.M41 = -Vector3.Dot(position, xAxis);  matrix_View.M42 = -Vector3.Dot(position, yAxis);  matrix_View.M43 = -Vector3.Dot(position, zAxis);  matrix_View.M44 = 1;  // Accumulated pitch and heading degrees need to be updated with current look at Matrix values   // (E.G. Look at target might be elevated by 80 degrees or 20 degrees to the left)  UpdateHeadingPitch();  Quaternion.CreateFromRotationMatrix(ref matrix_View, out orientation);  // Create the combined view-projection matrix  Matrix.Multiply(ref matrix_View, ref matrix_Projection, out matrix_ViewProj);  // Create the bounding frustum  bounding_Frustum.SetMatrix(matrix_ViewProj);   }xAxis = Camera's X axisyAxis = Camera's Y axiszAxis = Camera's Z axisThe camera looks at the target correctly but doesn't maintain its current orientation properly.  Everytime I call the method the camera appears to rotate through 90 degrees whilst looking at the correct target.  How can I keep the camera upright (yAxis = 0,1,0?) and still look at the target?Also, if I wish to look at an object that is in the same direction as the camera's yAxis (0,1,0) how can I look at it correctly and maintain the same orientation?   
Quote:The camera looks at the target correctly but doesn't maintain its current orientation properly.  Everytime I call the method the camera appears to rotate through 90 degrees whilst looking at the correct target.  How can I keep the camera upright (yAxis = 0,1,0?) and still look at the target?The problem appears to be that you are computing the y axis from the forward and up vectors, when it should be the x axis. Computing the 'y axis from the y axis' will have exactly the effect you describe, that is, the camera will rotation 90 degrees (more or less) each update. (If you're not sure why this is, work through a couple of iterations in your head and observe the resulting orientations.)Quote:Also, if I wish to look at an object that is in the same direction as the camera's yAxis (0,1,0) how can I look at it correctly and maintain the same orientation?One way or another, you'll need to 'remember' the previous orientation of the camera, and use it as input to the function that determines the new orientation.If your camera can only pitch and yaw, it might be easier to use spherical angles rather than a 'look at' function. Each update, compute the spherical angles that will orient the camera towards the target. If the yaw value cannot be uniquely determined, just leave it at its previous value. This should have the effect of allowing the camera to pitch to +/-90 degrees to follow the target, while leaving its yaw rotation unaffected.   ;Quote:Original post by jykThe problem appears to be that you are computing the y axis from the forward and up vectors, when it should be the x axis.I've solved that problem by using the correct axes in the cross products now:   public override void LookAt(Vector3 target)   {  zAxis = position - target;  zAxis.Normalize();  // Use world Y axis to keep camera upright  xAxis = Vector3.Cross(Vector3.UnitY, zAxis);  xAxis.Normalize();  // No need to normalise yAxis as x and z axes are already normalised  yAxis = Vector3.Cross(zAxis, xAxis);  matrix_View.M11 = xAxis.X;  matrix_View.M21 = xAxis.Y;  matrix_View.M31 = xAxis.Z;  matrix_View.M12 = yAxis.X;  matrix_View.M22 = yAxis.Y;  matrix_View.M32 = yAxis.Z;  matrix_View.M13 = zAxis.X;  matrix_View.M23 = zAxis.Y;  matrix_View.M33 = zAxis.Z;  // Projects position onto new local axes after rotation  matrix_View.M41 = -Vector3.Dot(position, xAxis);  matrix_View.M42 = -Vector3.Dot(position, yAxis);  matrix_View.M43 = -Vector3.Dot(position, zAxis);  matrix_View.M44 = 1;  // Accumulated pitch and heading degrees need to be updated with current look at Matrix values   // (E.G. Look at target might be elevated by 80 degrees or 20 degrees to the left)  UpdateHeadingPitch();  Quaternion.CreateFromRotationMatrix(ref matrix_View, out orientation);  // Create the combined view-projection matrix  Matrix.Multiply(ref matrix_View, ref matrix_Projection, out matrix_ViewProj);  // Create the bounding frustum  bounding_Frustum.SetMatrix(matrix_ViewProj);   }This still leaves two problems:1) What if the LengthSquared of zAxis < 0.00001f?2) How can I deal with looking directly up? (camera.Position + (0,1,0))Quote:Original post by jykIf your camera can only pitch and yaw, it might be easier to use spherical angles rather than a 'look at' function. Each update, compute the spherical angles that will orient the camera towards the target. If the yaw value cannot be uniquely determined, just leave it at its previous value. This should have the effect of allowing the camera to pitch to +/-90 degrees to follow the target, while leaving its yaw rotation unaffected.Is the same as spherical linear interpolation?  Could you perhaps give an example of how to compute the spherical angles to point the camera towards the target please?   ;Quote:This still leaves two problems:1) What if the LengthSquared of zAxis < 0.00001f?2) How can I deal with looking directly up? (camera.Position + (0,1,0))That's what using spherical angles addresses (but maybe you already know that).Quote:Is the same as spherical linear interpolation?  Could you perhaps give an example of how to compute the spherical angles to point the camera towards the target please?No, it's not the same as SLERP.The basic idea is that any unit-length 3-d vector can be represented using two angles. There are various ways of naming these angles, but we'll call them pitch and yaw.Assuming that +y is up, we'll call the angle of rotation about the y axis yaw and the angle relative to the ground (xz) plane pitch. Yaw is in the range [0, 360), while pitch is typically constrained to the range [-90,90].Given the vector from the camera to the target object, you can compute a corresponding set of spherical angles (yaw and pitch). A typical conversion might look something like this (typed into post, so no guarantee of correctness):float l = length(x, z); // sqrt(x^2 + z^2)if (l > epsilon) {    yaw = atan2(x, z);} // Else leave yaw at its previous value.pitch = atan2(y, l);You should then be able to apply the pitch and yaw values as a pair of Euler angles in the order x->y. Note that you may have to fiddle with the angles and/or apply additional transforms, depending on how your camera is set up (e.g. to adjust for -z being forward).   ;  Ok, I think I follow.  I'm creating the new "up" vector (yAxis) but first I need to convert the current yAxis to 2 Euler angles:  Vector3 up = Vector3.Cross(zAxis, xAxis);  // Create two Euler angles (yaw and pitch) from current camera yAxis vector  float yaw = (float)Math.Atan2(yAxis.X, yAxis.Z);  float l = (zAxis - xAxis).Length();  if (l > Math_Util.EPSILON)  { yaw = (float)Math.Atan2(up.X, up.Z);  }  float pitch = (float)Math.Atan2(up.Y, Math.Sqrt(up.X * up.X + up.Z * up.Z));The pitch can be changed each time, it is just the new yaw that needs to be checked.I can't figure out what to do with the new yaw and pitch?  Creating the view matrix from the yaw pitch and roll doesn't work:matrix_View = Matrix.CreateFromYawPitchRoll(yaw, pitch, 0);which has me a little confused as I thought I had understood it.;  The x, y, and z in my example are the elements of the vector from the camera to the target, i.e.:vector3 v = target.position - camera.position;float l = length(v.x, v.z); // sqrt(v.x^2 + v.z^2)if (l > epsilon) {    yaw = atan2(v.x, v.z);}pitch = atan2(v.y, l);Once you have these two angles, create a rotation matrix from them (be sure the rotations are applied in the order pitch->yaw), incorporate the camera position, and then invert the matrix - this will give you your view matrix.Really though, the only reason I recommended the use of spherical angles was to deal with the straight-up-or-down cases. For all other cases, the 'look at' method should really be sufficient. As for the straight-up and straight-down cases, it seems to me that these would arise so infrequently in practice that it would be fine to just choose another 'reference up' vector in those cases (as you appeared to be doing originally). This could result in your camera yawing suddenly as the target passes overhead or below, but it'll probably do this anyway. Then again, I'm not familiar with the details of your simulation - maybe, for whatever reason, these 'straight up' and 'straight down' cases are more likely to arise in practice in your case, which is why you want to make sure they're handled gracefully.   ;  I've got the following code working for looking straight up and down:  Vector3 v = target - position;  // Euler angles from vector v (spherical angles)  float pitch;  float yaw = 0;  float l = (float)Math.Sqrt(v.X * v.X + v.Z * v.Z);  if (l > Math_Util.EPSILON)  { yaw = (float)Math.Atan2(v.X, v.Z);  }  pitch = (float)Math.Atan2(v.Y, l);  matrix_View = Matrix.CreateFromYawPitchRoll(yaw, pitch, 0);  matrix_View.Translation = position;  matrix_View = Matrix.Invert(matrix_View);When looking at any other target other than straight up or down the camera doesn't look in the correct direction?Matrix.CreateFromYawPitchRoll applies Roll -> Pitch -> Yaw in that order.Have I calculated everything correctly?   ;  If you always set yaw to 0 before computing the new values, you're basically back where you started with the look-at function. The whole point of using spherical angles here is really to keep the previous yaw value in the event that it can't be computed, so making them local won't do you much good, I don't think.Anyway, other than that the code looks correct, but like I said you may have to tweak things depending on what coordinate system you're using and so on. Can you be more specific about the camera looking in the wrong direction? Does it pitch down when it should pitch up and vice versa? Does it always look in the exact opposite direction (e.g. a yaw of 180 degrees) from the target? Something else?   ;  After some much needed sleep I figured out the problem and LookAt now works for all cases:  Vector3 v = position - target;  // Calculate current yaw based on camera forward (zAxis)  float yaw = (float)Math.Atan2(zAxis.X, zAxis.Z);  float pitch;  // Euler angles from vector v (spherical angles)  float l = (float)Math.Sqrt(v.X * v.X + v.Z * v.Z);  if (l > Math_Util.EPSILON)  { yaw = (float)Math.Atan2(v.X, v.Z);  }  pitch = (float)-Math.Atan2(v.Y, l);  matrix_View = Matrix.CreateFromYawPitchRoll(yaw, pitch, 0);  matrix_View.Translation = position;  matrix_View = Matrix.Invert(matrix_View);Huge thanks for your patience with this one jyk, the problem of a "works with all targets camera look at method", has been bugging me for ages!   ;  Glad you got it working :)One thing I did notice though. Here:float yaw = (float)Math.Atan2(zAxis.X, zAxis.Z);You're computing yaw based on the previous update's forward vector. But what if the target stays directly above (or below) the camera for more than one update? On the second update, zAxis.X and zAxis.Z will be zero (or nearly so), and you'll again get an unexpected yaw value. This is why I suggested just keeping the yaw value from the previous update in this case, rather than computing it again from scratch.It's a minor issue though, and maybe this isn't a case that tends to come up in practice in your simulation.   