Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-05-12 11:00:31 +00:00
parent 1bd62eb5b9
commit 174d36e3b3
3 changed files with 180 additions and 56 deletions
+48
View File
@@ -0,0 +1,48 @@
/**
* DEVKIT — global mutable store for fake controller inputs.
*
* Lives outside React (plain object + listeners) so useControllerGrab can
* read it from inside useFrame with zero re-render cost. Mirrors the shape
* of a real WebXR XRInputSource just enough to feed our grab logic.
*/
import * as THREE from 'three';
export interface FakeHandState {
gripValue: number; // 0..1
triggerValue: number; // 0..1
hasPose: boolean; // true if controller is "tracked"
gripWorld: THREE.Matrix4; // world pose of the gripSpace
}
export interface FakeInputState {
active: boolean;
left: FakeHandState;
right: FakeHandState;
}
function makeHand(): FakeHandState {
return {
gripValue: 0,
triggerValue: 0,
hasPose: true,
gripWorld: new THREE.Matrix4(),
};
}
export const fakeInput: FakeInputState = {
active: false,
left: makeHand(),
right: makeHand(),
};
// Initial poses — a bit in front of the camera, hands apart at chest height
fakeInput.left.gripWorld.makeTranslation(-0.25, 1.2, -0.5);
fakeInput.right.gripWorld.makeTranslation(0.25, 1.2, -0.5);
export function setFakeActive(on: boolean) {
fakeInput.active = on;
}
export function isFakeActive(): boolean {
return fakeInput.active;
}
+43
View File
@@ -0,0 +1,43 @@
/**
* DEVKIT entry point. The whole devkit is gated by this single flag.
*
* Activation: append `?devkit=1` to URL OR run in console:
* localStorage.setItem('devkit', '1')
* Deactivation: `?devkit=0` OR localStorage.removeItem('devkit')
*
* To remove DevKit from the project entirely:
* 1. rm -rf src/devkit/
* 2. Strip `// DEVKIT:` blocks in XRSession.tsx & useControllerGrab.ts
*/
import { useEffect, useState } from 'react';
const KEY = 'devkit';
function readFlag(): boolean {
if (typeof window === 'undefined') return false;
const params = new URLSearchParams(window.location.search);
const urlVal = params.get(KEY);
if (urlVal === '1') {
localStorage.setItem(KEY, '1');
return true;
}
if (urlVal === '0') {
localStorage.removeItem(KEY);
return false;
}
return localStorage.getItem(KEY) === '1';
}
export function useDevKit(): boolean {
const [enabled, setEnabled] = useState(readFlag);
useEffect(() => {
const onStorage = () => setEnabled(readFlag());
window.addEventListener('storage', onStorage);
return () => window.removeEventListener('storage', onStorage);
}, []);
return enabled;
}
export function isDevKit(): boolean {
return readFlag();
}