From af52d5992c610a4252cc4fba891c055403c5a6af Mon Sep 17 00:00:00 2001 From: Michael Mainguy Date: Sat, 15 Nov 2025 07:36:57 -0600 Subject: [PATCH] Fix handle rotation to properly match mesh orientation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- package.json | 2 +- src/gizmos/ResizeGizmo/ResizeGizmoVisuals.ts | 32 ++++++++++++++++---- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index cc0acb7..74951e8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "immersive", "private": true, - "version": "0.0.8-22", + "version": "0.0.8-23", "type": "module", "license": "MIT", "engines": { diff --git a/src/gizmos/ResizeGizmo/ResizeGizmoVisuals.ts b/src/gizmos/ResizeGizmo/ResizeGizmoVisuals.ts index aae3e39..83cf59a 100644 --- a/src/gizmos/ResizeGizmo/ResizeGizmoVisuals.ts +++ b/src/gizmos/ResizeGizmo/ResizeGizmoVisuals.ts @@ -12,7 +12,8 @@ import { Color3, UtilityLayerRenderer, LinesMesh, - Vector3 + Vector3, + Quaternion } from "@babylonjs/core"; import { HandlePosition, HandleType } from "./types"; import { ResizeGizmoConfigManager } from "./ResizeGizmoConfig"; @@ -90,7 +91,7 @@ export class ResizeGizmoVisuals { // Update visuals this.updateBoundingBox(); - this.updateHandlePositions(); + this.updateHandleTransforms(); } /** @@ -240,7 +241,15 @@ export class ResizeGizmoVisuals { 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; // 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) { const mesh = this._handleMeshes.get(handle.id); 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); } } }