-
-
Notifications
You must be signed in to change notification settings - Fork 310
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
Conversation
WalkthroughThis 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
Possibly related PRs
Suggested Labels
Suggested Reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
⏰ Context from checks skipped due to timeout of 90000ms (2)
🔇 Additional comments (3)
✨ Finishing Touches
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? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
Codecov ReportAttention: Patch coverage is
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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
There was a problem hiding this 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.
ExposingsetRotation(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.
Recomputingthis.setAxis(...)
every timesetRotation
is called may be an acceptable trade-off. However, ifsetRotation
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 invalidLoader.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 inVector3.equals()
orQuaternion.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
📒 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 ofMatrix
andQuaternion
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.
UsingtransformCoordinate
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 toconnectedActualAnchor
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.
Passingthis._rotation
to_setLocalPose(1, value, this._rotation)
ensures consistent orientation.
50-52
: NewsetRotation
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 aVector3
to.rotation
presumably sets Euler angles. Confirm that your engine’srotation
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.
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); |
There was a problem hiding this comment.
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.
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); |
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); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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); | |
} |
Please check if the PR fulfills these requirements
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
Bug Fixes
Refactor
Tests