7772630f92
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
120 lines
4.0 KiB
TypeScript
120 lines
4.0 KiB
TypeScript
/**
|
|
* DEVKIT — Visual fake controllers + keyboard driver.
|
|
*
|
|
* Renders two spheres (L=blue, R=red) at the fake controller positions and
|
|
* listens to global keyboard for grip/trigger/movement. Mounted only when
|
|
* SimXR is active.
|
|
*
|
|
* Keys:
|
|
* Q / W → hold grip Left / Right (analog = 1.0)
|
|
* A / S → hold trigger Left / Right (analog = 1.0)
|
|
* 1 / 2 → select Left / Right as the active hand
|
|
* Arrows → move active hand on X (←→) / Y (↑↓)
|
|
* Shift+Arrows→ move active hand on Z (↑=forward, ↓=back)
|
|
* R → reset hand poses
|
|
*/
|
|
import { useEffect, useRef, useState } from 'react';
|
|
import { useFrame } from '@react-three/fiber';
|
|
import * as THREE from 'three';
|
|
import { fakeInput, setFakeActive } from './fakeInputStore';
|
|
|
|
const STEP = 0.03; // m per keypress
|
|
|
|
export function FakeControllers() {
|
|
const leftMesh = useRef<THREE.Mesh>(null);
|
|
const rightMesh = useRef<THREE.Mesh>(null);
|
|
const [, force] = useState(0);
|
|
const activeHand = useRef<'left' | 'right'>('right');
|
|
|
|
useEffect(() => {
|
|
setFakeActive(true);
|
|
return () => setFakeActive(false);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const keys = new Set<string>();
|
|
|
|
const apply = () => {
|
|
const pos = new THREE.Vector3();
|
|
const quat = new THREE.Quaternion();
|
|
const scl = new THREE.Vector3();
|
|
const hand = fakeInput[activeHand.current];
|
|
hand.gripWorld.decompose(pos, quat, scl);
|
|
|
|
const dx = (keys.has('ArrowRight') ? STEP : 0) - (keys.has('ArrowLeft') ? STEP : 0);
|
|
const dyOrZ = (keys.has('ArrowUp') ? STEP : 0) - (keys.has('ArrowDown') ? STEP : 0);
|
|
const shift = keys.has('Shift');
|
|
pos.x += dx;
|
|
if (shift) pos.z -= dyOrZ; // up = forward (-Z)
|
|
else pos.y += dyOrZ;
|
|
|
|
hand.gripWorld.compose(pos, quat, scl);
|
|
|
|
// grip / trigger
|
|
fakeInput.left.gripValue = keys.has('q') ? 1 : 0;
|
|
fakeInput.right.gripValue = keys.has('w') ? 1 : 0;
|
|
fakeInput.left.triggerValue = keys.has('a') ? 1 : 0;
|
|
fakeInput.right.triggerValue = keys.has('s') ? 1 : 0;
|
|
|
|
force((n) => n + 1);
|
|
};
|
|
|
|
const onDown = (e: KeyboardEvent) => {
|
|
const k = e.key === 'Shift' ? 'Shift' : e.key;
|
|
keys.add(k);
|
|
if (k === '1') activeHand.current = 'left';
|
|
else if (k === '2') activeHand.current = 'right';
|
|
else if (k === 'r' || k === 'R') {
|
|
fakeInput.left.gripWorld.makeTranslation(-0.25, 1.2, -0.5);
|
|
fakeInput.right.gripWorld.makeTranslation(0.25, 1.2, -0.5);
|
|
force((n) => n + 1);
|
|
return;
|
|
}
|
|
apply();
|
|
};
|
|
const onUp = (e: KeyboardEvent) => {
|
|
const k = e.key === 'Shift' ? 'Shift' : e.key;
|
|
keys.delete(k);
|
|
apply();
|
|
};
|
|
|
|
window.addEventListener('keydown', onDown);
|
|
window.addEventListener('keyup', onUp);
|
|
return () => {
|
|
window.removeEventListener('keydown', onDown);
|
|
window.removeEventListener('keyup', onUp);
|
|
};
|
|
}, []);
|
|
|
|
// Sync mesh visuals to fake state each frame
|
|
useFrame(() => {
|
|
if (leftMesh.current) {
|
|
const p = new THREE.Vector3().setFromMatrixPosition(fakeInput.left.gripWorld);
|
|
leftMesh.current.position.copy(p);
|
|
const grabbing = fakeInput.left.gripValue > 0.7;
|
|
const mat = leftMesh.current.material as THREE.MeshStandardMaterial;
|
|
mat.emissiveIntensity = grabbing ? 1.5 : 0.4;
|
|
}
|
|
if (rightMesh.current) {
|
|
const p = new THREE.Vector3().setFromMatrixPosition(fakeInput.right.gripWorld);
|
|
rightMesh.current.position.copy(p);
|
|
const grabbing = fakeInput.right.gripValue > 0.7;
|
|
const mat = rightMesh.current.material as THREE.MeshStandardMaterial;
|
|
mat.emissiveIntensity = grabbing ? 1.5 : 0.4;
|
|
}
|
|
});
|
|
|
|
return (
|
|
<>
|
|
<mesh ref={leftMesh}>
|
|
<sphereGeometry args={[0.04, 16, 16]} />
|
|
<meshStandardMaterial color="#3b82f6" emissive="#3b82f6" emissiveIntensity={0.4} />
|
|
</mesh>
|
|
<mesh ref={rightMesh}>
|
|
<sphereGeometry args={[0.04, 16, 16]} />
|
|
<meshStandardMaterial color="#ef4444" emissive="#ef4444" emissiveIntensity={0.4} />
|
|
</mesh>
|
|
</>
|
|
);
|
|
}
|