From dd80a3aa199b8f5d62bff7f9f889559ded68642f Mon Sep 17 00:00:00 2001 From: Adam Simpkins Date: Fri, 20 Jun 2025 15:02:26 -0700 Subject: [PATCH] Fix jolt_physics soft body vertex normal calculation The code previously iterated through each face and set all vertices to that face's normal. This resulted in each vertex getting the normal from just one face that it belonged to (whichever face was last in this array). This caused weird shading artifacts. This fixes the code so that the vertex normal is now the average normal of all faces that it belongs to. This results in "smooth shading" behavior for soft body meshes. This is still somewhat undesirable if the input mesh was using flat shading, but it looks less bad than the previous behavior of picking a normal at random from one attached face. This matches the behavior of GodotPhysicsServer3D. Fixes #107831. --- .../objects/jolt_soft_body_3d.cpp | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/modules/jolt_physics/objects/jolt_soft_body_3d.cpp b/modules/jolt_physics/objects/jolt_soft_body_3d.cpp index 7cc0b69e64c..d791922ce23 100644 --- a/modules/jolt_physics/objects/jolt_soft_body_3d.cpp +++ b/modules/jolt_physics/objects/jolt_soft_body_3d.cpp @@ -628,8 +628,13 @@ void JoltSoftBody3D::update_rendering_server(PhysicsServer3DRenderingServerHandl const int physics_vertex_count = (int)physics_vertices.size(); + normals.clear(); normals.resize(physics_vertex_count); + // Compute vertex normals using smooth-shading: + // Each vertex should use the average normal of all faces it is a part of. + // Iterate over each face, and add the face normal to each of the face vertices. + // By the end of the loop, each vertex normal will be the sum of all face normals it belongs to. for (const SoftBodyFace &physics_face : physics_faces) { // Jolt uses a different winding order, so we swap the indices to account for that. @@ -643,9 +648,18 @@ void JoltSoftBody3D::update_rendering_server(PhysicsServer3DRenderingServerHandl const Vector3 normal = (v2 - v0).cross(v1 - v0).normalized(); - normals[i0] = normal; - normals[i1] = normal; - normals[i2] = normal; + normals[i0] += normal; + normals[i1] += normal; + normals[i2] += normal; + } + // Normalize the vertex normals to have length 1.0 + for (Vector3 &n : normals) { + real_t len = n.length(); + // Some normals may have length 0 if the face was degenerate, + // so don't divide by zero. + if (len > CMP_EPSILON) { + n /= len; + } } const int mesh_vertex_count = shared->mesh_to_physics.size();