Spaceship gimbal lock

Recently I’ve been having lots of rotation - related problems. Most of them I managed to fix but this one I can’t fix yet.
We’ve been working on flying mechanics for our game. The player would have a ship that goes left, rght, up and down according to it’s local Z rotation. So if the ship is tilted on 90 degrees, right/left would be up/down and up/down would be right/left.

Everything works good except for when we try to steer the ship directly up/down. Then occurs gimbal lock and the ship can’t steer away.

So that’s the rotation code:

if(Input.GetAxis("Horizontal") >= 0.5 || Input.GetAxis("Horizontal") <= -0.5 ) rotationZ += -Input.GetAxis("Horizontal")*Time.deltaTime*200;
else rotationZ = Mathf.MoveTowards(rotationZ, 0, Time.deltaTime*120);
rotationZ = Mathf.Clamp(rotationZ, -90, 90);
transform.Rotate(rotationX*Time.deltaTime*60, rotationY*Time.deltaTime*60, 0, Space.Self);
transform.eulerAngles.z = Mathf.LerpAngle(transform.eulerAngles.z, rotationZ, Time.deltaTime*10);

I need to preserve the functionality of this and fix the lock that appears. Can somebody help me with that? It will be much appreciated :slight_smile:

You need to store and update your basis vectors and use them to calculate your transforms.
Using angles like what you are doing is too difficult.

Thanks for the reply nut I didn’t really get it.
What do you mean by basis vectors ? Are you talking about the local up, right and forward ?
I already tried to do it like that with Quaternion.LookRotation but it didn’t work for me either.

One solution is to store X, Y, and Z rotations separately (a Vector3 is fine), and recreate the rotation each frame.

For instance:

playerRotation.y += Input.GetAxis("Horizontal");
playerRotation.x += Input.GetAxis("Vertical");
playerRotation.z += Input.GetAxis("Spin");

playerTransform.rotation = Quaternion.Euler( playerRotation );

Yes, if I make it like that, gimbal lock wouldn’t appear, but if I tilt the ship it wouldn’t steer left and right properly.

I would just use quaternions. If you do a little research on them they are a lot easier to work with than eulerAngles.

i would use something like

and similar for all other rotations.

or just use two pivots
a game object that moves up and down
and parent that game object to something that moves left right and tilts left right.
i think the above is a bit more tricky and code intensvie though (but Im not 100% on that)

Ah, my bad.
I actually just used physics for my similar project - AddRelativeTorque spins things around nicely without me having to think about it. I must have run into the gimbal lock problem and then gotten lazy about it.

No problem, Loius, thanks for the reply though! :slight_smile:

AngleAxis looks like a good approach. I don’t really want to do the parenting thingie. I’ve done it before, it would solve the problem, but I need my hierarchy as simple as that so I don’t want any extra objects.

So about this approach. I like I see a problem here:

With my current system I constrain the Z rotation to -90 and 90 degrees. I don’t want my ship to tilt more or less than that (well, it’s not exactly a reallistic spaceship).

if(Input.GetAxis("Horizontal") >= 0.5 || Input.GetAxis("Horizontal") <= -0.5 ) rotationZ += -Input.GetAxis("Horizontal")*Time.deltaTime*200;
else rotationZ = Mathf.MoveTowards(rotationZ, 0, Time.deltaTime*120);
rotationZ = Mathf.Clamp(rotationZ, -90, 90);

And with this:
spaceShipObject.rotation *= Quaternion.AngleAxis(turn magnitue, Vector3.forward);
I’ll be able to do barrel rolls. What would be a good way to restrict Z rotation using this method ?

Since you are using GetAxis i would think you have a joystick approach, so if you want to clamp rotation between 90 degrees to left and 90 degrees to right I would use max horizontal left

//This should get your ending rotation (+ if left and - if right, or vice-versa)
endRotation = Quaternion(90/Input.GetAxis("Horizontal"), Vector3.forward);
//This allows you to smoothly shift from one rotation to another depending on time scale
//Look up how slerp is supposed to work exactly on these forums or Answers, they are much more indepth and show examples than docs.
Quaternion.Slerp(startRotation, endRotation, howFarInTimeScale);

No i haven’t tested this code myself, it is off the top of my head so it wont work exactly right from the get go, and you will have to tweak it to have it fit.

Well the problem here is:

endRotation = Quaternion(90/Input.GetAxis(“Horizontal”), Vector3.forward);

If the endRotation strongly equals Quaternion.AngleAxis(90/Input.GetAxis(“Horizontal”), Vector3.forward); then all the other player input will be ignored. The ship is only going to tilt and will not steer left/right/up/down.

I tried tons of different approaches.
So far the most effective one was to use transform.Rotate to rotate the player, then store it’s rotation in a variable, make the rotation equal to AngleAxis(rotationZ, transform.forward) and add the rotation stored above.

That would result in the player frantically spinning around Z axis when I try to tilt it, but at least no gimbal lock occured and at least the player was controllable in some manner.

Is it possible that there would be no other solution to this problem rather than making it a parent of something else and rotate both ?

I have been trying to achieve this for a long time now. I have the feeling that this is actually not possible without a parent transform.

Has anybody achieved a rotation system like that before ? I’ve been searching a lot and all the solutions I found had to deal with adding a parent transform. :frowning:

I have a similar problem with the gimbal lock. It would be nice if someone could help out here.

Sorry to hear this :frowning:
Sadly there is no solution to this problem. I tried thousands of things since my first post. I have now locked the rotation around the X axis so that the player cannot rotate the ship vertically. Yeah, there is no freedom of movement and you can forget about loops and all the other stuff aircrafts do but at least it’s better than gimbal lock.

I finally tried the thing with the parenting too. Gues what? While the base (which only yaws and tilts) is okay, the child ship gets gimbal locked when the same condition occurs. This is nightmare!

I’m having a hard time understanding exactly what you want… You want the ship to rotate free of gimbal lock, yet stay within +/- 90 degrees on Z…? but Z is relative to the ship so I’m not able to visualize this behavior. :slight_smile:

Perhaps you should do something simple like this:

		const float Speed = 50f;
		transform.rotation *= Quaternion.Euler(new Vector3(Input.GetAxis("Vertical"), Input.GetAxis("Horizontal"), 0f) * Time.deltaTime * Speed);

Then you might add some auto-straightening if the player is upside-down or isn’t currently steering… These types of controls can be tricky to get right, just look at Descent. :slight_smile:

Yey! A reply!
Well I actually tried this method before, didn’t really like the behavior though, if I started steering the ship upwards/downwards and sideways, the whole ship would end up tilted at some point.

What I want is this:
I want the ship to yaw and pitch using local space so when I tilt the ship on 90 degrees yaw would be pitch and pitch would be yaw.
The code from my first post does the job:

if(Input.GetAxis("Horizontal") >= 0.5 || Input.GetAxis("Horizontal") <= -0.5 ) rotationZ += -Input.GetAxis("Horizontal")*Time.deltaTime*200;

else rotationZ = Mathf.MoveTowards(rotationZ, 0, Time.deltaTime*120);

rotationZ = Mathf.Clamp(rotationZ, -90, 90);

transform.Rotate(rotationX*Time.deltaTime*60, rotationY*Time.deltaTime*60, 0, Space.Self);

transform.eulerAngles.z = Mathf.LerpAngle(transform.eulerAngles.z, rotationZ, Time.deltaTime*10);

However I get a gimbal lock when I steer the ship directly up or down.

Loiuses approach also does the job:

playerRotation.y += Input.GetAxis("Horizontal");

playerRotation.x += Input.GetAxis("Vertical");

playerRotation.z += Input.GetAxis("Spin");

playerTransform.rotation = Quaternion.Euler( playerRotation );

With this approach I won’t get gimbal lock but when I tilt the ship 90 degrees the pitch and yaw don’t stay local to the ship because they work in world space.

Let’s forget about locking the tilt to +/- 90 degrees for now. I first need to get the ship to pitch and yaw according to tilt without getting a gimbal lock.

All of this happens because you are trying to use eulers to represent quaternions. Eulers are representations of quaternions, and have some pretty bad results in gimbal.

My suggestion is to use LookAt and cheat.

Vector3 forward = transform.position + transform.forward + transform.right * Input.GetAxis("Horizontal") * yawModifier + transform.right * Input.GetAxis("Vertical") * pitchModifier;
Vector3 up = transform.up + transform.right * Input.GetAxis("Spin") * rollModifier;

transform.LookAt(forward, up);

Next, to cheat, we can force the ship to an up or down position by using something similar

Quaternion rotation = transform.rotation;
bool isUp = Vector3.Dot(transform.forward, Vector3.up) > 0;
forward = transform.position + (isUp ? Vector3.up : -Vector3.up);

transform.LookAt(forward, transform.up);
transform.Slerp(rotation, transform.rotation, Time.deltaTime * freeRotationSpeed);

Of course, I would limit the forcing of rotation to only when the player is not actively trying to change the rotation.

Wow, the lookAt method is pretty interesting. I love the way you call it (cheating) :smile: I tried it and it did work pretty well. However I didn’t get the second part of your code: Why forward becomes either transform.position+Vector3.up or transform.position-Vector3.up? Is this supposed to keep the ship from tilting when I just steer it left/right/up/down ?

Consider the second part a second look. This is where you look straight up, or straight down.

The statement:

(isUp ? Vector3.up : -Vector3.up);

can be written as such:

if(isUp){return Vector3.up;} else {return -Vector3.up;}

You will find that this notation is very slick and works well in many applications.

The purpose is to force the ship either up or down. Think that you are at a position in space. And you want your ship to look straight up. You simply take your ships position and add Vector3.up to it, then look at that position. Since you don’t want your ship to then default to a upwards direction, you supply the ship’s current UP as the look’s up. This means that the ship doesn’t wildly swing to a standard rotation, but uses the current one.

After that, we simply Slerp towards the upward/downward position to force the issue if that is what you desire.

Now, one edit to the first part I had. You need deltaTime in there so that you pitch, roll and yaw consistently. should have thought of that.

Yes, I’m familiar with ternary operators :slight_smile: The problem is that this code will always make the ship point either up or down and wouldn’t let the player steer away. I tried it and the exact same thing happened.

I don’t think I’ll need Time.deltaTime though as LookAt directly looks at the point that’s generated and the forward variable uses the input from the Axes which are pretty realtime AFAIK.