- Home /
The question is answered, right answer was accepted
Orientation Vs. Rotation: How do I specify more than 360deg rotation for a quaternion, in the editor?
Quaternions are shown in the editor window as Euler Angles. This is a nice simple way to descibe a quaternion. However, I'm not quite sure why it is limiting the Eueler angles to between 0 and 360. (At least once the game Launches: entering 380 for example will change to 20 at launch, and even after stopping)
Now obviously, a quaterion with Eueler angle (0,0,180) has the same orientation as (0,0,180+360).
Though they may have the same orintation, as far as I understood quaternions, this is NOT the same quaternion. Is that correct? The first specifies 1/2 a rotation about Z, the second specifies 1.5 rotations about Z. (perhaps these are actually UNIT quaternions for some reason - like a "normalized" rotation? http://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation)
If my only concern was the orientation, that would be fine. But, I was hoping to allow to user to specify a rotation animation with a quaternion. In this case, the diference between half a rotation vs one and a half rotations would be quite apparent.
Now I did find this script which allows me to use a different method to enter the quaternion in the Editor, but alas, as soon as the game starts, I see it revert the tranform's Euler angles back to 0 to 360. (http://docs.unity3d.com/ScriptReference/EditorGUILayout.Vector4Field.html)
Question: How can I specify more than one rotation for a unity quaternion, in the editor? If not possible, what would you suggest? I know it's not alot of work, but I was hoping to avoid creating my own class for this, since Transform alreday exists. But if that's my only option, guess I'll do it: though I'll start with just try to store the large angles in script component's variable, in the editor. Then init the Transform's quaternion (the way I want) in startup. Not sure how to do this though- if I use the Quaternion.EulerAngles function, it will clamp the angles 0-360, giving me an orientation rather than a rotation, right? Maybe Quaternion.AngleAxis will be the better function to use?
FYI: if relevant, here is the important part of my animation's Update() code.StartingTransform
is copied from the animated object's transform ( animatedTransformReference
) at animation initialization. FinalTrnasform
is entered by the programmer, in the editor(This is where I'm having the trouble), and transforms a copy of the animatedTransformReference
at initialization: This result is stored in workingFinalTransform
animatedTransformReference.localPosition = Vector3.Lerp(startingTransform.localPosition, workingFinalTransform.localPosition , fractionComplete);
animatedTransformReference.localRotation = Quaternion.Lerp(startingTransform.localRotation, workingFinalTransform.localRotation , fractionComplete);
animatedTransformReference.localScale = Vector3.Lerp(startingTransform.localScale, workingFinalTransform.localScale , fractionComplete);
Here is what I came up with so far: this component is added to the Transform used to define the animation, in the editor.
[AddComponent$$anonymous$$enu("Transform/UnclampedRotation")]
public class UnclampedRotation : $$anonymous$$onoBehaviour
{
public Vector3 axis;
public float angle;
public static Quaternion Lerp(Quaternion start,UnclampedRotation end, float fraction)
{
float fractionalEndAngle = end.angle * fraction;
Quaternion endQ = Quaternion.AngleAxis(fractionalEndAngle,end.axis);
return start*endQ;
}
}
and I altered the animation code like this:
animatedTransformReference.localPosition = Vector3.Lerp(startingTransform.localPosition, workingFinalTransform.localPosition , fractionComplete);
UnclampedRotation rotation= animationTransform.gameObject.GetComponent();
if(rotation!=null)
animatedTransformReference.localRotation = UnclampedRotation.Lerp(startingTransform.localRotation, rotation , fractionComplete);
else
animatedTransformReference.localRotation = Quaternion.Lerp(startingTransform.localRotation, workingFinalTransform.localRotation , fractionComplete);
animatedTransformReference.localScale = Vector3.Lerp(startingTransform.localScale, workingFinalTransform.localScale , fractionComplete);
You could write a custom editor for the Transform component to get the desired behaviour.
For custom components you could look into writing an UnclampedRotation PropertyAttribute and PropertyDrawer.
Vesuvian, thank you for the suggestion! I'm not sure I understand it though, as I'm not familiar with those things yet. Would this allow the values that I put into the transform's rotation, to be stored in my UnclampedQuaternion component? If so, any idea how to convert those 3 axis-angles, into a single axis angle (I'd think, using the Quaternion class wont work here)? OR.. perhaps I could alter the LERP function to work on 3 axis-angles....hmmm Edit: yeah, I created an UnclampedEulers class, with a different LERP function.. works that way too
From reading your question I suspect that the Transform rotation is being concatenated by the inspector for readability, is this correct?
If this is true, then doing the following should happen:
transform.rotation = Quaternion.Euler(361, 0, 0);
Debug.Log(transform.rotation.eulerAngles.x); // Should print 361
In this case I think you could avoid the clamping by writing your own Editor class that is applied to the builtin Transform component (I have seen NGUI do this).
Alternatively, PropertyAttribute lets you define new datatypes that can be presented in the inspector by a matching PropertyDrawer. You could make an UnclampedRotation class that inherits from PropertyAttribute and contains a Quaternion.
Alas, it is not. I thought it would be too. I ran the same test, the debug output is "1.00001". This is why I wrote my own storage and LERP function class. I made it a monobehavior so I could attach it to a transform (gameobject). I'm looking into the editorwindow stuff now, think this is the answer. Since unity doesn't actually clamp the angles show in the editor untill the scene starts, I should be able to (in the editor) copy them into my "Unclamped" component, right? P.S. why are my comments here always stripped of cr/lf?is this correct?
Answer by Glurth · Feb 11, 2015 at 05:12 PM
I hate answering my own question, especially considering @VesuvianPrime 's critical assistance. But, I wanted to provide everyone with a summary of what we came up with, and close the question.
The solution I went with required that I create my own rotation class to hold angles representing more than one rotation. Based on tests, it appears the Quaternion Class in Unity IS in fact a "Unit-Quaterntion", that is: it defines and Orientation, NOT an amount of Rotation.
There were a few ways to store these angles, Euler angles, Axis/Angle, etc... I chose Euler angles, mostly because it matched what we use in the editor. I only needed the lerp function to return an orientation for drawing, so that the only function I have defined in this class so far.
using UnityEngine;
using System.Collections;
[AddComponentMenu("Transform/UnclampedEulers")]
public class UnclampedEulers : MonoBehaviour {
public Vector3 angles;
public static Quaternion Lerp(Quaternion start,UnclampedEulers end, float fraction)
{
float fractionalEndAngleX = end.angles.x * fraction;
float fractionalEndAngleY = end.angles.y * fraction;
float fractionalEndAngleZ = end.angles.z * fraction;
Quaternion endQx = Quaternion.AngleAxis(fractionalEndAngleX,Vector3.right);
Quaternion endQy = Quaternion.AngleAxis(fractionalEndAngleY,Vector3.up);
Quaternion endQz = Quaternion.AngleAxis(fractionalEndAngleZ,Vector3.forward);
return start*endQx*endQy*endQz;
}
}
In my animation code I can now call UnclampedEulers lerp, rathar than the quaternion class's lerp. The big thing to notice is that we are now able to Lerp through more than one rotation.
//Replacing this: animatedTransformReference.localRotation = Quaternion.Lerp(startingTransform.localRotation, workingFinalTransform.localRotation , fractionComplete);
animatedTransformReference.localRotation = UnclampedEulers.Lerp(
startingTransform.localRotation,
workingFinalTransform.localRotation,//this is now an UnclampedUelers class variable
fractionComplete);
In order to feed the values we want into the UnclampedUelers class, from the editor, we need to create our own Editor class for it. The following will make the UnclampedEulers class invisible to the user, instead, it uses the values entered/displayed in the "regular" transform. This also ensure that the objects orientation(Quaternion), matches it's amount of rotation(UnclampedEulers).
[CustomEditor(typeof(Transform))]
public class UnclampedEulersEdit : Editor {
Vector3 eulerAngles;
public override void OnInspectorGUI() {
Transform targetTransform = (Transform)target;
UnclampedEulers Ueulers = targetTransform.gameObject.GetComponent<UnclampedEulers>();
if(Ueulers!=null)
eulerAngles=Ueulers.angles;
else
eulerAngles=targetTransform.localRotation.eulerAngles;
targetTransform.localPosition=EditorGUILayout.Vector3Field("Position",targetTransform.localPosition);
eulerAngles=EditorGUILayout.Vector3Field("Rotation",eulerAngles);
targetTransform.localScale=EditorGUILayout.Vector3Field("Scale",targetTransform.localScale);
if(Ueulers!=null)
Ueulers.angles=eulerAngles;
else
targetTransform.localRotation = Quaternion.Euler(eulerAngles);
}
}
Is there a more elegant solution to unclamp a eulerAngle? Without adding an editor class?
The editor part is not necessary, it's just for the user-interface in unity. After assigning the Euler angles from a Quaternion, to a Vector3, they are no longer clamped: you can assign any values to the Vector3. When you assign it back to the Quaternion, THAT is when it will be clamped. (THAT is what there is no way around)
It's been years since looking at this (thanks email notifications) and almost as long since I touched Unity, but wouldn't there be issues here with gimbal lock?
Follow this Question
Related Questions
Rotation Jumping values (0 to 180) 1 Answer
How to instantiate on custom rotation? 1 Answer
How rotate only z axis of Gameobject (2D) 1 Answer
Rotating a boat on two axes with force. 2 Answers