@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
JavaScript
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