Changes
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { useRef } from 'react';
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import * as THREE from 'three';
|
||||
|
||||
export interface GrabState {
|
||||
/** Analog grip pressure (0..1) */
|
||||
gripValue: number;
|
||||
/** Analog trigger pressure (0..1) */
|
||||
triggerValue: number;
|
||||
/** True when grip pressure crosses grab threshold (with hysteresis) */
|
||||
isGrabbing: boolean;
|
||||
/** World-space matrix of the gripSpace (Touch Plus controller pose) */
|
||||
gripWorld: THREE.Matrix4;
|
||||
/** True when controller pose is valid this frame */
|
||||
hasPose: boolean;
|
||||
}
|
||||
|
||||
export interface ControllerGrabSnapshot {
|
||||
left: GrabState;
|
||||
right: GrabState;
|
||||
}
|
||||
|
||||
const GRAB_ON = 0.7;
|
||||
const GRAB_OFF = 0.3;
|
||||
|
||||
function makeState(): GrabState {
|
||||
return {
|
||||
gripValue: 0,
|
||||
triggerValue: 0,
|
||||
isGrabbing: false,
|
||||
gripWorld: new THREE.Matrix4(),
|
||||
hasPose: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Polls WebXR input sources every frame and writes Touch Plus controller
|
||||
* state (grip analog, trigger analog, grip world pose, isGrabbing with
|
||||
* hysteresis) into a stable ref object.
|
||||
*
|
||||
* The returned ref is mutated in place — callers should also call useFrame()
|
||||
* with a *later* registration to read it. Pass a higher `renderPriority` to
|
||||
* the consumer's useFrame to ensure ordering.
|
||||
*/
|
||||
export function useControllerGrab() {
|
||||
const ref = useRef<ControllerGrabSnapshot>({
|
||||
left: makeState(),
|
||||
right: makeState(),
|
||||
});
|
||||
const { gl } = useThree();
|
||||
|
||||
useFrame((_state, _delta, frame: XRFrame | undefined) => {
|
||||
if (!frame) return;
|
||||
const session = frame.session;
|
||||
const referenceSpace = gl.xr.getReferenceSpace();
|
||||
if (!session || !referenceSpace) return;
|
||||
|
||||
// Reset hasPose flags
|
||||
ref.current.left.hasPose = false;
|
||||
ref.current.right.hasPose = false;
|
||||
|
||||
for (const source of session.inputSources) {
|
||||
if (source.handedness !== 'left' && source.handedness !== 'right') continue;
|
||||
const slot = ref.current[source.handedness];
|
||||
const gp = source.gamepad;
|
||||
|
||||
if (gp) {
|
||||
const gripBtn = gp.buttons[2];
|
||||
const trigBtn = gp.buttons[1];
|
||||
const gripValue = gripBtn ? (gripBtn.value || (gripBtn.pressed ? 1 : 0)) : 0;
|
||||
const trigValue = trigBtn ? (trigBtn.value || (trigBtn.pressed ? 1 : 0)) : 0;
|
||||
slot.gripValue = gripValue;
|
||||
slot.triggerValue = trigValue;
|
||||
// Hysteresis
|
||||
if (!slot.isGrabbing && gripValue > GRAB_ON) {
|
||||
slot.isGrabbing = true;
|
||||
console.log(`[XR][grab] ▶ ${source.handedness} START (v=${gripValue.toFixed(2)})`);
|
||||
} else if (slot.isGrabbing && gripValue < GRAB_OFF) {
|
||||
slot.isGrabbing = false;
|
||||
console.log(`[XR][grab] ◼ ${source.handedness} END`);
|
||||
}
|
||||
}
|
||||
|
||||
if (source.gripSpace) {
|
||||
const pose = frame.getPose(source.gripSpace, referenceSpace);
|
||||
if (pose) {
|
||||
slot.gripWorld.fromArray(pose.transform.matrix);
|
||||
slot.hasPose = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return ref;
|
||||
}
|
||||
Reference in New Issue
Block a user