# Sprite scales up with math.quat_from_to and go.set_rotation

#1

Any idea of the cause? The vectors are normalized.

The sprite seems to disappear (or rather scale up super massively enough to “disappear”?) when one vector is 1,0,0 and the other is -1,0,0

0 Likes

#2

`'vmath.quat_from_to'` docs

The result is undefined if the two vectors point in opposite directions

0 Likes

#3

What’s a good solution?

edit: I’ll calculate the euler angle instead and set that unless there’s a convenient solution. An issue is I don’t think it has to be perfectly opposite directions, it can just be close to opposite and it begins to bug out.

0 Likes

#4

I have these two functions in my utility module for rotating objects based on a vector.

``````-- Use one or the other depending on which axis is "forward"

local YVECT = vmath.vector3(0, 1, 0)
local XVECT = vmath.vector3(1, 0, 0)
local QUAT180 = vmath.quat_rotation_z(PI)

-- Quat needed to rotate the local Y axis to the supplied -unit- vector
function M.vec_to_quat_y(vec)
return vec.y == -1 and QUAT180 or vmath.quat_from_to(YVECT, vec)
end

-- Quat needed to rotate the local X axis to the supplied -unit- vector
function M.vec_to_quat_x(vec)
return vec.x == -1 and QUAT180 or vmath.quat_from_to(XVECT, vec)
end
``````

I just tested, and this works fine, no weird scaling:

``````	local v = vmath.normalize(vmath.vector3(-1, -0.0001, 0))
local r = util.vec_to_quat_x(v)
go.set_rotation(r)
``````
1 Like

#5

That makes sense, but it still happens for me, it only solves making it not disappear when it is -1, but as the vector nears the opposite the sprite still scales up. It’s a very narrow nearness to -1.

0 Likes

#6

Weird, I can’t reproduce it here. There are no other quaternions involved? If the threshold is so tiny, you could try broadening the condition for 180 degrees, maybe.

If you come up with an euler angle solution I would like to see it. Last I checked they were still very broken.

0 Likes

#7

I tried this again with a completely different project and I still get sprites scaling up as their direction nears the opposite.

Here’s the script for the behavior of this

``````local rendercam = require("rendercam.rendercam")

local YVECT = vmath.vector3(0, 1, 0)
local XVECT = vmath.vector3(1, 0, 0)
local QUAT180 = vmath.quat_rotation_z(math.pi)

local function vec_to_quat_x(vec)
return vec.x == -1 and QUAT180 or vmath.quat_from_to(XVECT, vec)
end

function init(self)
msg.post(".", "acquire_input_focus")
self.target = vmath.vector3()
self.direction = vmath.vector3(1,0,0)
self.position = go.get_position()
self.movement = vmath.vector3(1,1,0)
self.speed = 10
self.moving = false
self.basis = vmath.vector3(1,0,0)
end

function final(self)
-- Remove this function if not needed
end

function update(self, dt)
self.basis = vmath.vector3(1,0,0)
go.set_rotation(vec_to_quat_x(self.direction))

if self.moving == true then
self.movement = vmath.normalize(self.movement)
self.position = self.position + self.movement * self.speed
go.set_position(self.position)
end
pprint(self.position)
pprint(self.movement)
self.moving = false
end

function on_message(self, message_id, message, sender)

end

function on_input(self, action_id, action)
self.target = rendercam.screen_to_world_2d(action.screen_x, action.screen_y)
self.direction = vmath.normalize(self.target - self.position)

if action_id == hash("key_w") then
self.movement = self.movement + vmath.vector3(0,1,0)
self.moving = true
end
if action_id == hash("key_a") then
self.movement = self.movement + vmath.vector3(-1,0,0)
self.moving = true
end
if action_id == hash("key_s") then
self.movement = self.movement + vmath.vector3(0,-1,0)
self.moving = true
end
if action_id == hash("key_d") then
self.movement = self.movement + vmath.vector3(1,0,0)
self.moving = true
end

end

-- Remove this function if not needed
end
``````

I didn’t see you asking about euler solution before but this is what I did which seems to not break.

``````	self.gun_direction = math.atan2(rendercam_helper.look_direction.y, rendercam_helper.look_direction.x)*180/math.pi
go.set(self.gun_id, "euler.z",  self.gun_direction)

``````
2 Likes

#8

What if you rotate around z using `vmath.quat_rotation_z(math.atan2(pos.y - lookat.y, pos.x - lookat.x))`?

1 Like

#9

That seems to work without issue!

``````go.set_rotation(vmath.quat_rotation_z(math.atan2(self.direction.y, self.direction.x)))
``````
0 Likes

#10

Darn, weird. I used to use the `quat_rotation_z(atan2())` method, but then I found `quat_from_to` and figured that was less roundabout and would be faster (it is, a little bit). But it seems like there’s some precision issue. They give slightly different results when the vector is very close to 180°, with the `from_to` result going a bit above 1.

``````DEBUG:SCRIPT: Vec=vmath.vector3(-0.99999743700027, 0.0022675679065287, 0)
DEBUG:SCRIPT: 	from_to: 	vmath.quat(0, -0, 1.0015462636948, 0.0011320335324854)
DEBUG:SCRIPT: 	quatz_atan:	vmath.quat(0, 0, 0.99999934434891, 0.0011337555479258)
``````

It’s only an issue around that negative-x vector though. At any other angle the results of both are the same, down to the last decimal place.

3 Likes

#11

Ok, interesting. @Mathias_Westerdahl what’s your thought on vmath.quat_from_to() vs. vmath.quat_rotation_x()?

1 Like

#12

Personally, I’d look for the anomalous values first, to fully understand which part actually misbehaves. There are multiple ways the calculation can break. E.g. is it the `atan2` that produces weird values? or is it the `quat_rotation_z`? We know that atan2 flips from PI to -PI around the vector(-1,0), maybe that could be the source of the issue? Or, maybe, for some reason, the length of the quaternion is != 1.

Regarding `quat_from_to`, I’d use the current direction (simply save the direction from one frame to the next), to make minimal rotations. Ofc, you can still get opposite directions if you move the mouse too quickly, but you can detect that case.

0 Likes

#13

I believe this solution produces correct values all the time. It is the vmath.quat_from_to() that doesn’t behave as expected in certain circumstances.

1 Like

#14

Well, I think the “quat_from_to” behaves as expected (it says so in the documentation?).
The reason for this is that parts of the calculations doesn’t support certain cases. In this instance, it’s `sqrtf()`that doesn’t support values <= 0. So, if your unit vectors point in exactly opposite directions, they’ll trigger this case: source

Again, it’s good to detect such cases and choose another solution accordingly.

3 Likes

#15

The issue is that it doesn’t have to be perfectly opposite to have anomalies only very close to it.

In this post the solution that should be used is applied but it still behaves badly Sprite scales up with math.quat_from_to and go.set_rotation

0 Likes

#16

Ok. When it behaves badly, what are the vectors values? what are their lengths? and finally, what is the dot product between them?

1 Like

#17

In the linked example, it’s around -0.99999… instead of -1, which causes the visual glitches.

Here are sample values where you can see slight scale change which shouldn’t be there

DEBUG:SCRIPT: vmath.vector3(-0.99999624490738, 0.0027173811104149, 0)
DEBUG:SCRIPT: vmath.vector3(-0.99999833106995, 0.0018142003100365, 0)
DEBUG:SCRIPT: vmath.vector3(-0.99999839067459, -0.0017514014616609, 0)

It’s being compared with vmath.vector3(1, 0, 0)
They are normalized with vmath.normalize so they are supposed to be length of 1

self.direction = vmath.normalize(self.target - self.position)

pprint(vmath.dot(vmath.vector3(1,0,0), vmath.vector3(-0.99999624490738, 0.0027173811104149, 0)))

=

DEBUG:SCRIPT: -0.99999624490738

0 Likes

#18

I think you need to normalize the resulting quaternion as well, since what you get isn’t of length 1 (precision errors propagate)

``````local xaxis = vmath.vector3(1,0,0)
local v3 = vmath.normalize( vmath.vector3(-0.99999839067459, -0.0017514014616609, 0) )
-- vectors are very close to opposite (dot product is -0.99999850988388, which I really think should be considered "opposite"))
local rot = vmath.quat_from_to(v3, xaxis)

-- normalize
local rot4 = vmath.vector4(rot.x, rot.y, rot.z, rot.w)
print("rot4 ", rot, vmath.length(rot4))
rot4 = vmath.normalize(rot4)
rot = vmath.quat(rot4.x, rot4.y, rot4.z, rot4.w)

go.set_rotation(rot)
``````

Of course, it would be a good thing if we exposed `length` and `normalize` for quaternions
I’ll add that to this sprint

6 Likes

#19

Is this subject had any result? i hit the same issue in creating defgraph module

0 Likes

#20

never mind i ended up giving up from `vmath.quat_from_to` and use this solution.

0 Likes