Get side of collision object (SOLVED)

I need a function that takes the message table from a contact_point_response and returns which side of the other collision box it’s touching (top, bottom, left, or right). I’ve been trying to come up with something for a while, but I guess I don’t know enough about vector math to solve this.

Is the collision box rotation fixed? Then it should just be the normal? If the rotation is not fixed then you should be able to adjust the normal based on the rotation to see which side of the box was touched.

BoxSide.zip (3.6 KB)

Here’s my naive attempt. But it also appears to give the wrong results occasionally when looking at the left and right… there is a good chance someone smarter will be able to instantly know what’s wrong and how to fix it. :slight_smile:

1 Like

Looks like we both came up with the same thing. This is the code I made a while back:

-- Only checking for top side in this case
if vmath.dot(vmath.rotate(go.get_rotation(message.other_id), vmath.vector3(0, 1, 0)), message.normal) == -1 then

I’d guess the occasional wrong data is due to rounding / precision error? I don’t know how to avoid it in this case…

vmath.dot() doesn’t accept quats but it should?

Maybe dealing with quats directly would help?

What do you mean by this?

I tested converting the normal to a quat to see if it would work better with the rotation quat in terms of accuracy / precision but I don’t know enough about quaternions still to know how to do it properly. Give it a shot!

@Mathias_Westerdahl Any idea what to do about this situation? In the sample project I posted here Get side of collision object the left/right sides sometimes report as each other incorrectly I’m guessing due to how numbers on computers sometimes work and the loss of precision as they are changed but I’m not sure. Is the code wrong or some better way to do this?

2 Likes

Got it to work now! I still don’t understand why this is necessary but it seems to work all of the time now.

What I did was convert the quat rotation to a vector, then swapped the x,y and flipped the new y (to rotate clockwise by 90), then if it’s not top/bottom I did dot with the modified rotation.

BoxSide.zip (4.0 KB)

local function quat_dot(q1, q2)
	return q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w
end

local function round_decimal(number, decimal)
	decimal = 10 ^ (decimal or 0)
	return math.floor(number*decimal+0.5)/decimal
end

local function round(x)
	local a = x % 1
	x = x - a
	if a < 0.5 then a = 0
	else a = 1 end
	return x + a
end

function init(self)
	msg.post(".", "acquire_input_focus")
	self.released = true
	self.start_position = vmath.vector3(0,0,0)
	self.end_position = vmath.vector3(0,0,0)
	self.x = 0
	self.y = 0
	self.groups = {hash("default")}
	self.options = {all = false}
end

function final(self)
	-- Add finalization code here
	-- Learn more: https://defold.com/manuals/script/
	-- Remove this function if not needed
end

function update(self, dt)
	local length = vmath.length(self.end_position - self.start_position)
	if length ~= 0 and not self.released then
		local result = physics.raycast(self.start_position, self.end_position, self.groups, self.options)
		
		msg.post("@render:", "draw_line", { start_point = self.start_position, end_point = self.end_position, color = vmath.vector4(1, 1, 1, 0.5) } )
		if result and result[1] then
			if result[1].normal then
				local rotation = vmath.rotate(go.get_rotation(result[1].id), vmath.vector3(0,1,0))
				local rotation_quat = go.get_rotation(result[1].id)
				local normal_quat = vmath.quat_from_to(vmath.vector3(0,1,0), result[1].normal)

				msg.post("@render:", "draw_line", { start_point = vmath.vector3(100,100,0), end_point = vmath.vector3(100,100,0) + result[1].normal * 100, color = vmath.vector4(1, 1, 1, 0.5) } )
				msg.post("@render:", "draw_line", { start_point = vmath.vector3(100,100,0), end_point = vmath.vector3(100,100,0) + rotation * 100, color = vmath.vector4(0, 1, 1, 0.5) } )


				local dot = vmath.dot(rotation, result[1].normal)
				local rounded_dot = round(vmath.dot(rotation, result[1].normal))
				local dot_quat = quat_dot(rotation_quat, normal_quat)

				print(dot, rounded_dot)
				
				local text = ""
				if rounded_dot == 1 or rounded_dot == -1 then
					if rounded_dot == 1 then
						print("top")
						text = "top"
					else
						print("bottom")
						text = "bottom"
					end
				else
					rotation.x, rotation.y = rotation.y, rotation.x * -1
					local rounded_dot_lr = round(vmath.dot(rotation, result[1].normal))
					if rounded_dot_lr == 1 then
						print("right")
						text = "right"
					else
						print("left")
						text = "left"
						
					end
				end

				label.set_text("/label#label", text )
			end
		end
	end
end

function on_message(self, message_id, message, sender)
	pprint(message)
	if message_id == hash("ray_cast_response") then
		pprint(message)
	end
	if message_id == hash("ray_cast_missed") then
		pprint(message)
	end
end

function on_input(self, action_id, action)
	if action.released then
		self.released = true
	end
	if action.pressed and action.x then
		self.released = false
		self.start_position = vmath.vector3(action.x, action.y, 0)
	end
	if action.x then
		self.x = action.x
		self.y = action.y
		self.end_position = vmath.vector3(action.x, action.y, 0)
	end
end

function on_reload(self)
	-- Add reload-handling code here
	-- Learn more: https://defold.com/manuals/hot-reload/
	-- Remove this function if not needed
end

2 Likes

Thank you so much!

1 Like

I’ve never used the dot product of the quaternions before, so I cannot give much advice here, other than that quaternions do have special attributes that may or may not work the same as in 2d/3d.

For instance, note that q and -q represent the same rotation.
Having extra minus signs in the actual dot calculation can give quite different results, so I’d start looking there. (e.g from SO)

2 Likes