🚀 Auto-deploy: melhoria no snap e medição AR em 29/05/2026 20:32:41
This commit is contained in:
@@ -31,6 +31,9 @@ import {
|
||||
pushXRModelFaceNormal,
|
||||
computeXRCalibrationQuaternion,
|
||||
subscribeXRCalibration,
|
||||
pushXRRealPoint,
|
||||
pushXRVirtualPoint,
|
||||
computeVirtRealTransform,
|
||||
} from '@/components/three/xrCalibrationBus';
|
||||
|
||||
// --- Diagnóstico XR ---
|
||||
@@ -110,6 +113,7 @@ function XRModel({ sceneModel }: { sceneModel: import('@/stores/useModelStore').
|
||||
const ref = useRef<THREE.Group>(null);
|
||||
const calGroupRef = useRef<THREE.Group>(null);
|
||||
const localFrameRef = useRef<THREE.Group>(null);
|
||||
const topGroupRef = useRef<THREE.Group>(null);
|
||||
|
||||
// Escuta mudanças de calibração para re-renderizar no AR
|
||||
const [, setCalTick] = useState(0);
|
||||
@@ -229,6 +233,7 @@ function XRModel({ sceneModel }: { sceneModel: import('@/stores/useModelStore').
|
||||
|
||||
return (
|
||||
<group
|
||||
ref={topGroupRef}
|
||||
scale={[renderFactor, renderFactor, renderFactor]}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -248,6 +253,7 @@ function XRModel({ sceneModel }: { sceneModel: import('@/stores/useModelStore').
|
||||
// Calibração XR da Peça: captura normal da face do modelo em espaço de mundo.
|
||||
if (
|
||||
xrCalibration.modelId === sceneModel.id &&
|
||||
xrCalibration.alignType === 'cube' &&
|
||||
(xrCalibration.step === 'await-model-1' || xrCalibration.step === 'await-model-2' || xrCalibration.step === 'await-model-3') &&
|
||||
e.face && e.object
|
||||
) {
|
||||
@@ -267,6 +273,48 @@ function XRModel({ sceneModel }: { sceneModel: import('@/stores/useModelStore').
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Calibração Virt/Real da Peça: captura os pontos virtuais.
|
||||
if (
|
||||
xrCalibration.modelId === sceneModel.id &&
|
||||
xrCalibration.alignType === 'virt-real' &&
|
||||
(xrCalibration.step === 'await-virtual-1' || xrCalibration.step === 'await-virtual-2' || xrCalibration.step === 'await-virtual-3') &&
|
||||
localFrameRef.current
|
||||
) {
|
||||
const localPoint = localFrameRef.current.worldToLocal(e.point.clone());
|
||||
const localPivotPoint = localPoint.clone().sub(modelInfo.center);
|
||||
const stepNum = xrCalibration.step.endsWith('1') ? 1 : xrCalibration.step.endsWith('2') ? 2 : 3;
|
||||
|
||||
if (stepNum === 3 && xrCalibration.realPoints.length >= 3 && topGroupRef.current) {
|
||||
const vPoints = [...xrCalibration.virtualPoints, localPivotPoint];
|
||||
const realWorldPoints = xrCalibration.realPoints;
|
||||
const parentPoints = realWorldPoints.map(rw =>
|
||||
topGroupRef.current!.worldToLocal(rw.clone())
|
||||
);
|
||||
|
||||
const res = computeVirtRealTransform(vPoints, parentPoints);
|
||||
if (res) {
|
||||
const { quaternion, position } = res;
|
||||
useModelStore.getState().setCalibration(sceneModel.id, [quaternion.x, quaternion.y, quaternion.z, quaternion.w]);
|
||||
useModelStore.getState().setFineTuning({
|
||||
posX: position.x,
|
||||
posY: position.y,
|
||||
posZ: position.z,
|
||||
rotX: 0,
|
||||
rotY: 0,
|
||||
rotZ: 0,
|
||||
});
|
||||
pushXRVirtualPoint(localPivotPoint);
|
||||
toast.success("Peça virtual ajustada com sucesso na peça real!");
|
||||
} else {
|
||||
toast.error("Erro ao alinhar: pontos são colineares!");
|
||||
}
|
||||
} else {
|
||||
pushXRVirtualPoint(localPivotPoint);
|
||||
toast.success(`Ponto virtual ${stepNum} registrado!`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Translação */}
|
||||
@@ -760,6 +808,53 @@ function XRSnapHandler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ─── Desktop Aligner Fallback for Virt/Real Calibration ─
|
||||
function XRVirtRealDesktopAligner() {
|
||||
const { gl, camera, scene } = useThree();
|
||||
|
||||
useEffect(() => {
|
||||
const isAligning = xrCalibration.alignType === 'virt-real' &&
|
||||
(xrCalibration.step === 'await-real-1' ||
|
||||
xrCalibration.step === 'await-real-2' ||
|
||||
xrCalibration.step === 'await-real-3');
|
||||
if (!isAligning) return;
|
||||
if (gl.xr.isPresenting) return;
|
||||
|
||||
const mouse = new THREE.Vector2();
|
||||
const raycaster = new THREE.Raycaster();
|
||||
|
||||
const onClick = (e: MouseEvent) => {
|
||||
const rect = gl.domElement.getBoundingClientRect();
|
||||
mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
|
||||
mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
||||
raycaster.setFromCamera(mouse, camera);
|
||||
const intersects = raycaster.intersectObjects(scene.children, true);
|
||||
|
||||
const activeModelId = useModelStore.getState().activeModelId;
|
||||
const hit = intersects.find(i => {
|
||||
if (i.object.userData?.__edgeLine) return false;
|
||||
let cur: THREE.Object3D | null = i.object;
|
||||
while (cur) {
|
||||
if (cur.userData?.modelId === activeModelId) return false;
|
||||
cur = cur.parent;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const point = hit ? hit.point.clone() : new THREE.Vector3(0, 0, 0);
|
||||
pushXRRealPoint(point);
|
||||
|
||||
const stepNum = xrCalibration.step.endsWith('1') ? 1 : xrCalibration.step.endsWith('2') ? 2 : 3;
|
||||
toast.success(`Ponto real ${stepNum} registrado (Simulado)!`);
|
||||
};
|
||||
|
||||
gl.domElement.addEventListener('click', onClick);
|
||||
return () => gl.domElement.removeEventListener('click', onClick);
|
||||
}, [gl, camera, scene, xrCalibration.step, xrCalibration.alignType]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ─── XR Grid ───────────────────────────────────────────
|
||||
function XRGrid() {
|
||||
const showGrid = useModelStore((s) => s.showGrid);
|
||||
@@ -1025,6 +1120,7 @@ const XRSession = () => {
|
||||
)}
|
||||
|
||||
<XRSnapHandler />
|
||||
<XRVirtRealDesktopAligner />
|
||||
<XRGrid />
|
||||
<XRGridAutoFollower />
|
||||
</XR>
|
||||
|
||||
Reference in New Issue
Block a user