I lost about 5 hours yesterday to Unity not being able to check if two quaternions were equal when lerping. Instead it threw assertions and then gave me back quaternions full of NaNs... Wunderbar. (There is a chance that 4.3 doesn't have this issue anymore, we're not on 4 yet)
@farsicon: Unity and C#. I'm totally cool with not having quaternions, I'd just sculpt some rotation matrices instead, but Unity doesn't offer that functionality. I've done loads of really annoying rotational math before, but this was a new wrinkle on what was going on that caused me a ton of annoyance.
@Elyaradine: Apparently there would be more rounding errors if they used matrices, at least, so sayeth the documentation. I feel like if you want something to return to a snapped rotation, you have to animate it and then force it back to your target rotation at the end.
@dislekcia: The main problem for me is actually when we build modular content. We have a whole bunch of structures, or passageways, or ground pieces, that would be rotated and snapped together. If you Ctrl+Drag+Rotate an object in Unity, it should snap to the angles you set. It does... almost. And the result is a bunch of meshes that look fine at first, but which are almost-imperceptibly off. Your nice, modular building/environment/whatever thus ends up with hairline seams here and there because of the rounding errors, and you often don't see them until you've built to your device.
You can use the built-in Unity snapping to retro-actively snap them back to what they're supposed to be, though it's more a workaround than a solution, and sometimes they still leave angles as 90.0001.
@Microdot, @farsicon: The easiest way to test is to just do a Quaterion.Lerp(new Quaternion(), new Quaternion(), 0.5f); and see what happens.
@Elyaradine: That sounds messed up... What about writing a script that checks through rotations like that when a prefab is instantiated to assert on rounding errors like that (if you give it a snap tolerance or something)?
Math with Unity / Mono is always just a bit more interesting than usual... In one version Mathf.Epsilon was so small it would be set to 0 on some devices (so if(0 < Mathf.Epsilon) would always fail. @mattbenic also told me about a recent bug where Mathf.FloorToInt(6.0f) returned 5... (I think it was 6 and 5; it was some small number and it's one-less).
As long as your time value is a fraction and less than 1, Lerp will never make the two quaternions the same (i.e. using Time.deltaTime). The time value has to be 1 or greater for them to be the same (or as close to the same as possible, due to float errors). I had a lot of nosebleeds when I started working with quaternions.
Look at the following code and output (compare values of q2 and q3):
Now I may be wrong. But in the case of Quaternions, instead of Linear Interpolation, would you not rather use Spherical Interpolation (Quaternion.Slerp)? That is what I have been using anyway.
@farsicon: Actually, the new Quaternion() thing isn't the problem at all - I tried what I'm doing with Quaternion.Identity starting values and the entire rotation stopped working completely.
@Dipso: I don't have problems with how Lerp works, I know what's going on there, my issue is when Lerp itself throws an assertion and gives me back junk data in a quaternion that I can't seem to check for. Having a quat that's all { NaN; NaN; NaN; NaN } is pretty crap because it poisons anything else I try to do with it :(
Ok, here's what I'm trying to do: In DD there's a general animation script that allows us to build procedural animations for sprites. You basically add things like impulses, accelerations, rotational impulses, rotational accelerations, colours to fade to/from and the like at time targets and the animation interpolates all of that and behaves accordingly. We use it for everything from particle-like pops when a full particle emission would be too expensive, to animations for combat. The nice thing about it is that it allows us to inject a lot of random into an animation so we're not playing canned sequences all the time - each general animation that's sculpted by an event is going to be unique in some way. That helps a lot with grid-based games where lots of elements are visible at the same time. The other neat thing is that our animations are nestable and can trigger more animations (or ingame events) as needed, so that makes life a lot easier too.
I only recently needed rotational stuff, so that's why I was messing with quats. Basically, I wanted to be able to say "This is how fast the animation is going to rotate per second for the time period between here and here" and because the times can be randomised, I can't simply calculate a "total rotational distance" and go with that because a rotational acceleration might kick in and mess with the outcome.
The line that caused problems was this:
r = Quaternion.Lerp(new Quaternion(), childRotation, deltaPart); //<- problem
for (int i = 0; i < GetChildren.Count; i++)
{
GetChildren[i].transform.localRotation = GetChildren[i].transform.localRotation * r;
}
The idea was to lerp between a neutral starting rotation and the amount of rotation we're supposed to perform per second (hence using the delta) and then apply that to the child objects under the control of this animation. This worked great except for when childRotation was itself a brand new Quaternion. Essentially, the lerp was reporting that there was a non-normalised quat somewhere and it would fall over.
Performing this test beforehand if ((childRotation.x != 0f) || (childRotation.y != 0f) || (childRotation.z != 0f) || (childRotation.w != 0f)) prevented the issue, but seemed super inefficient and pretty damn stupid to have to do all the time, ew.
Switching to Slerp has prevented the issue entirely (and allowed me to ditch the icky pre-test), presumably because it doesn't try to normalise the resulting quaternion the same way Lerp does. Thanks @Pixel_Reaper!
Comments
@farsicon: Unity and C#. I'm totally cool with not having quaternions, I'd just sculpt some rotation matrices instead, but Unity doesn't offer that functionality. I've done loads of really annoying rotational math before, but this was a new wrinkle on what was going on that caused me a ton of annoyance.
@Elyaradine: Apparently there would be more rounding errors if they used matrices, at least, so sayeth the documentation. I feel like if you want something to return to a snapped rotation, you have to animate it and then force it back to your target rotation at the end.
What are you doing though - interested in more info :-)
You can use the built-in Unity snapping to retro-actively snap them back to what they're supposed to be, though it's more a workaround than a solution, and sometimes they still leave angles as 90.0001.
@Elyaradine: That sounds messed up... What about writing a script that checks through rotations like that when a prefab is instantiated to assert on rounding errors like that (if you give it a snap tolerance or something)?
This is an educational thread for coding jargon! :B
/Maths is weird sometimes
transform.rotation = Quaternion.Lerp(transform.rotation, transform.rotation, Time.deltaTime);
it works fine.
@dislekcia: Glad to be of service!
Hopefully it's not something stupid like that...
The time value has to be 1 or greater for them to be the same (or as close to the same as possible, due to float errors).
I had a lot of nosebleeds when I started working with quaternions.
Look at the following code and output (compare values of q2 and q3):
Code:
Quaternion q1 = Quaternion.Euler(10.0f, 20.0f, 30.0f);
Quaternion q2 = Quaternion.Euler(40.0f, 50.0f, 60.0f);
Quaternion q3;
Debug.Log("q1: " + q1.x + ", " + q1.y + ", " + q1.z + ", " + q1.w);
q3 = Quaternion.Lerp(q1, q2, 0.9999f);
Debug.Log("Almost 1:");
Debug.Log("q2: " + q2.x + ", " + q2.y + ", " + q2.z + ", " + q2.w);
Debug.Log("q3: " + q3.x + ", " + q3.y + ", " + q3.z + ", " + q3.w);
q3 = Quaternion.Lerp(q1, q2, 1.0f);
Debug.Log("Equal to 1:");
Debug.Log("q2: " + q2.x + ", " + q2.y + ", " + q2.z + ", " + q2.w);
Debug.Log("q3: " + q3.x + ", " + q3.y + ", " + q3.z + ", " + q3.w);
q3 = Quaternion.Lerp(q1, q2, 27.0f);
Debug.Log("Bigger than 1:");
Debug.Log("q2: " + q2.x + ", " + q2.y + ", " + q2.z + ", " + q2.w);
Debug.Log("q3: " + q3.x + ", " + q3.y + ", " + q3.z + ", " + q3.w);
Output:
q1: 0.1276794, 0.1448781, 0.2392983, 0.9515485
Almost 1:
q2: 0.4670123, 0.188938, 0.3006466, 0.8098231
q3: 0.4669816, 0.1889349, 0.3006426, 0.809843
Equal to 1:
q2: 0.4670123, 0.188938, 0.3006466, 0.8098231
q3: 0.4670123, 0.188938, 0.3006467, 0.8098232
Bigger than 1:
q2: 0.4670123, 0.188938, 0.3006466, 0.8098231
q3: 0.4670123, 0.188938, 0.3006467, 0.8098232
Depending on your situation, you can use Quaternion.Angle to test if two quaternions are the same, using a very small angle.
@Dipso: I don't have problems with how Lerp works, I know what's going on there, my issue is when Lerp itself throws an assertion and gives me back junk data in a quaternion that I can't seem to check for. Having a quat that's all { NaN; NaN; NaN; NaN } is pretty crap because it poisons anything else I try to do with it :(
Ok, here's what I'm trying to do: In DD there's a general animation script that allows us to build procedural animations for sprites. You basically add things like impulses, accelerations, rotational impulses, rotational accelerations, colours to fade to/from and the like at time targets and the animation interpolates all of that and behaves accordingly. We use it for everything from particle-like pops when a full particle emission would be too expensive, to animations for combat. The nice thing about it is that it allows us to inject a lot of random into an animation so we're not playing canned sequences all the time - each general animation that's sculpted by an event is going to be unique in some way. That helps a lot with grid-based games where lots of elements are visible at the same time. The other neat thing is that our animations are nestable and can trigger more animations (or ingame events) as needed, so that makes life a lot easier too.
I only recently needed rotational stuff, so that's why I was messing with quats. Basically, I wanted to be able to say "This is how fast the animation is going to rotate per second for the time period between here and here" and because the times can be randomised, I can't simply calculate a "total rotational distance" and go with that because a rotational acceleration might kick in and mess with the outcome.
The line that caused problems was this:
The idea was to lerp between a neutral starting rotation and the amount of rotation we're supposed to perform per second (hence using the delta) and then apply that to the child objects under the control of this animation. This worked great except for when childRotation was itself a brand new Quaternion. Essentially, the lerp was reporting that there was a non-normalised quat somewhere and it would fall over.
Performing this test beforehand if ((childRotation.x != 0f) || (childRotation.y != 0f) || (childRotation.z != 0f) || (childRotation.w != 0f)) prevented the issue, but seemed super inefficient and pretty damn stupid to have to do all the time, ew.
Switching to Slerp has prevented the issue entirely (and allowed me to ditch the icky pre-test), presumably because it doesn't try to normalise the resulting quaternion the same way Lerp does. Thanks @Pixel_Reaper!
Instantiate(Building01, new Vector3(20, 0, 0), Quaternion.identity);
Was mostly a copy paste, but somehow I feel justified in joining the big boys' club :P
No, I don't know what it means :P
Yes, it sounds like an alien :P