I decided to toy around with creating a 6DOF camera controller similar to games such as Descent. Everything works fine, until I try to lerp in a small 15 degree banking angle into the rotation.
The camera banks properly and is self righting when the mouse is brought back to the center of the screen. However when I turn the ship so it’s looking directly up or down along the world Y axis, I lose all freedom on anything but the Y.
Is this a classic case of gimbal lock, and if so how do I avoid it? Perform the Z rotation on a parent object? Somehow express the lerp using transform.rotate? Hints from searching the forums were a bit sparse.
The avoid it, just don’t mess around with eulerAngles and use quaternions instead, cause they are the only rotation representation taht does not suffer from gimbal lock.
You can still use your euler angle thinking if you want but you must use the Quaternion.Euler() function and then just multiply the old rotation with the Quaternion you get from the X - Y - Z rotation you apply in a single step normally
And yes that code is pretty surely generating gimbal lock casue your rotation around Vector3 right (sure you don’t want to use transform.right there?) does not check to remain in the ± 89 range and once you hit 90 you are basically doomed …
This is incorrect. To present just one counterexample, matrices don’t suffer from gimbal lock either; in fact, they have the exact same behavior with respect to gimbal lock as quaternions do.
I switched around to using quaternions for all the rotations. Still having issues with looking directly along the world Y axis. The camera spins wildly, as if it’s gimbal locked but the Z rotation I’m applying somehow breaks out of it.
So I’m wondering if Jesse is correct, the docs and everything I read on the internet are wrong and quaternions can still gimbal lock. Or perhaps eulerangle.z in my lerp is causing the problem, giving wildly fluctuating values or some such looking along the world Y axis.
I’ve explained how to implement gimbal-lock-free motion more times than I can count on various game development forums More on GDNet than here though, just 'cause I’ve been posting there longer. (I don’t know for sure that I’ve explained it in detail here or on Unity Answers, but I’d be willing to bet that I have.)
Anyway, in short, the way to avoid gimbal lock is not to build or combine rotations in ways that can lead to gimbal lock. What this means depends on the context, but usually it means modifying an orientation using incremental rotations rather than building an orientation ‘from scratch’ using a set of three Euler angles.
You didn’t specify what this is in response to; are you referring to my earlier post? If so, I can absolutely guarantee you that you’re mistaken.
I don’t see anything in that article that states that matrices are more susceptible to gimbal lock than quaternions.
Look, I’ve already been through this with other posters on the forum. It’s a simple fact that matrices and quaternions behave the same way with respect to gimbal lock, and I can prove it if you’ll give me the opportunity. Similarly, if your claim is that matrices are more susceptible to gimbal lock than quaternions, I invite you to submit proof of that.
With that in mind, I’ll issue the same challenge I did in the previous thread on this topic (the challenge was never met, by the way). That is, provide an example of an operation involving rotations that would lead to gimbal lock with matrices but not with quaternions. If your claim that matrices are more susceptible to gimbal lock than quaternions is correct, this should be easy to do.
@Quietus: Actually, the problem you’re seeing isn’t gimbal lock, per se. It’s just that modifying the global ‘z’ Euler angle (or the local one, for that matter) isn’t going to produce the results that you expect.
I haven’t tried this myself, but here’s what I’d suggest. Presumably, you want the roll/bank effect to be independent of the overall control of the vehicle. One way to achieve this would be to create two game objects, one the child of the other. The parent object would be controlled by a script much like your first one, but without the banking. The child object would then only bank, and would bank relative to the parent object. The banking angle would then be made proportional to the yaw velocity (or however you want to handle it).
There may be other solutions, but that’s the most straightforward way I can think of at the moment to get the behavior you’re looking for.
Matrices themself would not suffer from gimbal lock as matrices don’t do anything other than vector space transformations.
But in game engines, it heavily depends on what the engine does internally on if their usage results in gimbal lock or not, if the engine feeds rotation matrices from quaternions or euler angles.
There is no requirement to build them from the quaternion and I’ve seen several engines really building them from euler angles, so I normally, without knowing the inner working indepth enough of the engine, consider them to be “euler based” ie gimbal lock bound too, this saves me the headache afterwards as I know that independent of the implementation, quaternions will never fuck up.
Also quaternions, once you get the idea on them, are much easier to work with, to apply to stuff and when it comes to stuff like path following etc, they are out of competition above anything euler and matrix can do with similar simple code, math and performance.
They only look frightening at the first or frightening if you are not a maths / physics / computer science student yet think its wise to look at their mathematical base. If you only think of them as what they represent at the end and visualize that, they are significantly easier, I would say for most applications even easier to think off than euler and definitely much more understable than matrices So what I wanted to say is: there is no reason to not learn them, don’t fear them, they are your friends
That’s basically what I’m doing now. Similar to some of the stock cameras, tracking rotation angles myself as an offset from a reference and ditching the Euler angle.
Did some more testing and It was eulerAngles.z, which seemed to be fluctuating randomly when the camera is pointing along the Y axis for some reason. I seem to remember reading at some point that Quaternion.Slerp had similar problems, as the ‘shortest path’ calculations varied from frame to frame because of this.
Well, it sounds like we’re not really in disagreement then
That’s really the essence of it right there, yes.
Yes, it sounds as if (like many people) you simply ‘associate’ matrices with Euler angles more than you do quaternions, and therefore think of them as being more susceptible to gimbal lock than quaternions. Unfortunately, this way of looking at things has led to a lot of confusion online and elsewhere, as evidenced by the fact that many people firmly believe the matrices are inherently more susceptible to gimbal lock than quaternions (which is completely false).
Yeah, that should work too. The only potential issue is that as you bank, yaw will be relative to the new, ‘banked’ orientation rather than the original ‘unbanked’ orientation. If that’s acceptable, then that’ll work; otherwise, the ‘parenting’ solution I described earlier might be more appropriate.
(I’ve played Descent, but I can’t remember off the top of my head exactly how the banking effect works in that game. I think though that it’s more like what I described; that is, that the ‘banking’ effect doesn’t affect the yaw axis.)
Agreed, it’s just intended to a be cosmetic effect to give an added impression of rotation. As the ship is really just a camera, I’ll switch to the parented solution and see how it feels.
I still have other problems to work on. Such as the rotations are based on world axis vectors and not the local transform axis. As a result when you are upside down controls are reversed, when you’re sideways they’re even more unintuitive. It looks like from the docs that InverseTransformDirection is what I need.
I also think descent had an auto leveling system, but can’t remember how that worked. Like you, it’s been ages since I played it.
Actually, I think you had the right idea with your original script. You shouldn’t need to use InverseTransformDirection(); Transform.Rotate() already applies the rotation in local space, which is what you want. (And yeah, the control interface definitely shouldn’t be affected by orientation; if you’re ‘upside-down’ with respect to some reference or to your original orientation, the controls should continue to appear to function in the same way. In any case, your original approach of using Transform.Rotate() should give the right results.)
Yeah, I think the autoleveling effect was independent of any cosmetic banking effect. Actually, the autoleveling effect is probably the more straightforward of the two to implement. Basically, you’d raycast down (relative to the ship) to see if there’s a surface within some distance beneath the ship, and then apply an incremental, corrective rotation (for which you can use Quaternion.FromToRotation(), or whatever it’s called) to gradually align the ship with the normal of the surface beneath it.
I guess I could use the same modified banking code and plug that right into transform.rotate. It doesn’t look as pretty as all the quaternion manipulation, but it’s sure a hell of a lot less code. If I do and start getting gimbal lock and wild spinning again, I’ll stab my eyes out Jesse!
I’ll have to watch some videos on youtube to get a feel for how it works. Iirc it was when there was no user input, for which I could probably just create a 5% or so mouse input dead zone, since I’m using normalized mouse coordinates and not a mouse-look type delta.
Just in case I wasn’t completely clear earlier, I’m not suggesting re-introducing the aspects of your original code that were causing the undesirable behavior (specifically the manual manipulation of the ‘z’ rotation angle). Rather, what I’m suggesting would be a combination of your original code (specifically the part that uses Transform.Rotate() to apply local pitch and yaw) and your later code (which creates the ‘bank’ effect using a relative rotation).
If you really get stuck on this and find you can’t get it working the way you want, post back and maybe I can post an example script showing what I mean.
Oh I understood what you meant when discussing the parenting. I will certainly be doing that after I get past the current hurdles. It’s just working a day getting the quaternions working the way I want (albiet in the wrong coordinate system) then going back to the 3 line solution I started with!
I am sort of stuck though and could some rotation expertise. I could be that it’s almost midnight and I’m exhausted, but I can’t seem to figure out how to properly bank the craft in the same fashion at the quaternion version of the script.
That’s the latest version I’ve tried (of a few dozen), it spins along the local Z but instead of setting an explicit angle like I think it should it spins about as if I were using using transform.rotate. It doesn’t stop at either 15 or -15 rather just keeps spinning.
There’s probably a simple way I’m missing to bank the ship in a range sans euler angles, but after reading about quaternions all day I’m kind of fried. I’m back to square one, lol!
I also attempted to convert the quaternion rotations to local space as well, but it didn’t like that for some reason. It started spinning wildly whenever it approached any world axis. Maybe I just need sleep, or a glass of rum.
Yes, that’s the expected behavior given the code that you’ve written. This line:
Applies an incremental, local rotation; in other words, the above code basically has the same effect as using Transform.Rotate() (with the last argument left at its default value). As such, the banking won’t observe any sort of limit, but rather will be continuous (that is, the ship will continue to bank as long as it’s also yawing).
I hate to sound like a broken record, but I really think parenting is the solution. I downloaded your project, so if you really get stuck on this I could probably post a revised version of your script(s) showing what I mean.
Yes, but in the quaternion version the exact same same code has a limit and eases the banking when the mouse returns to center?
I tried the parenting solution, feeding the calculated rotation with transform.parent.rotation. The banking works now but I’ve notice other issues.
It doesn’t ‘feel’ anything like the quaternion version, which has what I’m aiming for. Nor does it have the self righting of the original version. Simply rotating on a single axis, seems to screw with the other axis so that when you come back from a full rotation say along the x axis and stop, the ship is not centered.
I think this all is because you never truly input only one axis with a mouse, in addition the quaternion version builds rotations relative to a reference object, while the transform.rotate version does not.
If you have a solution that has the proper characteristics, I’d sure like to see it.
I’m thinking now of using the quaternion script, with a different reference object depending on the camera’s orientation. That’s probably a dead end too, but I’m at a loss at this point. I thought a descent like controller would be a piece of cake, but it’s turned out to be quite a headache.