Animating rotation (DEF-1636) (SOLVED)

I have been trying to figure out a neat way of animating the transitional change of position for my character, but got stuck in trying to make the character turn towards the position… either I hit a gimbal-lock using “rotation” or a faulty quaternions using “rotation.z” resulting in a scaled object. (I can add gifs showing the visual issues later on request).

My last resort is to animate a different property representing the z rotation and setting this every update based on the self.crap value, but that’s an ugly hack so I would prefer not to. Appreciate any possible hint at a better solution :slight_smile:

go.property("move_speed", 10)
go.property("turn_speed", 0.1)

function on_message(self, message_id, message, sender)
	if message_id == hash("move_to") then
		local direction = go.get_world_position() - message.position

		-- Rotate to face target position
		local forward = vmath.normalize(direction)
		local rotation_z = math.atan2(forward.y, forward.x)
		local target_rotation = vmath.quat_rotation_z(rotation_z)
		go.animate(".", "rotation", go.PLAYBACK_ONCE_FORWARD, target_rotation, go.EASING_OUTCUBIC, self.turn_speed, 0)
		go.cancel_animations(".", "position")

		-- Move to target position
		local distance = vmath.length(direction)
		local duration = distance / self.move_speed
		go.animate(".", "position", go.PLAYBACK_ONCE_FORWARD, message.position, go.EASING_LINEAR, duration, self.turn_speed, function()
			msg.post(".", "stop")
		end)
	end
end

Have a good evening folks!

2 Likes

And yes, it’s gonna be the next big thing in terms of RTS

2 Likes

Hmm, any ideas @sicher?

Rotating Z

Rotating with Quarternion

1 Like

Easiest is probably to use the often forgotten property euler.

EDIT: The scale issue you see comes from the quaternion not being normalised when you only set the z. The squish-effect you see when animating the entire quaternion in some instances comes from something similar, a non-normalised lerp performed by the go.animate-system.

2 Likes

After contemplating your answer @Ragnar_Svensson

I’ve decided that currently it’s not possible to do this using only go.animate, since euler animations are limited to the 360 degrees available and Slerp isn’t an option. So for now I’ll resolve to this system.

go.property("move_speed", 10)
go.property("turn_speed", 0.1)

function update(self, dt)
	if self.target_rotation then
		local rot = vmath.slerp(dt*self.turn_speed, go.get_rotation(), self.target_rotation)
		go.set_rotation(rot)
	end
end

function on_message(self, message_id, message, sender)
	if message_id == hash("move_to") then
		local direction = go.get_world_position() - message.position
		if vmath.length_sqr(direction) == 0 then
			-- avoid repeated navigation and NAN values from 0 distance.
			return
		end

		-- Rotate to face target position
		local forward = vmath.normalize(direction)
		local rotation_z = math.atan2(forward.y, forward.x)
		self.target_rotation = vmath.quat_rotation_z(rotation_z)
		
		-- Move to target position
		local distance = vmath.length(direction)
		local duration = distance / self.move_speed
		local turn_delay = 1 / self.turn_speed
		go.cancel_animations(".", "position")
		go.animate(".", "position", go.PLAYBACK_ONCE_FORWARD, message.position, go.EASING_LINEAR, duration, turn_delay, function()
			msg.post(".", "stop")
		end)
	end
end

If the underlying system could support Slerp for the “rotation” property that would be amazing :slight_smile:

4 Likes

In what way do you mean the euler rotations are limited to 360?

1 Like

What you are doing is effectively to slerp the rotation property and that works fine. Alternatively you can animate “euler.z”…

2 Likes

Animating eular.z it would make a reverse spin when trying to go from 359 to 1 degrees instead of changing the two degrees only, so animating that isn’t an option unless there’s Slerp, if using 360+ degrees I could subtract the current rotation before turning to the other as an option.

3 Likes

Yes, that’s true.

1 Like

I’ve played around a bit more on euler.z and it seems to be a tad bit broken… when running this piece of code you will notice that it get stuck at either -80 or +80 degrees.

function update(self, dt)
	local rot = go.get(".", "euler.z")
	rot = rot - dt*10
	print(rot)
	go.set(".", "euler.z", rot)
end

EDIT: I tried to animate with negative and 360+ degrees but I notice that working in the field outside of the -80 and 80 degrees it starts flipping back and forth.

1 Like

Euler rotation is broken as you can’t use it twice when going over 90 degrees…
I’ve have been using nested dummy game object that nevers rotate more than 90 degree in any direction, and yes, you can barf now.

See this post: Euler property on game object

I’ll try to gather some of the engine team members tomorrow (Monday) and discuss a solution and see if we can prioritise any issues related to rotation.

Yes, there is a bug here. DEF-1636

1 Like

Any update or ETA on this bug?
I’m writing something that makes heavy use of rotation animation.

The current sprint is short (Easter coming up) so it’s probably not gonna make it until the next sprint.

1 Like

Solved in Defold 1.2.123

2 Likes