- Home /
Calculate Quaternion upward ? to keep the rotation of camera
May be its the old problems, but I still deeply troubled by this for few days long.
I working on the project to control the camera follow the target(player), those objects without any hierarchy relationship. which mean independent objects in the scene.
let's make it short, Imagine a spaceship to move around and camera following it. {Pic01}
when the camera on-top/at-bottom of that spaceship {pic02} camera will start fliping, it is because the upwards reference is based on world's upwards {in this case Vector3.up}, at that point Quaternion cannot calculate the upward of camera, both x & z axis are zero. I call this zero-point, correct me because I didn't know the name of this point.
therefore after camera get pass zero-point the camera will rotate 180 degree, but this is not the result that we want, we want it keep the camera's head point to the ground{Vector3.down}
so here is the question, can we calculate the upward ?? in my program I already have following values
Polar, Elevation, Distance related to chasing target.
target forwards, target upwards
here is the video to show the real issue.
Ahhh, okay. Thank you for the video clarification.
Yep, that's Gimbal Lock. You're rotating until you're in front of your target point and looking backwards towards it, while expecting the camera to be tilted upside-down slightly. That's where the solution in my answer (albeit based around the wrong axis at present) is a potential quick-fix for it, but certainly not the best approach.
Sorry to say, though, I'm dead tired and can't really come up with any good suggestions right now, but I would suggest tinkering around with Quaternion.AngleAxis to work out that problem. Effective use of it will let you limit rotation per axis relative to your baseline (right behind the character?) as well as having no trouble with rotation of any kind.
@canis: Show the code you use to rotate your camera. If you exclusively use quaternions this shouldn't happen. However i guess that you use LookAt or LookRotation which has "Vector3.up" as default up vector.
It's not really a gimbal lock as you usually can't recover that easily from that state. It's true that at the lowest point you loose one degree of freedom but you still rotate in the same direction. A gimbal lock takes away a different rotation axis than the one that causes the lock.
Do you use by chance one of the $$anonymous$$ouseLook script from Standard Assets?
I'm using the Quaternion.LookRotation(), however I can't calculate the right upwards for the camera.
// sorry little mess here, I already try to use the name that can represent the meaning.
// Position
cache.CameraOrbitPosition = obj.Coordinates.ToCartesian() + ChaseTarget.position;
cache.TargetOrbitPosition = (obj.ExtendLookAt.Enable) ? (obj.ExtendLookAt.LookAt.ToCartesian() + ChaseTarget.position) : ChaseTarget.position;
cache.TargetZoomedPosition = cache.TargetOrbitPosition + cache.TargetOrbitPosition.Direction(cache.CameraOrbitPosition) * obj.Camera$$anonymous$$ethod.ZoomSectionRef;
cache.CameraZoomedPosition = cache.CameraOrbitPosition - cache.CameraOrbitPosition.Direction(cache.TargetOrbitPosition) * obj.Camera$$anonymous$$ethod.ZoomSectionRef;
// Rotation
cache.CameraFacingDirection = (cache.TargetZoomedPosition - cache.CameraZoomedPosition).normalized;
cache.TargetForwardReference = (obj.Camera$$anonymous$$ethod.ForwardReference == null) ? ChaseTarget : obj.Camera$$anonymous$$ethod.ForwardReference;
// Question : Camera upward reference based on pitch angle?
// after geting pass from zeropoint, reverse upward & downward
// using calculate upward to find out the local roll the whole orbit for camera rotate
float orientationFix = $$anonymous$$athf.Abs(Vector3.Dot(cache.CameraFacingDirection, Vector3.up));
cache.CameraUpwardReference = Vector3.Lerp(Vector3.up, cache.CameraFacingDirection, orientationFix);
Debug.DrawRay(ControlRotation.transform.position, cache.CameraUpwardReference, Color.red);
cache.CameraRotationReference = Quaternion.LookRotation(cache.CameraFacingDirection, cache.CameraUpwardReference);
Well, that doesn't help much as you almost exclusively use your own variables and methods where we have no idea what they are doing exactly or what they return. You said you have polar and elevation values. Is the elevation clamped in any way? (between +-90?) in that case it's not possible because a pure polar angle and elevation angle doesn't contain any orientation around the look axis. You need another reference. Usually use use the old local right vector and use the cross product to calculate a new up vector.
However using polar angle (around world y) and elevation (around local x but allowing +-180°) will cause strange behaviour when you're upside down as left-right rotation will be inverted.
If you want a totally free rotation you should use only quaternions so you're not bound to eulerangles representation. You would use Quaternion.AngleAxis to calculate a relative rotation around a certain rotation axis (local up / local right / local forward) and multiply the old rotation with the relative rotation to get the new one. How much you should rotate around an axis should be based on your input. Though with the information given we have no idea where and how your input is actually transferred into rotation.
ps: for orbit cams it's usually the easiest approach to parent the camera to an empty gameobject and place the empty gameobject at the targets position. Now you just rotate this empty gameobject and have the camera offset in local space to "-dist" on the z axis. If you don't want to use that approach you can also first calculate the new totation and then place the camera at the target position offset at "-forward*dist"
Answer by Eno-Khaon · May 09, 2015 at 10:32 AM
First off, I believe the key problem you're encountering is Gimbal Lock, an incidental result of Euler Angles to calculate rotation (i.e. 0-360 degrees).
Essentially, there are multiple ways of representing the same rotation. Rotating 180 degrees on the Y axis can also be achieved by rotating 180 degrees on both the X and Z axes. Unity doesn't know which is intended, since they both look the same to it and are fundamentally being calculated as Quaternion rotations first.
With that out of the way... If I follow your description correctly, you're using transform.LookAt to follow the character. One simple approach to this would be to make use of the overloaded function transform.LookAt(Target, WorldUp). You could use something like
// C#
float orientationFix = Mathf.Abs(Vector3.Dot(targetPosition - cameraPosition, Vector3.Up));
Vector3 newUpVector = Vector3.Lerp(Vector3.up, Vector3.forward, orientationFix);
cameraObj.transform.LookAt(playerObj, newUpVector);
as a simple remedy to the problem. The use of Vector3.forward could be retooled based on the needs for your game.
That said, there are much more elegant and effective solutions to this problem (using much more thorough implementations of Quaternion rotations), but this should provide a smooth transition between perspectives, where the view would lean in and out based on the camera's current position.
Edit: I apologize if my example doesn't fit your orientations as intended, but I had a tough time following exactly how this is all being handled.
Thanks for your help, but may I ask, why you using
float orientationFix = $$anonymous$$athf.Abs(Vector3.Dot(FacingDirection, Vector3.up));
I not so sure Vector3.Dot 's usage in this code. and why use it as a percent reference to find out newUpVector ?? seems like it's one more step closer to the right answer..
@$$anonymous$$o $$anonymous$$haon sorry, my misstake, you idea seems solve a part of problem,
https://youtu.be/_e1g_Xq_R6g here is the result (red line = upward reference),
however, when the upward reference geting pass its zero-point, the issue still remain. but now the zero-point no longer at the world's up/down direction.
Answer by Bunny83 · May 09, 2015 at 12:17 PM
This is a fully working space-orientation orbit cam. When you don't assign a target it acts like a freelook cam.
using UnityEngine;
using System.Collections;
public class OrbitCam : MonoBehaviour
{
public Transform target;
public float dist = 10f;
void LateUpdate ()
{
var r = transform.rotation;
if (Input.GetMouseButton(0))
{
r = Quaternion.AngleAxis(Input.GetAxis("Mouse X"), transform.up) * r;
r = Quaternion.AngleAxis(-Input.GetAxis("Mouse Y"), transform.right) * r;
}
r = Quaternion.AngleAxis(Input.GetAxis("Horizontal"), transform.forward) * r;
transform.rotation = r;
if (target != null)
{
transform.position = target.position - transform.forward * dist;
}
}
}
Since you rotate around local axes you can already end up with any orientation even when you only rotate around two axes. That's why i added the rotation around the forward axis as well. You might want to use a different input than Horizontal for that ^^. Very common keys are "e" and "q" (like in space engineers as far as i remember).