 # Can't seem to get rotation working

(Nolan) #1

Hey folks, having some trouble implementing rotation in my game and would like to check some basic assumptions.

I’m building a simple 2-D Asteroids-style shooter. To model this, I’m using X/Z coordinates with the Y coordinate locked to 0. I’m imagining sitting at my desk as being in the ship’s cockpit. In that instance, the Y axis extends up from my chair through my head, the X goes left to right, and the Z extends out from the screen with the positive direction toward me. Right so far?

If so, I’m now trying to model rotation. My rotation will be left-right, as if I’m rotating in my chair. In that instance, I’m rotating about the Y axis extending upwards from my chair, so that’s yaw. Still good?

If so, here’s my attempt at a “rotate_left” action:

``````            if input_handler.action_is_down("rotate_left").unwrap() {
let rotation = transform.rotation();
let (roll, pitch, yaw) = rotation.euler_angles();
let delta = yaw+config.rotation_rate*delta; // `delta` is time.delta_seconds()
info!("Left: roll={}, pitch={}, yaw={}, delta={}", roll, pitch, yaw, delta);
transform.yaw_global(delta);
}
``````

Here are my logs:

``````[INFO][onslaught] Left: roll=0, pitch=0, yaw=0, delta=0.053532172
[INFO][onslaught] Left: roll=0, pitch=0.05353217, yaw=0, delta=0.051369745
[INFO][onslaught] Left: roll=0, pitch=0.10490192, yaw=0, delta=0.051944472
``````

Something isn’t matching up right, since I think I’m changing yaw but appear to be changing pitch. Am I reading my angles incorrectly? `transform.set_rotation_euler(...)` uses this order, and I’d assume getting the values back would be the same. I also think we’re using rustsim’s libraries under the hood, and this issue I filed seems to agree with everything I’ve done so far, with the very minor exception of how the code actually works. What am I getting wrong?

(Joël Lupien) #2

You comprehension seems correct. However, yaw is around the Y axis, and I think that euler angles will return it as the second value, and not the third. Try using the following order: pitch, yaw, roll.

(Gray Olson) #3

https://www.nalgebra.org/rustdoc/nalgebra/geometry/type.UnitQuaternion.html#method.euler_angles all the nalgebra functions do it in the order (roll, pitch, yaw)

(Nolan) #4

OK, thanks to everyone who helped me here and on Discord. Currently I have something that works, but I’m not sure why. Here are my rotation actions:

``````            if input_handler.action_is_down("rotate_left").unwrap() {
let rotation = transform.rotation();
let (x, y, z) = rotation.euler_angles();
let delta = config.rotation_rate*delta;
info!("Left: x={}, y={}, z={}, delta={}", x, y, z, delta);
transform.yaw_global(delta);
}
if input_handler.action_is_down("rotate_right").unwrap() {
let rotation = transform.rotation();
let (x, y, z) = rotation.euler_angles();
let delta = config.rotation_rate*delta;
info!("Left: x={}, y={}, z={}, delta={}", x, y, z, delta);
transform.yaw_global(-delta);
}
``````

So far so good. I can rotate, move forward, and my coordinates agree with what I think they should.

Next I want to present the character’s heading. I get that, if I’m rotating on two axis, angles can invert if I, say, rotate over 90 degrees around Z. But I thought that locking rotation about one or more axis could lessen or eliminate that. I.e. if I’m in a plane that theoretically can’t roll left/right, and I constrain nose tilt to ±90, then I’d essentially have a much simpler, more restrictive range of motion. I used to code space-based MUDs back in the day, and that’s how we had text-based pseudo-3D space flight. Your ship couldn’t roll, nose tilt was constrained, and you could either reason about where ships were based on your coordinates/bearings or you got nice ANSI graphics showing simplified 2-D views.

So to create a situation where left arrow increases my heading predictably up to and past 360, and right arrow predictably reduces my heading to 0 and swings it back to 360, why do I have to do this?:

``````                            let rotation = transform.rotation();
let (x, y, z) = rotation.euler_angles();
let mut degrees = y.to_degrees() as i32;
if x > 0. && z > 0. {
degrees = 180-degrees;
} else if x == 0. && y < 0. && z == 0. {
degrees = 360+degrees;
}
tts.speak(format!("{} degrees", degrees), true).unwrap();
``````

Granted, the number-munging hides a lot of it, but rotating just on the Y axis doesn’t rise linearly to `PI * 2` then return to 0. It rises to `PI / 4`, then drops, then the other axis flip to PI, then back to 0 when Y goes negative. I can probably simplify the conditional logic a bit, but I’m tired of fighting with basic orientation and am ready to work on gameplay. Anyhow, I guess this is OK as far as basic hacks go, but in the future I may want to do the more constrained 3-D motion I mentioned earlier. But this is the simplest case I can imagine, and I already don’t understand why I’m getting any axis rotation other than Y, so my hopes aren’t high. Thanks again.

(Joël Lupien) #5

Welcome to the fabulous world of quaternion math. I suggest looking up quaternion animations on youtube to better understand how they work, and how they translate to and from euler angles.

(Nolan) #6

Got it. So am I on the right track for displaying heading? Or is there a simpler approach? Happy to jump through these hoops if necessary, but given that my interest is display and not transformation, I’m wondering if there’s a better path.

Thanks for the help.

(Gray Olson) #7

Basically, if you want a representation of a single angle which can go up and down to any given angle predictably, you need to store that yourself and then convert it into the quaternion representation afterwards.

(Nolan) #8

Perfect, thanks! Just switched to tracking my own heading, changing it per rotation logic, then syncing it back to the transform. Works like a charm.