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.
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?
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
Thank you so much!
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)