fix(webclient): fix XR panel snapping to face away from user when moved#629
Conversation
…gged to the side Two bugs in the face-camera rotation logic in CloudXRUI: 1. Missing angle wrapping before damp(): eulerHelper.y lives in [-π, +π]. When the panel is near the side of the scene the target angle can flip from +π-ε to -π+ε — geometrically ~0° but numerically ~2π. damp() is a plain lerp so it interpolated the full ~2π the wrong way, spinning the panel to face backward. Fix wraps the diff to [-π, π] before calling damp() so it always takes the shortest rotation path. 2. 3D quaternion for a yaw-only problem: setFromUnitVectors(zAxis, dir3D) computed the shortest-path 3D rotation including height offset, so the extracted Euler Y was not the correct horizontal yaw when camera and panel heights differed. Replaced with Math.atan2(dx, dz) on the XZ plane which gives correct horizontal yaw directly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthrough
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
gareth-morgan-nv
left a comment
There was a problem hiding this comment.
LGTM. Maybe add a test?
Handle's defaultApply copies state.current.quaternion to the target on every
drag frame (useFrame priority -1) and again on drag release. With rotate={false},
state.current.quaternion is always the quaternion captured at drag-start, so it
continuously undoes whatever rotation face-camera (priority 0) applied, and
wipes all face-camera work entirely when the drag ends. This caused the panel
to snap to the rotation it had when the drag began — up to 90° away from the
correct facing direction.
Fix: pass a custom apply function (applyPositionSkipRotation) to Handle that
copies only position, leaving quaternion/rotation entirely under face-camera
control throughout and after the drag.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
f46a423 to
5968908
Compare
Closes #546
Summary
setFromUnitVectors(zAxis, dir3D)+setFromQuaternion('YXZ')to extract horizontal yaw. Becausedir3Dincluded the camera's height offset, the extracted Euler Y was not the true horizontal yaw. Replaced withMath.atan2(dx, dz)on the XZ plane.damp()call had no angle wrapping. When the target yaw crossed the ±π boundary,damp()took the ~2π wrong-way path, spinning the panel to face backward. Normalizeddiffto[-π, π]before callingdamp().@pmndrs/handle'sdefaultApplycopiesstate.current.quaternionto the target on every drag frame (useFrame priority -1, before face-camera at priority 0) and again on drag release. Withrotate={false}, that quaternion is always the one captured at drag-start, so it continuously reset the face-camera rotation mid-drag and wiped it entirely on release — snapping the panel up to 90° off after every drag. Fixed by passing a customapplyfunction (applyPositionSkipRotation) toHandlethat copies only position, leaving quaternion/rotation entirely under face-camera control.Test plan
🤖 Generated with Claude Code