Changes
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
@@ -5,7 +5,10 @@ import { useControllerGrab, ControllerGrabSnapshot } from '@/hooks/useController
|
||||
import { useModelStore } from '@/stores/useModelStore';
|
||||
|
||||
interface XRGrabbableProps {
|
||||
/** Allow uniform scaling via two-hand grab (distance between hands) */
|
||||
/**
|
||||
* @deprecated Mantido por compatibilidade — agora o zoom por duas mãos
|
||||
* está SEMPRE ativo (faz parte do mapeamento padrão do grip).
|
||||
*/
|
||||
allowScale?: boolean;
|
||||
/** When true, the active model is locked — grab is ignored entirely */
|
||||
lockedActive?: boolean;
|
||||
@@ -16,8 +19,8 @@ interface XRGrabbableProps {
|
||||
|
||||
interface SingleGrabRecord {
|
||||
hand: 'left' | 'right';
|
||||
/** Offset = gripWorld^-1 * groupWorld at grab start */
|
||||
offset: THREE.Matrix4;
|
||||
/** Offset (world) = groupWorldPos - controllerWorldPos at grab start. */
|
||||
offsetWorld: THREE.Vector3;
|
||||
}
|
||||
|
||||
interface DualGrabRecord {
|
||||
@@ -33,22 +36,22 @@ interface DualGrabRecord {
|
||||
startScale: number;
|
||||
}
|
||||
|
||||
const _tmpMat = new THREE.Matrix4();
|
||||
const _tmpMat2 = new THREE.Matrix4();
|
||||
const _tmpVecL = new THREE.Vector3();
|
||||
const _tmpVecR = new THREE.Vector3();
|
||||
const _tmpQuat = new THREE.Quaternion();
|
||||
|
||||
/**
|
||||
* Wraps children in a group whose pose is driven by Touch Plus grip:
|
||||
* • One-hand grab: model becomes an extension of that hand (6DOF).
|
||||
* • Two-hand grab: midpoint = pivot, axis between hands = rotation,
|
||||
* distance ratio = uniform scale (if allowScale).
|
||||
* Touch Plus grip mapping (Meta Quest 3):
|
||||
*
|
||||
* On release the group keeps its last pose (we don't write to the store —
|
||||
* the manual fine-tuning sliders remain available as additional offsets).
|
||||
* • One-hand grip → **PAN ONLY** (translação). A rotação e a escala
|
||||
* da peça NÃO mudam — o grip simples só arrasta.
|
||||
* • Two-hand grip → ORBIT (eixo entre as mãos) + ZOOM uniforme
|
||||
* (distância entre os controles). O zoom está sempre ligado.
|
||||
*
|
||||
* Trigger não interfere aqui (reservado para seleção/medição).
|
||||
*/
|
||||
export function XRGrabbable({ allowScale = false, lockedActive = false, onGrabStart, children }: XRGrabbableProps) {
|
||||
export function XRGrabbable({ lockedActive = false, onGrabStart, children }: XRGrabbableProps) {
|
||||
const groupRef = useRef<THREE.Group>(null);
|
||||
const grab = useControllerGrab();
|
||||
|
||||
@@ -63,7 +66,6 @@ export function XRGrabbable({ allowScale = false, lockedActive = false, onGrabSt
|
||||
const g = groupRef.current;
|
||||
if (!g) return;
|
||||
g.scale.set(1, 1, 1);
|
||||
// Abort any in-progress two-hand scaling so it doesn't snap back.
|
||||
dual.current = null;
|
||||
single.current = null;
|
||||
}, [scaleResetNonce]);
|
||||
@@ -75,7 +77,6 @@ export function XRGrabbable({ allowScale = false, lockedActive = false, onGrabSt
|
||||
const L = snap.left;
|
||||
const R = snap.right;
|
||||
|
||||
// Locked: ignore all grab input — release any in-progress grabs and bail.
|
||||
if (lockedActive) {
|
||||
if (single.current) single.current = null;
|
||||
if (dual.current) dual.current = null;
|
||||
@@ -94,7 +95,7 @@ export function XRGrabbable({ allowScale = false, lockedActive = false, onGrabSt
|
||||
haloRef.current.visible = maxGrip > 0.05;
|
||||
}
|
||||
|
||||
// ─── Two-hand mode ───────────────────────────────────────────
|
||||
// ─── Two-hand mode (orbit + zoom, sempre) ────────────────────
|
||||
if (lActive && rActive) {
|
||||
_tmpVecL.setFromMatrixPosition(L.gripWorld);
|
||||
_tmpVecR.setFromMatrixPosition(R.gripWorld);
|
||||
@@ -105,7 +106,6 @@ export function XRGrabbable({ allowScale = false, lockedActive = false, onGrabSt
|
||||
axisNow.divideScalar(distNow);
|
||||
|
||||
if (!dual.current) {
|
||||
// Capture
|
||||
group.updateMatrixWorld();
|
||||
dual.current = {
|
||||
startGroupWorld: group.matrixWorld.clone(),
|
||||
@@ -114,26 +114,21 @@ export function XRGrabbable({ allowScale = false, lockedActive = false, onGrabSt
|
||||
startDist: distNow,
|
||||
startScale: group.scale.x,
|
||||
};
|
||||
single.current = null; // dual takes over
|
||||
single.current = null;
|
||||
if (!everGrabbed.current) { everGrabbed.current = true; onGrabStart?.(); }
|
||||
console.log('[XR][grab] ◆ TWO-HAND start');
|
||||
console.log('[XR][grab] ◆ TWO-HAND start (orbit+zoom)');
|
||||
}
|
||||
|
||||
const d = dual.current;
|
||||
// Rotation delta: from startAxis to axisNow
|
||||
const deltaQuat = new THREE.Quaternion().setFromUnitVectors(d.startAxis, axisNow);
|
||||
// Scale ratio
|
||||
const scaleRatio = allowScale
|
||||
? THREE.MathUtils.clamp(distNow / d.startDist, 0.1, 10)
|
||||
: 1;
|
||||
// ZOOM sempre ativo no grip duplo
|
||||
const scaleRatio = THREE.MathUtils.clamp(distNow / d.startDist, 0.1, 10);
|
||||
|
||||
// Build target world: T(midNow) * R(delta) * S(ratio) * T(-startMid) * startGroupWorld
|
||||
const m = new THREE.Matrix4();
|
||||
const tToOrigin = new THREE.Matrix4().makeTranslation(-d.startMid.x, -d.startMid.y, -d.startMid.z);
|
||||
const tBack = new THREE.Matrix4().makeTranslation(midNow.x, midNow.y, midNow.z);
|
||||
const rot = new THREE.Matrix4().makeRotationFromQuaternion(deltaQuat);
|
||||
const scl = new THREE.Matrix4().makeScale(scaleRatio, scaleRatio, scaleRatio);
|
||||
m.identity()
|
||||
const m = new THREE.Matrix4().identity()
|
||||
.multiply(tBack)
|
||||
.multiply(rot)
|
||||
.multiply(scl)
|
||||
@@ -147,21 +142,31 @@ export function XRGrabbable({ allowScale = false, lockedActive = false, onGrabSt
|
||||
dual.current = null;
|
||||
}
|
||||
|
||||
// ─── One-hand mode ───────────────────────────────────────────
|
||||
// ─── One-hand mode (PAN ONLY) ────────────────────────────────
|
||||
const activeHand: 'left' | 'right' | null = lActive ? 'left' : rActive ? 'right' : null;
|
||||
if (activeHand) {
|
||||
const slot = activeHand === 'left' ? L : R;
|
||||
const ctrlPos = new THREE.Vector3().setFromMatrixPosition(slot.gripWorld);
|
||||
|
||||
if (!single.current || single.current.hand !== activeHand) {
|
||||
group.updateMatrixWorld();
|
||||
const offset = new THREE.Matrix4()
|
||||
.copy(slot.gripWorld).invert()
|
||||
.multiply(group.matrixWorld);
|
||||
single.current = { hand: activeHand, offset };
|
||||
const groupWorldPos = new THREE.Vector3().setFromMatrixPosition(group.matrixWorld);
|
||||
const offsetWorld = new THREE.Vector3().subVectors(groupWorldPos, ctrlPos);
|
||||
single.current = { hand: activeHand, offsetWorld };
|
||||
if (!everGrabbed.current) { everGrabbed.current = true; onGrabStart?.(); }
|
||||
console.log(`[XR][grab] ● ONE-HAND start (${activeHand})`);
|
||||
console.log(`[XR][grab] ● ONE-HAND start (${activeHand}) — pan only`);
|
||||
}
|
||||
_tmpMat.multiplyMatrices(slot.gripWorld, single.current.offset);
|
||||
applyWorldMatrixToLocal(group, _tmpMat);
|
||||
|
||||
// Target world position = controller + initial offset
|
||||
const targetWorldPos = new THREE.Vector3().addVectors(ctrlPos, single.current.offsetWorld);
|
||||
|
||||
// Convert to parent-local position (preserve current rotation/scale)
|
||||
if (group.parent) {
|
||||
group.parent.updateMatrixWorld();
|
||||
const invParent = new THREE.Matrix4().copy(group.parent.matrixWorld).invert();
|
||||
targetWorldPos.applyMatrix4(invParent);
|
||||
}
|
||||
group.position.copy(targetWorldPos);
|
||||
} else if (single.current) {
|
||||
single.current = null;
|
||||
}
|
||||
@@ -169,7 +174,6 @@ export function XRGrabbable({ allowScale = false, lockedActive = false, onGrabSt
|
||||
|
||||
return (
|
||||
<group ref={groupRef}>
|
||||
{/* Subtle halo proportional to analog grip — invisible by default */}
|
||||
<mesh ref={haloRef} visible={false}>
|
||||
<sphereGeometry args={[0.5, 16, 12]} />
|
||||
<meshBasicMaterial color="#22c55e" transparent opacity={0} depthWrite={false} />
|
||||
|
||||
Reference in New Issue
Block a user