Sprite scales up with math.quat_from_to and go.set_rotation

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

'vmath.quat_from_to' docs

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

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.

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

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.

2018-01-13 13_03_20-StarOde

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.

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)
	-- Add finalization code here
	-- 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

function on_reload(self)
	-- Add reload-handling code here
	-- 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

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

1 Like

That seems to work without issue!

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

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

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

1 Like

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.

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

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

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

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

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

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 :slight_smile:
I’ll add that to this sprint

6 Likes

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

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