🚀 Auto-deploy: melhoria no snap e medição AR em 04/08/2026 20:55:34

This commit is contained in:
2026-08-04 20:55:34 +00:00
parent 4f5538f3a8
commit 11bbf1e663
2 changed files with 62 additions and 28 deletions
+17 -1
View File
@@ -16,6 +16,8 @@ interface XRGrabbableProps {
lockedActive?: boolean; lockedActive?: boolean;
/** Called the first time the user grabs (e.g. to exit placement mode) */ /** Called the first time the user grabs (e.g. to exit placement mode) */
onGrabStart?: () => void; onGrabStart?: () => void;
/** Optional callback to sync transforms to local state instead of store */
onSync?: (pos: THREE.Vector3, quat: THREE.Quaternion, scale: THREE.Vector3) => void;
children: ReactNode; children: ReactNode;
} }
@@ -54,7 +56,7 @@ const _tmpQuat = new THREE.Quaternion();
* segurar ambos os botões de Grip (L+R) e mover as mãos em direções opostas (giro) ou * segurar ambos os botões de Grip (L+R) e mover as mãos em direções opostas (giro) ou
* afastar/aproximar as mãos (zoom). * afastar/aproximar as mãos (zoom).
*/ */
export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabStart, children }: XRGrabbableProps) { export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabStart, onSync, children }: XRGrabbableProps) {
const groupRef = useRef<THREE.Group>(null); const groupRef = useRef<THREE.Group>(null);
const grab = useControllerGrab(); const grab = useControllerGrab();
const session = useXR((s) => s.session); const session = useXR((s) => s.session);
@@ -88,6 +90,20 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta
const group = groupRef.current; const group = groupRef.current;
if (!group) return; if (!group) return;
if (onSync) {
const finalPos = new THREE.Vector3();
const finalQuat = new THREE.Quaternion();
const finalScale = new THREE.Vector3();
group.matrixWorld.decompose(finalPos, finalQuat, finalScale);
group.position.set(0, 0, 0);
group.quaternion.identity();
group.scale.set(1, 1, 1);
onSync(finalPos, finalQuat, finalScale);
return;
}
const state = useModelStore.getState(); const state = useModelStore.getState();
const activeModelId = state.activeModelId; const activeModelId = state.activeModelId;
if (!activeModelId) return; if (!activeModelId) return;
+45 -27
View File
@@ -19,6 +19,7 @@ import {
Hand, Hand,
} from "lucide-react"; } from "lucide-react";
import { Suspense } from "react"; import { Suspense } from "react";
import { XRGrabbable } from "@/components/three/XRGrabbable";
/** /**
* ============================================================ * ============================================================
@@ -167,36 +168,53 @@ const CubeControlado = ({
}); });
return ( return (
<mesh <XRGrabbable
ref={meshRef} allowScale={true}
castShadow lockedActive={lockedRef.current}
receiveShadow onSync={(pos, quat, scale) => {
position={[0, 0.5, 0]} posRef.current.x = pos.x;
onClick={(e) => { posRef.current.y = Math.max(0.5, pos.y);
e.stopPropagation(); posRef.current.z = pos.z;
if (!e.intersections[0].face) return;
const localNormal = e.intersections[0].face.normal;
if (alignPhase === 'IDLE') { const euler = new THREE.Euler().setFromQuaternion(quat, 'YXZ');
face1Ref.current.copy(localNormal); rotRef.current.x = euler.x;
setAlignPhase('WAIT_SURFACE'); rotRef.current.y = euler.y;
} else if (alignPhase === 'WAIT_FACE2') { rotRef.current.z = euler.z;
if (Math.abs(face1Ref.current.dot(localNormal)) < 0.1) {
face2Ref.current.copy(localNormal); scaleRef.current = scale.x;
setAlignPhase('WAIT_EDGE');
} else {
alert("Por favor, selecione uma face diferente da primeira (ortogonal) para alinhar a direção.");
}
}
}} }}
onPointerOver={(e) => { e.stopPropagation(); document.body.style.cursor = 'pointer'; }}
onPointerOut={() => { document.body.style.cursor = 'auto'; }}
> >
<boxGeometry args={[CUBE_SIZE, CUBE_SIZE, CUBE_SIZE]} /> <mesh
{materials.map((m, i) => ( ref={meshRef}
<primitive attach={`material-${i}`} object={m} key={i} /> castShadow
))} receiveShadow
</mesh> position={[0, 0.5, 0]}
onClick={(e) => {
e.stopPropagation();
if (!e.intersections[0].face) return;
const localNormal = e.intersections[0].face.normal;
if (alignPhase === 'IDLE') {
face1Ref.current.copy(localNormal);
setAlignPhase('WAIT_SURFACE');
} else if (alignPhase === 'WAIT_FACE2') {
if (Math.abs(face1Ref.current.dot(localNormal)) < 0.1) {
face2Ref.current.copy(localNormal);
setAlignPhase('WAIT_EDGE');
} else {
alert("Por favor, selecione uma face diferente da primeira (ortogonal) para alinhar a direção.");
}
}
}}
onPointerOver={(e) => { e.stopPropagation(); document.body.style.cursor = 'pointer'; }}
onPointerOut={() => { document.body.style.cursor = 'auto'; }}
>
<boxGeometry args={[CUBE_SIZE, CUBE_SIZE, CUBE_SIZE]} />
{materials.map((m, i) => (
<primitive attach={`material-${i}`} object={m} key={i} />
))}
</mesh>
</XRGrabbable>
); );
}; };