🚀 Auto-deploy: melhoria no snap e medição AR em 04/08/2026 20:55:34
This commit is contained in:
@@ -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
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user