feat: add Softbody attachement and mesh simplifier#476
feat: add Softbody attachement and mesh simplifier#476MasterLaplace wants to merge 3 commits intomainfrom
Conversation
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📝 WalkthroughWalkthroughThis PR introduces mesh simplification utilities with spatial hashing and vertex clustering algorithms, extends soft body physics to support attachment to rigid bodies with kinematic vertex control, adds a new DEBRIS physics layer for attached soft bodies, and updates documentation generation configuration with enhanced Doxygen settings. Changes
Sequence DiagramsequenceDiagram
participant Editor as Editor/Game
participant PhysicsPlugin as PhysicsPlugin
participant SoftBodySystem as SoftBodySystem
participant Scheduler as FixedTimeUpdate Scheduler
participant RigidBody as RigidBody
participant SoftBody as SoftBody Physics
Editor->>PhysicsPlugin: Initialize soft body with attachedEntityId
PhysicsPlugin->>SoftBodySystem: Create soft body (with attachment)
SoftBodySystem->>SoftBodySystem: SelectKinematicVerticesByY()
SoftBodySystem->>SoftBody: Store kinematicVertexIndices & localPositions
loop Each Frame (FixedTimeUpdate)
Scheduler->>SoftBodySystem: UpdateKinematicVertices()
SoftBodySystem->>RigidBody: Get parent transform
SoftBodySystem->>SoftBodySystem: Compute world positions from local
SoftBodySystem->>SoftBody: Update kinematic vertex positions
Scheduler->>SoftBodySystem: SyncSoftBodyVertices()
SoftBodySystem->>SoftBody: Apply vertex positions to physics
Scheduler->>SoftBodySystem: Regular physics update
SoftBody->>RigidBody: Kinematic vertices follow parent
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- Introduced MeshSimplifier.hpp for mesh simplification, including settings and methods for reducing vertex count while preserving shape. - Enhanced SoftBody component to support attachment to rigid bodies, allowing kinematic vertices to follow parent transforms. - Implemented UpdateKinematicVertices system to update positions of kinematic vertices based on parent body transforms. - Updated physics plugin to register new systems for kinematic vertex updates. - Modified soft body system to handle kinematic vertex selection and attachment logic. - Improved broad phase layer handling to include a new DEBRIS layer for soft bodies. - Adjusted collision filtering to ensure proper interactions between moving objects and debris.
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Doxyfile.cfg (1)
2672-2684:⚠️ Potential issue | 🟡 Minor
CALL_GRAPHandCALLER_GRAPHare ineffective withHAVE_DOT = NO.
CALL_GRAPH = YES(Line 2672) andCALLER_GRAPH = YES(Line 2684) both requireHAVE_DOT = YES(currentlyNOat Line 2500) and thedottool (from Graphviz) to be installed. WithHAVE_DOT = NO, these settings are silently ignored and no call/caller graphs will be generated.Either set
HAVE_DOT = YESand install Graphviz in the CI workflow, or set these graph tags toNOto avoid confusion.
🤖 Fix all issues with AI agents
In `@src/plugin/object/src/utils/MeshSimplifier.cpp`:
- Around line 497-509: The loop in MeshSimplifier.cpp that computes cell
coordinates for each vertex (using variables cx, cy, cz in the for loop
iterating over vertices) casts floor(...) results to uint32_t which can UB if
the float is slightly negative; modify the computation to avoid casting negative
floats by clamping the float result to >= 0.0f before static_cast (or switch to
signed ints and clamp after), and apply the same fix to the identical pattern
later in the file (the block around the second occurrence at the lines mentioned
in the review). Ensure you still clamp to cellsX-1 / cellsY-1 / cellsZ-1 after
conversion.
- Around line 391-428: The SimplificationResult bestResult is returned
uninitialized if maxIterations == 0; update the function to either early-handle
the zero-iteration case or fully initialize bestResult before the loop: set
bestResult.originalVertexCount to mesh.GetVertices().size(), set
bestResult.simplifiedVertexCount to that same value (or to 0/appropriate
default), set bestResult.wasSimplified to false, and initialize
bestResult.vertexMap to an empty map/vector. Ensure these initializations occur
before the for-loop (or return a valid result immediately when maxIterations ==
0) so callers of SimplifyMesh/SimplificationResult get a well-defined object.
In `@src/plugin/object/src/utils/MeshSimplifier.hpp`:
- Around line 68-69: The field preserveBoundaries on MeshSimplifier is declared
but never used; either remove it or implement boundary-preservation checks in
the simplification routines. Locate the MeshSimplifier class and either delete
the preserveBoundaries member and any builder/exposure methods, or thread its
value into the simplifier logic: check preserveBoundaries inside SimplifyMesh,
SimplifyMeshToTarget, SimplifyMeshByVoxelization and DeduplicateVertices and
modify edge/collapse selection to skip collapsing or altering edges that are
boundary edges (edges with only one adjacent face). Ensure the builder methods
remain consistent with the chosen approach (remove builder when removing the
field, or keep and honor the flag when implementing).
In `@src/plugin/physics/src/component/SoftBody.hpp`:
- Around line 272-275: The comment on SoftBody::attachedEntityId references a
nonexistent AttachToEntity() method; update the comment or add the method.
Either implement a SoftBody::AttachToEntity(Engine::EntityId) method (or
similarly named attach function) that sets attachedEntityId and documents
intended behavior, or change the inline comment for attachedEntityId to refer to
the actual API used to attach (e.g., set attachedEntityId directly or call the
existing attach function name) so the doc matches the code.
In `@src/plugin/physics/src/system/SoftBodySystem.cpp`:
- Around line 709-727: The per-tick Log::Warn calls inside the
SyncSoftBodyVertices loop (the block that checks ConvexHullMeshCollider and
MeshCollider and currently calls Log::Warn "SoftBody: Using ... mesh as geometry
for SoftBody" or "SoftBody: No Mesh component found...") will spam logs; either
change these Log::Warn calls to Log::Debug, or add a one-time flag (e.g., a bool
on the SoftBody component or an internal map keyed by entity) that records that
the warning has been emitted and only logs on the first occurrence, setting the
flag after emitting; update the calls referring to ConvexHullMeshCollider,
MeshCollider and the "No Mesh" path accordingly and ensure the continue behavior
remains unchanged.
- Around line 58-100: The implementation of SelectKinematicVerticesByY does not
match its name/docs — it selects vertices via a uniform stride instead of by
lowest Y; either restore Y-based selection or rename and update docs. To fix: if
you want Y-based behavior, sort or index the input vertices by glm::vec3.y
(preserving original indices), select the lowest N indices (compute count using
std::ceil with std::clamp like currently done) and return those indices; update
variables like kinematicIndices accordingly and keep the 0%/100% short-circuits.
Alternatively, if you prefer uniform selection, rename
SelectKinematicVerticesByY to SelectKinematicVerticesUniform (and update the
function comment to describe uniform/stride selection) and adjust any callers to
use the new name.
- Around line 494-514: The code reads softBody.attachedEntityId.value() and
calls registry.try_get<...>(attachedEntity) without verifying the entity exists;
update the SoftBodySystem code to first check registry.valid(attachedEntity) (or
equivalent) after obtaining attachedEntity and before any try_get calls, and if
the entity is not valid log a warning (similar to the existing message) and skip
the attachment logic so attachedBodyID is not set from an invalid entity.
- Line 842: Remove the unused local variable by deleting the line that assigns
auto &bodyInterface = physicsManager.GetBodyInterface(); inside
UpdateKinematicVertices; if any later code intended to use the BodyInterface,
instead access it where needed via physicsManager.GetBodyInterface() inline or
introduce a used variable with a clear name, but otherwise remove the unused
declaration to eliminate the warning.
- Around line 858-871: Update the lock ordering to consistently acquire the
parent body lock before the soft-body lock to avoid ABBA inversion between
UpdateKinematicVertices and SyncSoftBodyVertices; in SyncSoftBodyVertices
reorder the BodyLockRead for the parent (using internal.attachedToBodyID) to be
taken before the soft-body BodyLockRead (using internal.bodyID), preserve the
Succeeded checks and early-continue behavior, and ensure you keep the same lock
types (read vs read) and variable names so the locking semantics match
UpdateKinematicVertices exactly.
🧹 Nitpick comments (3)
.github/workflows/deploy_doxygen_page.yml (1)
4-8: Docs won't auto-regenerate on source changes.The workflow only triggers on changes to the workflow file itself and
Doxyfile.cfg. Source code changes undersrc/won't trigger a rebuild. If this is intentional (relying onworkflow_dispatchfor manual runs), consider adding a brief comment to clarify.src/plugin/object/src/utils/MeshSimplifier.hpp (1)
29-29: Unused<optional>include.The
<optional>header is included but nothing in this header usesstd::optional. Consider removing it to avoid unnecessary compile-time dependencies.src/plugin/physics/src/system/SoftBodySystem.cpp (1)
146-155: Weak hash combiner may cause excessive collisions on large meshes.The current
h1 ^ (h2 << 1) ^ (h3 << 2)has poor bit mixing — small shifts cause many collisions for vertices with similar coordinates. A common improvement:♻️ Suggested hash combiner
size_t operator()(const glm::vec3 &v) const { - // Use a simple hash combining x, y, z components size_t h1 = std::hash<float>{}(v.x); size_t h2 = std::hash<float>{}(v.y); size_t h3 = std::hash<float>{}(v.z); - return h1 ^ (h2 << 1) ^ (h3 << 2); + // boost::hash_combine style mixing + size_t seed = h1; + seed ^= h2 + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= h3 + 0x9e3779b9 + (seed << 6) + (seed >> 2); + return seed; }
| // Start with base settings | ||
| SimplificationSettings settings = SimplificationSettings::ForSoftBody(targetVertexCount); | ||
| Component::Mesh currentMesh = mesh; | ||
| SimplificationResult bestResult; | ||
| bestResult.originalVertexCount = static_cast<uint32_t>(mesh.GetVertices().size()); | ||
|
|
||
| for (uint32_t iter = 0; iter < maxIterations; ++iter) | ||
| { | ||
| auto result = SimplifyMesh(currentMesh, settings); | ||
|
|
||
| if (result.simplifiedVertexCount <= targetVertexCount) | ||
| { | ||
| // Target reached | ||
| result.originalVertexCount = bestResult.originalVertexCount; | ||
| return result; | ||
| } | ||
|
|
||
| // Not enough reduction, increase aggressiveness | ||
| if (result.simplifiedVertexCount >= currentMesh.GetVertices().size() * 0.95f) | ||
| { | ||
| // No significant progress, increase merge distance more aggressively | ||
| settings.mergeDistance *= 2.0f; | ||
| settings.aggressiveness = std::min(settings.aggressiveness + 0.15f, 1.0f); | ||
| } | ||
| else | ||
| { | ||
| // Some progress, moderate increase | ||
| settings.mergeDistance *= 1.5f; | ||
| settings.aggressiveness = std::min(settings.aggressiveness + 0.1f, 1.0f); | ||
| } | ||
|
|
||
| currentMesh = result.mesh; | ||
| bestResult = result; | ||
| bestResult.originalVertexCount = static_cast<uint32_t>(mesh.GetVertices().size()); | ||
| } | ||
|
|
||
| return bestResult; | ||
| } |
There was a problem hiding this comment.
Uninitialized bestResult fields returned when maxIterations == 0.
If maxIterations is 0, the loop never executes and bestResult is returned at Line 427 with simplifiedVertexCount, wasSimplified, and vertexMap all uninitialized/default. The static analysis tool also flagged this (uninitvar).
Initialize bestResult fully or handle the maxIterations == 0 edge case explicitly.
Proposed fix
SimplificationResult bestResult;
bestResult.originalVertexCount = static_cast<uint32_t>(mesh.GetVertices().size());
+ bestResult.simplifiedVertexCount = bestResult.originalVertexCount;
+ bestResult.wasSimplified = false;
+ bestResult.mesh = mesh;
+ bestResult.vertexMap.resize(bestResult.originalVertexCount);
+ for (uint32_t i = 0; i < bestResult.originalVertexCount; ++i)
+ bestResult.vertexMap[i] = i;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Start with base settings | |
| SimplificationSettings settings = SimplificationSettings::ForSoftBody(targetVertexCount); | |
| Component::Mesh currentMesh = mesh; | |
| SimplificationResult bestResult; | |
| bestResult.originalVertexCount = static_cast<uint32_t>(mesh.GetVertices().size()); | |
| for (uint32_t iter = 0; iter < maxIterations; ++iter) | |
| { | |
| auto result = SimplifyMesh(currentMesh, settings); | |
| if (result.simplifiedVertexCount <= targetVertexCount) | |
| { | |
| // Target reached | |
| result.originalVertexCount = bestResult.originalVertexCount; | |
| return result; | |
| } | |
| // Not enough reduction, increase aggressiveness | |
| if (result.simplifiedVertexCount >= currentMesh.GetVertices().size() * 0.95f) | |
| { | |
| // No significant progress, increase merge distance more aggressively | |
| settings.mergeDistance *= 2.0f; | |
| settings.aggressiveness = std::min(settings.aggressiveness + 0.15f, 1.0f); | |
| } | |
| else | |
| { | |
| // Some progress, moderate increase | |
| settings.mergeDistance *= 1.5f; | |
| settings.aggressiveness = std::min(settings.aggressiveness + 0.1f, 1.0f); | |
| } | |
| currentMesh = result.mesh; | |
| bestResult = result; | |
| bestResult.originalVertexCount = static_cast<uint32_t>(mesh.GetVertices().size()); | |
| } | |
| return bestResult; | |
| } | |
| // Start with base settings | |
| SimplificationSettings settings = SimplificationSettings::ForSoftBody(targetVertexCount); | |
| Component::Mesh currentMesh = mesh; | |
| SimplificationResult bestResult; | |
| bestResult.originalVertexCount = static_cast<uint32_t>(mesh.GetVertices().size()); | |
| bestResult.simplifiedVertexCount = bestResult.originalVertexCount; | |
| bestResult.wasSimplified = false; | |
| bestResult.mesh = mesh; | |
| bestResult.vertexMap.resize(bestResult.originalVertexCount); | |
| for (uint32_t i = 0; i < bestResult.originalVertexCount; ++i) | |
| bestResult.vertexMap[i] = i; | |
| for (uint32_t iter = 0; iter < maxIterations; ++iter) | |
| { | |
| auto result = SimplifyMesh(currentMesh, settings); | |
| if (result.simplifiedVertexCount <= targetVertexCount) | |
| { | |
| // Target reached | |
| result.originalVertexCount = bestResult.originalVertexCount; | |
| return result; | |
| } | |
| // Not enough reduction, increase aggressiveness | |
| if (result.simplifiedVertexCount >= currentMesh.GetVertices().size() * 0.95f) | |
| { | |
| // No significant progress, increase merge distance more aggressively | |
| settings.mergeDistance *= 2.0f; | |
| settings.aggressiveness = std::min(settings.aggressiveness + 0.15f, 1.0f); | |
| } | |
| else | |
| { | |
| // Some progress, moderate increase | |
| settings.mergeDistance *= 1.5f; | |
| settings.aggressiveness = std::min(settings.aggressiveness + 0.1f, 1.0f); | |
| } | |
| currentMesh = result.mesh; | |
| bestResult = result; | |
| bestResult.originalVertexCount = static_cast<uint32_t>(mesh.GetVertices().size()); | |
| } | |
| return bestResult; | |
| } |
🧰 Tools
🪛 Cppcheck (2.19.0)
[warning] 427-427: Uninitialized variables
(uninitvar)
🤖 Prompt for AI Agents
In `@src/plugin/object/src/utils/MeshSimplifier.cpp` around lines 391 - 428, The
SimplificationResult bestResult is returned uninitialized if maxIterations == 0;
update the function to either early-handle the zero-iteration case or fully
initialize bestResult before the loop: set bestResult.originalVertexCount to
mesh.GetVertices().size(), set bestResult.simplifiedVertexCount to that same
value (or to 0/appropriate default), set bestResult.wasSimplified to false, and
initialize bestResult.vertexMap to an empty map/vector. Ensure these
initializations occur before the for-loop (or return a valid result immediately
when maxIterations == 0) so callers of SimplifyMesh/SimplificationResult get a
well-defined object.
| for (uint32_t i = 0; i < vertices.size(); ++i) | ||
| { | ||
| const glm::vec3 &v = vertices[i]; | ||
|
|
||
| // Calculate cell coordinates | ||
| uint32_t cx = static_cast<uint32_t>(std::floor((v.x - minBound.x) * invCellSize)); | ||
| uint32_t cy = static_cast<uint32_t>(std::floor((v.y - minBound.y) * invCellSize)); | ||
| uint32_t cz = static_cast<uint32_t>(std::floor((v.z - minBound.z) * invCellSize)); | ||
|
|
||
| // Clamp to grid bounds | ||
| cx = std::min(cx, cellsX - 1); | ||
| cy = std::min(cy, cellsY - 1); | ||
| cz = std::min(cz, cellsZ - 1); |
There was a problem hiding this comment.
Potential undefined behavior: casting negative float to uint32_t.
If floating-point imprecision causes (v.x - minBound.x) to be slightly negative, std::floor yields -1.0f, and static_cast<uint32_t>(-1.0f) is undefined behavior in C++. The std::min clamp on Lines 507-509 happens after the cast, so it doesn't prevent this.
Clamp to 0.0f before the cast, or use signed integers and clamp afterward.
Proposed fix
- uint32_t cx = static_cast<uint32_t>(std::floor((v.x - minBound.x) * invCellSize));
- uint32_t cy = static_cast<uint32_t>(std::floor((v.y - minBound.y) * invCellSize));
- uint32_t cz = static_cast<uint32_t>(std::floor((v.z - minBound.z) * invCellSize));
-
- // Clamp to grid bounds
- cx = std::min(cx, cellsX - 1);
- cy = std::min(cy, cellsY - 1);
- cz = std::min(cz, cellsZ - 1);
+ uint32_t cx = static_cast<uint32_t>(std::clamp(std::floor((v.x - minBound.x) * invCellSize), 0.0f, static_cast<float>(cellsX - 1)));
+ uint32_t cy = static_cast<uint32_t>(std::clamp(std::floor((v.y - minBound.y) * invCellSize), 0.0f, static_cast<float>(cellsY - 1)));
+ uint32_t cz = static_cast<uint32_t>(std::clamp(std::floor((v.z - minBound.z) * invCellSize), 0.0f, static_cast<float>(cellsZ - 1)));Note: the same pattern appears on Lines 550-555 — apply the same fix there.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for (uint32_t i = 0; i < vertices.size(); ++i) | |
| { | |
| const glm::vec3 &v = vertices[i]; | |
| // Calculate cell coordinates | |
| uint32_t cx = static_cast<uint32_t>(std::floor((v.x - minBound.x) * invCellSize)); | |
| uint32_t cy = static_cast<uint32_t>(std::floor((v.y - minBound.y) * invCellSize)); | |
| uint32_t cz = static_cast<uint32_t>(std::floor((v.z - minBound.z) * invCellSize)); | |
| // Clamp to grid bounds | |
| cx = std::min(cx, cellsX - 1); | |
| cy = std::min(cy, cellsY - 1); | |
| cz = std::min(cz, cellsZ - 1); | |
| for (uint32_t i = 0; i < vertices.size(); ++i) | |
| { | |
| const glm::vec3 &v = vertices[i]; | |
| // Calculate cell coordinates | |
| uint32_t cx = static_cast<uint32_t>(std::clamp(std::floor((v.x - minBound.x) * invCellSize), 0.0f, static_cast<float>(cellsX - 1))); | |
| uint32_t cy = static_cast<uint32_t>(std::clamp(std::floor((v.y - minBound.y) * invCellSize), 0.0f, static_cast<float>(cellsY - 1))); | |
| uint32_t cz = static_cast<uint32_t>(std::clamp(std::floor((v.z - minBound.z) * invCellSize), 0.0f, static_cast<float>(cellsZ - 1))); |
🤖 Prompt for AI Agents
In `@src/plugin/object/src/utils/MeshSimplifier.cpp` around lines 497 - 509, The
loop in MeshSimplifier.cpp that computes cell coordinates for each vertex (using
variables cx, cy, cz in the for loop iterating over vertices) casts floor(...)
results to uint32_t which can UB if the float is slightly negative; modify the
computation to avoid casting negative floats by clamping the float result to >=
0.0f before static_cast (or switch to signed ints and clamp after), and apply
the same fix to the identical pattern later in the file (the block around the
second occurrence at the lines mentioned in the review). Ensure you still clamp
to cellsX-1 / cellsY-1 / cellsZ-1 after conversion.
| /// Whether to preserve boundary edges (edges with only one adjacent face) | ||
| bool preserveBoundaries = true; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n 'preserveBoundaries' --type=cpp --type-add 'cpp:*.hpp'Repository: EngineSquared/EngineSquared
Length of output: 332
🏁 Script executed:
#!/bin/bash
# Get context around the lines where preserveBoundaries appears
head -n 110 src/plugin/object/src/utils/MeshSimplifier.hpp | tail -n 50
# Search for conditional usage of preserveBoundaries (if statements, ternary, etc.)
echo "=== Searching for conditional checks on preserveBoundaries ==="
rg -n 'if.*preserveBoundaries|preserveBoundaries.*if|?.*preserveBoundaries' --type=cpp --type-add 'cpp:*.hpp'
# Also check for any .cpp file
echo "=== Checking for MeshSimplifier.cpp ==="
find . -name 'MeshSimplifier.cpp' -type fRepository: EngineSquared/EngineSquared
Length of output: 1983
🏁 Script executed:
#!/bin/bash
# Search for any reference to preserveBoundaries in the cpp file
echo "=== All references in MeshSimplifier.cpp ==="
rg -n 'preserveBoundaries' src/plugin/object/src/utils/MeshSimplifier.cpp
# Search for if/conditional patterns checking the field
echo "=== Searching for conditional checks ==="
rg -n 'if\s*\(' src/plugin/object/src/utils/MeshSimplifier.cpp -A 2 | grep -C 2 'preserveBoundaries'
# Get a broader view of the cpp file to understand what operations it performs
echo "=== File size and structure ==="
wc -l src/plugin/object/src/utils/MeshSimplifier.cppRepository: EngineSquared/EngineSquared
Length of output: 239
🏁 Script executed:
#!/bin/bash
# Read the entire cpp file to see what's actually implemented
cat -n src/plugin/object/src/utils/MeshSimplifier.cpp | head -n 150Repository: EngineSquared/EngineSquared
Length of output: 5442
🏁 Script executed:
#!/bin/bash
# Continue reading the cpp file, looking for the main Simplify function
sed -n '150,400p' src/plugin/object/src/utils/MeshSimplifier.cpp | cat -nRepository: EngineSquared/EngineSquared
Length of output: 10178
🏁 Script executed:
#!/bin/bash
# Read the rest of the cpp file
tail -n +400 src/plugin/object/src/utils/MeshSimplifier.cpp | cat -nRepository: EngineSquared/EngineSquared
Length of output: 13393
preserveBoundaries setting is unused and should be removed or implemented.
The field is declared with intent to preserve boundary edges (edges with only one adjacent face), exposed through builder methods, but never actually checked in any of the simplification implementations (SimplifyMesh, SimplifyMeshToTarget, SimplifyMeshByVoxelization, or DeduplicateVertices). Either implement the boundary preservation logic in the mesh simplification algorithm or remove the field to prevent misleading callers about available functionality.
🤖 Prompt for AI Agents
In `@src/plugin/object/src/utils/MeshSimplifier.hpp` around lines 68 - 69, The
field preserveBoundaries on MeshSimplifier is declared but never used; either
remove it or implement boundary-preservation checks in the simplification
routines. Locate the MeshSimplifier class and either delete the
preserveBoundaries member and any builder/exposure methods, or thread its value
into the simplifier logic: check preserveBoundaries inside SimplifyMesh,
SimplifyMeshToTarget, SimplifyMeshByVoxelization and DeduplicateVertices and
modify edge/collapse selection to skip collapsing or altering edges that are
boundary edges (edges with only one adjacent face). Ensure the builder methods
remain consistent with the chosen approach (remove builder when removing the
field, or keep and honor the flag when implementing).
| /// Entity ID of the rigid body to attach to (optional) | ||
| /// When set, kinematic vertices will follow this body's transform | ||
| /// Use AttachToEntity() to set this properly | ||
| std::optional<Engine::EntityId> attachedEntityId = std::nullopt; |
There was a problem hiding this comment.
Documentation references nonexistent AttachToEntity() method.
Line 274 says "Use AttachToEntity() to set this properly" but no such method exists on the SoftBody struct. Either add the method or update the comment to describe the correct usage pattern.
🤖 Prompt for AI Agents
In `@src/plugin/physics/src/component/SoftBody.hpp` around lines 272 - 275, The
comment on SoftBody::attachedEntityId references a nonexistent AttachToEntity()
method; update the comment or add the method. Either implement a
SoftBody::AttachToEntity(Engine::EntityId) method (or similarly named attach
function) that sets attachedEntityId and documents intended behavior, or change
the inline comment for attachedEntityId to refer to the actual API used to
attach (e.g., set attachedEntityId directly or call the existing attach function
name) so the doc matches the code.
| /** | ||
| * @brief Select kinematic vertex indices based on percentage (lowest Y values) | ||
| * | ||
| * This function selects a percentage of vertices to be kinematic (fixed to parent). | ||
| * It prioritizes vertices with the lowest Y coordinate (bottom of mesh). | ||
| * | ||
| * @param vertices The deduplicated vertices | ||
| * @param percent Percentage of vertices to select [0.0 - 1.0] | ||
| * @return Vector of vertex indices to make kinematic | ||
| */ | ||
| static std::vector<uint32_t> SelectKinematicVerticesByY(const std::vector<glm::vec3> &vertices, float percent) | ||
| { | ||
| if (vertices.empty() || percent <= 0.0f) | ||
| return {}; | ||
|
|
||
| // Special case: 100% kinematic - return all vertices | ||
| if (percent >= 1.0f) | ||
| { | ||
| std::vector<uint32_t> allIndices; | ||
| allIndices.reserve(vertices.size()); | ||
| for (uint32_t i = 0; i < vertices.size(); ++i) | ||
| { | ||
| allIndices.push_back(i); | ||
| } | ||
| return allIndices; | ||
| } | ||
|
|
||
| // For partial kinematic, use uniform spatial distribution instead of just Y | ||
| // This ensures anchor points are spread across the entire mesh | ||
| size_t count = static_cast<size_t>(std::ceil(vertices.size() * std::clamp(percent, 0.0f, 1.0f))); | ||
| count = std::max(count, size_t(1)); | ||
|
|
||
| std::vector<uint32_t> kinematicIndices; | ||
| kinematicIndices.reserve(count); | ||
|
|
||
| size_t stride = std::max(size_t(1), vertices.size() / count); | ||
| for (size_t i = 0; kinematicIndices.size() < count && i < vertices.size(); i += stride) | ||
| { | ||
| kinematicIndices.push_back(static_cast<uint32_t>(i)); | ||
| } | ||
|
|
||
| return kinematicIndices; | ||
| } |
There was a problem hiding this comment.
Function name and documentation are inconsistent with implementation.
The function is named SelectKinematicVerticesByY and the doc comment states it "prioritizes vertices with the lowest Y coordinate," but the actual implementation (lines 85–97) uses a uniform stride-based distribution with no Y-sorting at all. The comment on line 85 even acknowledges this: "use uniform spatial distribution instead of just Y."
Either rename the function (e.g., SelectKinematicVerticesUniform) and update the doc, or restore the Y-based selection logic to match the current contract.
🤖 Prompt for AI Agents
In `@src/plugin/physics/src/system/SoftBodySystem.cpp` around lines 58 - 100, The
implementation of SelectKinematicVerticesByY does not match its name/docs — it
selects vertices via a uniform stride instead of by lowest Y; either restore
Y-based selection or rename and update docs. To fix: if you want Y-based
behavior, sort or index the input vertices by glm::vec3.y (preserving original
indices), select the lowest N indices (compute count using std::ceil with
std::clamp like currently done) and return those indices; update variables like
kinematicIndices accordingly and keep the 0%/100% short-circuits. Alternatively,
if you prefer uniform selection, rename SelectKinematicVerticesByY to
SelectKinematicVerticesUniform (and update the function comment to describe
uniform/stride selection) and adjust any callers to use the new name.
| if (softBody.attachedEntityId.has_value()) | ||
| { | ||
| // Get the attached body's BodyID | ||
| auto attachedEntity = softBody.attachedEntityId.value(); | ||
| if (auto *rigidInternal = registry.try_get<Component::RigidBodyInternal>(attachedEntity)) | ||
| { | ||
| attachedBodyID = rigidInternal->bodyID; | ||
| Log::Info( | ||
| fmt::format("SoftBody: Attaching to rigid body entity {}", static_cast<uint32_t>(attachedEntity))); | ||
| } | ||
| else if (auto *vehicleInternal = registry.try_get<Component::VehicleInternal>(attachedEntity)) | ||
| { | ||
| attachedBodyID = vehicleInternal->chassisBodyID; | ||
| Log::Info( | ||
| fmt::format("SoftBody: Attaching to vehicle entity {}", static_cast<uint32_t>(attachedEntity))); | ||
| } | ||
| else | ||
| { | ||
| Log::Warn( | ||
| "SoftBody: attachedEntityId specified but entity has no RigidBodyInternal or VehicleInternal"); | ||
| } |
There was a problem hiding this comment.
Missing entity validity check before accessing attached entity's components.
softBody.attachedEntityId.value() is a user-provided entity ID. If that entity has been destroyed or was never created, calling registry.try_get<...>(attachedEntity) on an invalid entity is undefined behavior in entt. Add a validity check before the lookups.
🛡️ Proposed fix
if (softBody.attachedEntityId.has_value())
{
// Get the attached body's BodyID
auto attachedEntity = softBody.attachedEntityId.value();
+ if (!registry.valid(attachedEntity))
+ {
+ Log::Warn("SoftBody: attachedEntityId refers to an invalid entity");
+ }
- if (auto *rigidInternal = registry.try_get<Component::RigidBodyInternal>(attachedEntity))
+ else if (auto *rigidInternal = registry.try_get<Component::RigidBodyInternal>(attachedEntity))🤖 Prompt for AI Agents
In `@src/plugin/physics/src/system/SoftBodySystem.cpp` around lines 494 - 514, The
code reads softBody.attachedEntityId.value() and calls
registry.try_get<...>(attachedEntity) without verifying the entity exists;
update the SoftBodySystem code to first check registry.valid(attachedEntity) (or
equivalent) after obtaining attachedEntity and before any try_get calls, and if
the entity is not valid log a warning (similar to the existing message) and skip
the attachment logic so attachedBodyID is not set from an invalid entity.
| { | ||
| if (auto *convexHullMesh = registry.try_get<Component::ConvexHullMeshCollider>(entity); | ||
| convexHullMesh && convexHullMesh->mesh && !convexHullMesh->mesh->GetVertices().empty()) | ||
| { | ||
| mesh = &(convexHullMesh->mesh.value()); | ||
| Log::Warn("SoftBody: Using ConvexHullMeshCollider mesh as geometry for SoftBody"); | ||
| } | ||
| else if (auto *meshCollider = registry.try_get<Component::MeshCollider>(entity); | ||
| meshCollider && meshCollider->mesh && !meshCollider->mesh->GetVertices().empty()) | ||
| { | ||
| mesh = &(meshCollider->mesh.value()); | ||
| Log::Warn("SoftBody: Using MeshCollider mesh as geometry for SoftBody"); | ||
| } | ||
| else | ||
| { | ||
| Log::Warn("SoftBody: No Mesh component found during sync. Skipping vertex update."); | ||
| continue; | ||
| } | ||
| } |
There was a problem hiding this comment.
Per-frame Log::Warn in the sync loop will spam logs every physics tick.
Lines 714 and 720 emit a warning every time SyncSoftBodyVertices runs (every fixed-timestep tick) if the entity uses a collider mesh instead of a dedicated Mesh component. This will produce massive log output during normal operation.
Either suppress these after the first occurrence (e.g., a flag on the internal component) or downgrade to Log::Debug.
🤖 Prompt for AI Agents
In `@src/plugin/physics/src/system/SoftBodySystem.cpp` around lines 709 - 727, The
per-tick Log::Warn calls inside the SyncSoftBodyVertices loop (the block that
checks ConvexHullMeshCollider and MeshCollider and currently calls Log::Warn
"SoftBody: Using ... mesh as geometry for SoftBody" or "SoftBody: No Mesh
component found...") will spam logs; either change these Log::Warn calls to
Log::Debug, or add a one-time flag (e.g., a bool on the SoftBody component or an
internal map keyed by entity) that records that the warning has been emitted and
only logs on the first occurrence, setting the flag after emitting; update the
calls referring to ConvexHullMeshCollider, MeshCollider and the "No Mesh" path
accordingly and ensure the continue behavior remains unchanged.
| if (!physicsManager.IsPhysicsActivated()) | ||
| return; | ||
|
|
||
| auto &bodyInterface = physicsManager.GetBodyInterface(); |
There was a problem hiding this comment.
Unused variable bodyInterface.
bodyInterface is fetched on line 842 but never referenced in UpdateKinematicVertices. Remove it to avoid the unused-variable warning.
🧹 Proposed fix
if (!physicsManager.IsPhysicsActivated())
return;
- auto &bodyInterface = physicsManager.GetBodyInterface();
-
// Update kinematic vertex positions for attached soft bodies📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| auto &bodyInterface = physicsManager.GetBodyInterface(); |
🤖 Prompt for AI Agents
In `@src/plugin/physics/src/system/SoftBodySystem.cpp` at line 842, Remove the
unused local variable by deleting the line that assigns auto &bodyInterface =
physicsManager.GetBodyInterface(); inside UpdateKinematicVertices; if any later
code intended to use the BodyInterface, instead access it where needed via
physicsManager.GetBodyInterface() inline or introduce a used variable with a
clear name, but otherwise remove the unused declaration to eliminate the
warning.
f757131 to
1de556d
Compare
|


Unrelated to any issue
Summary by CodeRabbit
Release Notes
New Features
Chores