Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix rotation error when add joint #2526

Merged
merged 6 commits into from
Jan 24, 2025
Merged

Conversation

luzhuang
Copy link
Contributor

@luzhuang luzhuang commented Jan 24, 2025

Please check if the PR fulfills these requirements

  • The commit message follows our guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)

What is the current behavior? (You can also link to an open issue here)

What is the new behavior (if this is a feature change)?

Does this PR introduce a breaking change? (What changes might users need to make in their application due to this PR?)

Other information:

Summary by CodeRabbit

  • New Features

    • Added support for setting joint rotations.
    • Enhanced physics joint positioning and rotation controls.
  • Bug Fixes

    • Improved precision of anchor calculations in physics joints.
    • Optimized transform update logic in dynamic colliders.
  • Refactor

    • Streamlined component parsing in hierarchy loading.
    • Updated internal matrix and quaternion operations for physics joints.
  • Tests

    • Added test cases for joint rotation scenarios.

@luzhuang luzhuang added bug Something isn't working physics Engine's physical system ignore for release ignore for release labels Jan 24, 2025
@luzhuang luzhuang requested a review from GuoLei1990 January 24, 2025 02:41
Copy link

coderabbitai bot commented Jan 24, 2025

Walkthrough

This pull request introduces enhancements to the physics and joint systems across multiple files. The changes focus on improving transform and rotation handling in dynamic colliders and joints. Key modifications include adding static temporary variables for efficient calculations, introducing new methods for rotation and anchor management, and extending interfaces to support more precise joint transformations. The changes aim to provide more robust mathematical operations for physics-related transformations without altering existing public interfaces.

Changes

File Change Summary
packages/core/src/physics/DynamicCollider.ts Added static private variables _tempVector3 and _tempQuat. Modified _onLateUpdate to use these variables for more efficient world transform calculations.
packages/core/src/physics/joint/Joint.ts Added static private _tempMatrix. Introduced _updateRotation() method. Enhanced _calculateConnectedAnchor() with matrix transformations.
packages/design/src/physics/joints/IJoint.ts Added setRotation(value: Quaternion) method to the interface. Imported Quaternion type.
packages/loader/src/resource-deserialize/resources/parser/HierarchyParser.ts Added _parseAddedComponents and _addComponents methods. Modified _parseComponents to handle entities with stripped IDs.
packages/physics-physx/src/joint/PhysXHingeJoint.ts Removed _anchor and _connectedAnchor. Added _axis and _connectedAxisRotationQuaternion. Implemented setRotation method.
packages/physics-physx/src/joint/PhysXJoint.ts Added _anchor, _connectedAnchor, and _rotation properties. Implemented setRotation method.
tests/src/core/physics/Joint.test.ts Added two new test cases: "rotated" and "rotated with connectedCollider".

Possibly related PRs

Suggested Labels

high priority

Suggested Reviewers

  • GuoLei1990
  • singlecoder

Poem

🐰 Joints and colliders dance with grace,
Matrix magic leaves no trace!
Rotations spin, transforms align,
Physics code now more divine!
A rabbit's leap of math delight! 🌟


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 372fe9d and 5399a6e.

📒 Files selected for processing (1)
  • packages/core/src/physics/DynamicCollider.ts (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: e2e (22.x)
  • GitHub Check: codecov
🔇 Additional comments (3)
packages/core/src/physics/DynamicCollider.ts (3)

12-13: LGTM! Good performance optimization.

The addition of static temporary variables for vector and quaternion calculations helps reduce garbage collection pressure by reusing objects.


396-405: LGTM! Good optimization to prevent unnecessary updates.

The addition of equality checks before updating transforms prevents unnecessary triggering of onValueChanged events, which could improve performance.


396-405: Verify if this fixes the joint rotation issue.

Please confirm that these changes resolve the rotation error when adding joints. Consider adding a test case that specifically verifies:

  1. Joint rotation behavior before and after adding the joint
  2. Transform synchronization between the joint and the dynamic collider
✅ Verification successful

The changes correctly handle joint rotation synchronization

The implementation properly maintains transform synchronization between joints and dynamic colliders by:

  • Preventing unnecessary transform updates while preserving accuracy
  • Using proper equality checks for both position and rotation
  • Maintaining consistent behavior with the existing joint rotation test cases
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for related joint rotation tests
echo "Searching for joint rotation tests..."
rg -l "rotation.*joint|joint.*rotation" --type ts

# Look for potential joint rotation issues in commit history
echo "Checking commit history for joint rotation issues..."
git log --grep="rotation.*joint|joint.*rotation" --since="6 months ago"

Length of output: 389


Script:

#!/bin/bash
# Search for test files related to joints and colliders
echo "Searching for test files..."
rg -l "describe.*Joint|test.*Joint" --type ts

# Look for joint initialization and setup code
echo "Searching for joint setup code..."
ast-grep --pattern 'class $_ implements IJoint {
  $$$
}'

# Search for transform sync code in joints
echo "Searching for transform sync in joints..."
rg -A 5 "transform.*sync|worldTransform" --type ts

Length of output: 8873


Script:

#!/bin/bash
# Examine joint rotation test cases
echo "Checking joint rotation test cases..."
rg -A 10 "rotation.*test|test.*rotation" tests/src/core/physics/HingeJoint.test.ts tests/src/core/physics/Joint.test.ts

# Look for any rotation-related assertions
echo "Searching for rotation assertions..."
rg -A 5 "expect.*rotation|assert.*rotation" tests/src/core/physics/

Length of output: 7527

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

codecov bot commented Jan 24, 2025

Codecov Report

Attention: Patch coverage is 92.15686% with 8 lines in your changes missing coverage. Please review.

Project coverage is 68.58%. Comparing base (e8f8359) to head (5399a6e).
Report is 16 commits behind head on main.

Files with missing lines Patch % Lines
...ce-deserialize/resources/parser/HierarchyParser.ts 80.48% 8 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2526      +/-   ##
==========================================
- Coverage   69.01%   68.58%   -0.44%     
==========================================
  Files         957      957              
  Lines      100078   100123      +45     
  Branches     8560     8571      +11     
==========================================
- Hits        69070    68669     -401     
- Misses      30752    31198     +446     
  Partials      256      256              
Flag Coverage Δ
unittests 68.58% <92.15%> (-0.44%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (8)
packages/core/src/physics/joint/Joint.ts (1)

279-289: Joint rotation update logic looks correct.
Your approach in _updateRotation() correctly computes a relative quaternion when a connected collider exists, and defaults to the entity’s own rotation otherwise. Consider reusing a static quaternion to reduce allocations in performance-critical contexts.

packages/design/src/physics/joints/IJoint.ts (1)

29-34: New method for setting joint rotation is consistent with the design.
Exposing setRotation(value: Quaternion) ensures external control over joint orientation. For clarity, consider adding remarks explaining which coordinate space the rotation is relative to (world or local).

packages/physics-physx/src/joint/PhysXHingeJoint.ts (3)

13-15: Consider initializing or validating _axis.
If _axis remains undefined at usage, it could lead to subtle issues. Initializing it explicitly or adding null checks would ensure safer usage.

-  private _axis: Vector3;
+  private _axis: Vector3 = new Vector3();

30-33: Recalculate axis only when needed.
Recomputing this.setAxis(...) every time setRotation is called may be an acceptable trade-off. However, if setRotation is called frequently, consider caching or deferring this recalculation for potential performance gains.


57-61: Validate passed anchor if context requires.
Currently, there's no explicit check for invalid or null anchors. This is likely intentional, but if runtime errors or weird behavior can occur with invalid anchors, consider adding safety checks or logs.

packages/loader/src/resource-deserialize/resources/parser/HierarchyParser.ts (2)

38-38: Prefer arrow functions or method references without .bind.
Using arrow functions in class fields can reduce noise from .bind(this). However, this is stylistic and can be ignored if .bind usage remains consistent.


302-316: Verify component class resolution & handle errors.
A missing or invalid Loader.getClass(key) call may break parsing. Consider logging or handling exceptions if the class cannot be found.

packages/core/src/physics/DynamicCollider.ts (1)

396-402: Consider epsilon-based comparisons.
Floating point inaccuracies may cause unexpected mismatches in Vector3.equals() or Quaternion.equals(). If minor deltas are acceptable, use approximate comparisons.

-if (!Vector3.equals(outPosition, worldPosition) || !Quaternion.equals(outRotation, worldRotationQuaternion)) {
+if (!Vector3.equals(outPosition, worldPosition, 1e-5) || !Quaternion.equals(outRotation, worldRotationQuaternion, 1e-5)) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7fc8cc7 and 372fe9d.

📒 Files selected for processing (7)
  • packages/core/src/physics/DynamicCollider.ts (2 hunks)
  • packages/core/src/physics/joint/Joint.ts (6 hunks)
  • packages/design/src/physics/joints/IJoint.ts (2 hunks)
  • packages/loader/src/resource-deserialize/resources/parser/HierarchyParser.ts (5 hunks)
  • packages/physics-physx/src/joint/PhysXHingeJoint.ts (2 hunks)
  • packages/physics-physx/src/joint/PhysXJoint.ts (2 hunks)
  • tests/src/core/physics/Joint.test.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: e2e (22.x)
  • GitHub Check: codecov
🔇 Additional comments (14)
packages/core/src/physics/joint/Joint.ts (6)

2-2: Import statement alignment looks good.
Your addition of Matrix and Quaternion for advanced calculations is valid.


18-18: Use of a static temporary matrix is appropriate to reduce allocations.
Declaring _tempMatrix helps avoid creating new Matrix objects in loops or frequently called methods.


44-44: Rotation update on connectedCollider assignment is a sensible approach.
Invoking _updateRotation() ensures the joint’s rotation is synchronized whenever the connected collider changes. Confirm that this behavior is desired even if the collider is null.


201-201: Re-initializing joint rotation upon enabling the scene is good practice.
This ensures the rotation is set properly before the joint becomes active.


242-246: Matrix-based transform of anchors is clear and robust.
Using transformCoordinate to convert the anchor’s local position to world space, then applying the inverse of the connected collider’s world matrix, accurately yields the connected anchor’s local position. Keep an eye on non-uniform scaling if extended usage arises.


248-248: Fallback for disconnected collider appears consistent.
Applying the entity’s world transform directly to connectedActualAnchor ensures continuity when no connected collider is set.

packages/physics-physx/src/joint/PhysXJoint.ts (4)

14-16: Protected fields for anchors and rotation improve internal state tracking.
Initializations for _anchor, _connectedAnchor, and _rotation are clearly defined, facilitating direct usage within _setLocalPose. Just ensure these vectors are properly updated when switching collider references.


39-39: Storing anchor in _anchor aligns with local pose logic.
This assignment maintains the anchor for subsequent transformations or re-initializations.


46-48: Connected anchor now accounts for the joint’s stored rotation.
Passing this._rotation to _setLocalPose(1, value, this._rotation) ensures consistent orientation.


50-52: New setRotation method properly copies and applies the quaternion.
Copying into _rotation and immediately applying the local pose ensures synchronization with connected anchor. If you need an identity rotation for the second actor in certain cases, consider validating _connectedAnchor or the connected collider before applying.

packages/loader/src/resource-deserialize/resources/parser/HierarchyParser.ts (1)

55-55: Add coverage for _parseAddedComponents.
You're now calling _parseAddedComponents after regular parsing. Ensure you have tests verifying that added components parse and attach properly to stripped entities.

packages/core/src/physics/DynamicCollider.ts (1)

12-14: Good GC optimization.
Using static temp variables is a great way to reduce frequent object allocations in tight loops and real-time physics updates.

tests/src/core/physics/Joint.test.ts (2)

137-147: Validate Euler vs quaternion usage.
Assigning a Vector3 to .rotation presumably sets Euler angles. Confirm that your engine’s rotation property expects Euler angles, not quaternions.


148-166: Great coverage for connected collider rotation.
These additional checks ensure both primary and connected colliders maintain rotations—helpful for verifying multi-body constraints.

Comment on lines +39 to +49
this._axis = value;
const xAxis = PhysXHingeJoint._xAxis;
const axisRotationQuaternion = this._axisRotationQuaternion;
xAxis.set(1, 0, 0);
const angle = Math.acos(Vector3.dot(xAxis, value));
Vector3.cross(xAxis, value, xAxis);
Quaternion.rotationAxisAngle(xAxis, angle, axisRotationQuaternion);
this._setLocalPose(0, this._anchor, axisRotationQuaternion);
this._setLocalPose(1, this._connectedAnchor, axisRotationQuaternion);
const connectedAxisRotationQuaternion = this._connectedAxisRotationQuaternion;
Quaternion.multiply(this._rotation, axisRotationQuaternion, connectedAxisRotationQuaternion);
this._setLocalPose(1, this._connectedAnchor, connectedAxisRotationQuaternion);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Clamp the domain of Math.acos.
When computing the angle via Math.acos(Vector3.dot(xAxis, value)), floating-point imprecision may cause the argument to exceed the [-1, 1] range, resulting in NaN. Safeguard by clamping the dot product.

 const angle = Math.acos(
-  Vector3.dot(xAxis, value)
+  Math.min(1, Math.max(-1, Vector3.dot(xAxis, value)))
 );
📝 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.

Suggested change
this._axis = value;
const xAxis = PhysXHingeJoint._xAxis;
const axisRotationQuaternion = this._axisRotationQuaternion;
xAxis.set(1, 0, 0);
const angle = Math.acos(Vector3.dot(xAxis, value));
Vector3.cross(xAxis, value, xAxis);
Quaternion.rotationAxisAngle(xAxis, angle, axisRotationQuaternion);
this._setLocalPose(0, this._anchor, axisRotationQuaternion);
this._setLocalPose(1, this._connectedAnchor, axisRotationQuaternion);
const connectedAxisRotationQuaternion = this._connectedAxisRotationQuaternion;
Quaternion.multiply(this._rotation, axisRotationQuaternion, connectedAxisRotationQuaternion);
this._setLocalPose(1, this._connectedAnchor, connectedAxisRotationQuaternion);
this._axis = value;
const xAxis = PhysXHingeJoint._xAxis;
const axisRotationQuaternion = this._axisRotationQuaternion;
xAxis.set(1, 0, 0);
const angle = Math.acos(Math.min(1, Math.max(-1, Vector3.dot(xAxis, value))));
Vector3.cross(xAxis, value, xAxis);
Quaternion.rotationAxisAngle(xAxis, angle, axisRotationQuaternion);
this._setLocalPose(0, this._anchor, axisRotationQuaternion);
const connectedAxisRotationQuaternion = this._connectedAxisRotationQuaternion;
Quaternion.multiply(this._rotation, axisRotationQuaternion, connectedAxisRotationQuaternion);
this._setLocalPose(1, this._connectedAnchor, connectedAxisRotationQuaternion);

Comment on lines +137 to +154
private _parseAddedComponents() {
const entityMap = this.context.entityMap;
const entityConfigMap = this.context.entityConfigMap;
const strippedIds = this.context.strippedIds;
const promises = [];

for (let i = 0, n = strippedIds.length; i < n; i++) {
const entityConfig = entityConfigMap.get(strippedIds[i]) as IStrippedEntity;
const prefabContext = this._prefabContextMap.get(entityMap.get(entityConfig.prefabInstanceId));
const entity = prefabContext.entityMap.get(entityConfig.prefabSource.entityId);
this._addComponents(entity, entityConfig.components, promises);
}
for (const waitingList of this.context.componentWaitingMap.values()) {
waitingList.forEach((resolve) => resolve(null));
}

return Promise.all(promises);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Check for null prefabContext.
If _prefabContextMap lacks an entry for the entity, prefabContext could be undefined, causing a runtime error at line 146. Consider adding a fallback or error log.

+    if (!prefabContext) {
+      console.warn(`No prefab context found for ${entityConfig.prefabInstanceId}. Skipping component addition.`);
+      continue;
+    }
📝 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.

Suggested change
private _parseAddedComponents() {
const entityMap = this.context.entityMap;
const entityConfigMap = this.context.entityConfigMap;
const strippedIds = this.context.strippedIds;
const promises = [];
for (let i = 0, n = strippedIds.length; i < n; i++) {
const entityConfig = entityConfigMap.get(strippedIds[i]) as IStrippedEntity;
const prefabContext = this._prefabContextMap.get(entityMap.get(entityConfig.prefabInstanceId));
const entity = prefabContext.entityMap.get(entityConfig.prefabSource.entityId);
this._addComponents(entity, entityConfig.components, promises);
}
for (const waitingList of this.context.componentWaitingMap.values()) {
waitingList.forEach((resolve) => resolve(null));
}
return Promise.all(promises);
}
private _parseAddedComponents() {
const entityMap = this.context.entityMap;
const entityConfigMap = this.context.entityConfigMap;
const strippedIds = this.context.strippedIds;
const promises = [];
for (let i = 0, n = strippedIds.length; i < n; i++) {
const entityConfig = entityConfigMap.get(strippedIds[i]) as IStrippedEntity;
const prefabContext = this._prefabContextMap.get(entityMap.get(entityConfig.prefabInstanceId));
if (!prefabContext) {
console.warn(`No prefab context found for ${entityConfig.prefabInstanceId}. Skipping component addition.`);
continue;
}
const entity = prefabContext.entityMap.get(entityConfig.prefabSource.entityId);
this._addComponents(entity, entityConfig.components, promises);
}
for (const waitingList of this.context.componentWaitingMap.values()) {
waitingList.forEach((resolve) => resolve(null));
}
return Promise.all(promises);
}

@GuoLei1990 GuoLei1990 merged commit 0070d14 into galacean:main Jan 24, 2025
8 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working ignore for release ignore for release physics Engine's physical system
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants