🚀 Auto-deploy: melhoria no snap e medição AR em 28/05/2026 21:40:54
This commit is contained in:
@@ -21,6 +21,8 @@ interface SingleGrabRecord {
|
||||
hand: 'left' | 'right';
|
||||
/** Offset (world) = groupWorldPos - controllerWorldPos at grab start. */
|
||||
offsetWorld: THREE.Vector3;
|
||||
/** Altura Y de mundo da peça no momento em que o grab iniciou, para travar horizontalmente */
|
||||
startWorldY: number;
|
||||
}
|
||||
|
||||
interface DualGrabRecord {
|
||||
@@ -44,8 +46,8 @@ const _tmpQuat = new THREE.Quaternion();
|
||||
/**
|
||||
* Touch Plus grip mapping (Meta Quest 3):
|
||||
*
|
||||
* • One-hand grip → **PAN ONLY** (translação). A rotação e a escala
|
||||
* da peça NÃO mudam — o grip simples só arrasta.
|
||||
* • One-hand grip → **PAN ONLY (RESTRICTED HORIZONTALLY)** (translação). A rotação e a escala
|
||||
* da peça NÃO mudam — o grip simples só arrasta no plano horizontal X/Z para preservar a altura Y calibrada.
|
||||
* • Two-hand grip → ORBIT (eixo entre as mãos) + ZOOM uniforme
|
||||
* (distância entre os controles). O zoom está sempre ligado.
|
||||
*
|
||||
@@ -144,7 +146,7 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta
|
||||
dual.current = null;
|
||||
}
|
||||
|
||||
// ─── One-hand mode (PAN ONLY) ────────────────────────────────
|
||||
// ─── One-hand mode (PAN ONLY - HORIZONTAL X/Z) ────────────────
|
||||
const activeHand: 'left' | 'right' | null = lActive ? 'left' : rActive ? 'right' : null;
|
||||
if (activeHand) {
|
||||
const slot = activeHand === 'left' ? L : R;
|
||||
@@ -154,14 +156,21 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta
|
||||
group.updateMatrixWorld();
|
||||
const groupWorldPos = new THREE.Vector3().setFromMatrixPosition(group.matrixWorld);
|
||||
const offsetWorld = new THREE.Vector3().subVectors(groupWorldPos, ctrlPos);
|
||||
single.current = { hand: activeHand, offsetWorld };
|
||||
|
||||
// Salva a altura Y original de mundo
|
||||
const startWorldY = groupWorldPos.y;
|
||||
|
||||
single.current = { hand: activeHand, offsetWorld, startWorldY };
|
||||
if (!everGrabbed.current) { everGrabbed.current = true; onGrabStart?.(); }
|
||||
console.log(`[XR][grab] ● ONE-HAND start (${activeHand}) — pan only`);
|
||||
console.log(`[XR][grab] ● ONE-HAND start (${activeHand}) — horizontal pan`);
|
||||
}
|
||||
|
||||
// Target world position = controller + initial 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!
|
||||
targetWorldPos.y = single.current.startWorldY;
|
||||
|
||||
// Convert to parent-local position (preserve current rotation/scale)
|
||||
if (group.parent) {
|
||||
group.parent.updateMatrixWorld();
|
||||
|
||||
@@ -615,6 +615,9 @@ function GridFloorRow() {
|
||||
const setGridAutoFollow = useModelStore((s) => s.setGridAutoFollow);
|
||||
const setGridY = useModelStore((s) => s.setGridY);
|
||||
const nudgeGridY = useModelStore((s) => s.nudgeGridY);
|
||||
const gridCalibMode = useModelStore((s) => s.gridCalibMode);
|
||||
const setGridCalibMode = useModelStore((s) => s.setGridCalibMode);
|
||||
|
||||
const placeAtFloor = () => {
|
||||
// Headset eye Y minus avg standing eye height (1.6 m)
|
||||
const floor = camera.position.y - 1.6;
|
||||
@@ -635,6 +638,8 @@ function GridFloorRow() {
|
||||
onClick={() => nudgeGridY(0.01)} />
|
||||
<XR3DButton position={[0.07, -0.175, 0]} size={[0.075, 0.022]} label="Pousar chão"
|
||||
onClick={placeAtFloor} />
|
||||
<XR3DButton position={[0.155, -0.175, 0]} size={[0.075, 0.022]} label={gridCalibMode ? "Calibrando" : "Calibrar"}
|
||||
active={gridCalibMode} onClick={() => setGridCalibMode(!gridCalibMode)} />
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
+15
-3
@@ -233,7 +233,19 @@ function XRModel({ sceneModel }: { sceneModel: import('@/stores/useModelStore').
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
// Calibração XR: captura normal da face do modelo em espaço de mundo.
|
||||
// Calibração do Grid em AR (clonando os mesmos princípios do modo viewer)
|
||||
const gridCalibMode = useModelStore.getState().gridCalibMode;
|
||||
if (gridCalibMode && e.point) {
|
||||
useModelStore.setState({
|
||||
gridY: e.point.y - 0.001,
|
||||
gridAutoFollow: false,
|
||||
gridCalibMode: false,
|
||||
showGrid: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Calibração XR da Peça: captura normal da face do modelo em espaço de mundo.
|
||||
if (
|
||||
xrCalibration.modelId === sceneModel.id &&
|
||||
(xrCalibration.step === 'await-model-1' || xrCalibration.step === 'await-model-2' || xrCalibration.step === 'await-model-3') &&
|
||||
@@ -328,8 +340,8 @@ function ControllerFineTuning({ freeMove }: { freeMove: boolean }) {
|
||||
for (const source of inputSources) {
|
||||
const gp = source.gamepad;
|
||||
if (!gp) continue;
|
||||
const gripBtn = gp.buttons[2];
|
||||
const trigBtn = gp.buttons[1];
|
||||
const trigBtn = gp.buttons[0];
|
||||
const gripBtn = gp.buttons[1];
|
||||
const gripVal = gripBtn ? (gripBtn.value || (gripBtn.pressed ? 1 : 0)) : 0;
|
||||
if (gripBtn?.pressed) gripHeld = true;
|
||||
if (gripVal > 0.3) anyGripHeld = true; // matches grab hysteresis OFF
|
||||
|
||||
Reference in New Issue
Block a user