every frame, i am using intersect_ray to find where the mouse hits to place something
the issue is that using the default Godot Physics engine gives inaccurate surface normals
i have tried Jolt Physics and it does report more accurate normals but i can’t use since it causes various glitches in my game.
they are mostly accurate but always slightly off, which is unacceptable for my building system
var c_part_move = null
var c_current_part_hovered = null
var c_silhoutte: MeshInstance3D = (func ():
var instance = MeshInstance3D.new()
var mesh = BoxMesh.new()
var material = StandardMaterial3D.new()
material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
material.albedo_color = Color.from_rgba8(74, 255, 74, 67)
mesh.material = material
instance.mesh = mesh
instance.top_level = true
return instance
).call()
var c_anchor = false
var c_weld_target = null
var c_rotation = Vector3(0, 0, 0)
var c_last_normal = Vector3.ZERO
var move_tool_ray_query_params: PhysicsRayQueryParameters3D = (func ():
var ray_query = PhysicsRayQueryParameters3D.new()
ray_query.collide_with_areas = false
ray_query.collision_mask = 0b101
return ray_query
).call()
func align_with_y(in_xform, new_y):
var xform = Transform3D(in_xform)
xform.basis.y = new_y
xform.basis.x = -xform.basis.z.cross(new_y)
xform.basis = xform.basis.orthonormalized()
return xform
# ... inside a function
var camera: Camera3D = player.get_node("CameraPivot/Mount").get_child(0)
var mouse = viewport.get_mouse_position()
var ray_length = 100
var from = camera.project_ray_origin(mouse)
var to = from + camera.project_ray_normal(mouse) * ray_length
move_tool_ray_query_params.from = from
move_tool_ray_query_params.to = to
move_tool_ray_query_params.exclude = [c_part_move.get_rid()]
var result = space.intersect_ray(move_tool_ray_query_params)
# hit nothing, don't update position
if result.size() == 0: return
var normal: Vector3 = result.normal
c_silhoutte.position = result.position
var rotate_pivot = c_silhoutte.position
var rotate_angle = deg_to_rad(90)
c_silhoutte.transform = \
align_with_y(c_silhoutte.transform, normal)
c_silhoutte.transform = \
c_silhoutte.transform.translated_local(c_silhoutte.mesh.size / 2)
if Input.is_action_just_released("tool_build_rotate_x"):
c_rotation += Vector3(1, 0, 0)
if Input.is_action_just_released("tool_build_rotate_y"):
c_rotation += Vector3(0, 1, 0)
if Input.is_action_just_released("tool_build_rotate_z"):
c_rotation += Vector3(0, 0, 1)
c_silhoutte.rotation = normal
for x in range(c_rotation.x):
rotate_around_pivot(c_silhoutte, rotate_pivot, Vector3(1, 0, 0), rotate_angle)
for y in range(c_rotation.y):
rotate_around_pivot(c_silhoutte, rotate_pivot, Vector3(0, 1, 0), rotate_angle)
for z in range(c_rotation.z):
rotate_around_pivot(c_silhoutte, rotate_pivot, Vector3(0, 0, 1), rotate_angle)
var is_on_terrain = false
for chunk in root.get_node("Game/Terrain").get_children():
if chunk.get_instance_id() == result.collider_id:
is_on_terrain = true
if not is_on_terrain:
var collider = result.collider
if collider.get_parent() != root.get_node("Game/Parts"):
c_weld_target = null
return
c_weld_target = collider
else: c_weld_target = null
#c_silhoutte.rotation += c_rotation
#if c_rotation != Vector3.ZERO: c_silhoutte.rotate_object_local(c_rotation.normalized(), deg_to_rad(900))
c_last_normal = normal
c_anchor = is_on_terrain
sorry for the messy code, i havent refactored it yet
is there a way to increase surface normal accuracy?
sorry if something is unclear, this is my first post on the forum
to be clear, c_part_move is the wood block there which will be moved and c_silhoutte is the green transparent box. this code runs to update c_silhoutte’s position so that when the mouse is lifted, c_part_move is placed at c_silhoutte position & rotation
There is a lot of non-trivial code and logic here, but at a first glance you should generally avoid manipulating basis properties directly in this way:
You can use look_at(...) for nodes and looking_at(...) for transforms and bases. So, Basis.looking_at(new_y.cross(old_basis.x), new_y) or something similar. I don’t know how much ‘accuracy’ that’ll add, but floating point errors are unavoidable. Also can I ask in what way exactly the results are off? Maybe you can fix that simply by some minute snapping.
thank you, ill try that
the normals are off by ~10 degrees in a random direction (depending on the camera direction i believe) using godot physics and much smaller in jolt physics
i can’t use jolt physics but i’ll do as you said and if it doesnt work ill try snapping
ive modified align_with_y like you said but the normals are still inaccurate
here you can see the little red orb with a grey bit sticking out
its an indicator i’ve made to show the raycast results’ position and normal
its supposed to be exactly aligned with the other thing you’re dragging on top of but here it clearly is not
and the results are even weirder on the side of a box
(i cant upload more than one image but they are misaligned)
sometimes it looks like the normals are parallel to surface instead of perpendicular for some reason when dragging onto the side of a box
they aren’t exactly aligned to terrain either
sorry for the late reply, took a while to take 5 images then realize i can only put 1
Did some testing and googling, and realized that this is actually a bad case for the looking_at function.
Try your luck with quaternions (my solution, not thoroughly tested):
# this modifies only the basis, so you simply set the position separately
# or rewrite the function however you prefer :)
func align_with_normal_quat(input_basis : Basis, normal_vector : Vector3) -> Basis:
var rot_basis := Basis(Quaternion(input_basis.y, normal_vector))
return rot_basis * input_basis
And if that fails look at this issue with a more tested solution.
i tried the function but the issue persisted. the problem is in the raycasting i do itself
though this function is very helpful as my original function that i was using was very troublesome and caused me problems in several of my projects
i’ll look at the other solution
thank you for all your effort by the way
yeah, unfortunately, it just seems that Godot’s intersect_ray isn’t accurate enough for my use case. i’ll have to look into another way of calculating normals to align things
Hm, I still think the problem lies on a higher level, surface normals shouldn’t give such errors (10 degrees is way beyond arithmetic errors). Try isolating parts of the code - so, e.g. using the same meshes test only the raycasting code, after that try different mesh.
the red indicator is what i created to debug the raycast specifically
here is some code i excluded from the function for brevity:
root.get_node("Game/PivotIndicator").position = result.position
root.get_node("Game/PivotIndicator").rotation = normal
the box and the debug indicator are always perfectly aligned, but the indicator itself is misaligned which means its an issue with the raycast itself.
i agree that surface normals shouldnt be that inaccurate, but switching to jolt physics does make it more accurate, which indicates that it is an issue with the raycasting itself
there are three things i could do now:
switch to jolt physics and fix all issues that occur in my game with it (the issue wont completely disappear though)
manually calculate the normals since i only care about collisions with parts (very simple boxes) or terrain
tweak the raycast parameters or try a Raycast3D instead of intersect_ray
You also forget the collision shape itself, a lot of problems can stem from issues (sometimes obscure) with the way they were created or generated. Beyond that I’m not versed enough with godot physics to offer any more advice, unfortunately.
the wood boxes are a simple CollisionShape3D in the scene with a normal BoxMesh, but the terrain is created by SurfaceTool and does call generate_normals() at the end
i’ll look into it, thanks again for all your help
i’ve been completely unable to make any progress on this issue at all
would love if anybody has any ideas to try to help
this bug is major enough that i have to completely pause all work on my game until it is fixed