🚀 Auto-deploy: melhoria no snap e medição AR em 30/05/2026 10:28:12
This commit is contained in:
@@ -3,6 +3,7 @@ import { useFrame } from '@react-three/fiber';
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { useControllerGrab, ControllerGrabSnapshot } from '@/hooks/useControllerGrab';
|
import { useControllerGrab, ControllerGrabSnapshot } from '@/hooks/useControllerGrab';
|
||||||
import { useModelStore } from '@/stores/useModelStore';
|
import { useModelStore } from '@/stores/useModelStore';
|
||||||
|
import { activeModelGroupRef } from './xrCalibrationBus';
|
||||||
|
|
||||||
interface XRGrabbableProps {
|
interface XRGrabbableProps {
|
||||||
/**
|
/**
|
||||||
@@ -72,7 +73,71 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta
|
|||||||
single.current = null;
|
single.current = null;
|
||||||
}, [scaleResetNonce]);
|
}, [scaleResetNonce]);
|
||||||
|
|
||||||
useFrame(() => {
|
useEffect(() => {
|
||||||
|
activeModelGroupRef.current = groupRef.current;
|
||||||
|
return () => {
|
||||||
|
if (activeModelGroupRef.current === groupRef.current) {
|
||||||
|
activeModelGroupRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const syncGrabToStore = () => {
|
||||||
|
const group = groupRef.current;
|
||||||
|
if (!group) return;
|
||||||
|
|
||||||
|
const state = useModelStore.getState();
|
||||||
|
const activeModelId = state.activeModelId;
|
||||||
|
if (!activeModelId) return;
|
||||||
|
|
||||||
|
const activeModel = state.models.find(m => m.id === activeModelId);
|
||||||
|
if (!activeModel) return;
|
||||||
|
|
||||||
|
const ft = { ...activeModel.fineTuning };
|
||||||
|
const renderFactor = state.scaleRatio?.factor ?? 1;
|
||||||
|
|
||||||
|
// Model's matrix relative to XRGrabbable
|
||||||
|
const posModel = new THREE.Vector3(ft.posX * renderFactor, ft.posY * renderFactor, ft.posZ * renderFactor);
|
||||||
|
const rotModel = new THREE.Euler(
|
||||||
|
(ft.rotX * Math.PI) / 180,
|
||||||
|
(ft.rotY * Math.PI) / 180,
|
||||||
|
(ft.rotZ * Math.PI) / 180,
|
||||||
|
'YXZ'
|
||||||
|
);
|
||||||
|
const quatModel = new THREE.Quaternion().setFromEuler(rotModel);
|
||||||
|
const scaleModel = new THREE.Vector3(ft.scale * renderFactor, ft.scale * renderFactor, ft.scale * renderFactor);
|
||||||
|
|
||||||
|
const mModel = new THREE.Matrix4().compose(posModel, quatModel, scaleModel);
|
||||||
|
|
||||||
|
// Grab matrix of XRGrabbable group
|
||||||
|
const mGrab = new THREE.Matrix4().compose(group.position, group.quaternion, group.scale);
|
||||||
|
|
||||||
|
// Combined matrix
|
||||||
|
const mCombined = new THREE.Matrix4().multiplyMatrices(mGrab, mModel);
|
||||||
|
|
||||||
|
const finalPos = new THREE.Vector3();
|
||||||
|
const finalQuat = new THREE.Quaternion();
|
||||||
|
const finalScale = new THREE.Vector3();
|
||||||
|
mCombined.decompose(finalPos, finalQuat, finalScale);
|
||||||
|
|
||||||
|
// Reset grabbable group to identity so the transform is held entirely by fineTuning
|
||||||
|
group.position.set(0, 0, 0);
|
||||||
|
group.quaternion.identity();
|
||||||
|
group.scale.set(1, 1, 1);
|
||||||
|
|
||||||
|
// Save to store
|
||||||
|
state.setFineTuning({
|
||||||
|
posX: finalPos.x / renderFactor,
|
||||||
|
posY: finalPos.y / renderFactor,
|
||||||
|
posZ: finalPos.z / renderFactor,
|
||||||
|
rotX: (new THREE.Euler().setFromQuaternion(finalQuat, 'YXZ').x * 180) / Math.PI,
|
||||||
|
rotY: (new THREE.Euler().setFromQuaternion(finalQuat, 'YXZ').y * 180) / Math.PI,
|
||||||
|
rotZ: (new THREE.Euler().setFromQuaternion(finalQuat, 'YXZ').z * 180) / Math.PI,
|
||||||
|
scale: finalScale.x / renderFactor,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useFrame((_state, dt) => {
|
||||||
const group = groupRef.current;
|
const group = groupRef.current;
|
||||||
if (!group) return;
|
if (!group) return;
|
||||||
const snap: ControllerGrabSnapshot = grab.current;
|
const snap: ControllerGrabSnapshot = grab.current;
|
||||||
@@ -144,6 +209,7 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta
|
|||||||
} else if (dual.current) {
|
} else if (dual.current) {
|
||||||
console.log('[XR][grab] ◆ TWO-HAND end');
|
console.log('[XR][grab] ◆ TWO-HAND end');
|
||||||
dual.current = null;
|
dual.current = null;
|
||||||
|
syncGrabToStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── One-hand mode (PAN ONLY - HORIZONTAL X/Z) ────────────────
|
// ─── One-hand mode (PAN ONLY - HORIZONTAL X/Z) ────────────────
|
||||||
@@ -162,10 +228,26 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta
|
|||||||
|
|
||||||
single.current = { hand: activeHand, offsetWorld, startWorldY };
|
single.current = { hand: activeHand, offsetWorld, startWorldY };
|
||||||
if (!everGrabbed.current) { everGrabbed.current = true; onGrabStart?.(); }
|
if (!everGrabbed.current) { everGrabbed.current = true; onGrabStart?.(); }
|
||||||
console.log(`[XR][grab] ● ONE-HAND start (${activeHand}) — horizontal pan`);
|
console.log(`[XR][grab] ● ONE-HAND start (${activeHand}) — horizontal pan & rotation`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Target world position = controller + initial offset
|
// Rotação Y via analógico do controle ativo
|
||||||
|
const gp = slot.gamepad;
|
||||||
|
if (gp && gp.axes.length >= 4) {
|
||||||
|
const stickX = gp.axes[2]; // horizontal do thumbstick
|
||||||
|
const deadzone = 0.15;
|
||||||
|
if (Math.abs(stickX) > deadzone) {
|
||||||
|
const rotationSpeed = 1.5; // rad por segundo
|
||||||
|
const angle = Math.sign(stickX) * (Math.abs(stickX) - deadzone) * rotationSpeed * dt;
|
||||||
|
const q = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), -angle);
|
||||||
|
group.quaternion.multiplyQuaternions(q, group.quaternion);
|
||||||
|
|
||||||
|
// Rotaciona o offset para girar ao redor da mão
|
||||||
|
single.current.offsetWorld.applyQuaternion(q);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Target world position = controller + initial/rotated offset
|
||||||
const targetWorldPos = new THREE.Vector3().addVectors(ctrlPos, single.current.offsetWorld);
|
const targetWorldPos = new THREE.Vector3().addVectors(ctrlPos, single.current.offsetWorld);
|
||||||
|
|
||||||
// Trava a translação no plano horizontal (XZ de mundo), preservando a altura Y original de calibração!
|
// Trava a translação no plano horizontal (XZ de mundo), preservando a altura Y original de calibração!
|
||||||
@@ -179,7 +261,9 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta
|
|||||||
}
|
}
|
||||||
group.position.copy(targetWorldPos);
|
group.position.copy(targetWorldPos);
|
||||||
} else if (single.current) {
|
} else if (single.current) {
|
||||||
|
console.log('[XR][grab] ● ONE-HAND end');
|
||||||
single.current = null;
|
single.current = null;
|
||||||
|
syncGrabToStore();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -270,3 +270,5 @@ export function computeVirtRealTransform(
|
|||||||
|
|
||||||
return { quaternion, position };
|
return { quaternion, position };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const activeModelGroupRef = { current: null as THREE.Group | null };
|
||||||
|
|||||||
+36
-1
@@ -34,6 +34,7 @@ import {
|
|||||||
pushXRRealPoint,
|
pushXRRealPoint,
|
||||||
pushXRVirtualPoint,
|
pushXRVirtualPoint,
|
||||||
computeVirtRealTransform,
|
computeVirtRealTransform,
|
||||||
|
activeModelGroupRef,
|
||||||
} from '@/components/three/xrCalibrationBus';
|
} from '@/components/three/xrCalibrationBus';
|
||||||
|
|
||||||
// --- Diagnóstico XR ---
|
// --- Diagnóstico XR ---
|
||||||
@@ -973,10 +974,43 @@ function XRVirtRealDesktopAligner() {
|
|||||||
function XRGrid() {
|
function XRGrid() {
|
||||||
const showGrid = useModelStore((s) => s.showGrid);
|
const showGrid = useModelStore((s) => s.showGrid);
|
||||||
const gridY = useModelStore((s) => s.gridY);
|
const gridY = useModelStore((s) => s.gridY);
|
||||||
|
const activeModelId = useModelStore((s) => s.activeModelId);
|
||||||
|
const activeModel = useModelStore((s) => s.models.find(m => m.id === activeModelId));
|
||||||
|
const groupRef = useRef<THREE.Group>(null);
|
||||||
|
|
||||||
|
useFrame(() => {
|
||||||
|
if (!showGrid || !groupRef.current) return;
|
||||||
|
|
||||||
|
let px = 0;
|
||||||
|
let pz = 0;
|
||||||
|
let ry = 0;
|
||||||
|
|
||||||
|
if (activeModelGroupRef.current) {
|
||||||
|
const g = activeModelGroupRef.current;
|
||||||
|
const wp = new THREE.Vector3();
|
||||||
|
g.getWorldPosition(wp);
|
||||||
|
px = wp.x;
|
||||||
|
pz = wp.z;
|
||||||
|
|
||||||
|
const wq = new THREE.Quaternion();
|
||||||
|
g.getWorldQuaternion(wq);
|
||||||
|
const euler = new THREE.Euler().setFromQuaternion(wq, 'YXZ');
|
||||||
|
ry = euler.y;
|
||||||
|
} else if (activeModel) {
|
||||||
|
px = activeModel.fineTuning.posX;
|
||||||
|
pz = activeModel.fineTuning.posZ;
|
||||||
|
ry = (activeModel.fineTuning.rotY * Math.PI) / 180;
|
||||||
|
}
|
||||||
|
|
||||||
|
groupRef.current.position.set(px, gridY, pz);
|
||||||
|
groupRef.current.rotation.set(0, ry, 0);
|
||||||
|
});
|
||||||
|
|
||||||
if (!showGrid) return null;
|
if (!showGrid) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<group ref={groupRef}>
|
||||||
<Grid
|
<Grid
|
||||||
position={[0, gridY, 0]}
|
|
||||||
infiniteGrid
|
infiniteGrid
|
||||||
cellSize={0.01}
|
cellSize={0.01}
|
||||||
sectionSize={0.1}
|
sectionSize={0.1}
|
||||||
@@ -987,6 +1021,7 @@ function XRGrid() {
|
|||||||
fadeDistance={5}
|
fadeDistance={5}
|
||||||
fadeStrength={1}
|
fadeStrength={1}
|
||||||
/>
|
/>
|
||||||
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user