UNPKG

@needle-tools/engine

Version:

Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.

189 lines 7.55 kB
import { RoomEvents } from "../engine_networking.js"; import { getParam } from "../engine_utils.js"; const debug = getParam("debugwebxr"); class XRUserState { controllerStates = []; userId; context; userStateEvtName; constructor(userId, context) { this.userId = userId; this.context = context; this.userStateEvtName = "xr-sync-user-state-" + userId; this.context.connection.beginListen(this.userStateEvtName, this.onReceivedControllerState); } dispose() { this.context.connection.stopListen(this.userStateEvtName, this.onReceivedControllerState); } onReceivedControllerState = (state) => { if (debug) console.log(`XRSync: Received change for ${this.userId}: ${state.type} ${state.handedness}; tracked=${state.isTracking}`); let found = false; for (let i = 0; i < this.controllerStates.length; i++) { const ctrl = this.controllerStates[i]; if (ctrl.index === state.index) { this.controllerStates[i] = state; found = true; break; } } if (!found) { this.controllerStates.push(state); } }; update(session) { if (this.context.connection.isConnected == false) return; for (let i = this.controllerStates.length - 1; i >= 0; i--) { const state = this.controllerStates[i]; let foundController = false; for (let i = 0; i < session.controllers.length; i++) { const ctrl = session.controllers[i]; if (ctrl.index === state.index) { foundController = true; } } if (!foundController) { // controller was removed if (debug) console.log(`XRSync: ${state.type} ${state.handedness} removed`, state.index); this.controllerStates.splice(i, 1); this.sendControllerRemoved(state); } } for (const ctrl of session.controllers) { this.updateControllerStates(ctrl); } } onExitXR(_session) { for (const state of this.controllerStates) { this.sendControllerRemoved(state); } this.controllerStates.length = 0; } sendControllerRemoved(state) { state.isTracking = false; state.guid = ""; this.context.connection.send(this.userStateEvtName, state); this.context.connection.sendDeleteRemoteState(state.guid); } updateControllerStates(ctrl) { // this.context.connection.send(this.userStateEvtName, {}); const existing = this.controllerStates.find(x => x.index === ctrl.index); if (existing) { let hasChanged = false; hasChanged ||= existing.isTracking != ctrl.isTracking; if (hasChanged) { existing.isTracking = ctrl.isTracking; this.context.connection.send(this.userStateEvtName, existing); } } else { const state = { guid: this.userId + "-" + ctrl.index, isTracking: ctrl.isTracking, handedness: ctrl.side, index: ctrl.index, type: ctrl.hand ? "hand" : "controller" }; this.controllerStates.push(state); this.context.connection.send(this.userStateEvtName, state); if (debug) console.log(`XRSync: ${state.type} ${state.handedness} added`, state.index); } } } export class NeedleXRSync { hasState(userId) { if (!userId) return false; return this._states.has(userId); } /** Is the left controller or hand tracked */ isTracking(userId, handedness) { if (!userId) return undefined; const user = this._states.get(userId); if (!user) return undefined; const ctrl = user.controllerStates.find(x => x.handedness === handedness); return ctrl?.isTracking || false; } /** Is it hand tracking or a controller */ getDeviceType(userId, handedness) { if (!userId) return undefined; const user = this._states.get(userId); if (!user) return undefined; const ctrl = user.controllerStates.find(x => x.handedness === handedness); return ctrl?.type || "unknown"; } context; constructor(context) { this.context = context; this.context.connection.beginListen(RoomEvents.JoinedRoom, this.onJoinedRoom); this.context.connection.beginListen(RoomEvents.LeftRoom, this.onLeftRoom); this.context.connection.beginListen(RoomEvents.UserJoinedRoom, this.onOtherUserJoinedRoom); this.context.connection.beginListen(RoomEvents.UserLeftRoom, this.onOtherUserLeftRoom); } destroy() { this.context.connection.stopListen(RoomEvents.JoinedRoom, this.onJoinedRoom); this.context.connection.stopListen(RoomEvents.LeftRoom, this.onLeftRoom); this.context.connection.stopListen(RoomEvents.UserJoinedRoom, this.onOtherUserJoinedRoom); this.context.connection.stopListen(RoomEvents.UserLeftRoom, this.onOtherUserLeftRoom); } onJoinedRoom = () => { if (this.context.connection.connectionId) { if (!this._states.has(this.context.connection.connectionId)) { if (debug) console.log("XRSync: Local user joined room", this.context.connection.connectionId); this._states.set(this.context.connection.connectionId, new XRUserState(this.context.connection.connectionId, this.context)); } for (const user of this.context.connection.usersInRoom()) { if (!this._states.has(user)) { this._states.set(user, new XRUserState(user, this.context)); } } } }; onLeftRoom = () => { if (this.context.connection.connectionId) { if (!this._states.has(this.context.connection.connectionId)) { const state = this._states.get(this.context.connection.connectionId); state?.dispose(); this._states.delete(this.context.connection.connectionId); } } }; onOtherUserJoinedRoom = (evt) => { const userId = evt.userId; if (!this._states.has(userId)) { if (debug) console.log("XRSync: Remote user joined room", userId); this._states.set(userId, new XRUserState(userId, this.context)); } }; onOtherUserLeftRoom = (evt) => { const userId = evt.userId; if (!this._states.has(userId)) { const state = this._states.get(userId); state?.dispose(); this._states.delete(userId); } }; _states = new Map(); onUpdate(session) { if (this.context.connection.isConnected && this.context.connection.connectionId) { const localState = this._states.get(this.context.connection.connectionId); localState?.update(session); } } onExitXR(session) { if (this.context.connection.isConnected && this.context.connection.connectionId) { const localState = this._states.get(this.context.connection.connectionId); localState?.onExitXR(session); } } } //# sourceMappingURL=NeedleXRSync.js.map