🚀 Auto-deploy: melhoria no snap e medição AR em 30/05/2026 10:10:48
This commit is contained in:
+110
-1
@@ -476,6 +476,115 @@ function XRGridLandingHandler() {
|
||||
);
|
||||
}
|
||||
|
||||
/** Component that renders a laser from the camera (headset) to the virtual/real calibration target. */
|
||||
function XRVirtRealCalibLaser({ reticlePos }: { reticlePos: THREE.Vector3 | null }) {
|
||||
const { camera } = useThree();
|
||||
const geom = useMemo(() => new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(), new THREE.Vector3(0, 0, -1)]), []);
|
||||
const mat = useMemo(() => new THREE.LineBasicMaterial({ color: '#eab308', transparent: true, opacity: 0.8, depthTest: false }), []);
|
||||
const lineRef = useRef<THREE.Line>(null);
|
||||
|
||||
useFrame(() => {
|
||||
if (lineRef.current && reticlePos) {
|
||||
const posAttr = geom.attributes.position as THREE.BufferAttribute;
|
||||
// Laser origin slightly below eyes
|
||||
posAttr.setXYZ(0, camera.position.x, camera.position.y - 0.05, camera.position.z);
|
||||
posAttr.setXYZ(1, reticlePos.x, reticlePos.y, reticlePos.z);
|
||||
posAttr.needsUpdate = true;
|
||||
geom.computeBoundingSphere();
|
||||
lineRef.current.visible = true;
|
||||
} else if (lineRef.current) {
|
||||
lineRef.current.visible = false;
|
||||
}
|
||||
});
|
||||
|
||||
return <primitive ref={lineRef} object={new THREE.Line(geom, mat)} />;
|
||||
}
|
||||
|
||||
/** Handles physics surface hit-testing using WebXR for virtual-real piece alignment. */
|
||||
function XRVirtRealCalibHandler() {
|
||||
const [, force] = useState(0);
|
||||
useEffect(() => subscribeXRCalibration(() => force(t => t + 1)), []);
|
||||
|
||||
const isRealStep = xrCalibration.alignType === 'virt-real' &&
|
||||
(xrCalibration.step === 'await-real-1' ||
|
||||
xrCalibration.step === 'await-real-2' ||
|
||||
xrCalibration.step === 'await-real-3');
|
||||
|
||||
const reticleRef = useRef<THREE.Group>(null);
|
||||
const [reticlePos, setReticlePos] = useState<THREE.Vector3 | null>(null);
|
||||
|
||||
// Continuous hit-test from viewer center
|
||||
useXRHitTest(
|
||||
isRealStep
|
||||
? (results, getWorldMatrix) => {
|
||||
if (results.length === 0) {
|
||||
setReticlePos(null);
|
||||
return;
|
||||
}
|
||||
const matrixHelper = new THREE.Matrix4();
|
||||
getWorldMatrix(matrixHelper, results[0]);
|
||||
const positionHelper = new THREE.Vector3().setFromMatrixPosition(matrixHelper);
|
||||
setReticlePos(positionHelper.clone());
|
||||
}
|
||||
: undefined,
|
||||
'viewer'
|
||||
);
|
||||
|
||||
const handleSelect = useCallback(() => {
|
||||
if (!isRealStep || !reticlePos) return;
|
||||
|
||||
const stepNum = xrCalibration.step.endsWith('1') ? 1 : xrCalibration.step.endsWith('2') ? 2 : 3;
|
||||
pushXRRealPoint(reticlePos.clone());
|
||||
toast.success(`Ponto real ${stepNum} registrado!`);
|
||||
}, [isRealStep, reticlePos]);
|
||||
|
||||
// Update reticle position
|
||||
useFrame(() => {
|
||||
if (reticleRef.current) {
|
||||
if (reticlePos && isRealStep) {
|
||||
reticleRef.current.visible = true;
|
||||
reticleRef.current.position.copy(reticlePos);
|
||||
} else {
|
||||
reticleRef.current.visible = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!isRealStep) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<XRVirtRealCalibLaser reticlePos={reticlePos} />
|
||||
|
||||
{/* Golden reticle */}
|
||||
<group ref={reticleRef as any}>
|
||||
<mesh onClick={handleSelect}>
|
||||
<ringGeometry args={[0.08, 0.1, 32]} />
|
||||
<meshBasicMaterial color="#eab308" side={THREE.DoubleSide} transparent opacity={0.8} depthTest={false} />
|
||||
</mesh>
|
||||
<mesh rotation={[-Math.PI / 2, 0, 0]}>
|
||||
<ringGeometry args={[0.005, 0.015, 16]} />
|
||||
<meshBasicMaterial color="#eab308" side={THREE.DoubleSide} transparent opacity={0.6} depthTest={false} />
|
||||
</mesh>
|
||||
<mesh>
|
||||
<ringGeometry args={[0.11, 0.115, 64]} />
|
||||
<meshBasicMaterial color="#eab308" side={THREE.DoubleSide} transparent opacity={0.3} depthTest={false} />
|
||||
</mesh>
|
||||
</group>
|
||||
|
||||
{/* Tap plane to capture select events */}
|
||||
<mesh
|
||||
position={[0, 0, -2]}
|
||||
onClick={handleSelect}
|
||||
visible={false}
|
||||
>
|
||||
<planeGeometry args={[10, 10]} />
|
||||
<meshBasicMaterial transparent opacity={0} />
|
||||
</mesh>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/** Renders the currently-active model (the one wrapped by grab/placement). */
|
||||
function XRActiveModel() {
|
||||
const models = useModelStore((s) => s.models);
|
||||
@@ -1065,7 +1174,7 @@ const XRSession = () => {
|
||||
<ControllerLocomotion rigRef={rigRef} />
|
||||
<VisibilityApplier />
|
||||
<XRGridLandingHandler />
|
||||
|
||||
<XRVirtRealCalibHandler />
|
||||
|
||||
{/* In-world HUD — visible inside passthrough where DOM overlays cannot reach */}
|
||||
<XRHudInWorld
|
||||
|
||||
Reference in New Issue
Block a user