From 8d14b710468d5eb92c012f76690acc58e5e2c7e2 Mon Sep 17 00:00:00 2001 From: Giga Date: Fri, 27 Oct 2023 13:22:25 +1300 Subject: [PATCH 1/3] Housekeeping --- src/modules/entity/EntityProperties.ts | 24 ++++++++++++------------ src/modules/entity/index.ts | 9 +++++---- src/stores/application-store.ts | 8 ++++---- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/modules/entity/EntityProperties.ts b/src/modules/entity/EntityProperties.ts index 3b7ecc96..41c25b3f 100644 --- a/src/modules/entity/EntityProperties.ts +++ b/src/modules/entity/EntityProperties.ts @@ -13,20 +13,20 @@ import { WebInputMode } from "@vircadia/web-sdk"; export type EntityType = -"Unknown" | "Box" | "Sphere" | "Shape" | "Model" | -"Text" | "Image" | "Web" | "ParticleEffect" | -"Line" | "PolyLine" | "PolyVox" | "Grid" | "Gizmo" | -"Light" | "Zone" | "Material"; + "Unknown" | "Box" | "Sphere" | "Shape" | "Model" | + "Text" | "Image" | "Web" | "ParticleEffect" | + "Line" | "PolyLine" | "PolyVox" | "Grid" | "Gizmo" | + "Light" | "Zone" | "Material"; export type Shape = -"Circle" | "Cone" | "Cube" | "Cylinder" | "Dodecahedron" | -"Hexagon" | "Icosahedron" | "Octagon" | "Octahedron" | -"Quad" | "Sphere" | "Tetrahedron" | "Torus" | "Triangle"; + "Circle" | "Cone" | "Cube" | "Cylinder" | "Dodecahedron" | + "Hexagon" | "Icosahedron" | "Octagon" | "Octahedron" | + "Quad" | "Sphere" | "Tetrahedron" | "Torus" | "Triangle"; export type ShapeType = "none" | "box" | "sphere" | "cylinder" | -"capsule-x" | "capsule-y" | "capsule-z" | "cylinder-x" | "cylinder-y" | "cylinder-z" | -"hull" | "compound" | "simple-hull" | "simple-compound" | "static-mesh" | -"plane" | "ellipsoid" | "circle" | "multisphere"; + "capsule-x" | "capsule-y" | "capsule-z" | "cylinder-x" | "cylinder-y" | "cylinder-z" | + "hull" | "compound" | "simple-hull" | "simple-compound" | "static-mesh" | + "plane" | "ellipsoid" | "circle" | "multisphere"; export type MaterialMappingMode = "uv" | "projected"; @@ -74,7 +74,7 @@ export interface IColorProperty { export interface IAmbientLightProperty { ambientIntensity?: number | undefined; - ambientURL? : string | undefined; + ambientURL?: string | undefined; } export interface IKeyLightProperty { @@ -88,7 +88,7 @@ export interface IKeyLightProperty { export interface ISkyboxProperty { color: IColorProperty | undefined; - url:string | undefined; + url: string | undefined; } export interface IHazeProperty { diff --git a/src/modules/entity/index.ts b/src/modules/entity/index.ts index 9778e5ba..d72272ab 100644 --- a/src/modules/entity/index.ts +++ b/src/modules/entity/index.ts @@ -9,12 +9,13 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -export type { IEntity, IModelEntity, IShapeEntity, ILightEntity, IZoneEntity, - IImageEntity } from "./EntityInterfaces"; -export type { IVector3Property, IQuaternionProperty, IColorProperty, +export type { IEntity, IModelEntity, IShapeEntity, ILightEntity, IZoneEntity, IImageEntity } from "./EntityInterfaces"; +export type { + IVector3Property, IQuaternionProperty, IColorProperty, IAmbientLightProperty, IKeyLightProperty, ISkyboxProperty, IHazeProperty, IBloomProperty, IGrabProperty, - EntityType } from "./EntityProperties"; + EntityType +} from "./EntityProperties"; export type { IEntityDescription } from "./EntityDescription"; export { EntityBuilder } from "./EntityBuilder"; export { EntityManager } from "./EntityManager"; diff --git a/src/stores/application-store.ts b/src/stores/application-store.ts index 83c16a6f..2fe02e48 100644 --- a/src/stores/application-store.ts +++ b/src/stores/application-store.ts @@ -80,14 +80,14 @@ export const useApplicationStore = defineStore("application", { avatarsInfo: new Map() }, messages: { - messages: [] as Array, + messages: new Array(), nextMessageId: 22, maxMessages: 150 }, // Information about the audio system. audio: { - inputsList: [] as Array, - outputsList: [] as Array, + inputsList: new Array(), + outputsList: new Array(), user: { connected: false, hasInputAccess: false, @@ -153,7 +153,7 @@ export const useApplicationStore = defineStore("application", { }, // Conference data. conference: { - activeRooms: [] as Array, + activeRooms: new Array(), currentRoom: {} as JitsiRoomInfo }, // State of interactions with the player's avatar. From 93f039e966d3d187ab787edc4dca12d3e05a6e84 Mon Sep 17 00:00:00 2001 From: Giga Date: Wed, 29 Nov 2023 23:13:50 +1300 Subject: [PATCH 2/3] Initial draft of the Interaction Controller --- .../controller/InteractionController.ts | 353 ++++++++++++++++++ .../avatar/controller/inputs/keyboardInput.ts | 47 +-- src/modules/avatar/index.ts | 1 + .../components/components/ModelComponent.ts | 53 +-- src/modules/scene/vscene.ts | 23 +- src/stores/application-store.ts | 4 +- 6 files changed, 399 insertions(+), 82 deletions(-) create mode 100644 src/modules/avatar/controller/InteractionController.ts diff --git a/src/modules/avatar/controller/InteractionController.ts b/src/modules/avatar/controller/InteractionController.ts new file mode 100644 index 00000000..c4c5788a --- /dev/null +++ b/src/modules/avatar/controller/InteractionController.ts @@ -0,0 +1,353 @@ +// +// InteractionController.ts +// +// This controller handles the interactions between the local avatar +// and interaction targets (previously referred to as animatables or sit-objects). +// +// Created by Giga on 27 Oct 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import { + type AbstractMesh, + Color3, + type InstancedMesh, + type Mesh, + type Node, + type TransformNode, + Vector3 +} from "@babylonjs/core"; +import { ScriptComponent } from "@Modules/script"; +import { Renderer, VScene } from "@Modules/scene"; +import { applicationStore } from "@Stores/index"; +import { LabelEntity } from "@Modules/entity/entities"; + +export const InteractiveModelTypes = [ + { name: "chair", condition: /^(?:animate_sitting|animate_seat)/iu }, + { name: "emoji_people", condition: /^animate_/iu } +] as const; + +export class InteractionTarget { + animation = ""; + private _color = Color3.Black(); + private _exitPosition = Vector3.Zero(); + private _icon = ""; + private _label: Nullable; + private _parent: AbstractMesh | Mesh | TransformNode; + private _popDistance = 0; + private _position = Vector3.Zero(); + private _rotation = Vector3.Zero(); + private _text = ""; + + constructor(parent: AbstractMesh | Mesh | TransformNode) { + this._parent = parent; + this._remakeLabel(); + } + + /** + * The color of the interaction target's label. + */ + get color(): Color3 { + return this._color; + } + + set color(value: string | Color3) { + if (typeof value === "string") { + this._color = Color3.FromHexString(value); + } else { + this._color = value; + } + this._remakeLabel(); + } + + /** + * The position the avatar should snap to when the interaction has finished. + * (Relative to the interaction target). + */ + get exitPosition(): Vector3 { + return this._exitPosition; + } + + set exitPosition(value: string | Vector3) { + this._exitPosition = InteractionTarget._parseVector3(value); + } + + /** + * The absolute position of the interaction target in the world. + */ + get absolutePosition(): Vector3 { + return this._parent.getAbsolutePosition().add(this._position); + } + + /** + * The icon to show on the interaction target's label. + */ + get icon(): string { + return this._icon; + } + + set icon(value: string) { + this._icon = value; + this._remakeLabel(); + } + + /** + * This distance from the interaction target at which its label should fade out. + */ + get popDistance(): number { + return this._popDistance; + } + + set popDistance(value: number) { + this._popDistance = value; + this._remakeLabel(); + } + + /** + * The position of the interaction target relative to its parent object. + * (The avatar will be snapped to this position while interacting). + */ + get position(): Vector3 { + return this._position; + } + + set position(value: string | Vector3) { + this._position = InteractionTarget._parseVector3(value); + this._remakeLabel(); + } + + /** + * The rotation of the interaction target relative to its parent object. + * (The avatar will be snapped to this rotation while interacting). + */ + get rotation(): Vector3 { + return this._rotation; + } + + set rotation(value: string | Vector3) { + const rotation = InteractionTarget._parseVector3(value); + // Convert from degrees to radians. + rotation.x = rotation.x * Math.PI / 180; + rotation.y = rotation.y * Math.PI / 180; + rotation.z = rotation.z * Math.PI / 180; + this._rotation = rotation; + } + + /** + * The text to show on the interaction target's label. + */ + get text(): string { + return this._text; + } + + set text(value: string) { + this._text = value; + this._remakeLabel(); + } + + private _remakeLabel(): void { + LabelEntity.remove(this._label); + this._label = LabelEntity.create( + this._parent, + 0, + this._icon || this._text, + Boolean(this._icon), + this._color, + this._popDistance + ); + if (this._label) { + this._label.position = this._position; + } + } + + private static _parseVector3(vector: string | Vector3): Vector3 { + if (typeof vector === "string") { + const output = Vector3.Zero(); + if (!vector || typeof vector !== "string") { + return output; + } + const parsedString = (/^(?[\d.-]+)[,\s]+(?[\d.-]+)[,\s]+(?[\d.-]+)/iu).exec(vector); + if (!parsedString?.groups?.x || !parsedString?.groups?.y || !parsedString?.groups?.z) { + return output; + } + output.x = parseFloat(parsedString.groups.x); + output.y = parseFloat(parsedString.groups.y); + output.z = parseFloat(parsedString.groups.z); + return output; + } + return vector.clone(); + } + + /** + * Create a new set of interaction targets based on the metadata properties of a given mesh. + * @param mesh The mesh to read the metadata from. + * @returns An array of interaction targets. + */ + public static fromMesh(mesh: AbstractMesh | Mesh | InstancedMesh | TransformNode): Array { + const meshExtras = mesh.metadata?.gltf?.extras as Nullable>; + const targets = new Array(); + if (!meshExtras || typeof meshExtras !== "object") { + return targets; + } + for (const [property, value] of Object.entries(meshExtras)) { + const parsedProperty = (/^vircadia_sit_(?[\d]+)_(?[\w]+)/iu).exec(property); + if (!parsedProperty?.groups?.index || !parsedProperty?.groups?.type) { + continue; + } + const index = parseInt(parsedProperty.groups.index, 10); + const type = parsedProperty.groups.type.toLowerCase(); + if (!targets[index]) { + targets[index] = new InteractionTarget(mesh); + } + switch (type) { + case "position": + targets[index].position = this._parseVector3(String(value)); + break; + case "rotation": + targets[index].rotation = this._parseVector3(String(value)); + break; + case "exit_position": + targets[index].exitPosition = this._parseVector3(String(value)); + break; + case "icon": + targets[index].icon = String(value); + break; + case "text": + targets[index].text = String(value); + break; + case "color": + targets[index].color = String(value); + break; + case "label_distance": + targets[index].popDistance = typeof value === "string" ? parseFloat(value) : Number(value); + break; + case "animation": + targets[index].animation = String(value); + break; + default: + break; + } + } + return targets; + } +} + +export class InteractionController extends ScriptComponent { + private _vscene: VScene; + + constructor(vscene: VScene) { + super(InteractionController.typeName); + this._vscene = vscene; + } + + /** + * A string identifying the type of this component. + * @returns "InteractionController" + */ + public get componentType(): string { + return InteractionController.typeName; + } + + static get typeName(): string { + return "InteractionController"; + } + + /** + * Register an interaction target with the scene. + * The scene keeps a reference to the target, so any changes to the original target will be reflected in the scene. + * @param target The target to register. + */ + public addTarget(target: InteractionTarget): void { + applicationStore.interactions.targets.push(target); + } + + /** + * Register multiple interaction targets with the scene. + * The scene keeps references to the targets, so any changes to the original targets will be reflected in the scene. + * @param targets The targets to register. + */ + public addTargets(targets: Array): void { + for (const target of targets) { + applicationStore.interactions.targets.push(target); + } + } + + /** + * Remove an interaction target from the scene. + * @param target The target to remove. + */ + public removeTarget(target: InteractionTarget): void { + const index = applicationStore.interactions.targets.indexOf(target); + applicationStore.interactions.targets.splice(index, 1); + } + + /** + * Remove multiple interaction targets from the scene. + * @param target The targets to remove. + */ + public removeTargets(targets: Array): void { + for (const target of targets) { + const index = applicationStore.interactions.targets.indexOf(target); + applicationStore.interactions.targets.splice(index, 1); + } + } + + /** + * Retrieve the interaction target that is currently closest to the local avatar. + * @returns The closest interaction target, or `undefined` if none was found or was within the max interaction distance. + */ + public getNearestTarget(): Nullable { + const avatar = Renderer.getScene().getMyAvatar(); + const avatarAbsolutePosition = avatar?.getAbsolutePosition(); + if (!avatarAbsolutePosition) { + return undefined; + } + + // Filter out any targets that are too far away, or don't have an absolute position. + const distances = new Array<[InteractionTarget | TransformNode | AbstractMesh, number]>(); + // Search through the registered targets. + for (const target of applicationStore.interactions.targets) { + if (!target) { + continue; + } + if (!(target instanceof InteractionTarget) && !("getAbsolutePosition" in target)) { + continue; + } + const distance = target.absolutePosition + .subtract(avatarAbsolutePosition) + .length(); + if (distance <= applicationStore.interactions.interactionDistance) { + distances.push([target as InteractionTarget, distance]); + } + } + // Search through the scene. + const sceneNodes = this._scene.getNodes() as (Node | TransformNode | AbstractMesh)[]; + const targetNodes = sceneNodes.filter((node) => (/^animate_/iu).test(node.name)); + for (const node of targetNodes) { + if (!("getAbsolutePosition" in node)) { + continue; + } + const distance = node.getAbsolutePosition() + .subtract(avatarAbsolutePosition) + .length(); + if (distance <= applicationStore.interactions.interactionDistance) { + distances.push([node, distance]); + } + } + + // If there are multiple interactive targets in range, use the closest one. + if (distances.length > 0) { + const closestTarget = distances.reduce((a, b) => (a[1] <= b[1] ? a : b)); + if (closestTarget[0] instanceof InteractionTarget) { + return closestTarget[0]; + } + return InteractionTarget.fromMesh(closestTarget[0])[0]; + } + + return undefined; + } +} diff --git a/src/modules/avatar/controller/inputs/keyboardInput.ts b/src/modules/avatar/controller/inputs/keyboardInput.ts index a16b828f..1f9f1e4a 100644 --- a/src/modules/avatar/controller/inputs/keyboardInput.ts +++ b/src/modules/avatar/controller/inputs/keyboardInput.ts @@ -22,9 +22,6 @@ import { ActionEvent, Scalar, IAction, - AbstractMesh, - Node, - TransformNode, Vector3, Quaternion } from "@babylonjs/core"; @@ -184,36 +181,12 @@ export class KeyboardInput implements IInputHandler { // Sit. if (sourceEvent.code === userStore.controls.keyboard.movement.sit?.keycode) { // Get the player's avatar mesh. - const avatarMesh = this._scene.meshes.find((mesh) => mesh.name === "MyAvatar"); - + const avatarMesh = Renderer.getScene().getMyAvatar(); if (avatarMesh) { - // Check for sittable objects. - const sitObjects = this._scene.getNodes().filter((node) => (/^animate_/iu).test(node.name)) as (Node | TransformNode | AbstractMesh)[]; - - // Filter out any that are too far away, or don't have an absolute position. - const avatarAbsolutePosition = avatarMesh.getAbsolutePosition(); - const distances = [] as [TransformNode | AbstractMesh, number][]; - sitObjects.forEach((object) => { - if (!("getAbsolutePosition" in object)) { - return; - } - const distance = object.getAbsolutePosition() - .subtract(avatarAbsolutePosition) - .length(); - if (distance <= applicationStore.interactions.interactionDistance) { - distances.push([object, distance]); - } - }); - - // If there are some sittable objects in range, use the closest one. - if (distances.length > 0) { - const selectedSitObject = distances.reduce((previousObject, currentObject) => { - if (previousObject[1] <= currentObject[1]) { - return previousObject; - } - return currentObject; - }); - + // Get the nearest interactive target. + const controller = Renderer.getScene().interactionController; + const target = controller?.getNearestTarget(); + if (target) { // Clear the move direction state. this._state.moveDir = Vector3.Zero(); @@ -222,7 +195,7 @@ export class KeyboardInput implements IInputHandler { this._state.state = State.Pose; let animation = Action.SitBeanbag; for (const entry of AnimationMap.entries()) { - if (selectedSitObject[0].name.includes(entry[1].name)) { + if (target.animation.includes(entry[1].name)) { animation = entry[0]; break; } @@ -233,10 +206,10 @@ export class KeyboardInput implements IInputHandler { Renderer.getScene().sceneController?.removeGravity(); // Snap to the sittable object. - avatarMesh.setAbsolutePosition(selectedSitObject[0].getAbsolutePosition()); - avatarMesh.rotationQuaternion = selectedSitObject[0].absoluteRotationQuaternion?.clone() - .multiply(new Quaternion(0, -1, 0, 0)) - ?? avatarMesh.rotationQuaternion; + avatarMesh.setAbsolutePosition(target.absolutePosition); + avatarMesh.rotationQuaternion = target.rotation.clone() + .toQuaternion() + .multiply(new Quaternion(0, -1, 0, 0)); } else { // Otherwise, sit on the ground. this._state.duration = 0; diff --git a/src/modules/avatar/index.ts b/src/modules/avatar/index.ts index c8393d02..cfe47502 100644 --- a/src/modules/avatar/index.ts +++ b/src/modules/avatar/index.ts @@ -10,6 +10,7 @@ // export { InputController } from "./controller/inputController"; +export { InteractionController } from "./controller/InteractionController"; export { ScriptAvatarController } from "./controller/scriptAvatarController"; export { MyAvatarController } from "./controller/myAvatarController"; export { AvatarMapper } from "./AvatarMapper"; diff --git a/src/modules/entity/components/components/ModelComponent.ts b/src/modules/entity/components/components/ModelComponent.ts index 78cdf7ab..3dd6b51c 100644 --- a/src/modules/entity/components/components/ModelComponent.ts +++ b/src/modules/entity/components/components/ModelComponent.ts @@ -11,11 +11,11 @@ import { MeshComponent, DEFAULT_MESH_RENDER_GROUP_ID } from "@Modules/object"; import { - SceneLoader, - PhysicsImpostor, AbstractMesh, - TransformNode, Node, + PhysicsImpostor, + SceneLoader, + TransformNode, } from "@babylonjs/core"; import { IModelEntity } from "../../EntityInterfaces"; import { LabelEntity } from "@Modules/entity/entities"; @@ -23,14 +23,12 @@ import { updateContentLoadingProgress } from "@Modules/scene/LoadingScreen"; import { applicationStore } from "@Stores/index"; import Log from "@Modules/debugging/log"; import { LODManager } from "@Modules/scene/LODManager"; - -const InteractiveModelTypes = [ - { name: "chair", condition: /^(?:animate_sitting|animate_seat)/iu }, - { name: "emoji_people", condition: /^animate_/iu }, -]; +import { InteractionTarget, InteractiveModelTypes } from "@Modules/avatar/controller/InteractionController"; +import { Renderer } from "@Modules/scene"; export class ModelComponent extends MeshComponent { private _modelURL = ""; + private _interactionTargets = new Array(); public get componentType(): string { return ModelComponent.typeName; @@ -76,36 +74,18 @@ export class ModelComponent extends MeshComponent { // Add a label to any of the model's children if they match any of the InteractiveModelTypes. const defaultLabelHeight = 0.6; - const labelOffset = 0.25; - const labelPopDistance = - applicationStore.interactions.interactionDistance; + const labelPopDistance = applicationStore.interactions.interactionDistance; const childNodes = this.mesh.getChildren( - (node) => "getBoundingInfo" in node, + (node) => "position" in node, false ) as (AbstractMesh | TransformNode | Node)[]; - childNodes.forEach((childNode) => { - const genericModelType = InteractiveModelTypes.find( - (type) => type.condition.test(childNode.name) - ); - if ( - !genericModelType || - !("getBoundingInfo" in childNode) - ) { - return; + for (const node of childNodes) { + if (!("position" in node)) { + continue; } - const boundingInfo = childNode.getBoundingInfo(); - const height = - boundingInfo.maximum.y - boundingInfo.minimum.y; - LabelEntity.create( - childNode, - height + labelOffset, - genericModelType.name, - true, - undefined, - labelPopDistance, - () => !applicationStore.interactions.isInteracting - ); - }); + const controller = Renderer.getScene().interactionController; + controller?.addTargets(InteractionTarget.fromMesh(node)); + } result.transformNodes.forEach((childNode) => { const genericModelType = InteractiveModelTypes.find( (type) => type.condition.test(childNode.name) @@ -201,6 +181,11 @@ export class ModelComponent extends MeshComponent { } } + public dispose(): void { + Renderer.getScene().interactionController?.removeTargets(this._interactionTargets); + super.dispose(); + } + protected _getMass(entity: IModelEntity): number { if (entity.dynamic && entity.dimensions && entity.density) { return ( diff --git a/src/modules/scene/vscene.ts b/src/modules/scene/vscene.ts index d61a1d47..951e77e2 100644 --- a/src/modules/scene/vscene.ts +++ b/src/modules/scene/vscene.ts @@ -45,10 +45,11 @@ import { requireScripts, } from "@Modules/script"; import { + AvatarMapper, InputController, + InteractionController, MyAvatarController, ScriptAvatarController, - AvatarMapper, } from "@Modules/avatar"; import { IEntity, @@ -85,14 +86,12 @@ export class VScene { _avatarAnimationGroups: AnimationGroup[] = []; _resourceManager: Nullable = null; _domainController: Nullable = null; + _interactionController: Nullable = null; _sceneController: Nullable = null; _sceneManager: Nullable = null; _currentSceneURL = ""; - private _onMyAvatarModelChangedObservable: Observable = - new Observable(); - - private _onEntityEventObservable: Observable = - new Observable(); + private _onMyAvatarModelChangedObservable = new Observable(); + private _onEntityEventObservable = new Observable(); constructor(pEngine: Engine, pSceneId = 0) { if (process.env.NODE_ENV === "development") { @@ -146,6 +145,10 @@ export class VScene { return this._css3DRenderer; } + public get interactionController(): Nullable { + return this._interactionController; + } + public get sceneController(): Nullable { return this._sceneController; } @@ -262,15 +265,12 @@ export class VScene { const positionOffset = avatar.calcMovePOV(0, 0, 1.5); const position = avatar.position.add(positionOffset); this._teleportMyAvatar(position); - this._myAvatar?.lookAt(avatar.position, Math.PI); } } public stopMyAvatar(): void { - const controller = this._myAvatar?.getComponent( - InputController.typeName - ); + const controller = this._myAvatar?.getComponent(InputController.typeName); if (controller instanceof InputController) { controller.isStopped = true; } @@ -673,6 +673,9 @@ export class VScene { this._sceneController = new SceneController(this); this._sceneManager.addComponent(this._sceneController); + + this._interactionController = new InteractionController(this); + this._sceneManager.addComponent(this._interactionController); } if (this._domainController) { diff --git a/src/stores/application-store.ts b/src/stores/application-store.ts index 2fe02e48..2361afe2 100644 --- a/src/stores/application-store.ts +++ b/src/stores/application-store.ts @@ -18,6 +18,7 @@ import { DomainAudioClient } from "@Modules/domain/audio"; import { DomainAvatarClient } from "@Modules/domain/avatar"; import { AssignmentClientState } from "@Modules/domain/client"; import { ConnectionState, Domain } from "@Modules/domain/domain"; +import { InteractionTarget } from "@Modules/avatar/controller/InteractionController"; import type { ChatMessage } from "@Modules/domain/message"; import type { WebEntity } from "@Modules/entity/entities"; import type { IWebEntity } from "@Modules/entity/EntityInterfaces"; @@ -159,7 +160,8 @@ export const useApplicationStore = defineStore("application", { // State of interactions with the player's avatar. interactions: { interactionDistance: 1.5, - isInteracting: false + isInteracting: false, + targets: new Array() } }), From 73d11d174b026c80b05bba9158b008c8fa80e3c9 Mon Sep 17 00:00:00 2001 From: Giga Date: Wed, 29 Nov 2023 23:13:58 +1300 Subject: [PATCH 3/3] Housekeeping --- src/modules/entity/EntityProperties.ts | 2 +- src/modules/entity/components/scripts/TeleportController.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/modules/entity/EntityProperties.ts b/src/modules/entity/EntityProperties.ts index 41c25b3f..04399fc3 100644 --- a/src/modules/entity/EntityProperties.ts +++ b/src/modules/entity/EntityProperties.ts @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import { WebInputMode } from "@vircadia/web-sdk"; +import type { WebInputMode } from "@vircadia/web-sdk"; export type EntityType = diff --git a/src/modules/entity/components/scripts/TeleportController.ts b/src/modules/entity/components/scripts/TeleportController.ts index 8fca5138..b1d95c2d 100644 --- a/src/modules/entity/components/scripts/TeleportController.ts +++ b/src/modules/entity/components/scripts/TeleportController.ts @@ -9,8 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* eslint-disable @typescript-eslint/no-magic-numbers */ - import Log from "@Modules/debugging/log"; import { inspector } from "@Modules/script"; import { Renderer, VScene } from "@Modules/scene";