From b3ddb8803594191338078925bd8e1e45bdde62f3 Mon Sep 17 00:00:00 2001 From: Mikael Hermansson Date: Wed, 28 May 2025 19:48:30 +0200 Subject: [PATCH] Fix Area3D signal emissions when using Jolt Physics --- modules/jolt_physics/objects/jolt_area_3d.cpp | 28 +++++++++----- modules/jolt_physics/objects/jolt_area_3d.h | 7 ++++ .../objects/jolt_shaped_object_3d.cpp | 5 ++- .../spaces/jolt_contact_listener_3d.cpp | 38 +++++-------------- .../spaces/jolt_contact_listener_3d.h | 1 - 5 files changed, 40 insertions(+), 39 deletions(-) diff --git a/modules/jolt_physics/objects/jolt_area_3d.cpp b/modules/jolt_physics/objects/jolt_area_3d.cpp index 2a7f706ca00..1c89c93a61a 100644 --- a/modules/jolt_physics/objects/jolt_area_3d.cpp +++ b/modules/jolt_physics/objects/jolt_area_3d.cpp @@ -113,12 +113,14 @@ void JoltArea3D::_add_shape_pair(Overlap &p_overlap, const JPH::BodyID &p_body_i p_overlap.rid = other_object->get_rid(); p_overlap.instance_id = other_object->get_instance_id(); - ShapeIndexPair &shape_indices = p_overlap.shape_pairs[{ p_other_shape_id, p_self_shape_id }]; + HashMap::Iterator shape_pair = p_overlap.shape_pairs.find(ShapeIDPair(p_other_shape_id, p_self_shape_id)); + if (shape_pair == p_overlap.shape_pairs.end()) { + const int other_shape_index = other_object->find_shape_index(p_other_shape_id); + const int self_shape_index = find_shape_index(p_self_shape_id); + shape_pair = p_overlap.shape_pairs.insert(ShapeIDPair(p_other_shape_id, p_self_shape_id), ShapeIndexPair(other_shape_index, self_shape_index)); + } - shape_indices.other = other_object->find_shape_index(p_other_shape_id); - shape_indices.self = find_shape_index(p_self_shape_id); - - p_overlap.pending_added.push_back(shape_indices); + p_overlap.pending_added.push_back(shape_pair->value); _events_changed(); } @@ -143,12 +145,20 @@ void JoltArea3D::_flush_events(OverlapsById &p_objects, const Callable &p_callba Overlap &overlap = E->value; if (p_callback.is_valid()) { - for (ShapeIndexPair &shape_indices : overlap.pending_removed) { - _report_event(p_callback, PhysicsServer3D::AREA_BODY_REMOVED, overlap.rid, overlap.instance_id, shape_indices.other, shape_indices.self); + for (const ShapeIndexPair &shape_indices : overlap.pending_added) { + int &ref_count = overlap.ref_counts[shape_indices]; + if (ref_count++ == 0) { + _report_event(p_callback, PhysicsServer3D::AREA_BODY_ADDED, overlap.rid, overlap.instance_id, shape_indices.other, shape_indices.self); + } } - for (ShapeIndexPair &shape_indices : overlap.pending_added) { - _report_event(p_callback, PhysicsServer3D::AREA_BODY_ADDED, overlap.rid, overlap.instance_id, shape_indices.other, shape_indices.self); + for (const ShapeIndexPair &shape_indices : overlap.pending_removed) { + int &ref_count = overlap.ref_counts[shape_indices]; + ERR_CONTINUE(ref_count <= 0); + if (--ref_count == 0) { + _report_event(p_callback, PhysicsServer3D::AREA_BODY_REMOVED, overlap.rid, overlap.instance_id, shape_indices.other, shape_indices.self); + overlap.ref_counts.erase(shape_indices); + } } } diff --git a/modules/jolt_physics/objects/jolt_area_3d.h b/modules/jolt_physics/objects/jolt_area_3d.h index d4cd5ec05c4..d014105f114 100644 --- a/modules/jolt_physics/objects/jolt_area_3d.h +++ b/modules/jolt_physics/objects/jolt_area_3d.h @@ -73,6 +73,12 @@ private: ShapeIndexPair(int p_other, int p_self) : other(p_other), self(p_self) {} + static uint32_t hash(const ShapeIndexPair &p_pair) { + uint32_t hash = hash_murmur3_one_32(p_pair.other); + hash = hash_murmur3_one_32(p_pair.self, hash); + return hash_fmix32(hash); + } + friend bool operator==(const ShapeIndexPair &p_lhs, const ShapeIndexPair &p_rhs) { return (p_lhs.other == p_rhs.other) && (p_lhs.self == p_rhs.self); } @@ -80,6 +86,7 @@ private: struct Overlap { HashMap shape_pairs; + HashMap ref_counts; LocalVector pending_added; LocalVector pending_removed; RID rid; diff --git a/modules/jolt_physics/objects/jolt_shaped_object_3d.cpp b/modules/jolt_physics/objects/jolt_shaped_object_3d.cpp index 5f40c325245..9ac43699d81 100644 --- a/modules/jolt_physics/objects/jolt_shaped_object_3d.cpp +++ b/modules/jolt_physics/objects/jolt_shaped_object_3d.cpp @@ -304,7 +304,10 @@ void JoltShapedObject3D::commit_shapes(bool p_optimize_compound) { return; } - previous_jolt_shape = jolt_shape; + if (previous_jolt_shape == nullptr) { + previous_jolt_shape = jolt_shape; + } + jolt_shape = new_shape; space->get_body_iface().SetShape(jolt_body->GetID(), jolt_shape, false, JPH::EActivation::DontActivate); diff --git a/modules/jolt_physics/spaces/jolt_contact_listener_3d.cpp b/modules/jolt_physics/spaces/jolt_contact_listener_3d.cpp index 226244b9840..64a4d4214c0 100644 --- a/modules/jolt_physics/spaces/jolt_contact_listener_3d.cpp +++ b/modules/jolt_physics/spaces/jolt_contact_listener_3d.cpp @@ -246,13 +246,22 @@ bool JoltContactListener3D::_try_evaluate_area_overlap(const JPH::Body &p_body1, return false; } - auto evaluate = [&](const auto &p_area, const auto &p_object, const JPH::SubShapeIDPair &p_shape_pair) { + auto has_shifted = [](const JoltShapedObject3D &p_object, const JPH::SubShapeID &p_sub_shape_id) { + return p_object.get_previous_jolt_shape() != nullptr && p_object.get_jolt_shape()->GetSubShapeUserData(p_sub_shape_id) != p_object.get_previous_jolt_shape()->GetSubShapeUserData(p_sub_shape_id); + }; + + auto evaluate = [&](const JoltArea3D &p_area, const auto &p_object, const JPH::SubShapeIDPair &p_shape_pair) { const MutexLock write_lock(write_mutex); if (p_area.can_monitor(p_object)) { if (!area_overlaps.has(p_shape_pair)) { area_overlaps.insert(p_shape_pair); area_enters.insert(p_shape_pair); + } else if (has_shifted(p_area, p_shape_pair.GetSubShapeID1()) || has_shifted(p_object, p_shape_pair.GetSubShapeID2())) { + // A shape has taken on the `JPH::SubShapeID` value of another shape, likely because of the other shape having been replaced or moved + // in some way, so we force the area to refresh its internal mappings by exiting and entering this shape pair. + area_exits.insert(p_shape_pair); + area_enters.insert(p_shape_pair); } } else { if (area_overlaps.erase(p_shape_pair)) { @@ -456,32 +465,6 @@ void JoltContactListener3D::_flush_area_enters() { area_enters.clear(); } -void JoltContactListener3D::_flush_area_shifts() { - for (const JPH::SubShapeIDPair &shape_pair : area_overlaps) { - auto is_shifted = [&](const JPH::BodyID &p_body_id, const JPH::SubShapeID &p_sub_shape_id) { - const JoltShapedObject3D *object = space->try_get_shaped(p_body_id); - ERR_FAIL_NULL_V(object, false); - - if (object->get_previous_jolt_shape() == nullptr) { - return false; - } - - const JPH::Shape ¤t_shape = *object->get_jolt_shape(); - const JPH::Shape &previous_shape = *object->get_previous_jolt_shape(); - - const uint32_t current_id = (uint32_t)current_shape.GetSubShapeUserData(p_sub_shape_id); - const uint32_t previous_id = (uint32_t)previous_shape.GetSubShapeUserData(p_sub_shape_id); - - return current_id != previous_id; - }; - - if (is_shifted(shape_pair.GetBody1ID(), shape_pair.GetSubShapeID1()) || is_shifted(shape_pair.GetBody2ID(), shape_pair.GetSubShapeID2())) { - area_enters.insert(shape_pair); - area_exits.insert(shape_pair); - } - } -} - void JoltContactListener3D::_flush_area_exits() { for (const JPH::SubShapeIDPair &shape_pair : area_exits) { const JPH::BodyID &body_id1 = shape_pair.GetBody1ID(); @@ -523,7 +506,6 @@ void JoltContactListener3D::pre_step() { void JoltContactListener3D::post_step() { _flush_contacts(); - _flush_area_shifts(); _flush_area_exits(); _flush_area_enters(); } diff --git a/modules/jolt_physics/spaces/jolt_contact_listener_3d.h b/modules/jolt_physics/spaces/jolt_contact_listener_3d.h index 7a32e3a520c..ad4ce475c9d 100644 --- a/modules/jolt_physics/spaces/jolt_contact_listener_3d.h +++ b/modules/jolt_physics/spaces/jolt_contact_listener_3d.h @@ -120,7 +120,6 @@ class JoltContactListener3D final void _flush_contacts(); void _flush_area_enters(); - void _flush_area_shifts(); void _flush_area_exits(); public: