Jolt Physics: Remove sharing shared soft body settings from SoftBody3D

Since the soft body meshes are always copied before simulation, the shared map never actually shared data with anything. This also makes it possible to create the mesh in local space the 2nd time it gets added to a space.

This fixes the following 2 problems:
* Inertia calculation becomes inaccurate when the vertices are far away from the origin. This triggered a harmless assert on 2nd insert.
* The pressure calculations become inaccurate when the vertices are far away from the origin. Since we update the soft body position to the center of mass of all vertices at the end of every physics update, this problem lasted only for a single frame after reinserting and could cause a little jolt.
This commit is contained in:
Jorrit Rouwe
2025-11-10 22:00:14 +01:00
parent 2cc031f3a3
commit 6141e1dcc8
2 changed files with 76 additions and 128 deletions

View File

@ -74,15 +74,11 @@ JPH::ObjectLayer JoltSoftBody3D::_get_object_layer() const {
void JoltSoftBody3D::_space_changing() { void JoltSoftBody3D::_space_changing() {
JoltObject3D::_space_changing(); JoltObject3D::_space_changing();
// Note that we should not use `in_space()` as the condition here, since we could have cleared the mesh at this point. if (in_space()) {
if (jolt_body != nullptr) {
jolt_settings = new JPH::SoftBodyCreationSettings(jolt_body->GetSoftBodyCreationSettings()); jolt_settings = new JPH::SoftBodyCreationSettings(jolt_body->GetSoftBodyCreationSettings());
jolt_settings->mPosition = JPH::RVec3::sZero(); // We'll be getting the vertices in world space when we re-insert the body so we need to reset the position here.
jolt_settings->mSettings = nullptr; jolt_settings->mSettings = nullptr;
jolt_settings->mVertexRadius = JoltProjectSettings::soft_body_point_radius; jolt_settings->mVertexRadius = JoltProjectSettings::soft_body_point_radius;
} }
_deref_shared_data();
} }
void JoltSoftBody3D::_space_changed() { void JoltSoftBody3D::_space_changed() {
@ -100,14 +96,14 @@ void JoltSoftBody3D::_add_to_space() {
return; return;
} }
const bool has_valid_shared = _ref_shared_data(); JPH::SoftBodySharedSettings *shared_settings = _create_shared_settings();
ERR_FAIL_COND(!has_valid_shared); ERR_FAIL_NULL(shared_settings);
JPH::CollisionGroup::GroupID group_id = 0; JPH::CollisionGroup::GroupID group_id = 0;
JPH::CollisionGroup::SubGroupID sub_group_id = 0; JPH::CollisionGroup::SubGroupID sub_group_id = 0;
JoltGroupFilter::encode_object(this, group_id, sub_group_id); JoltGroupFilter::encode_object(this, group_id, sub_group_id);
jolt_settings->mSettings = shared->settings; jolt_settings->mSettings = shared_settings;
jolt_settings->mUserData = reinterpret_cast<JPH::uint64>(this); jolt_settings->mUserData = reinterpret_cast<JPH::uint64>(this);
jolt_settings->mObjectLayer = _get_object_layer(); jolt_settings->mObjectLayer = _get_object_layer();
jolt_settings->mCollisionGroup = JPH::CollisionGroup(nullptr, group_id, sub_group_id); jolt_settings->mCollisionGroup = JPH::CollisionGroup(nullptr, group_id, sub_group_id);
@ -124,10 +120,7 @@ void JoltSoftBody3D::_add_to_space() {
jolt_settings = nullptr; jolt_settings = nullptr;
} }
bool JoltSoftBody3D::_ref_shared_data() { JPH::SoftBodySharedSettings *JoltSoftBody3D::_create_shared_settings() {
HashMap<RID, Shared>::Iterator iter_shared_data = mesh_to_shared.find(mesh);
if (iter_shared_data == mesh_to_shared.end()) {
RenderingServer *rendering = RenderingServer::get_singleton(); RenderingServer *rendering = RenderingServer::get_singleton();
// TODO: calling RenderingServer::mesh_surface_get_arrays() from the physics thread // TODO: calling RenderingServer::mesh_surface_get_arrays() from the physics thread
@ -135,21 +128,17 @@ bool JoltSoftBody3D::_ref_shared_data() {
// This method blocks on the main thread to return data, but the main thread may be // This method blocks on the main thread to return data, but the main thread may be
// blocked waiting on us in PhysicsServer3D::sync(). // blocked waiting on us in PhysicsServer3D::sync().
const Array mesh_data = rendering->mesh_surface_get_arrays(mesh, 0); const Array mesh_data = rendering->mesh_surface_get_arrays(mesh, 0);
ERR_FAIL_COND_V(mesh_data.is_empty(), false); ERR_FAIL_COND_V(mesh_data.is_empty(), nullptr);
const PackedInt32Array mesh_indices = mesh_data[RenderingServer::ARRAY_INDEX]; const PackedInt32Array mesh_indices = mesh_data[RenderingServer::ARRAY_INDEX];
ERR_FAIL_COND_V(mesh_indices.is_empty(), false); ERR_FAIL_COND_V(mesh_indices.is_empty(), nullptr);
const PackedVector3Array mesh_vertices = mesh_data[RenderingServer::ARRAY_VERTEX]; const PackedVector3Array mesh_vertices = mesh_data[RenderingServer::ARRAY_VERTEX];
ERR_FAIL_COND_V(mesh_vertices.is_empty(), false); ERR_FAIL_COND_V(mesh_vertices.is_empty(), nullptr);
iter_shared_data = mesh_to_shared.insert(mesh, Shared()); JPH::SoftBodySharedSettings *settings = new JPH::SoftBodySharedSettings();
JPH::Array<JPH::SoftBodySharedSettings::Vertex> &physics_vertices = settings->mVertices;
LocalVector<int> &mesh_to_physics = iter_shared_data->value.mesh_to_physics; JPH::Array<JPH::SoftBodySharedSettings::Face> &physics_faces = settings->mFaces;
JPH::SoftBodySharedSettings &settings = *iter_shared_data->value.settings;
JPH::Array<JPH::SoftBodySharedSettings::Vertex> &physics_vertices = settings.mVertices;
JPH::Array<JPH::SoftBodySharedSettings::Face> &physics_faces = settings.mFaces;
HashMap<Vector3, int> vertex_to_physics; HashMap<Vector3, int> vertex_to_physics;
@ -165,6 +154,8 @@ bool JoltSoftBody3D::_ref_shared_data() {
int physics_index_count = 0; int physics_index_count = 0;
const JPH::RVec3 body_position = jolt_settings->mPosition;
for (int i = 0; i < mesh_index_count; i += 3) { for (int i = 0; i < mesh_index_count; i += 3) {
int physics_face[3]; int physics_face[3];
@ -175,7 +166,7 @@ bool JoltSoftBody3D::_ref_shared_data() {
HashMap<Vector3, int>::Iterator iter_physics_index = vertex_to_physics.find(vertex); HashMap<Vector3, int>::Iterator iter_physics_index = vertex_to_physics.find(vertex);
if (iter_physics_index == vertex_to_physics.end()) { if (iter_physics_index == vertex_to_physics.end()) {
physics_vertices.emplace_back(JPH::Float3((float)vertex.x, (float)vertex.y, (float)vertex.z), JPH::Float3(0.0f, 0.0f, 0.0f), 1.0f); physics_vertices.emplace_back(JPH::Float3((float)(vertex.x - body_position.GetX()), (float)(vertex.y - body_position.GetY()), (float)(vertex.z - body_position.GetZ())), JPH::Float3(0.0f, 0.0f, 0.0f), 1.0f);
iter_physics_index = vertex_to_physics.insert(vertex, physics_index_count++); iter_physics_index = vertex_to_physics.insert(vertex, physics_index_count++);
} }
@ -204,36 +195,14 @@ bool JoltSoftBody3D::_ref_shared_data() {
JPH::SoftBodySharedSettings::VertexAttributes vertex_attrib; JPH::SoftBodySharedSettings::VertexAttributes vertex_attrib;
vertex_attrib.mCompliance = vertex_attrib.mShearCompliance = inverse_stiffness; vertex_attrib.mCompliance = vertex_attrib.mShearCompliance = inverse_stiffness;
settings.CreateConstraints(&vertex_attrib, 1, JPH::SoftBodySharedSettings::EBendType::None); settings->CreateConstraints(&vertex_attrib, 1, JPH::SoftBodySharedSettings::EBendType::None);
float multiplier = 1.0f - shrinking_factor; float multiplier = 1.0f - shrinking_factor;
for (JPH::SoftBodySharedSettings::Edge &e : settings.mEdgeConstraints) { for (JPH::SoftBodySharedSettings::Edge &e : settings->mEdgeConstraints) {
e.mRestLength *= multiplier; e.mRestLength *= multiplier;
} }
settings.Optimize(); settings->Optimize();
} else {
iter_shared_data->value.ref_count++;
}
shared = &iter_shared_data->value; return settings;
return true;
}
void JoltSoftBody3D::_deref_shared_data() {
if (unlikely(shared == nullptr)) {
return;
}
HashMap<RID, Shared>::Iterator iter = mesh_to_shared.find(mesh);
if (unlikely(iter == mesh_to_shared.end())) {
return;
}
if (--iter->value.ref_count == 0) {
mesh_to_shared.remove(iter);
}
shared = nullptr;
} }
void JoltSoftBody3D::_update_mass() { void JoltSoftBody3D::_update_mass() {
@ -250,7 +219,7 @@ void JoltSoftBody3D::_update_mass() {
vertex.mInvMass = inverse_vertex_mass; vertex.mInvMass = inverse_vertex_mass;
} }
pin_vertices(*this, pinned_vertices, shared->mesh_to_physics, physics_vertices); pin_vertices(*this, pinned_vertices, mesh_to_physics, physics_vertices);
} }
void JoltSoftBody3D::_update_pressure() { void JoltSoftBody3D::_update_pressure() {
@ -353,10 +322,6 @@ JoltSoftBody3D::~JoltSoftBody3D() {
} }
} }
bool JoltSoftBody3D::in_space() const {
return JoltObject3D::in_space() && shared != nullptr;
}
void JoltSoftBody3D::add_collision_exception(const RID &p_excepted_body) { void JoltSoftBody3D::add_collision_exception(const RID &p_excepted_body) {
exceptions.push_back(p_excepted_body); exceptions.push_back(p_excepted_body);
@ -394,7 +359,6 @@ void JoltSoftBody3D::set_mesh(const RID &p_mesh) {
return; return;
} }
_deref_shared_data();
mesh = p_mesh; mesh = p_mesh;
_mesh_changed(); _mesh_changed();
} }
@ -410,9 +374,8 @@ bool JoltSoftBody3D::is_sleeping() const {
void JoltSoftBody3D::apply_vertex_impulse(int p_index, const Vector3 &p_impulse) { void JoltSoftBody3D::apply_vertex_impulse(int p_index, const Vector3 &p_impulse) {
ERR_FAIL_COND_MSG(!in_space(), vformat("Failed to apply impulse to '%s'. Doing so without a physics space is not supported when using Jolt Physics. If this relates to a node, try adding the node to a scene tree first.", to_string())); ERR_FAIL_COND_MSG(!in_space(), vformat("Failed to apply impulse to '%s'. Doing so without a physics space is not supported when using Jolt Physics. If this relates to a node, try adding the node to a scene tree first.", to_string()));
ERR_FAIL_NULL(shared); ERR_FAIL_INDEX(p_index, (int)mesh_to_physics.size());
ERR_FAIL_INDEX(p_index, (int)shared->mesh_to_physics.size()); const int physics_index = mesh_to_physics[p_index];
const int physics_index = shared->mesh_to_physics[p_index];
ERR_FAIL_COND_MSG(physics_index < 0, vformat("Soft body vertex %d was not used by a face and has been omitted for '%s'. No impulse can be applied.", p_index, to_string())); ERR_FAIL_COND_MSG(physics_index < 0, vformat("Soft body vertex %d was not used by a face and has been omitted for '%s'. No impulse can be applied.", p_index, to_string()));
ERR_FAIL_COND_MSG(pinned_vertices.has(physics_index), vformat("Failed to apply impulse to point at index %d for '%s'. Point was found to be pinned.", static_cast<int>(physics_index), to_string())); ERR_FAIL_COND_MSG(pinned_vertices.has(physics_index), vformat("Failed to apply impulse to point at index %d for '%s'. Point was found to be pinned.", static_cast<int>(physics_index), to_string()));
@ -677,11 +640,11 @@ void JoltSoftBody3D::update_rendering_server(PhysicsServer3DRenderingServerHandl
} }
} }
const int mesh_vertex_count = shared->mesh_to_physics.size(); const int mesh_vertex_count = mesh_to_physics.size();
const JPH::RVec3 body_position = jolt_body->GetCenterOfMassPosition(); const JPH::RVec3 body_position = jolt_body->GetCenterOfMassPosition();
for (int i = 0; i < mesh_vertex_count; ++i) { for (int i = 0; i < mesh_vertex_count; ++i) {
const int physics_index = shared->mesh_to_physics[i]; const int physics_index = mesh_to_physics[i];
if (physics_index >= 0) { if (physics_index >= 0) {
const Vector3 vertex = to_godot(body_position + physics_vertices[(size_t)physics_index].mPosition); const Vector3 vertex = to_godot(body_position + physics_vertices[(size_t)physics_index].mPosition);
const Vector3 normal = normals[(uint32_t)physics_index]; const Vector3 normal = normals[(uint32_t)physics_index];
@ -697,9 +660,8 @@ void JoltSoftBody3D::update_rendering_server(PhysicsServer3DRenderingServerHandl
Vector3 JoltSoftBody3D::get_vertex_position(int p_index) { Vector3 JoltSoftBody3D::get_vertex_position(int p_index) {
ERR_FAIL_COND_V_MSG(!in_space(), Vector3(), vformat("Failed to retrieve point position for '%s'. Doing so without a physics space is not supported when using Jolt Physics. If this relates to a node, try adding the node to a scene tree first.", to_string())); ERR_FAIL_COND_V_MSG(!in_space(), Vector3(), vformat("Failed to retrieve point position for '%s'. Doing so without a physics space is not supported when using Jolt Physics. If this relates to a node, try adding the node to a scene tree first.", to_string()));
ERR_FAIL_NULL_V(shared, Vector3()); ERR_FAIL_INDEX_V(p_index, (int)mesh_to_physics.size(), Vector3());
ERR_FAIL_INDEX_V(p_index, (int)shared->mesh_to_physics.size(), Vector3()); const int physics_index = mesh_to_physics[p_index];
const int physics_index = shared->mesh_to_physics[p_index];
ERR_FAIL_COND_V_MSG(physics_index < 0, Vector3(), vformat("Soft body vertex %d was not used by a face and has been omitted for '%s'. Position cannot be returned.", p_index, to_string())); ERR_FAIL_COND_V_MSG(physics_index < 0, Vector3(), vformat("Soft body vertex %d was not used by a face and has been omitted for '%s'. Position cannot be returned.", p_index, to_string()));
const JPH::SoftBodyMotionProperties &motion_properties = static_cast<const JPH::SoftBodyMotionProperties &>(*jolt_body->GetMotionPropertiesUnchecked()); const JPH::SoftBodyMotionProperties &motion_properties = static_cast<const JPH::SoftBodyMotionProperties &>(*jolt_body->GetMotionPropertiesUnchecked());
@ -712,9 +674,8 @@ Vector3 JoltSoftBody3D::get_vertex_position(int p_index) {
void JoltSoftBody3D::set_vertex_position(int p_index, const Vector3 &p_position) { void JoltSoftBody3D::set_vertex_position(int p_index, const Vector3 &p_position) {
ERR_FAIL_COND_MSG(!in_space(), vformat("Failed to set point position for '%s'. Doing so without a physics space is not supported when using Jolt Physics. If this relates to a node, try adding the node to a scene tree first.", to_string())); ERR_FAIL_COND_MSG(!in_space(), vformat("Failed to set point position for '%s'. Doing so without a physics space is not supported when using Jolt Physics. If this relates to a node, try adding the node to a scene tree first.", to_string()));
ERR_FAIL_NULL(shared); ERR_FAIL_INDEX(p_index, (int)mesh_to_physics.size());
ERR_FAIL_INDEX(p_index, (int)shared->mesh_to_physics.size()); const int physics_index = mesh_to_physics[p_index];
const int physics_index = shared->mesh_to_physics[p_index];
ERR_FAIL_COND_MSG(physics_index < 0, vformat("Soft body vertex %d was not used by a face and has been omitted for '%s'. Position cannot be set.", p_index, to_string())); ERR_FAIL_COND_MSG(physics_index < 0, vformat("Soft body vertex %d was not used by a face and has been omitted for '%s'. Position cannot be set.", p_index, to_string()));
JPH::SoftBodyMotionProperties &motion_properties = static_cast<JPH::SoftBodyMotionProperties &>(*jolt_body->GetMotionPropertiesUnchecked()); JPH::SoftBodyMotionProperties &motion_properties = static_cast<JPH::SoftBodyMotionProperties &>(*jolt_body->GetMotionPropertiesUnchecked());
@ -748,9 +709,8 @@ void JoltSoftBody3D::unpin_all_vertices() {
bool JoltSoftBody3D::is_vertex_pinned(int p_index) const { bool JoltSoftBody3D::is_vertex_pinned(int p_index) const {
ERR_FAIL_COND_V_MSG(!in_space(), false, vformat("Failed retrieve pin status of point for '%s'. Doing so without a physics space is not supported when using Jolt Physics. If this relates to a node, try adding the node to a scene tree first.", to_string())); ERR_FAIL_COND_V_MSG(!in_space(), false, vformat("Failed retrieve pin status of point for '%s'. Doing so without a physics space is not supported when using Jolt Physics. If this relates to a node, try adding the node to a scene tree first.", to_string()));
ERR_FAIL_NULL_V(shared, false); ERR_FAIL_INDEX_V(p_index, (int)mesh_to_physics.size(), false);
ERR_FAIL_INDEX_V(p_index, (int)shared->mesh_to_physics.size(), false); const int physics_index = mesh_to_physics[p_index];
const int physics_index = shared->mesh_to_physics[p_index];
return pinned_vertices.has(physics_index); return pinned_vertices.has(physics_index);
} }

View File

@ -42,21 +42,12 @@
class JoltSpace3D; class JoltSpace3D;
class JoltSoftBody3D final : public JoltObject3D { class JoltSoftBody3D final : public JoltObject3D {
struct Shared {
LocalVector<int> mesh_to_physics;
JPH::Ref<JPH::SoftBodySharedSettings> settings = new JPH::SoftBodySharedSettings();
int ref_count = 1;
};
inline static HashMap<RID, Shared> mesh_to_shared;
HashSet<int> pinned_vertices; HashSet<int> pinned_vertices;
LocalVector<RID> exceptions; LocalVector<RID> exceptions;
LocalVector<Vector3> normals; LocalVector<Vector3> normals;
const Shared *shared = nullptr;
RID mesh; RID mesh;
LocalVector<int> mesh_to_physics;
JPH::SoftBodyCreationSettings *jolt_settings = new JPH::SoftBodyCreationSettings(); JPH::SoftBodyCreationSettings *jolt_settings = new JPH::SoftBodyCreationSettings();
@ -76,8 +67,7 @@ class JoltSoftBody3D final : public JoltObject3D {
virtual void _add_to_space() override; virtual void _add_to_space() override;
bool _ref_shared_data(); JPH::SoftBodySharedSettings *_create_shared_settings();
void _deref_shared_data();
void _update_mass(); void _update_mass();
void _update_pressure(); void _update_pressure();
@ -101,8 +91,6 @@ public:
JoltSoftBody3D(); JoltSoftBody3D();
virtual ~JoltSoftBody3D() override; virtual ~JoltSoftBody3D() override;
bool in_space() const;
void add_collision_exception(const RID &p_excepted_body); void add_collision_exception(const RID &p_excepted_body);
void remove_collision_exception(const RID &p_excepted_body); void remove_collision_exception(const RID &p_excepted_body);
bool has_collision_exception(const RID &p_excepted_body) const; bool has_collision_exception(const RID &p_excepted_body) const;