91 lines
2.3 KiB
JavaScript
91 lines
2.3 KiB
JavaScript
import * as THREE from 'three';
|
|
import { HTTP_URL } from '../config.js';
|
|
|
|
/**
|
|
* VRPositionTracker - Tracks the user's HMD position in VR world space.
|
|
* Reads camera world position each frame and periodically sends it to the server.
|
|
*/
|
|
export class VRPositionTracker {
|
|
constructor(camera, dolly, { sendInterval = 500 } = {}) {
|
|
this.camera = camera;
|
|
this.dolly = dolly;
|
|
this.sendInterval = sendInterval;
|
|
|
|
// Current position (updated every frame)
|
|
this.position = new THREE.Vector3();
|
|
this.rotation = new THREE.Euler();
|
|
|
|
// For throttled server updates
|
|
this._lastSendTime = 0;
|
|
this._enabled = false;
|
|
this._worldPos = new THREE.Vector3();
|
|
}
|
|
|
|
/** Start tracking and sending updates */
|
|
enable() {
|
|
this._enabled = true;
|
|
console.log('VR Position Tracker enabled');
|
|
}
|
|
|
|
/** Stop tracking */
|
|
disable() {
|
|
this._enabled = false;
|
|
}
|
|
|
|
/** Call every frame from the animate loop */
|
|
update() {
|
|
if (!this._enabled) return;
|
|
|
|
// Get camera world position (accounts for dolly + XR offset)
|
|
this.camera.getWorldPosition(this._worldPos);
|
|
this.position.copy(this._worldPos);
|
|
this.rotation.copy(this.camera.rotation);
|
|
|
|
// Throttled send to server
|
|
const now = performance.now();
|
|
if (now - this._lastSendTime >= this.sendInterval) {
|
|
this._lastSendTime = now;
|
|
this._sendPosition();
|
|
}
|
|
}
|
|
|
|
/** Get current position as a plain object */
|
|
getPosition() {
|
|
return {
|
|
x: this.position.x,
|
|
y: this.position.y,
|
|
z: this.position.z,
|
|
};
|
|
}
|
|
|
|
/** Get current rotation as a plain object */
|
|
getRotation() {
|
|
return {
|
|
x: this.rotation.x,
|
|
y: this.rotation.y,
|
|
z: this.rotation.z,
|
|
};
|
|
}
|
|
|
|
/** Send position to server (fire-and-forget) */
|
|
_sendPosition() {
|
|
const payload = {
|
|
x: parseFloat(this.position.x.toFixed(3)),
|
|
y: parseFloat(this.position.y.toFixed(3)),
|
|
z: parseFloat(this.position.z.toFixed(3)),
|
|
rx: parseFloat(this.rotation.x.toFixed(3)),
|
|
ry: parseFloat(this.rotation.y.toFixed(3)),
|
|
rz: parseFloat(this.rotation.z.toFixed(3)),
|
|
timestamp: Date.now(),
|
|
};
|
|
|
|
fetch(`${HTTP_URL}/vr/position`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
}).catch(() => {
|
|
// Silent fail - position updates are best-effort
|
|
});
|
|
}
|
|
}
|