From feb9299c9ff526068b22e23009b303cb9b0569ae Mon Sep 17 00:00:00 2001 From: Kalila <69767640+digisomni@users.noreply.github.com> Date: Thu, 19 Oct 2023 18:46:28 +0800 Subject: [PATCH 1/4] Testing. --- src/components/MainScene.vue | 530 +++--- src/layouts/MainLayout.vue | 1529 +++++++++-------- src/modules/entity/EntityManager.ts | 284 +-- .../scene/controllers/domainController.ts | 509 +++--- src/modules/scene/renderer.ts | 320 ++-- src/modules/scene/vscene.ts | 23 +- 6 files changed, 1615 insertions(+), 1580 deletions(-) diff --git a/src/components/MainScene.vue b/src/components/MainScene.vue index 1eda413f..a7332344 100644 --- a/src/components/MainScene.vue +++ b/src/components/MainScene.vue @@ -1,257 +1,273 @@ - - - - - - - + + + + + + + diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index 35cc9e27..9277fac7 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -1,763 +1,766 @@ - - - - - - - + + + + + + + diff --git a/src/modules/entity/EntityManager.ts b/src/modules/entity/EntityManager.ts index 060987ab..5e7d10b8 100644 --- a/src/modules/entity/EntityManager.ts +++ b/src/modules/entity/EntityManager.ts @@ -1,140 +1,144 @@ -// -// EntityManager.ts -// -// Created by Nolan Huang on 3 Aug 2022. -// Copyright 2022 Vircadia contributors. -// Copyright 2022 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 { IEntity } from "./EntityInterfaces"; -import { Observable } from "@babylonjs/core"; -import { EntityServer, EntityProperties } from "@vircadia/web-sdk"; -import { EntityType as PackageEntityType } from "./package/DomainProperties"; -import { Entity, ShapeEntity, ModelEntity, LightEntity, ZoneEntity, ImageEntity, MaterialEntity, WebEntity } from "./entities"; -import Log from "@Modules/debugging/log"; - -type EntityFactory = (id:string) => Entity; - -export class EntityManager { - _entityServer: EntityServer; - _entities: Map; - _onEntityAdded: Observable; - _onEntityRemoved: Observable; - _entityPropertiesArray = new Array(); - _entityFactories = new Map(); - - constructor(entityServer: EntityServer) { - this._entityServer = entityServer; - this._entityServer.entityData.connect(this._handleOnEntityData.bind(this)); - - this._entities = new Map(); - this._onEntityAdded = new Observable(); - this._onEntityRemoved = new Observable(); - - // register EntityFactories - this._entityFactories.set(PackageEntityType.Box, (id) => new ShapeEntity(id, "Box")); - this._entityFactories.set(PackageEntityType.Sphere, (id) => new ShapeEntity(id, "Sphere")); - this._entityFactories.set(PackageEntityType.Shape, (id) => new ShapeEntity(id, "Shape")); - - this._entityFactories.set(PackageEntityType.Model, (id) => new ModelEntity(id)); - this._entityFactories.set(PackageEntityType.Light, (id) => new LightEntity(id)); - this._entityFactories.set(PackageEntityType.Zone, (id) => new ZoneEntity(id)); - this._entityFactories.set(PackageEntityType.Image, (id) => new ImageEntity(id)); - this._entityFactories.set(PackageEntityType.Material, (id) => new MaterialEntity(id)); - this._entityFactories.set(PackageEntityType.Web, (id) => new WebEntity(id)); - } - - public get onEntityAdded(): Observable { - return this._onEntityAdded; - } - - public get onEntityRemoved(): Observable { - return this._onEntityRemoved; - } - - public hasEntity(id: string): boolean { - return this._entities.has(id); - } - - public getEntity(id: string): IEntity | undefined { - return this._entities.get(id); - } - - public removeEntity(id: string): boolean { - const entity = this._entities.get(id); - if (entity) { - this._entities.delete(id); - this._onEntityRemoved.notifyObservers(entity); - return true; - } - return false; - } - - public clear(): void { - this.clear(); - } - - public createEntity(props: EntityProperties): IEntity | undefined { - const factory = this._entityFactories.get(props.entityType); - if (!factory) { - Log.warn(Log.types.ENTITIES, `Unknown entity type: ${props.entityType}`); - return undefined; - } - const entity = factory(props.entityItemID.stringify()); - entity.copyFormPacketData(props); - // prevent to emit change event - entity.update(); - - this._addEntity(entity); - - return entity; - } - - public updateEntity(props: EntityProperties): void { - const entity = this._entities.get(props.entityItemID.stringify()); - if (entity) { - entity.copyFormPacketData(props); - } - } - - public update(): void { - // NOTE: - // update exist entities to prevent the add and change event notified in the same frame - this._entities.forEach((entity) => { - entity.update(); - }); - - if (this._entityPropertiesArray.length > 0) { - this._entityPropertiesArray.forEach((props) => { - const entity = this._entities.get(props.entityItemID.stringify()); - if (entity) { - entity.copyFormPacketData(props); - } else { - this.createEntity(props); - } - }); - this._entityPropertiesArray = []; - } - } - - private _handleOnEntityData(data: EntityProperties[]): void { - if (data.length > 0) { - console.log(Log.types.ENTITIES, - `Receive entity data:`, data); - - this._entityPropertiesArray = this._entityPropertiesArray.concat(data); - } - } - - private _addEntity(entity: Entity): void { - if (this._entities.get(entity.id)) { - throw new Error(`Entity ${entity.id} already exists.`); - } - - this._entities.set(entity.id, entity); - this._onEntityAdded.notifyObservers(entity); - } -} +// +// EntityManager.ts +// +// Created by Nolan Huang on 3 Aug 2022. +// Copyright 2022 Vircadia contributors. +// Copyright 2022 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 { IEntity } from "./EntityInterfaces"; +import { Observable } from "@babylonjs/core"; +import { EntityServer, EntityProperties } from "@vircadia/web-sdk"; +import { EntityType as PackageEntityType } from "./package/DomainProperties"; +import { Entity, ShapeEntity, ModelEntity, LightEntity, ZoneEntity, ImageEntity, MaterialEntity, WebEntity } from "./entities"; +import Log from "@Modules/debugging/log"; + +type EntityFactory = (id: string) => Entity; + +export class EntityManager { + _entityServer: EntityServer; + _entities: Map; + _onEntityAdded: Observable; + _onEntityRemoved: Observable; + _entityPropertiesArray = new Array(); + _entityFactories = new Map(); + + constructor(entityServer: EntityServer) { + this._entityServer = entityServer; + this._entityServer.entityData.connect(this._handleOnEntityData.bind(this)); + + this._entities = new Map(); + this._onEntityAdded = new Observable(); + this._onEntityRemoved = new Observable(); + + // register EntityFactories + this._entityFactories.set(PackageEntityType.Box, (id) => new ShapeEntity(id, "Box")); + this._entityFactories.set(PackageEntityType.Sphere, (id) => new ShapeEntity(id, "Sphere")); + this._entityFactories.set(PackageEntityType.Shape, (id) => new ShapeEntity(id, "Shape")); + + this._entityFactories.set(PackageEntityType.Model, (id) => new ModelEntity(id)); + this._entityFactories.set(PackageEntityType.Light, (id) => new LightEntity(id)); + this._entityFactories.set(PackageEntityType.Zone, (id) => new ZoneEntity(id)); + this._entityFactories.set(PackageEntityType.Image, (id) => new ImageEntity(id)); + this._entityFactories.set(PackageEntityType.Material, (id) => new MaterialEntity(id)); + this._entityFactories.set(PackageEntityType.Web, (id) => new WebEntity(id)); + } + + public get onEntityAdded(): Observable { + return this._onEntityAdded; + } + + public get onEntityRemoved(): Observable { + return this._onEntityRemoved; + } + + public hasEntity(id: string): boolean { + return this._entities.has(id); + } + + public getEntity(id: string): IEntity | undefined { + return this._entities.get(id); + } + + public removeEntity(id: string): boolean { + const entity = this._entities.get(id); + if (entity) { + this._entities.delete(id); + this._onEntityRemoved.notifyObservers(entity); + return true; + } + return false; + } + + public clear(): void { + console.info(Log.types.ENTITIES, "$$$ Clear all entities."); + this._entities.forEach((entity) => { + this.removeEntity(entity.id); + }); + console.info(Log.types.ENTITIES, "$$$ Clear all entities done."); + } + + public createEntity(props: EntityProperties): IEntity | undefined { + const factory = this._entityFactories.get(props.entityType); + if (!factory) { + Log.warn(Log.types.ENTITIES, `Unknown entity type: ${props.entityType}`); + return undefined; + } + const entity = factory(props.entityItemID.stringify()); + entity.copyFormPacketData(props); + // prevent to emit change event + entity.update(); + + this._addEntity(entity); + + return entity; + } + + public updateEntity(props: EntityProperties): void { + const entity = this._entities.get(props.entityItemID.stringify()); + if (entity) { + entity.copyFormPacketData(props); + } + } + + public update(): void { + // NOTE: + // update exist entities to prevent the add and change event notified in the same frame + this._entities.forEach((entity) => { + entity.update(); + }); + + if (this._entityPropertiesArray.length > 0) { + this._entityPropertiesArray.forEach((props) => { + const entity = this._entities.get(props.entityItemID.stringify()); + if (entity) { + entity.copyFormPacketData(props); + } else { + this.createEntity(props); + } + }); + this._entityPropertiesArray = []; + } + } + + private _handleOnEntityData(data: EntityProperties[]): void { + if (data.length > 0) { + console.log(Log.types.ENTITIES, + `Receive entity data:`, data); + + this._entityPropertiesArray = this._entityPropertiesArray.concat(data); + } + } + + private _addEntity(entity: Entity): void { + if (this._entities.get(entity.id)) { + throw new Error(`Entity ${entity.id} already exists.`); + } + + this._entities.set(entity.id, entity); + this._onEntityAdded.notifyObservers(entity); + } +} diff --git a/src/modules/scene/controllers/domainController.ts b/src/modules/scene/controllers/domainController.ts index 009092e7..4d353218 100644 --- a/src/modules/scene/controllers/domainController.ts +++ b/src/modules/scene/controllers/domainController.ts @@ -1,253 +1,256 @@ -// -// DomainController.ts -// -// Created by Nolan Huang on 1 Aug 2022. -// Copyright 2022 Vircadia contributors. -// Copyright 2022 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 -// - -/* eslint-disable @typescript-eslint/no-magic-numbers */ - -import { AvatarMixer, Uuid, DomainServer } from "@vircadia/web-sdk"; -import type { ScriptAvatar, EntityServer, Camera as DomainCamera } from "@vircadia/web-sdk"; -import type { Camera } from "@babylonjs/core"; -import { MyAvatarController } from "@Modules/avatar"; -import { DomainManager } from "@Modules/domain"; -import { AssignmentClientState, Client } from "@Modules/domain/client"; -import { ConnectionState, Domain } from "@Modules/domain/domain"; -import { EntityManager, type IEntity, EntityMapper } from "@Modules/entity"; -import { GameObject } from "@Modules/object"; -import type { VScene } from "@Modules/scene/vscene"; -import { ScriptComponent, inspectorAccessor, inspector } from "@Modules/script"; -import Log from "@Modules/debugging/log"; - -export class DomainController extends ScriptComponent { - private _avatarMixer: Nullable = null; - private _entityServer: Nullable = null; - private _domainConnectionState: ConnectionState = ConnectionState.DISCONNECTED; - private _entityManager: Nullable = null; - private _domainCamera: Nullable = null; - private _vscene: Nullable; - private _camera: Nullable = null; - - @inspector() - public sessionID = ""; - - constructor() { - super("DomainController"); - this._handleActiveDomainStateChange = this._handleActiveDomainStateChange.bind(this); - } - - public set vscene(value: VScene) { - this._vscene = value; - } - - @inspectorAccessor() - public get domainState(): string { - return DomainServer.stateToString(this._domainConnectionState); - } - - @inspectorAccessor() - public get avatarMixerState(): string { - return this._avatarMixer - ? AvatarMixer.stateToString(this._avatarMixer.state) - : AvatarMixer.stateToString(AssignmentClientState.UNAVAILABLE); - } - - @inspectorAccessor() - public get entityServerState(): string { - return this._entityServer - ? Client.stateToString(this._entityServer.state) - : Client.stateToString(AssignmentClientState.UNAVAILABLE); - } - - - /** - * Gets a string identifying the type of this Component - * @returns "DomainController" string - */ - // eslint-disable-next-line class-methods-use-this - public get componentType(): string { - return "DomainController"; - } - - public onInitialize(): void { - Log.debug(Log.types.OTHER, - `DomainController onInitialize`); - - // Listen for the domain to connect and disconnect - DomainManager.onActiveDomainStateChange.connect(this._handleActiveDomainStateChange.bind(this)); - - GameObject.dontDestroyOnLoad(this._gameObject as GameObject); - } - - - public onUpdate(): void { - if (this._entityManager) { - this._entityManager.update(); - } - - // this._syncCamera(); - } - - public onStop(): void { - Log.debug(Log.types.OTHER, `DomainController onStop`); - DomainManager.onActiveDomainStateChange.disconnect(this._handleActiveDomainStateChange.bind(this)); - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public _handleActiveDomainStateChange(domain: Domain, state: ConnectionState, info: string): void { - Log.debug(Log.types.NETWORK, `Active Domain state change: ${Domain.stateToString(state)}`); - - if (state === ConnectionState.CONNECTED) { - void this._handleDomainConnected(domain); - - } else if (state === ConnectionState.DISCONNECTED) { - this._vscene?.unloadAllAvatars(); - - const myAvatarController = this._vscene?._myAvatar?.getComponent(MyAvatarController.typeName); - if (myAvatarController instanceof MyAvatarController) { - myAvatarController.myAvatar = null; - } - - const avatarList = this._avatarMixer?.avatarList; - if (avatarList) { - avatarList.avatarAdded.disconnect(this._handleAvatarAdded); - avatarList.avatarRemoved.disconnect(this._handleAvatarRemoved); - } - - this._avatarMixer = null; - this._entityServer = null; - this._entityManager = null; - this._domainCamera = null; - this._camera = null; - } - - this._domainConnectionState = state; - } - - private async _handleDomainConnected(domain: Domain): Promise { - if (!this._vscene) { - return; - } - - this._entityServer = domain.EntityClient; - if (this._entityServer) { - this._entityServer.onStateChanged = this._handleOnEntityServerStateChanged.bind(this); - } - - await this._vscene.load(); - this._vscene.teleportMyAvatar(domain.Location); - - if (domain.DomainClient) { - this.sessionID = domain.DomainClient.sessionUUID.stringify(); - } - Log.debug(Log.types.AVATAR, `Session ID: ${this.sessionID}`); - - this._avatarMixer = domain.AvatarClient?.Mixer; - const myAvatarInterface = domain.AvatarClient?.MyAvatar; - if (myAvatarInterface) { - if (myAvatarInterface.skeletonModelURL === "") { - myAvatarInterface.skeletonModelURL = this._vscene.myAvatarModelURL; - } - - const myAvatarController = this._vscene._myAvatar?.getComponent(MyAvatarController.typeName); - if (myAvatarController instanceof MyAvatarController) { - myAvatarController.myAvatar = myAvatarInterface; - } - } - - const avatarList = this._avatarMixer?.avatarList; - if (avatarList) { - avatarList.avatarAdded.connect(this._handleAvatarAdded); - avatarList.avatarRemoved.connect(this._handleAvatarRemoved); - - const uuids = avatarList.getAvatarIDs(); - const emptyId = new Uuid(); - - uuids.forEach((uuid) => { - // filter my avatar - if (uuid.stringify() !== emptyId.stringify()) { - this._handleAvatarAdded(uuid); - } - }); - } - - this._camera = this._vscene.camera; - this._domainCamera = domain.Camera; - // this._syncCamera(); - } - - private _handleAvatarAdded = (sessionID: Uuid): void => { - const avatarList = this._avatarMixer?.avatarList; - if (avatarList) { - Log.debug(Log.types.AVATAR, - `AvatarAdded. Session ID: ${sessionID.stringify()}`); - - const domain = avatarList.getAvatar(sessionID); - - if (domain.skeletonModelURL !== "") { - void this._vscene?.loadAvatar(sessionID, domain); - } - - domain.skeletonModelURLChanged.connect(() => { - this._handleAvatarSkeletonModelURLChanged(sessionID, domain); - }); - } - }; - - private _handleAvatarRemoved = (sessionID: Uuid): void => { - Log.debug(Log.types.AVATAR, - `handleAvatarRemoved. Session ID: ${sessionID.stringify()}`); - this._vscene?.unloadAvatar(sessionID); - - }; - - private _handleAvatarSkeletonModelURLChanged(sessionID: Uuid, domain:ScriptAvatar): void { - Log.debug(Log.types.AVATAR, - `handleAvatarSkeletonModelURLChanged. Session ID: ${sessionID.stringify()}, ${domain.skeletonModelURL}`); - - void this._vscene?.loadAvatar(sessionID, domain); - } - - private _handleOnEntityServerStateChanged(state: AssignmentClientState): void { - Log.info(Log.types.ENTITIES, - `Entity Sever state changed. New state: ${Client.stateToString(state)}`); - - if (state === AssignmentClientState.CONNECTED) { - this._entityManager = new EntityManager(this._entityServer as EntityServer); - this._entityManager.onEntityAdded.add(this._handleOnEntityAdded.bind(this)); - this._entityManager.onEntityRemoved.add(this._handleOnEntityRemoved.bind(this)); - } else if (state === AssignmentClientState.DISCONNECTED) { - this._entityManager = null; - } - } - - private _handleOnEntityAdded(entity: IEntity): void { - Log.debug(Log.types.ENTITIES, - `Add entity ${entity.id} - name:${entity.name as string} - type: ${entity.type}`); - - this._vscene?.loadEntity(entity); - } - - private _handleOnEntityRemoved(entity: IEntity): void { - Log.debug(Log.types.ENTITIES, - `Remove entity ${entity.id} - name:${entity.name as string} - type: ${entity.type}`); - - this._vscene?.removeEntity(entity.id); - } - - private _syncCamera(): void { - if (this._domainCamera && this._camera) { - this._domainCamera.position = EntityMapper.mapToVector3Property(this._camera.globalPosition); - this._domainCamera.orientation = EntityMapper.mapToQuaternionProperty(this._camera.absoluteRotation); - } - } -} +// +// DomainController.ts +// +// Created by Nolan Huang on 1 Aug 2022. +// Copyright 2022 Vircadia contributors. +// Copyright 2022 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 +// + +/* eslint-disable @typescript-eslint/no-magic-numbers */ + +import { AvatarMixer, Uuid, DomainServer } from "@vircadia/web-sdk"; +import type { ScriptAvatar, EntityServer, Camera as DomainCamera } from "@vircadia/web-sdk"; +import type { Camera } from "@babylonjs/core"; +import { MyAvatarController } from "@Modules/avatar"; +import { DomainManager } from "@Modules/domain"; +import { AssignmentClientState, Client } from "@Modules/domain/client"; +import { ConnectionState, Domain } from "@Modules/domain/domain"; +import { EntityManager, type IEntity, EntityMapper } from "@Modules/entity"; +import { GameObject } from "@Modules/object"; +import type { VScene } from "@Modules/scene/vscene"; +import { ScriptComponent, inspectorAccessor, inspector } from "@Modules/script"; +import Log from "@Modules/debugging/log"; + +export class DomainController extends ScriptComponent { + private _avatarMixer: Nullable = null; + private _entityServer: Nullable = null; + private _domainConnectionState: ConnectionState = ConnectionState.DISCONNECTED; + private _entityManager: Nullable = null; + private _domainCamera: Nullable = null; + private _vscene: Nullable; + private _camera: Nullable = null; + + @inspector() + public sessionID = ""; + + constructor() { + super("DomainController"); + this._handleActiveDomainStateChange = this._handleActiveDomainStateChange.bind(this); + } + + public set vscene(value: VScene) { + this._vscene = value; + } + + @inspectorAccessor() + public get domainState(): string { + return DomainServer.stateToString(this._domainConnectionState); + } + + @inspectorAccessor() + public get avatarMixerState(): string { + return this._avatarMixer + ? AvatarMixer.stateToString(this._avatarMixer.state) + : AvatarMixer.stateToString(AssignmentClientState.UNAVAILABLE); + } + + @inspectorAccessor() + public get entityServerState(): string { + return this._entityServer + ? Client.stateToString(this._entityServer.state) + : Client.stateToString(AssignmentClientState.UNAVAILABLE); + } + + + /** + * Gets a string identifying the type of this Component + * @returns "DomainController" string + */ + // eslint-disable-next-line class-methods-use-this + public get componentType(): string { + return "DomainController"; + } + + public onInitialize(): void { + Log.debug(Log.types.OTHER, + `DomainController onInitialize`); + + // Listen for the domain to connect and disconnect + DomainManager.onActiveDomainStateChange.connect(this._handleActiveDomainStateChange.bind(this)); + + GameObject.dontDestroyOnLoad(this._gameObject as GameObject); + } + + + public onUpdate(): void { + if (this._entityManager) { + this._entityManager.update(); + } + + // this._syncCamera(); + } + + public onStop(): void { + Log.debug(Log.types.OTHER, `DomainController onStop`); + DomainManager.onActiveDomainStateChange.disconnect(this._handleActiveDomainStateChange.bind(this)); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public _handleActiveDomainStateChange(domain: Domain, state: ConnectionState, info: string): void { + Log.debug(Log.types.NETWORK, `Active Domain state change: ${Domain.stateToString(state)}`); + + if (state === ConnectionState.CONNECTED) { + void this._handleDomainConnected(domain); + + } else if (state === ConnectionState.DISCONNECTED) { + console.warn("$$$ Domain disconnected. Unloading scene."); + this._vscene?.unloadAllAvatars(); + + const myAvatarController = this._vscene?._myAvatar?.getComponent(MyAvatarController.typeName); + if (myAvatarController instanceof MyAvatarController) { + myAvatarController.myAvatar = null; + } + + const avatarList = this._avatarMixer?.avatarList; + if (avatarList) { + avatarList.avatarAdded.disconnect(this._handleAvatarAdded); + avatarList.avatarRemoved.disconnect(this._handleAvatarRemoved); + } + + this._entityManager?.clear(); + + this._avatarMixer = null; + this._entityServer = null; + this._entityManager = null; + this._domainCamera = null; + this._camera = null; + } + + this._domainConnectionState = state; + } + + private async _handleDomainConnected(domain: Domain): Promise { + if (!this._vscene) { + return; + } + + this._entityServer = domain.EntityClient; + if (this._entityServer) { + this._entityServer.onStateChanged = this._handleOnEntityServerStateChanged.bind(this); + } + + await this._vscene.load(); + this._vscene.teleportMyAvatar(domain.Location); + + if (domain.DomainClient) { + this.sessionID = domain.DomainClient.sessionUUID.stringify(); + } + Log.debug(Log.types.AVATAR, `Session ID: ${this.sessionID}`); + + this._avatarMixer = domain.AvatarClient?.Mixer; + const myAvatarInterface = domain.AvatarClient?.MyAvatar; + if (myAvatarInterface) { + if (myAvatarInterface.skeletonModelURL === "") { + myAvatarInterface.skeletonModelURL = this._vscene.myAvatarModelURL; + } + + const myAvatarController = this._vscene._myAvatar?.getComponent(MyAvatarController.typeName); + if (myAvatarController instanceof MyAvatarController) { + myAvatarController.myAvatar = myAvatarInterface; + } + } + + const avatarList = this._avatarMixer?.avatarList; + if (avatarList) { + avatarList.avatarAdded.connect(this._handleAvatarAdded); + avatarList.avatarRemoved.connect(this._handleAvatarRemoved); + + const uuids = avatarList.getAvatarIDs(); + const emptyId = new Uuid(); + + uuids.forEach((uuid) => { + // filter my avatar + if (uuid.stringify() !== emptyId.stringify()) { + this._handleAvatarAdded(uuid); + } + }); + } + + this._camera = this._vscene.camera; + this._domainCamera = domain.Camera; + // this._syncCamera(); + } + + private _handleAvatarAdded = (sessionID: Uuid): void => { + const avatarList = this._avatarMixer?.avatarList; + if (avatarList) { + Log.debug(Log.types.AVATAR, + `AvatarAdded. Session ID: ${sessionID.stringify()}`); + + const domain = avatarList.getAvatar(sessionID); + + if (domain.skeletonModelURL !== "") { + void this._vscene?.loadAvatar(sessionID, domain); + } + + domain.skeletonModelURLChanged.connect(() => { + this._handleAvatarSkeletonModelURLChanged(sessionID, domain); + }); + } + }; + + private _handleAvatarRemoved = (sessionID: Uuid): void => { + Log.debug(Log.types.AVATAR, + `handleAvatarRemoved. Session ID: ${sessionID.stringify()}`); + this._vscene?.unloadAvatar(sessionID); + + }; + + private _handleAvatarSkeletonModelURLChanged(sessionID: Uuid, domain: ScriptAvatar): void { + Log.debug(Log.types.AVATAR, + `handleAvatarSkeletonModelURLChanged. Session ID: ${sessionID.stringify()}, ${domain.skeletonModelURL}`); + + void this._vscene?.loadAvatar(sessionID, domain); + } + + private _handleOnEntityServerStateChanged(state: AssignmentClientState): void { + Log.info(Log.types.ENTITIES, + `Entity Sever state changed. New state: ${Client.stateToString(state)}`); + + if (state === AssignmentClientState.CONNECTED) { + this._entityManager = new EntityManager(this._entityServer as EntityServer); + this._entityManager.onEntityAdded.add(this._handleOnEntityAdded.bind(this)); + this._entityManager.onEntityRemoved.add(this._handleOnEntityRemoved.bind(this)); + } else if (state === AssignmentClientState.DISCONNECTED) { + this._entityManager = null; + } + } + + private _handleOnEntityAdded(entity: IEntity): void { + Log.debug(Log.types.ENTITIES, + `Add entity ${entity.id} + name:${entity.name as string} + type: ${entity.type}`); + + this._vscene?.loadEntity(entity); + } + + private _handleOnEntityRemoved(entity: IEntity): void { + Log.debug(Log.types.ENTITIES, + `Remove entity ${entity.id} + name:${entity.name as string} + type: ${entity.type}`); + + this._vscene?.removeEntity(entity.id); + } + + private _syncCamera(): void { + if (this._domainCamera && this._camera) { + this._domainCamera.position = EntityMapper.mapToVector3Property(this._camera.globalPosition); + this._domainCamera.orientation = EntityMapper.mapToQuaternionProperty(this._camera.absoluteRotation); + } + } +} diff --git a/src/modules/scene/renderer.ts b/src/modules/scene/renderer.ts index cec81636..10bd9c24 100644 --- a/src/modules/scene/renderer.ts +++ b/src/modules/scene/renderer.ts @@ -1,158 +1,162 @@ -// -// renderer.ts -// -// Copyright 2021 Vircadia contributors. -// Copyright 2022 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 { Engine, type Nullable } from "@babylonjs/core"; -import { applicationStore } from "@Stores/index"; -import { Config } from "@Base/config"; -import { WebGPUEngine } from "@babylonjs/core/Engines/webgpuEngine"; -import { VScene } from "@Modules/scene/vscene"; -import { CustomLoadingScreen } from "@Modules/scene/LoadingScreen"; - -/** - * Static methods controlling the rendering of the scene(s). - */ -export class Renderer { - private static _engine = undefined; - private static _renderingScenes = undefined; - private static _webgpuSupported = false; - private static _intervalId = > null; - - /** - * Initialize the rendering engine. - * @param canvas The canvas element to render the scene onto. - * @param loadingScreen The element to show when the scene is loading. - */ - public static async initialize(canvas: HTMLCanvasElement, loadingScreen: HTMLElement): Promise { - this._webgpuSupported = await WebGPUEngine.IsSupportedAsync; - // FIXME: Temporarily disable WebGPU on MacOS until update to a Babylon version that supports it. - this._webgpuSupported = false; - if (this._webgpuSupported) { - this._engine = new WebGPUEngine(canvas, { - deviceDescriptor: { - requiredFeatures: [ - "depth-clip-control", - "depth32float-stencil8", - "texture-compression-bc", - "texture-compression-etc2", - "texture-compression-astc", - "timestamp-query", - "indirect-first-instance" - ] - } - }); - this._engine.loadingScreen = new CustomLoadingScreen(loadingScreen); - await (this._engine as WebGPUEngine).initAsync(); - this._engine.displayLoadingUI(); - } else { - this._engine = new Engine(canvas, true); - this._engine.renderEvenInBackground = true; - this._engine.loadingScreen = new CustomLoadingScreen(loadingScreen); - this._engine.displayLoadingUI(); - } - - this._renderingScenes = new Array(); - - // Update renderer statistics for the UI. - setInterval(() => { - if (this._engine) { - if (this._renderingScenes.length > 0 && this._renderingScenes[0]) { - applicationStore.renderer.fps = this._engine.getFps(); - applicationStore.renderer.cameraLocation = this._renderingScenes[0]._scene.activeCamera?.globalPosition.clone(); - applicationStore.renderer.cameraRotation = this._renderingScenes[0]._scene.activeCamera?.absoluteRotation.clone(); - } - } - }, Number(Config.getItem("Renderer.StatUpdateSeconds", "1000"))); - } - - /** - * Create a new Vircadia Scene and append it to the render queue. - * @param index `(Optional)` The index of the render queue to place the scene into. - * @returns A reference to the new scene. - */ - public static createScene(index = this._renderingScenes.length): VScene { - const scene = new VScene(this._engine, index); - this._renderingScenes[index] = scene; - return scene; - } - - /** - * Get a particular Vircadia Scene from the render queue. - * @param index `(Optional)` The index of the scene in the render queue. If not specified, retrieves the first scene in the queue. - * @returns A reference to the requested scene. - */ - public static getScene(index = 0): VScene { - return this._renderingScenes[index]; - } - - /** - * Resize the rendered view to match the size of the canvas. - */ - public static resize(): void { - if (!this._webgpuSupported) { - this._engine?.resize(); - } - } - - /** - * Start the render loop, rendering all queued scenes to the canvas. - * @param scenes `(Optional)` A queue of scenes to render. - */ - public static startRenderLoop(scenes?: VScene[]): void { - if (scenes) { - this._renderingScenes = scenes; - } - this._runRenderLoop(); - document.addEventListener("visibilitychange", this._runRenderLoop.bind(this), false); - } - - /** - * Handle running the render loop. - * - * NOTE: - * The render loop of Babylon's engine relies on `requestAnimationFrame()`. - * Most browsers stop running animation-frame callbacks in background tabs in order to improve performance and battery life. - * To make scene still render in the background, use `setInterval()` to run the render loop when the web page is hidden. - */ - private static _runRenderLoop(): void { - if (document.hidden) { - this._engine.stopRenderLoop(); - if (!this._intervalId) { - const backgroundFrameTime = 16; - this._intervalId = setInterval(this._render.bind(this), backgroundFrameTime); - } - } else { - if (this._intervalId) { - clearInterval(this._intervalId); - this._intervalId = null; - } - this._engine.runRenderLoop(this._render.bind(this)); - } - } - - /** - * Render one frame from all scenes in the render queue. - */ - private static _render(): void { - this._renderingScenes.forEach((vscene) => { - vscene.render(); - }); - } - - /** - * Dispose of all scenes in the render queue and stop the render loop. - */ - public static dispose(): void { - this._renderingScenes.forEach((vscene) => { - vscene.dispose(); - }); - this._renderingScenes = []; - this._engine.stopRenderLoop(); - } -} +// +// renderer.ts +// +// Copyright 2021 Vircadia contributors. +// Copyright 2022 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 { Engine, type Nullable } from "@babylonjs/core"; +import { applicationStore } from "@Stores/index"; +import { Config } from "@Base/config"; +import { WebGPUEngine } from "@babylonjs/core/Engines/webgpuEngine"; +import { VScene } from "@Modules/scene/vscene"; +import { CustomLoadingScreen } from "@Modules/scene/LoadingScreen"; + +/** + * Static methods controlling the rendering of the scene(s). + */ +export class Renderer { + private static _engine = undefined; + private static _renderingScenes = undefined; + private static _webgpuSupported = false; + private static _intervalId = >null; + + /** + * Initialize the rendering engine. + * @param canvas The canvas element to render the scene onto. + * @param loadingScreen The element to show when the scene is loading. + */ + public static async initialize(canvas: HTMLCanvasElement, loadingScreen: HTMLElement): Promise { + this._webgpuSupported = await WebGPUEngine.IsSupportedAsync; + // FIXME: Temporarily disable WebGPU on MacOS until update to a Babylon version that supports it. + this._webgpuSupported = false; + if (this._webgpuSupported) { + this._engine = new WebGPUEngine(canvas, { + deviceDescriptor: { + requiredFeatures: [ + "depth-clip-control", + "depth32float-stencil8", + "texture-compression-bc", + "texture-compression-etc2", + "texture-compression-astc", + "timestamp-query", + "indirect-first-instance" + ] + } + }); + this._engine.loadingScreen = new CustomLoadingScreen(loadingScreen); + await (this._engine as WebGPUEngine).initAsync(); + this._engine.displayLoadingUI(); + } else { + this._engine = new Engine(canvas, true); + this._engine.renderEvenInBackground = true; + this._engine.loadingScreen = new CustomLoadingScreen(loadingScreen); + this._engine.displayLoadingUI(); + } + + this._renderingScenes = new Array(); + + // Update renderer statistics for the UI. + setInterval(() => { + if (this._engine) { + if (this._renderingScenes.length > 0 && this._renderingScenes[0]) { + applicationStore.renderer.fps = this._engine.getFps(); + applicationStore.renderer.cameraLocation = this._renderingScenes[0]._scene.activeCamera?.globalPosition.clone(); + applicationStore.renderer.cameraRotation = this._renderingScenes[0]._scene.activeCamera?.absoluteRotation.clone(); + } + } + }, Number(Config.getItem("Renderer.StatUpdateSeconds", "1000"))); + } + + /** + * Create a new Vircadia Scene and append it to the render queue. + * @param index `(Optional)` The index of the render queue to place the scene into. + * @returns A reference to the new scene. + */ + public static createScene(index = this._renderingScenes.length): VScene { + const scene = new VScene(this._engine, index); + this._renderingScenes[index] = scene; + return scene; + } + + /** + * Get a particular Vircadia Scene from the render queue. + * @param index `(Optional)` The index of the scene in the render queue. If not specified, retrieves the first scene in the queue. + * @returns A reference to the requested scene. + */ + public static getScene(index = 0): VScene { + return this._renderingScenes[index]; + } + + /** + * Resize the rendered view to match the size of the canvas. + */ + public static resize(): void { + if (!this._webgpuSupported) { + this._engine?.resize(); + } + } + + /** + * Start the render loop, rendering all queued scenes to the canvas. + * @param scenes `(Optional)` A queue of scenes to render. + */ + public static startRenderLoop(scenes?: VScene[]): void { + if (scenes) { + this._renderingScenes = scenes; + } + this._runRenderLoop(); + document.addEventListener("visibilitychange", this._runRenderLoop.bind(this), false); + } + + /** + * Handle running the render loop. + * + * NOTE: + * The render loop of Babylon's engine relies on `requestAnimationFrame()`. + * Most browsers stop running animation-frame callbacks in background tabs in order to improve performance and battery life. + * To make scene still render in the background, use `setInterval()` to run the render loop when the web page is hidden. + */ + private static _runRenderLoop(): void { + if (document.hidden) { + this._engine.stopRenderLoop(); + if (!this._intervalId) { + const backgroundFrameTime = 16; + this._intervalId = setInterval(this._render.bind(this), backgroundFrameTime); + } + } else { + if (this._intervalId) { + clearInterval(this._intervalId); + this._intervalId = null; + } + this._engine.runRenderLoop(this._render.bind(this)); + } + } + + /** + * Render one frame from all scenes in the render queue. + */ + private static _render(): void { + if (this._renderingScenes.length > 1) { + console.warn("$$$ Multiple scenes are not supported yet."); + } + this._renderingScenes.forEach((vscene) => { + vscene.render(); + }); + } + + /** + * Dispose of all scenes in the render queue and stop the render loop. + */ + public static dispose(): void { + this._renderingScenes.forEach((vscene) => { + vscene.dispose(); + }); + this._renderingScenes = []; + this._engine.stopRenderLoop(); + console.log("$$$ Renderer disposed."); + } +} diff --git a/src/modules/scene/vscene.ts b/src/modules/scene/vscene.ts index cac3d6fd..af395472 100644 --- a/src/modules/scene/vscene.ts +++ b/src/modules/scene/vscene.ts @@ -179,7 +179,7 @@ export class VScene { if (sceneUrl !== "" && this._currentSceneURL === sceneUrl) { return; } - + console.info("$$$ reloading"); this._currentSceneURL = sceneUrl ?? ""; this.showLoadingUI(); @@ -226,8 +226,10 @@ export class VScene { } public dispose(): void { + console.info("$$$ disposing"); this._scene.dispose(); this._css3DRenderer?.removeAllCSS3DObjects(); + console.info("$$$ disposed"); } public resetMyAvatarPositionAndOrientation(): void { @@ -244,8 +246,8 @@ export class VScene { const q = location.orientation.length > 0 ? AvatarMapper.mapToLocalOrientation( - DataMapper.stringToQuaternion(location.orientation) - ) + DataMapper.stringToQuaternion(location.orientation) + ) : undefined; this._teleportMyAvatar( @@ -310,6 +312,7 @@ export class VScene { const mesh = this._scene.getMeshById(id); if (mesh) { this._scene.removeMesh(mesh, true); + console.info(Log.types.ENTITIES, `$$$ SCENE Remove entity: ${id}`); } } @@ -436,12 +439,12 @@ export class VScene { (boundingVectors.max.x - boundingVectors.min.x + (boundingVectors.max.z - boundingVectors.min.z)) / - 4 || defaultColliderProperties.radius, + 4 || defaultColliderProperties.radius, avatarHeight || defaultColliderProperties.height, new Vector3( defaultColliderProperties.offset.x, (avatarHeight || defaultColliderProperties.height) / 2 + - defaultColliderProperties.offset.y, + defaultColliderProperties.offset.y, defaultColliderProperties.offset.z ) ); @@ -492,8 +495,8 @@ export class VScene { (value: boolean) => { nametagColor = value ? Color3.FromHexString( - applicationStore.theme.colors.primary - ) + applicationStore.theme.colors.primary + ) : undefined; NametagEntity.removeAll(this._myAvatar); if (this._myAvatar) { @@ -611,8 +614,8 @@ export class VScene { (value: boolean) => { nametagColor = value ? Color3.FromHexString( - applicationStore.theme.colors.primary - ) + applicationStore.theme.colors.primary + ) : undefined; const nametagAvatar = this._avatarList.get(stringId); NametagEntity.removeAll(nametagAvatar); @@ -664,7 +667,9 @@ export class VScene { private async _createScene(): Promise { if (!this._scene) { this._scene = new Scene(this._engine); + console.info("$$$ NULL create scene"); } + console.info("$$$ THINKS IT FOUND create scene"); // use right handed system to match vircadia coordinate system this._scene.useRightHandedSystem = true; this._resourceManager = new ResourceManager(this._scene); From d2a1047c6026897a31685ea9270184d464d2d22d Mon Sep 17 00:00:00 2001 From: Kalila <69767640+digisomni@users.noreply.github.com> Date: Thu, 19 Oct 2023 21:18:34 +0800 Subject: [PATCH 2/4] Update PR deploy. --- .github/workflows/pr_to_master_deploy.yml | 79 ++++++++++++----------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/.github/workflows/pr_to_master_deploy.yml b/.github/workflows/pr_to_master_deploy.yml index 01c0a716..74c82c83 100644 --- a/.github/workflows/pr_to_master_deploy.yml +++ b/.github/workflows/pr_to_master_deploy.yml @@ -1,39 +1,40 @@ -# Automatically deploy the latest changes of a branch to the FTP server. -on: - # Triggers the workflow on PRs in "master" - pull_request: - branches: [ "master" ] - - # Allows this workflow to be run manually from the Actions tab. - workflow_dispatch: - -name: 🚀 PR Auto-Deploy -jobs: - web-deploy: - name: 🎉 Deploy - runs-on: ubuntu-latest - steps: - - name: 🚚 Get latest code - uses: actions/checkout@v2 - - - uses: actions/setup-node@master - with: - node-version: 20.x - - - name: 📥 Install project dependencies - run: npm install - - - name: ⚒️ Build the project - env: - NODE_OPTIONS: "--max_old_space_size=4096" - run: npm run build - - - name: 📂 Sync files - uses: SamKirkland/FTP-Deploy-Action@4.3.3 - with: - server: ftp.vircadia.com - username: ${{ secrets.ftp_username }} - password: ${{ secrets.ftp_password }} - local-dir: dist/spa/ - server-dir: /staging/${{ github.head_ref }}/ - dry-run: false +# Automatically deploy the latest changes of a branch to the FTP server. +on: + # Triggers the workflow on PRs in "master" + pull_request: + branches: [ "master" ] + + # Allows this workflow to be run manually from the Actions tab. + workflow_dispatch: + +name: 🚀 PR Auto-Deploy +jobs: + web-deploy: + name: 🎉 Deploy + runs-on: ubuntu-latest + steps: + - name: 🚚 Get latest code + uses: actions/checkout@v2 + + - uses: actions/setup-node@master + with: + node-version: 20.x + + - name: 📥 Install project dependencies + run: npm install + + - name: ⚒️ Build the project + env: + NODE_OPTIONS: "--max_old_space_size=4096" + VRCA_HOSTED_URL: "https://app.vircadia.com/staging/${{ github.head_ref }}/" + run: npm run build + + - name: 📂 Sync files + uses: SamKirkland/FTP-Deploy-Action@4.3.3 + with: + server: ftp.vircadia.com + username: ${{ secrets.ftp_username }} + password: ${{ secrets.ftp_password }} + local-dir: dist/spa/ + server-dir: /staging/${{ github.head_ref }}/ + dry-run: false From 6bb243976eb0229722e325425a9bda0655e27864 Mon Sep 17 00:00:00 2001 From: Kalila <69767640+digisomni@users.noreply.github.com> Date: Thu, 19 Oct 2023 21:32:59 +0800 Subject: [PATCH 3/4] Remove testing debug logs. --- src/components/MainScene.vue | 1 - src/layouts/MainLayout.vue | 1 - src/modules/entity/EntityManager.ts | 5 ++--- src/modules/scene/controllers/domainController.ts | 1 - src/modules/scene/renderer.ts | 4 ---- 5 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/components/MainScene.vue b/src/components/MainScene.vue index a7332344..de20bcd0 100644 --- a/src/components/MainScene.vue +++ b/src/components/MainScene.vue @@ -220,7 +220,6 @@ export default defineComponent({ * Connect to the Domain server. */ async connect(): Promise { - console.log("$$$ Connecting to domain..."); let location: string | undefined = Array.isArray(this.$route.params.location) ? this.$route.params.location.join("/") : this.$route.params.location; diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index 9277fac7..ae2a4fc0 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -576,7 +576,6 @@ export default defineComponent({ * The state of the connection to the Domain server. */ domainServerState(): string { - console.info("$$$ MAINLAYOUT domainServerState: ", this.applicationStore.domain.connectionState); return this.applicationStore.domain.connectionState ?? "DISCONNECTED"; }, /** diff --git a/src/modules/entity/EntityManager.ts b/src/modules/entity/EntityManager.ts index 5e7d10b8..777e8087 100644 --- a/src/modules/entity/EntityManager.ts +++ b/src/modules/entity/EntityManager.ts @@ -74,11 +74,10 @@ export class EntityManager { } public clear(): void { - console.info(Log.types.ENTITIES, "$$$ Clear all entities."); + Log.info(Log.types.ENTITIES, "Clearing all entities."); this._entities.forEach((entity) => { this.removeEntity(entity.id); }); - console.info(Log.types.ENTITIES, "$$$ Clear all entities done."); } public createEntity(props: EntityProperties): IEntity | undefined { @@ -126,7 +125,7 @@ export class EntityManager { private _handleOnEntityData(data: EntityProperties[]): void { if (data.length > 0) { - console.log(Log.types.ENTITIES, + Log.info(Log.types.ENTITIES, `Receive entity data:`, data); this._entityPropertiesArray = this._entityPropertiesArray.concat(data); diff --git a/src/modules/scene/controllers/domainController.ts b/src/modules/scene/controllers/domainController.ts index 4d353218..0ec519c7 100644 --- a/src/modules/scene/controllers/domainController.ts +++ b/src/modules/scene/controllers/domainController.ts @@ -106,7 +106,6 @@ export class DomainController extends ScriptComponent { void this._handleDomainConnected(domain); } else if (state === ConnectionState.DISCONNECTED) { - console.warn("$$$ Domain disconnected. Unloading scene."); this._vscene?.unloadAllAvatars(); const myAvatarController = this._vscene?._myAvatar?.getComponent(MyAvatarController.typeName); diff --git a/src/modules/scene/renderer.ts b/src/modules/scene/renderer.ts index 10bd9c24..ec10a6aa 100644 --- a/src/modules/scene/renderer.ts +++ b/src/modules/scene/renderer.ts @@ -140,9 +140,6 @@ export class Renderer { * Render one frame from all scenes in the render queue. */ private static _render(): void { - if (this._renderingScenes.length > 1) { - console.warn("$$$ Multiple scenes are not supported yet."); - } this._renderingScenes.forEach((vscene) => { vscene.render(); }); @@ -157,6 +154,5 @@ export class Renderer { }); this._renderingScenes = []; this._engine.stopRenderLoop(); - console.log("$$$ Renderer disposed."); } } From 12dc00cb23b39d1905fd6498378a141dc9e3ae4d Mon Sep 17 00:00:00 2001 From: Kalila <69767640+digisomni@users.noreply.github.com> Date: Thu, 19 Oct 2023 21:37:11 +0800 Subject: [PATCH 4/4] Remove further unnecessary debug logs. --- src/modules/scene/vscene.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/modules/scene/vscene.ts b/src/modules/scene/vscene.ts index af395472..2c1381a6 100644 --- a/src/modules/scene/vscene.ts +++ b/src/modules/scene/vscene.ts @@ -179,7 +179,6 @@ export class VScene { if (sceneUrl !== "" && this._currentSceneURL === sceneUrl) { return; } - console.info("$$$ reloading"); this._currentSceneURL = sceneUrl ?? ""; this.showLoadingUI(); @@ -226,10 +225,8 @@ export class VScene { } public dispose(): void { - console.info("$$$ disposing"); this._scene.dispose(); this._css3DRenderer?.removeAllCSS3DObjects(); - console.info("$$$ disposed"); } public resetMyAvatarPositionAndOrientation(): void { @@ -312,7 +309,6 @@ export class VScene { const mesh = this._scene.getMeshById(id); if (mesh) { this._scene.removeMesh(mesh, true); - console.info(Log.types.ENTITIES, `$$$ SCENE Remove entity: ${id}`); } } @@ -667,9 +663,7 @@ export class VScene { private async _createScene(): Promise { if (!this._scene) { this._scene = new Scene(this._engine); - console.info("$$$ NULL create scene"); } - console.info("$$$ THINKS IT FOUND create scene"); // use right handed system to match vircadia coordinate system this._scene.useRightHandedSystem = true; this._resourceManager = new ResourceManager(this._scene);