Fix handle rotation to properly match mesh orientation

Handle meshes now correctly rotate to match the target mesh's world-space
orientation instead of appearing axis-aligned.

Root Cause:
- Handle positions from HandleGeometry are calculated in world space
- Setting mesh.position treats values as local space
- This created coordinate system mismatch when rotation was also set
- Result: rotation appeared to have no effect

Solution:
- Extract rotation from mesh world matrix using quaternion decomposition
- Set rotation FIRST (before position)
- Use setAbsolutePosition() for world-space positioning
- This ensures rotation and position work correctly together

Changes:
- Import Quaternion from @babylonjs/core
- Update createHandleMeshes(): decompose world matrix, set rotation,
  then use setAbsolutePosition()
- Rename updateHandlePositions() to updateHandleTransforms()
- Update updateHandleTransforms(): same rotation-then-position approach
- Add null check for _targetMesh in updateHandleTransforms()

Technical Details:
- computeWorldMatrix(true) gets complete transform including parent
- decompose() extracts pure rotation as quaternion (avoids gimbal lock)
- setAbsolutePosition() correctly handles world-space coords with rotation
- Order matters: rotation before position for correct transformation

Result:
 Handle box shapes visually tilt/rotate with mesh
 Handles remain correctly positioned on OBB
 Both wireframe and individual handles rotate together

Files modified:
- ResizeGizmoVisuals.ts: Handle rotation implementation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2025-11-15 07:36:57 -06:00
parent 5fbf2b87c1
commit af52d5992c
2 changed files with 27 additions and 7 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "immersive", "name": "immersive",
"private": true, "private": true,
"version": "0.0.8-22", "version": "0.0.8-23",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"engines": { "engines": {

View File

@ -12,7 +12,8 @@ import {
Color3, Color3,
UtilityLayerRenderer, UtilityLayerRenderer,
LinesMesh, LinesMesh,
Vector3 Vector3,
Quaternion
} from "@babylonjs/core"; } from "@babylonjs/core";
import { HandlePosition, HandleType } from "./types"; import { HandlePosition, HandleType } from "./types";
import { ResizeGizmoConfigManager } from "./ResizeGizmoConfig"; import { ResizeGizmoConfigManager } from "./ResizeGizmoConfig";
@ -90,7 +91,7 @@ export class ResizeGizmoVisuals {
// Update visuals // Update visuals
this.updateBoundingBox(); this.updateBoundingBox();
this.updateHandlePositions(); this.updateHandleTransforms();
} }
/** /**
@ -240,7 +241,15 @@ export class ResizeGizmoVisuals {
this._utilityLayer.utilityLayerScene this._utilityLayer.utilityLayerScene
); );
mesh.position = handle.position.clone(); // Extract and set rotation first (from world matrix)
const worldMatrix = this._targetMesh.computeWorldMatrix(true);
const rotation = new Quaternion();
worldMatrix.decompose(undefined, rotation, undefined);
mesh.rotationQuaternion = rotation;
// Set world-space position (works correctly with rotation)
mesh.setAbsolutePosition(handle.position);
mesh.isPickable = true; mesh.isPickable = true;
// Create material // Create material
@ -277,13 +286,24 @@ export class ResizeGizmoVisuals {
} }
/** /**
* Update handle positions * Update handle transforms (position and rotation)
*/ */
private updateHandlePositions(): void { private updateHandleTransforms(): void {
if (!this._targetMesh) {
return;
}
for (const handle of this._handles) { for (const handle of this._handles) {
const mesh = this._handleMeshes.get(handle.id); const mesh = this._handleMeshes.get(handle.id);
if (mesh) { if (mesh) {
mesh.position = handle.position.clone(); // Update rotation to match target mesh (from world matrix)
const worldMatrix = this._targetMesh.computeWorldMatrix(true);
const rotation = new Quaternion();
worldMatrix.decompose(undefined, rotation, undefined);
mesh.rotationQuaternion = rotation;
// Set world-space position (works correctly with rotation)
mesh.setAbsolutePosition(handle.position);
} }
} }
} }