- Home /
limit rotation of x axis in a quaternion
i have a script wich makes an npc look at the player. when i jump towards the npc i dont want the npc to do a "backflip" to continue looking at me. i basicly want to clam rotation along one axis. here is my script
var player : GameObject;
var detectDistance = 20;
var minDistance = 3;
var attackDistance = 4;
var moveSpeed = 8;
var alive = true;
var damping : float = 5;
var maxXRot = 20;
function Start () {
player = GameObject.FindGameObjectWithTag("Player");
}
function Update () {
var distance = Vector3.Distance(player.transform.position, gameObject.transform.position);
var rotation = Quaternion.LookRotation(player.transform.position - transform.position);
if(distance <= detectDistance && distance >= minDistance && alive){
transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * damping);
}
}
Answer by robertbu · Jan 12, 2014 at 07:23 PM
I don't know of a clean simple solution to this problem. So here is a more complex ugly solution. I verified that it compiles, but I've run out of time to setup a scene to test the code, so it is untested at this point.
The idea is to test to see if the angle is above some maxAngle. If it is, the look at point is moved down. to make this happen, the target point is projected onto a mathematical plane facing up and running through transform.position. We can then use the distance to the point on the plane as the 'x' and the height of the point above the plane as the 'y' and use Atan2() to calculate the angle. If is out of the range of -maxAngle to +maxAngle, we adjust the height of the point to bring it into range:
#pragma strict
var player : Transform;
var detectDistance = 20;
var minDistance = 3;
var attackDistance = 4;
var moveSpeed = 8;
var alive = true;
var damping : float = 5;
//var maxXRot = 20;
var maxAngle = 45.0;
function Start () {
player = GameObject.FindGameObjectWithTag("Player").transform;
}
function Update () {
var distance = Vector3.Distance(player.position, gameObject.transform.position);
if(distance <= detectDistance && distance >= minDistance && alive) {
var dir = player.position - transform.position;
var planePoint = ProjectPointOnPlane(Vector3.up, transform.position, player.position);
var dist = Vector3.Distance(planePoint, transform.position);
var angle = Mathf.Atan2(dir.y, dist) * Mathf.Rad2Deg;
if (Mathf.Abs(angle) > maxAngle) {
var sign = Mathf.Sign(angle);
dir.y = sign * Mathf.Tan(maxAngle * Mathf.Deg2Rad) * dist;
}
var rotation = Quaternion.LookRotation(dir);
transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * damping);
}
}
function ProjectPointOnPlane(planeNormal : Vector3, planePoint : Vector3, point : Vector3) : Vector3 {
var dist = Vector3.Dot(planeNormal, (point - planePoint));
return point + planeNormal.normalized * -dist;
}
With calculations this complicated I give it less than 50/50 I got everything right the first shot. If you cannot get it working, let me know, and I'll work up a test scene when I have a bit more time. Note I changed your 'Player' to a Transform rather than a GameObject.
Not to be competitve, but I recommend you read my answer as well as the OP.
The volume of math and points of failure in this code is quite redundant given the nature of the problem - even though the algorithm is conceptually quite simple.
Using a more appropriate representation for the orientation than a quaternion, and only using the quaternion to feed Unity what it needs makes the problem 'disappear naturally'. I'd guess this is the sort of 'clean' solution you refer to at the start of your answer...
Answer by semiessessi · Jan 12, 2014 at 07:32 PM
Quaternion rotations are not for interpolating in this way. Having constraints allows us to do nice things - quaternions are the golden sledgehammer for when we don't know anything, as well as being a memory efficient way to encode an orientation (which is why Unity and many game engines use them internally).
Slerping or lerping a quaternion will never give you a nice result if you want any kind of constraints to apply throughout the interpolation, as the middle parts of the interpolation will most often wobble around and not be constrained to any particular plane - even if the start and end rotations are. In your case this manifests as a backflip.
A very simple solution here is to not operate on quaternions at all and to keep track of an angle yourself - which is sufficient to describe orientation in a plane (e.g. the xz plane). You can then use Quaternion.AxisAngle to construct the correct transform every frame (or every change if you really want some more performance) and stomp over whatever is in the transform. If you do a regular lerp on the angle then everything will remain in the plane normal to the axis vector.
Your axis in this case would be Vector3( 0, 1, 0 ) - assuming you are using the positive y direction for up.
EDIT: just a thought, but if you want to constrain to the family of planes within a certain angle of the xz plane then you can use two angles and do two rotations, using the transform.right (or .left) as the axis after the first rotation.
Normally this is recommended against because it has the 'gimbal lock' problem which is a strong motivation for using quaternions in other situations (camera lerping). However this is precisely the behaviour you want on a character's head which is tracking an object - it is what your head does in real life - so actually you want gimbal locking and two angles.
You can still account for being able to tilt the head back further than vertical if you want - just don't use the limit that stops at gimbal lock - you can rotate through gimbal lock and the rotation around xz will be reversed - just like what happens if you tilt your head back and arch your back to see behind you. The key point is that you can control the angles precisely with trivial logic (Mathf.Clamp). Interpolating these angles again stops 'crazy' in betweens and gives you control so that you could if you wanted to implement something like the head moving first and the body following, or tracking in x first then y, or any proportion of those things mixed together.
From the sounds of it you have a much simpler model than this where you are rotating an entire mesh - so I don't think this is relevant, but you might find it useful to know in future...
Your answer
Follow this Question
Related Questions
Align GameObject to Terrain angle 2 Answers
Clamping a character's head rotation. 1 Answer
How to Clamp a Quaternion Camera Rotation? 0 Answers
Clamp Quaternions Rotation for camera 1 Answer
Edited mouselook script rotation clamp not working 0 Answers