godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
87.27k stars 19.61k forks source link

Aligning objects to specific normals sets object scale to zero #85903

Open levidavidmurray opened 7 months ago

levidavidmurray commented 7 months ago

Tested versions

Reproducible in 4.2.stable and 4.1.1.stable

System information

Godot v4.2.stable - Windows 10.0.22621 - Vulkan (Forward+) - dedicated NVIDIA GeForce GTX 1070 (NVIDIA; 31.0.15.3713) - AMD Ryzen 7 2700X Eight-Core Processor (16 Threads)

Issue description

Aligning object's Y-axis with collision normal results in scale (0, 0, 0) for specific normals

https://github.com/godotengine/godot/assets/46084870/74521573-962c-4ddd-a9fb-fbedcc6a2b40

Grey mesh (GM) contains the problematic "back wall" face. Red mesh (RM) works. Blue mesh (BM) works.

Expected Changing the object's alignment from GM "floor" face normal to GM "back wall" face normal should rotate the object as expected and maintain scale of 1.

Actual Changing the object's alignment from GM "floor" face normal to GM "back wall" face normal results in scale of Vector3.ZERO.

Worth Mentioning

IMPORTANT: While typing that last point, I came to the realization that the y value float in (0, -0, 1) is truncated. Printing the y-value for the "back wall" normals that align properly results in -0.0000000437114 for BM and -0.00000005960464 for RM. GM is simply 0. All meshes are perfectly aligned in both Blender and Godot, so it seems this discrepancy is occurring elsewhere.

I initially thought the alignment code I'm relying on fails when attempting to align to a perfectly aligned normal, but some of the other normals that alignment works with are perfectly aligned normals 🤔

Steps to reproduce

The grey mesh (GM) contains the seemingly problematic normals. It was created in blender by scaling a cube, deleting the front face, recalculating normals inside, applying all transforms, and exporting as gltf/glb (collider autogenerated with -col suffix). Generating collider separately within Godot makes no difference. Solidifying makes no difference.

image

The red mesh (RM), while seemingly similar, doesn't contain any problematic normals. It was created in blender by rotating a plane 90 degrees on x-axis, extruding front face, deleting front face, recalculating normals inside, applying all transforms, and exporting as gltf/glb (collider auto-generated with -col suffix).

image

The blue mesh (BM) is a primitive PlaneMesh made within Godot, collider generated via Mesh > Create Trimesh Static Body.

I'm attempting to align an object to a collision normal with the following function:

func align_with_normal(xform: Transform3D, normal: Vector3) -> Transform3D:
    xform.basis.y = normal
    xform.basis.x = -xform.basis.z.cross(normal)
    xform.basis = xform.basis.orthonormalized()
    return xform

Example usage:

# debug_mesh.top_level = true
# debug_mesh.position = result.position
# var xform = debug_mesh.transform
# debug_mesh.transform = align_with_normal(xform, result.normal)
# ^^^  doesn't make a difference either way  vvv 
debug_mesh.global_position = result.position
var xform = debug_mesh.global_transform
debug_mesh.global_transform = align_with_normal(xform, result.normal)

Minimal reproduction project (MRP)

normal_alignment_bug.zip

levidavidmurray commented 7 months ago

I made some changes to align_with_normal. Seems to align as expected now.

func align_with_normal(xform: Transform3D, n2: Vector3) -> Transform3D:
    var n1 = xform.basis.y.normalized()
    var cosa = n1.dot(n2)
    if cosa >= 0.99:
        return xform
    var alpha = acos(cosa)
    var axis = n1.cross(n2).normalized()
    if axis == Vector3.ZERO:
        axis = Vector3.FORWARD # normals are in opposite directions
    return xform.rotated(axis, alpha)

To any future googlers: make sure to set the object's position again after calling align_with_normal to prevent the object being out of place for a single frame.

Calinou commented 7 months ago

There's always some level of approximation expected in physics engines for performance reasons, so some values being returned as near-zero when they should technically be zero sounds expected to me.

KoB-Kirito commented 3 months ago

I searched for a solution to this for hours now. This worked in the end, but the position is mostly not correct afterwards. Just move the object after alignment and you're dandy.