# rotation glitches when reaching 360 degrees

edited November 26

im trying to create weapon sway based on the cameras' and players' rotation, the main idea works but when reaching the 360degrees it glitches. maybe i need the local rotation , but i cant figure it out.
how do i make it work? tnx

``````func doweaponsway(delta):
#calculate rotation

var wantedz
var wantedx
var wantedy

var xrot = \$CamRoot.rotation_degrees.x  - oldxrot
var yrot = self.rotation_degrees.y- oldyrot
wantedx = lerp(weaponsway.rotation_degrees.x,xrot,2 * delta)
wantedy = lerp(weaponsway.rotation_degrees.y,yrot,2 * delta)

weaponsway.rotation_degrees.x = clamp(wantedx, -2, 2)
weaponsway.rotation_degrees.y = clamp(wantedy, -2, 2)
#weaponsway.rotation_degrees.z =clamp(wantedz,-2,2)

oldxrot = \$CamRoot.rotation_degrees.x
oldyrot = self.rotation_degrees.y
``````
«1

• edited November 26

Welcome to the forums @DJM!

I would recommend having the rotation for each axis in the weapon sway be a different node. This is because in Godot (and other game engines), having multiple axes of rotation that go over 180 degrees can lead to other axes being flipped to negative 180. I have found that when working with rotation and clamping, using separate nodes for each axes of rotation makes it MUCH easier. For example, instead of having `weaponsway` modified on the X and Y, you'd want to have something like this:

• weaponsway_x
• weaponsway_y
• (other weapon sway nodes here)

Then in your code you can modify the `rotation_degrees` of `weaponsway_x` for the x axis and `weaponsway_y` for the y axis. That may help with the 360 degrees issue if the reason it stops working is because of the -180 degree issue.

Also, using `rotation_degrees` is the local rotation. If you want the global rotation of a node, you'll need to use `node.global_transform.basis` to get the node's global rotations • tnx! im happy ive made the switch to godot.
sadly seperating the rotation axis on different nodes doesnt fix my issue.
ive also tried the global transform basis u suggested

in unity i used to use >
Mathf.DeltaAngle to get the shortest distance between rotations, i dont know if there is a similar function in gdscript

• @DJM said:
tnx! im happy ive made the switch to godot.
sadly seperating the rotation axis on different nodes doesnt fix my issue.
ive also tried the global transform basis u suggested

A shame that didn't fix it, but thankfully it means it's not the -180 issue that is causing the problem! I've found the -180 problem can be tricky to work around, so that's good that it's not what is causing the issue here.

in unity i used to use >
Mathf.DeltaAngle to get the shortest distance between rotations, i dont know if there is a similar function in gdscript

I don't think there is anything built in that is similar to the `Mathf.DeltaAngle` function unfortunately.
This StackOverflow solution provides a language agnostic way to get the difference of angles, though I'm not sure if the `mod` function in GDScript returns the sign or not. This Godot StackOverflow question also has an answer with many examples on lerping between two angles, which may work for getting the difference as well.

• actually i dont want to lerp between two angles. i only need to get the float between the current and last rotation per frame to do weaponsway calculations.
the issue thats occurring whit my current code is that when the current rotation is 0 and the last was 359 the weapon sway gets a short glitch because of the huge difference when i subtract the values

• edited November 26

This is a problem with Euler angles. As they go from 0 to 360 (or -180 to 180) then you will always have problems at the end of the range, especially when interpolating the value. Think of it like this, you want to rotate 30 degrees. But the angle is from 340 to 370 (or 10). If you just interpolate the values (340 and 10) instead of rotating 30 degrees, it will rotate 330 degrees, and in the wrong direction. You can account of this by looking at the values and adjusting them, but it is an inherent issue with using Euler angles.

A few things can help. First, try to always get 0 to 360 to make the math easier (so you don't get negative values or values above 360):

``````var angle = fmod(angle + 360.0, 360.0)
``````

Then you can find the smaller angle. If you know your delta angle will never be more than 180 degrees (a pretty fair assumption for most games):

``````var angle_delta = angle - last_angle
angle_delta = fmod(angle_delta + 360.0, 360.0)
if angle_delta > 180.0:
angle_delta = angle_delta - 360.0
``````
• edited November 26

Working with separate (euler) angles in 3d is just not worth the effort. You're in for a constant barrage of annoying gotchas Do it the grownup way and represent your current and wanted orientation with quaternions, and interpolate between them. You can even simplify by just using two lookat vectors and slerp between them.

``````var current: Vector3 # current lookat direction
var wanted: Vector3 # wanted lookat direction

func _process(delta):
wanted = calculate_wanted()
current = current.slerp(wanted, fraction)
look_at(current, Vector3.UP)
``````
• tnx ! that works perfect

heres the code in case anybody wonders to do the same thing

`func doweaponsway(delta):
#calculate rotation

``````var xrot = fmod( \$CamRoot.rotation_degrees.x + 360.0, 360)
var xrotdelta = xrot - oldxrot
xrotdelta = fmod(xrotdelta + 360.0, 360.0)
if xrotdelta > 180:
xrotdelta = xrotdelta - 360

var yrot = fmod( self.rotation_degrees.y + 360.0, 360)
var yrotdelta = yrot - oldyrot
yrotdelta = fmod(yrotdelta + 360.0, 360.0)
if yrotdelta > 180:
yrotdelta = yrotdelta - 360

var wantedx = lerp(weaponsway.rotation_degrees.x,xrotdelta,2 * delta)
var wantedy = lerp(weaponsway.rotation_degrees.y,yrotdelta,2 * delta)

weaponsway.rotation_degrees.x = clamp(wantedx, -2, 2)
weaponsway.rotation_degrees.y = clamp(wantedy, -2, 2)

oldxrot =fmod( \$CamRoot.rotation_degrees.x + 360.0, 360)
oldyrot = fmod( self.rotation_degrees.y + 360.0, 360)
``````

`

• @xyz said:
Working with separate (euler) angles in 3d is just not worth the effort. You're in for a constant barrage of annoying gotchas Do it the grownup way and represent your current and wanted orientation with quaternions, and interpolate between them. You can even simplify by just using two lookat vectors and slerp between them.

``````var current: Vector3 # current lookat direction
var wanted: Vector3 # wanted lookat direction

func _process(delta):
wanted = calculate_wanted()
current = current.slerp(wanted, fraction)
look_at(current, Vector3.UP)
``````

im not sure how to use that,
how to calculate "wanted" and what is "fraction" in your example?

• Wanted would be the next angle, current would be the previous angle, and fraction is a variable you set that controls how fast it interpolates.

• edited November 26

And yes, avoid using Euler angles if you can. They make things easier in the beginning, and they are simpler to understand, but using more advanced structures like Quaternions end up with more robust functionality, less bugs, and less code.

• @cybereality said:
And yes, avoid using Euler angles if you can. They make things easier in the beginning, and they are simpler to understand, but using more advanced structures like Quaternions end up with more robust functionality, less bugs, and less code.

ok
im having trouble figuring out the local forward lookat from the camroot.
if i try the suggestion the weapon doesnt seem to point into the view direction im looking

• edited November 26

@DJM said:

@xyz said:
Working with separate (euler) angles in 3d is just not worth the effort. You're in for a constant barrage of annoying gotchas Do it the grownup way and represent your current and wanted orientation with quaternions, and interpolate between them. You can even simplify by just using two lookat vectors and slerp between them.

``````var current: Vector3 # current lookat direction
var wanted: Vector3 # wanted lookat direction

func _process(delta):
wanted = calculate_wanted()
current = current.slerp(wanted, fraction)
look_at(current, Vector3.UP)
``````

im not sure how to use that,
how to calculate "wanted" and what is "fraction" in your example?

This was more at pseudocode level just to show the principle. We can discuss specifics but it'd be much easier if you could post how your nodes are set up.

• @xyz said:

@DJM said:

@xyz said:
Working with separate (euler) angles in 3d is just not worth the effort. You're in for a constant barrage of annoying gotchas Do it the grownup way and represent your current and wanted orientation with quaternions, and interpolate between them. You can even simplify by just using two lookat vectors and slerp between them.

``````var current: Vector3 # current lookat direction
var wanted: Vector3 # wanted lookat direction

func _process(delta):
wanted = calculate_wanted()
current = current.slerp(wanted, fraction)
look_at(current, Vector3.UP)
``````

im not sure how to use that,
how to calculate "wanted" and what is "fraction" in your example?

This was more at pseudocode level just to show the principle. We can discuss specifics but it'd me much easier if you could post how your nodes are set up.

here u go

• edited November 26

Ok, here's a quick version using quaternions:

``````func _process(delta):
var softness = 3.0
do_sway(\$weaponsway, calculate_sway_offset(), delta * softness)

func do_sway(sway_node, offset: Vector2, fract: float):
var wanted_quat = Quat(Vector3.UP, offset.x) * Quat(Vector3.RIGHT, offset.y)
var current_quat = Quat(sway_node.transform.basis)
sway_node.transform.basis = Basis(current_quat.slerp(wanted_quat, fract))
``````

You just need to implement calculate_sway_offset() to return horizontal and vertical sway offsets as Vector2. You can calculate it as you currently do from camera rotation deltas between frames.

• @xyz said:
Ok, here's a quick version using quaternions:

``````func _process(delta):
var softness = 3.0
do_sway(\$weaponsway, calculate_sway_offset(), delta * softness)

func do_sway(sway_node, offset: Vector2, fract: float):
var wanted_quat = Quat(Vector3.UP, offset.x) * Quat(Vector3.RIGHT, offset.y)
var current_quat = Quat(sway_node.transform.basis)
sway_node.transform.basis = Basis(current_quat.slerp(wanted_quat, fract))
``````

You just need to implement calculate_sway_offset() to return horizontal and vertical sway offsets as Vector2. You can calculate it as you currently do from camera rotation deltas between frames.

what do i put in the "calculate sway offset" func? how to return the vector2 values?

im currently calculating the movement in degrees so wont that have the same euler issues as i allready had?

• edited November 27

I'd do it from controller/mouse deltas, or if you must deal with movement - again from transformation basis/quaternions.

• edited November 27

Here's a way to calculate offsets from difference in rotation between frames. Using quaternions will ensure there are no wrapping and gimbal problems. You'll need to maintain player/camera basis from the previous frame:

``````calculate_sway_offset(basis_last_frame: Basis, basis_current_frame: Basis):
var q1 = basis_last_frame.get_rotation_quat()
var q2 = basis_current_frame.get_rotation_quat()
var q = q1.inverse() * q2
var eu = q.get_euler()
return Vector2(eu.y, eu.x)
``````
• Right, but this is also why Euler is much easier when starting out. It took me years to fully understand Quaternions, but I guess if you use Godot it does most of the math for you (but you still kind of have to understand what you're doing).

• edited November 27

@xyz said:
Here's a way to calculate offsets from difference in rotation between frames. Using quaternions will ensure there are no wrapping and gimbal problems. You'll need to maintain player/camera basis from the previous frame:

``````calculate_sway_offset(basis_last_frame: Basis, basis_current_frame: Basis):
var q1 = basis_last_frame.get_rotation_quat()
var q2 = basis_current_frame.get_rotation_quat()
var q = q1.inverse() * q2
var eu = q.get_euler()
return Vector2(eu.y, eu.x)
``````

ok heres what ive got right now, based on your code

``````func _process(delta):

var softness = 3.0
do_sway(weaponsway, calculate_sway_offset(), delta * softness)

calculate_sway_offset(basis_last_frame: Basis, basis_current_frame: Basis):
var q1 = basis_last_frame.get_rotation_quat()
var q2 = basis_current_frame.get_rotation_quat()
var q = q1.inverse() * q2
var eu = q.get_euler()
return Vector2(eu.y, eu.x)

func do_sway(sway_node, offset: Vector2, fract: float):
var offset_quaternion: Quat = Quat(Vector3.UP, offset.x) * Quat(Vector3.RIGHT, offset.y)
sway_node.transform.basis = Basis(Quat(sway_node.transform.basis).slerp(offset_quaternion, fract))
``````

im not sure what to write in the > do_sway(weaponsway, calculate_sway_offset( "what goes here?"), delta * softness)
i cant use mouse rotation , because it will sway the weapon even if theres no rotation happening.

• edited November 27

@DJM said:
im not sure what to write in the > do_sway(weaponsway, calculate_sway_offset( "what goes here?"), delta * softness)
i cant use mouse rotation , because it will sway the weapon even if theres no rotation happening.

What were you trying to base the sway on in the first place? It's typically done based on mouse input. In your initial post you said you want to base it on player and camera rotations. I'm not sure I understand why are you trying to base it on both.

In my example, I assumed you want to do it based on changes in camera rotation - bigger the change, larger the offset. To do this you use camera rotations in previous and current frames (represented by transform basis) as arguments to that function. The function extracts "horizontal" and "vertical" camera rotatation deltas from total rotation of the camera between consecutive frames.

``````var basis_last_frame = Basis()
func _process(delta):
var basis_this_frame = \$cam.transform.basis
var offset = calculate_sway_offset(basis_last_frame, basis_this_frame)
do_sway(\$sway, offset, delta * softness)
basis_last_frame = basis_this_frame;
``````

\$sway and \$cam are paths to your actual sway and camera nodes. In fact \$cam stands for the node that does actual rotation in respect to global space. I'm assuming it's the player node in your case.

Of course you can base this offset on other things, depending on your system and actual effect you want to achieve. So it's up to you to decide that. I'd base it simply on InputEventMouseMotion.relative. In which case the whole calculate_sway_offset() function in my previous post can simply return mouse offsets. It just needs to return zero offset if mouse motion didn't happen in the current frame and it'll all work fine.

• edited November 27

@cybereality said:
Right, but this is also why Euler is much easier when starting out. It took me years to fully understand Quaternions, but I guess if you use Godot it does most of the math for you (but you still kind of have to understand what you're doing).

One doesn't really need to fully understand quaternions and all the related math to be able to use them for 3d graphics. They have a wide general usage in math but in the world of computer graphics quaternion can be seen simply as a matrix-light that covers only rotations. You can use it as a black box that has certain neat properties and does certain useful things for you. Most people successfully use 4x4 transformation matrices this way.

But yeah, it's true that quaternions may look like an overkill for simple stuff that can kinda be solved using eulers. Especially when you see them for the first time. However eulers grossly fall short if you need to deal with lots of 3d orientation/targeting stuff, as is the case with first person shooters. Quaternions can handle most of such problems with ease in just a few lines of code.

• @xyz said:

@DJM said:
im not sure what to write in the > do_sway(weaponsway, calculate_sway_offset( "what goes here?"), delta * softness)
i cant use mouse rotation , because it will sway the weapon even if theres no rotation happening.

What were you trying to base the sway on in the first place? It's typically done based on mouse input. In your initial post you said you want to base it on player and camera rotations. I'm not sure I understand why are you trying to base it on both.

In my example, I assumed you want to do it based on changes in camera rotation - bigger the change, larger the offset. To do this you use camera rotations in previous and current frames (represented by transform basis) as arguments to that function. The function extracts "horizontal" and "vertical" camera rotatation deltas from total rotation of the camera between consecutive frames.

``````var basis_last_frame = Basis()
func _process(delta):
var basis_this_frame = \$cam.transform.basis
var offset = calculate_sway_offset(basis_last_frame, basis_this_frame)
do_sway(\$sway, offset, delta * softness)
basis_last_frame = basis_this_frame;
``````

\$sway and \$cam are paths to your actual sway and camera nodes. In fact \$cam stands for the node that does actual rotation in respect to global space. I'm assuming it's the player node in your case.

Of course you can base this offset on other things, depending on your system and actual effect you want to achieve. So it's up to you to decide that. I'd base it simply on InputEventMouseMotion.relative. In which case the whole calculate_sway_offset() function in my previous post can simply return mouse offsets. It just needs to return zero offset if mouse motion didn't happen in the current frame and it'll all work fine.

it sort of works but sway happens only on one axis, if i set \$cam to the player node it rotates horizontaly if i set \$cam to the camera node it happens only vertically

idont ant to use mouse motion as when u are looking down and the rotation is clamped , u will still get weapon sway even if there is no actual rotation happening

• edited November 27

Well that's probably because different nodes handle rotations on different axes in your setup.
You can simply do the basis thing for both nodes, calculate both offsets and then use horizontal offset from one, and vertical offset from the other.

``````var cam_basis_last_frame = Basis()
var player_basis_last_frame = Basis()

func _process(delta):
var cam_basis_this_frame = \$cam.transform.basis
var player_basis_this_frame = \$player.transform.basis
var offset1 = calculate_sway_offset(cam_basis_last_frame, cam_basis_this_frame)
var offset2 = calculate_sway_offset(player_basis_last_frame, player_basis_this_frame)
var offset = Vector2(offset1.x, offset2.y) #may need to swap 1 and 2
do_sway(\$sway, offset, delta * softness)
cam_basis_last_frame = cam_basis_this_frame;
player_basis_last_frame = player_basis_this_frame;
``````
• edited November 27

I'm beginning to think that wrapped-euler approach may be simpler for calculating the offsets here • @xyz said:
I'm beginning to think that wrapped-euler approach may be simpler for calculating the offsets here so my initial idea was better?

• edited November 27

@DJM said:

@xyz said:
I'm beginning to think that wrapped-euler approach may be simpler for calculating the offsets here so my initial idea was better?

It's the same thing. Quaternions will elegantly eliminate all wrapping and gimbal lock glitches, which was the problem in the first place. Since angles are already separated into different nodes, @cybereality's euler wrap solution will do that job as well. However, even if offset is obtained directly from euler angles I'd still use quaternion slerping for setting the sway node rotation.

If you're working on a first person shooter thingy, don't shy sway from quaternions, but rather get acquainted with them. You'll need their help again... sooner or later • @xyz said:
Well that's probably because different nodes handle rotations on different axes in your setup.
You can simply do the basis thing for both nodes, calculate both offsets and then use horizontal offset from one, and vertical offset from the other.

``````var cam_basis_last_frame = Basis()
var player_basis_last_frame = Basis()

func _process(delta):
var cam_basis_this_frame = \$cam.transform.basis
var player_basis_this_frame = \$player.transform.basis
var offset1 = calculate_sway_offset(cam_basis_last_frame, cam_basis_this_frame)
var offset2 = calculate_sway_offset(player_basis_last_frame, player_basis_this_frame)
var offset = Vector2(offset1.x, offset2.y) #may need to swap 1 and 2
do_sway(\$sway, offset, delta * softness)
cam_basis_last_frame = cam_basis_this_frame;
player_basis_last_frame = player_basis_this_frame;
``````

tried it, now the gunsway rotates out of camera view and just gets all the rotation from the player and cam applied

• Let's see the actual code.

• @xyz said:
Let's see the actual code.

`func _process(delta):
window_activity()
var softness = 3.0
var cam_basis_last_frame = Basis()
var player_basis_last_frame = Basis()

``````var cam_basis_this_frame = \$CamRoot.transform.basis
var player_basis_this_frame = self.transform.basis
var offset1 = calculate_sway_offset(cam_basis_last_frame, cam_basis_this_frame)
var offset2 = calculate_sway_offset(player_basis_last_frame, player_basis_this_frame)
var offset = Vector2(offset2.x, offset1.y) #may need to swap 1 and 2
do_sway(weaponsway, offset, delta * softness)
cam_basis_last_frame = cam_basis_this_frame;
player_basis_last_frame = player_basis_this_frame;
``````

func calculate_sway_offset(basis_last_frame: Basis, basis_current_frame: Basis):
var q1 = basis_last_frame.get_rotation_quat()
var q2 = basis_current_frame.get_rotation_quat()
var q = q1.inverse() * q2
var eu = q.get_euler()
return Vector2(eu.y, eu.x)

func do_sway(sway_node, offset: Vector2, fract: float):
var offset_quaternion: Quat = Quat(Vector3.UP, offset.x) * Quat(Vector3.RIGHT, offset.y)
sway_node.transform.basis = Basis(Quat(sway_node.transform.basis).slerp(offset_quaternion, fract))
`

• edited November 27

cam_basis_last_frame and player_basis_last_frame need to be sctipt-level properties, not local variables. They are used to "remember" basis values from the last frame. If you declare them as local variables inside _process(), like you're doing now, they'll just be deleted when function exits, serving no purpose.

p.s. please fix the code formatting.