🚀 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 { useControllerGrab, ControllerGrabSnapshot } from '@/hooks/useControllerGrab';
|
||||
import { useModelStore } from '@/stores/useModelStore';
|
||||
import { activeModelGroupRef } from './xrCalibrationBus';
|
||||
|
||||
interface XRGrabbableProps {
|
||||
/**
|
||||
@@ -72,7 +73,71 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta
|
||||
single.current = null;
|
||||
}, [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;
|
||||
if (!group) return;
|
||||
const snap: ControllerGrabSnapshot = grab.current;
|
||||
@@ -144,6 +209,7 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta
|
||||
} else if (dual.current) {
|
||||
console.log('[XR][grab] ◆ TWO-HAND end');
|
||||
dual.current = null;
|
||||
syncGrabToStore();
|
||||
}
|
||||
|
||||
// ─── 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 };
|
||||
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);
|
||||
|
||||
// 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);
|
||||
} else if (single.current) {
|
||||
console.log('[XR][grab] ● ONE-HAND end');
|
||||
single.current = null;
|
||||
syncGrabToStore();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -270,3 +270,5 @@ export function computeVirtRealTransform(
|
||||
|
||||
return { quaternion, position };
|
||||
}
|
||||
|
||||
export const activeModelGroupRef = { current: null as THREE.Group | null };
|
||||
|
||||
+36
-1
@@ -34,6 +34,7 @@ import {
|
||||
pushXRRealPoint,
|
||||
pushXRVirtualPoint,
|
||||
computeVirtRealTransform,
|
||||
activeModelGroupRef,
|
||||
} from '@/components/three/xrCalibrationBus';
|
||||
|
||||
// --- Diagnóstico XR ---
|
||||
@@ -973,10 +974,43 @@ function XRVirtRealDesktopAligner() {
|
||||
function XRGrid() {
|
||||
const showGrid = useModelStore((s) => s.showGrid);
|
||||
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;
|
||||
|
||||
return (
|
||||
<group ref={groupRef}>
|
||||
<Grid
|
||||
position={[0, gridY, 0]}
|
||||
infiniteGrid
|
||||
cellSize={0.01}
|
||||
sectionSize={0.1}
|
||||
@@ -987,6 +1021,7 @@ function XRGrid() {
|
||||
fadeDistance={5}
|
||||
fadeStrength={1}
|
||||
/>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user