diff --git a/src/components/three/ControllerLocomotion.tsx b/src/components/three/ControllerLocomotion.tsx
index 7855fd1..b2da62b 100644
--- a/src/components/three/ControllerLocomotion.tsx
+++ b/src/components/three/ControllerLocomotion.tsx
@@ -87,6 +87,8 @@ export function ControllerLocomotion({ rigRef }: Props) {
const ctrlPose = frame.getPose(activeSource.targetRaySpace, referenceSpace);
if (ctrlPose) {
const m = new THREE.Matrix4().fromArray(ctrlPose.transform.matrix);
+ // Aplica a matriz de mundo do rig para alinhar a origem física com a locomoção do rig
+ m.premultiply(rig.matrixWorld);
origin.setFromMatrixPosition(m);
const q = new THREE.Quaternion().setFromRotationMatrix(m);
dir.set(0, 0, -1).applyQuaternion(q).normalize();
diff --git a/src/components/three/XRControllerMeasure.tsx b/src/components/three/XRControllerMeasure.tsx
index cd62bd7..b701be7 100644
--- a/src/components/three/XRControllerMeasure.tsx
+++ b/src/components/three/XRControllerMeasure.tsx
@@ -131,6 +131,10 @@ export function XRControllerMeasure() {
if (!pose) return;
const m = new THREE.Matrix4().fromArray(pose.transform.matrix);
+ const xrRig = useModelStore.getState().xrRig;
+ if (xrRig) {
+ m.premultiply(xrRig.matrixWorld);
+ }
tmpOrigin.current.setFromMatrixPosition(m);
tmpQuat.current.setFromRotationMatrix(m);
tmpDir.current.set(0, 0, -1).applyQuaternion(tmpQuat.current).normalize();
diff --git a/src/hooks/useControllerGrab.ts b/src/hooks/useControllerGrab.ts
index 0fcb596..726a7a1 100644
--- a/src/hooks/useControllerGrab.ts
+++ b/src/hooks/useControllerGrab.ts
@@ -3,6 +3,7 @@ import { useFrame, useThree } from '@react-three/fiber';
import * as THREE from 'three';
// DEVKIT: remove this import + the fake-input block in useFrame to strip devkit
import { fakeInput, isFakeActive } from '@/devkit/fakeInputStore';
+import { useModelStore } from '@/stores/useModelStore';
export interface GrabState {
/** Analog grip pressure (0..1) */
@@ -108,6 +109,10 @@ export function useControllerGrab() {
const pose = frame.getPose(source.gripSpace, referenceSpace);
if (pose) {
slot.gripWorld.fromArray(pose.transform.matrix);
+ const xrRig = useModelStore.getState().xrRig;
+ if (xrRig) {
+ slot.gripWorld.premultiply(xrRig.matrixWorld);
+ }
slot.hasPose = true;
}
}
diff --git a/src/pages/XRSession.tsx b/src/pages/XRSession.tsx
index 34f7042..562c5c7 100644
--- a/src/pages/XRSession.tsx
+++ b/src/pages/XRSession.tsx
@@ -1157,7 +1157,10 @@ const XRSession = () => {
-
+ {
+ (rigRef as React.MutableRefObject).current = el;
+ useModelStore.getState().setXRRig(el);
+ }}>
diff --git a/src/stores/useModelStore.ts b/src/stores/useModelStore.ts
index 56ad8dd..edd9d27 100644
--- a/src/stores/useModelStore.ts
+++ b/src/stores/useModelStore.ts
@@ -372,6 +372,9 @@ interface ModelStore {
ifcMaterialColors: boolean;
setIfcMaterialColors: (enabled: boolean) => void;
+
+ xrRig: THREE.Group | null;
+ setXRRig: (rig: THREE.Group | null) => void;
}
/** Sync top-level legacy `model` / `fineTuning` from active SceneModel. */
@@ -384,6 +387,9 @@ function syncActive(state: Pick): { mode
}
export const useModelStore = create((set, get) => ({
+ xrRig: null,
+ setXRRig: (rig) => set({ xrRig: rig }),
+
// ── Multi-model ─────────────
models: [],
activeModelId: null,