From 3b58c6fcb8845661d08f55030fd77072af8fe07c Mon Sep 17 00:00:00 2001 From: Kalila <69767640+digisomni@users.noreply.github.com> Date: Wed, 16 Oct 2024 14:58:34 +1100 Subject: [PATCH] 2024.2.1 (#226) * unparent skybox from zone Skybox is no longer attached to zone and now displays behind everything else as "infinite." This is one step towards allowing for multiple zones to work properly. * Fixed a repeated typo across multiple files - changed entity.copyFormPacketData(props) to entity.copyFromPacketData(props) in multiple files - changed `"Receive entity data:`, data)" to "Receive entity data:", data.length.toString())" * Add compound shape url handling to zone entity. * Added initial mesh to Zones - each zone now has a child mesh matching dimensions. Used "shapeType" so that we can change the mesh later for custom shapes. * Set skybox to a fixed distance of 10000 -skybox set to 10000 instead of pulling the zone dimensions * redirected sara.glb to local file -we were loading this file from a server rather than from the local copy (which was already in the assets folder). * Zone now detects when MainCamera is within it's bounds -detections for when a camera exists or enters a zone. * Fixed "undefined" ZoneEntityController name ZoneEntityController will now get it's suffix and ID from it's parent Zone * Add timer to Zone check - Instead of checking which zone we are in every frame, I've set the timer to every 200ms for the time being. * Fixed small bugs to reduce start warnings -minor bugfixes -some defined but unused elements commented out with TODO * Reverting changes -pushed at the wrong stage. Whoopsie. * Initial support for multiple zones added -you can now have multiple zones that will switch skybox backgrounds and settings depending on which zone's bounding box/shape the camera within at the time. * Zone Properties & Skybox Sync -Switching of Zone Properties and the display of skyboxes are now on the same timer found in ZoneManager.ts * -foundation for compound mesh shaped zones -This lays the foundation for being able to use a glb file as your zone shape. * Custom compound mesh for zones now working -you can now enter a compound URL (glb) and use this to control the shape of your zone. * Added option to disable bevels and regain 15-20 fps -created "const enableBevels" Bool and set default to "false" for the time being. * Added the option to enable or disable alpha effects on nametags/labels added new "const enableAlpha " property and switched off transparency effects on labels as default. * Attempt to fix macOS build. --------- Co-authored-by: Aitolda <21160271+Aitolda@users.noreply.github.com> --- .gitignore | 26 + desktop/src-tauri/Cargo.toml | 47 +- quasar.config.js | 8 +- src/modules/entity/EntityInterfaces.ts | 207 ++++---- src/modules/entity/EntityManager.ts | 10 +- src/modules/entity/EntityProperties.ts | 47 +- .../entity/builders/ZoneEntityBuilder.ts | 21 +- .../components/components/SkyboxComponent.ts | 41 +- .../controllers/ZoneEntityController.ts | 325 +++++++++--- src/modules/entity/entities/Entity.ts | 2 +- src/modules/entity/entities/ImageEntity.ts | 4 +- src/modules/entity/entities/LabelEntity.ts | 167 +++--- src/modules/entity/entities/LightEntity.ts | 4 +- src/modules/entity/entities/ModelEntity.ts | 4 +- src/modules/entity/entities/ShapeEntity.ts | 4 +- src/modules/entity/entities/WebEntity.ts | 4 +- src/modules/entity/entities/ZoneEntity.ts | 474 +++++++++--------- src/modules/entity/entities/materialEntity.ts | 4 +- src/modules/scene/ZoneManager.ts | 71 +++ .../scene/controllers/sceneController.ts | 29 +- src/modules/scene/renderer.ts | 2 +- src/modules/scene/vscene.ts | 8 +- src/vircadia-world | 2 +- 23 files changed, 947 insertions(+), 564 deletions(-) create mode 100644 src/modules/scene/ZoneManager.ts diff --git a/.gitignore b/.gitignore index 8f3a30df..3e8ca0a2 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,29 @@ yarn-error.log* *.njsproj *.sln VERSION.json + +# Ignore BabylonJS_Docs folder +/BabylonJS_Docs/ +public/local-assets/cityscape2048f.png +public/local-assets/Collisions_10-25-2023.glb +public/local-assets/Decor_10-25-2023.glb +public/local-assets/Foliage_10-25-2023.glb +public/local-assets/FurnitureCulling_10-25-2023.glb +public/local-assets/Headquarters_10-25-2023.glb +public/local-assets/Hologram_10-25-2023.glb +public/local-assets/LandscapeTilesLOD_10-25-2023.glb +public/local-assets/LandscapeTrimLOD_10-25-2023.glb +public/local-assets/LandscapeWalkwayLOD_10-25-2023.glb +public/local-assets/OuterWrap_10-25-2023.glb +public/local-assets/Path_10-25-2023.glb +public/local-assets/Reflections1k.hdr +public/local-assets/SitObjects_10-25-2023.glb +public/local-assets/SitObjectsNoSit_10-25-2023.glb +public/local-assets/Water_10-25-2023.glb +public/local-assets/Waterfalls_10-25-2023.glb +src/modules/directory_structure.txt +public/local-assets/Skybox2k.png +public/local-assets/Reflections4.hdr +public/local-assets/monkey.glb +public/local-assets/Cylinder.glb +public/local-assets/Meters.glb diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml index f425ae99..69b66b06 100644 --- a/desktop/src-tauri/Cargo.toml +++ b/desktop/src-tauri/Cargo.toml @@ -1,23 +1,24 @@ -[package] -name = "vircadia-desktop" -version = "0.0.0" -description = "" -authors = ["DigiSomni", "Giga", "Vircadia Contributors"] -license = "" -repository = "" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[build-dependencies] -tauri-build = { version = "1.5", features = [] } - -[dependencies] -tauri = { version = "1.5", features = ["shell-open"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" - -[features] -# this feature is used for production builds or when `devPath` points to the filesystem -# DO NOT REMOVE!! -custom-protocol = ["tauri/custom-protocol"] +[package] +name = "vircadia-desktop" +version = "0.0.0" +description = "" +authors = ["DigiSomni", "Giga", "Vircadia Contributors"] +license = "" +repository = "" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[build-dependencies] +tauri-build = { version = "1.5", features = [] } + +[dependencies] +tauri = { version = "1.5", features = ["shell-open"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +time = "0.3.35" + +[features] +# this feature is used for production builds or when `devPath` points to the filesystem +# DO NOT REMOVE!! +custom-protocol = ["tauri/custom-protocol"] diff --git a/quasar.config.js b/quasar.config.js index ca1712e6..6ffbd420 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -189,10 +189,10 @@ module.exports = configure(function (ctx) { JSON.stringify({ HTP45FSQ: { name: "Sara", - image: "https://staging.vircadia.com/O12OR634/UA92/sara-cropped-small.webp", - file: "https://staging.vircadia.com/O12OR634/UA92/sara.glb", + image: "/assets/models/avatars/sara-cropped-small.webp", + file: "/assets/models/avatars/sara.glb", scale: 1, - starred: true, + starred: false, }, KLM23NOP: { name: "Mark", @@ -391,7 +391,7 @@ module.exports = configure(function (ctx) { image: "/assets/models/avatars/Maria-small.webp", file: "/assets/models/avatars/Maria.glb", scale: 1, - starred: false, + starred: true, }), }, }, diff --git a/src/modules/entity/EntityInterfaces.ts b/src/modules/entity/EntityInterfaces.ts index 60279eb1..fbbaf54d 100644 --- a/src/modules/entity/EntityInterfaces.ts +++ b/src/modules/entity/EntityInterfaces.ts @@ -1,102 +1,105 @@ -// -// IEntity.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 { IEntityProperties, ISpatialProperties, IBehaviorProperties, - IModelEProperties, IShapeProperties, ILightProperties, - IZoneProperties, IImageProperties, IMaterialProperties, IWebProperties } from "./EntityProperties"; -import { Observable } from "@babylonjs/core"; - -export interface IEntity extends - IEntityProperties, - ISpatialProperties, - IBehaviorProperties { - - onCommonPropertiesChanged?: Observable; - onParentChanged?: Observable; - onPositionAndRotationChanged?: Observable; - onDimensionChanged?: Observable; - onRenderModeChanged?: Observable; - onScriptChanged?: Observable; - onUserDataChanged?: Observable; - onCollisionPropertiesChanged?: Observable; - onPhysicsPropertiesChanged?: Observable; -} - -export interface IModelEntity extends - IEntity, - IModelEProperties { - - onModelURLChanged?: Observable; - onAnimationChanged?: Observable -} - -export type JitsiSettings = { - roomID: string; - roomName: string; -}; - -export type WebExtensions = { - jitsi: JitsiSettings | undefined -}; - -export interface IWebEntity extends - IEntity, - IWebProperties { - - onColorChanged: Observable; - onSourceURLChanged: Observable; - onWebPropertiesChanged: Observable; -} - -export interface IShapeEntity extends - IEntity, - IShapeProperties { - - onShapeChanged?: Observable; - onColorChanged?: Observable; -} - -export interface ILightEntity extends - IEntity, - ILightProperties { - - onLightPropertiesChanged?: Observable; - onLightTypeChanged?: Observable; -} - -export interface IZoneEntity extends - IEntity, - IZoneProperties { - - onShapeTypeChanged: Observable; - onAmbientLightPropertiesChanged: Observable; - onKeyLightPropertiesChanged: Observable; - onSkyboxPropertiesChanged: Observable; - onHazePropertiesChanged: Observable; - onBloomPropertiesChanged: Observable; -} - -export interface IImageEntity extends - IEntity, - IImageProperties { - - onColorChanged: Observable; - onImageURLChanged: Observable -} - -export interface IMaterialEntity extends - IEntity, - IMaterialProperties { - - onMaterialDataChanged?: Observable; - onMaterialMappingModeChanged?: Observable; - onMaterialPriorityChanged?: Observable; - onParentMaterialNameChanged?: Observable; -} +// +// IEntity.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 { + IEntityProperties, ISpatialProperties, IBehaviorProperties, + IModelEProperties, IShapeProperties, ILightProperties, + IZoneProperties, IImageProperties, IMaterialProperties, IWebProperties +} from "./EntityProperties"; +import { Observable } from "@babylonjs/core"; + +export interface IEntity extends + IEntityProperties, + ISpatialProperties, + IBehaviorProperties { + + onCommonPropertiesChanged?: Observable; + onParentChanged?: Observable; + onPositionAndRotationChanged?: Observable; + onDimensionChanged?: Observable; + onRenderModeChanged?: Observable; + onScriptChanged?: Observable; + onUserDataChanged?: Observable; + onCollisionPropertiesChanged?: Observable; + onPhysicsPropertiesChanged?: Observable; +} + +export interface IModelEntity extends + IEntity, + IModelEProperties { + + onModelURLChanged?: Observable; + onAnimationChanged?: Observable +} + +export type JitsiSettings = { + roomID: string; + roomName: string; +}; + +export type WebExtensions = { + jitsi: JitsiSettings | undefined +}; + +export interface IWebEntity extends + IEntity, + IWebProperties { + + onColorChanged: Observable; + onSourceURLChanged: Observable; + onWebPropertiesChanged: Observable; +} + +export interface IShapeEntity extends + IEntity, + IShapeProperties { + + onShapeChanged?: Observable; + onColorChanged?: Observable; +} + +export interface ILightEntity extends + IEntity, + ILightProperties { + + onLightPropertiesChanged?: Observable; + onLightTypeChanged?: Observable; +} + +export interface IZoneEntity extends + IEntity, + IZoneProperties { + + onShapeTypeChanged: Observable; + onCompoundShapeURLChanged: Observable; + onAmbientLightPropertiesChanged: Observable; + onKeyLightPropertiesChanged: Observable; + onSkyboxPropertiesChanged: Observable; + onHazePropertiesChanged: Observable; + onBloomPropertiesChanged: Observable; +} + +export interface IImageEntity extends + IEntity, + IImageProperties { + + onColorChanged: Observable; + onImageURLChanged: Observable +} + +export interface IMaterialEntity extends + IEntity, + IMaterialProperties { + + onMaterialDataChanged?: Observable; + onMaterialMappingModeChanged?: Observable; + onMaterialPriorityChanged?: Observable; + onParentMaterialNameChanged?: Observable; +} diff --git a/src/modules/entity/EntityManager.ts b/src/modules/entity/EntityManager.ts index 777e8087..8f05cbf2 100644 --- a/src/modules/entity/EntityManager.ts +++ b/src/modules/entity/EntityManager.ts @@ -87,7 +87,7 @@ export class EntityManager { return undefined; } const entity = factory(props.entityItemID.stringify()); - entity.copyFormPacketData(props); + entity.copyFromPacketData(props); // prevent to emit change event entity.update(); @@ -99,7 +99,7 @@ export class EntityManager { public updateEntity(props: EntityProperties): void { const entity = this._entities.get(props.entityItemID.stringify()); if (entity) { - entity.copyFormPacketData(props); + entity.copyFromPacketData(props); } } @@ -114,7 +114,7 @@ export class EntityManager { this._entityPropertiesArray.forEach((props) => { const entity = this._entities.get(props.entityItemID.stringify()); if (entity) { - entity.copyFormPacketData(props); + entity.copyFromPacketData(props); } else { this.createEntity(props); } @@ -124,9 +124,9 @@ export class EntityManager { } private _handleOnEntityData(data: EntityProperties[]): void { - if (data.length > 0) { + if (data && data.length > 0) { Log.info(Log.types.ENTITIES, - `Receive entity data:`, data); + "Receive entity data:", data.length.toString()); this._entityPropertiesArray = this._entityPropertiesArray.concat(data); } diff --git a/src/modules/entity/EntityProperties.ts b/src/modules/entity/EntityProperties.ts index 3b7ecc96..d635ab2b 100644 --- a/src/modules/entity/EntityProperties.ts +++ b/src/modules/entity/EntityProperties.ts @@ -13,20 +13,39 @@ 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"; - -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"; + "Circle" | "Cone" | "Cube" | "Cylinder" | "Dodecahedron" | + "Hexagon" | "Icosahedron" | "Octagon" | "Octahedron" | + "Quad" | "Sphere" | "Tetrahedron" | "Torus" | "Triangle"; + +export const ShapeType = { + None: "none", + Box: "box", + Sphere: "sphere", + Cylinder: "cylinder", + CapsuleX: "capsule-x", + CapsuleY: "capsule-y", + CapsuleZ: "capsule-z", + CylinderX: "cylinder-x", + CylinderY: "cylinder-y", + CylinderZ: "cylinder-z", + Hull: "hull", + Compound: "compound", + SimpleHull: "simple-hull", + SimpleCompound: "simple-compound", + StaticMesh: "static-mesh", + Plane: "plane", + Ellipsoid: "ellipsoid", + Circle: "circle", + Multisphere: "multisphere" +} as const; + +export type ShapeType = typeof ShapeType[keyof typeof ShapeType]; export type MaterialMappingMode = "uv" | "projected"; @@ -74,7 +93,7 @@ export interface IColorProperty { export interface IAmbientLightProperty { ambientIntensity?: number | undefined; - ambientURL? : string | undefined; + ambientURL?: string | undefined; } export interface IKeyLightProperty { @@ -88,7 +107,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/builders/ZoneEntityBuilder.ts b/src/modules/entity/builders/ZoneEntityBuilder.ts index 72a9b1b9..a2bbc6d4 100644 --- a/src/modules/entity/builders/ZoneEntityBuilder.ts +++ b/src/modules/entity/builders/ZoneEntityBuilder.ts @@ -11,11 +11,26 @@ import { IEntity, IZoneEntity } from "../EntityInterfaces"; import { GameObject } from "@Base/modules/object"; -import { ZoneEntityController } from "../components"; +import { ZoneEntityController } from "../components/controllers/ZoneEntityController"; export class ZoneEntityBuilder { - // eslint-disable-next-line class-methods-use-this public build(gameObject: GameObject, entity: IEntity): void { - gameObject.addComponent(new ZoneEntityController(entity as IZoneEntity)); + const zoneEntity = entity as IZoneEntity; + const controller = new ZoneEntityController(zoneEntity); + gameObject.addComponent(controller); + + // Log the zone entity properties before creating the mesh + console.log(`Building zone entity ${zoneEntity.id}`); + console.log(`Shape type: ${zoneEntity.shapeType}`); + console.log(`Compound shape URL: ${zoneEntity.compoundShapeURL}`); + + // Delay the mesh creation to ensure all properties are set + setTimeout(() => { + controller.createZoneMesh(); + }, 0); + + console.log(`Zone entity built for zone ${zoneEntity.id}`); + console.log(`GameObject name: ${gameObject.name}`); + console.log(`Zone position: ${gameObject.position.toString()}`); } } diff --git a/src/modules/entity/components/components/SkyboxComponent.ts b/src/modules/entity/components/components/SkyboxComponent.ts index 10be740f..df32a823 100644 --- a/src/modules/entity/components/components/SkyboxComponent.ts +++ b/src/modules/entity/components/components/SkyboxComponent.ts @@ -12,13 +12,16 @@ import { MeshComponent, DEFAULT_MESH_RENDER_GROUP_ID } from "@Modules/object"; -import { Scene, MeshBuilder, StandardMaterial, Texture, CubeTexture, EquiRectangularCubeTexture, BaseTexture } from "@babylonjs/core"; +import { Scene, MeshBuilder, StandardMaterial, Texture, CubeTexture, EquiRectangularCubeTexture, BaseTexture, Camera } from "@babylonjs/core"; import { ISkyboxProperty, IVector3Property } from "../../EntityProperties"; import { EntityMapper } from "../../package"; import { AssetUrl } from "../../builders/asset"; export class SkyboxComponent extends MeshComponent { - static readonly DefaultSkyBoxSize = 2000; + private _camera: Camera | null = null; + private _scene: Scene | null = null; + + static readonly DefaultSkyBoxSize = 10000; static readonly DefaultCubeMapSize = 1024; /** @@ -37,32 +40,29 @@ export class SkyboxComponent extends MeshComponent { return "Skybox"; } - public load(props: ISkyboxProperty, dimensions: IVector3Property | undefined, id: string): void { + public load(props: ISkyboxProperty, id: string, camera: Camera): void { if (this._mesh) { this._mesh.dispose(false, true); this._mesh = null; } - if (!this._gameObject) { - return; - } + this._camera = camera; + this._scene = camera.getScene(); + + // Always create the skybox with the default size + const skyBox = MeshBuilder.CreateBox(this.componentType, + { size: SkyboxComponent.DefaultSkyBoxSize }, + this._scene); - let skyBox = null; - const scene = this._gameObject ? this._gameObject?.getScene() : null; - if (dimensions) { - skyBox = MeshBuilder.CreateBox(this.componentType, - { width: dimensions.x, height: dimensions.y, depth: dimensions.z }, - scene); - } else { - skyBox = MeshBuilder.CreateBox(this.componentType, - { size: SkyboxComponent.DefaultSkyBoxSize }, scene); - } skyBox.infiniteDistance = true; skyBox.id = id; skyBox.isPickable = false; skyBox.isNearGrabbable = false; skyBox.isNearPickable = false; - skyBox.renderingGroupId = DEFAULT_MESH_RENDER_GROUP_ID; + skyBox.renderingGroupId = 0; // Ensure it's rendered first + + // Attach the skybox directly to the scene, not to the camera + skyBox.parent = null; this.mesh = skyBox; @@ -70,12 +70,11 @@ export class SkyboxComponent extends MeshComponent { } public update(props: ISkyboxProperty): void { - if (this._mesh) { - const scene = this._mesh.getScene(); + if (this._mesh && this._scene) { let material = this._mesh.material as StandardMaterial; if (!material) { - material = new StandardMaterial(`${this._mesh.name}_${this._mesh.id}`, scene); + material = new StandardMaterial(`${this._mesh.name}_${this._mesh.id}`, this._scene); material.backFaceCulling = false; material.disableLighting = true; this._mesh.material = material; @@ -86,7 +85,7 @@ export class SkyboxComponent extends MeshComponent { if (props.url && props.url !== material.reflectionTexture?.name) { material.reflectionTexture?.dispose(); - material.reflectionTexture = this._createReflectionTexture(props.url, scene); + material.reflectionTexture = this._createReflectionTexture(props.url, this._scene); } } } diff --git a/src/modules/entity/components/controllers/ZoneEntityController.ts b/src/modules/entity/components/controllers/ZoneEntityController.ts index 9fe22841..35cf8485 100644 --- a/src/modules/entity/components/controllers/ZoneEntityController.ts +++ b/src/modules/entity/components/controllers/ZoneEntityController.ts @@ -21,11 +21,12 @@ import { AssetUrl } from "../../builders"; import { HemisphericLight, Vector3, DefaultRenderingPipeline, CubeTexture, HDRCubeTexture, VolumetricLightScatteringPostProcess, Nullable, - Camera, Texture, StandardMaterial + Camera, Texture, StandardMaterial, SceneLoader, MeshBuilder, Mesh, Color3, Quaternion, AbstractMesh } from "@babylonjs/core"; import { IVector3Property } from "../../EntityProperties"; import { EntityMapper } from "../../package"; import { userStore } from "@Stores/index"; +import { ShapeType } from "../../EntityProperties"; type EnvironmentSettings = { environmentTexture?: string | undefined, @@ -75,9 +76,20 @@ export class ZoneEntityController extends EntityController { private _haze: Nullable = null; private _vls: Nullable = null; private _watchingGraphicsSettings = false; + private _zoneMesh: AbstractMesh | null = null; constructor(entity: IZoneEntity) { - super(entity, ZoneEntityController.typeName); + // Extract a unique identifier from the zone's ID + let uniqueId = "unknown"; + if (entity.id) { + const parts = entity.id.split('_'); + uniqueId = parts.length > 1 ? parts[1] : entity.id; + } + + // Create a unique ID for this controller + const controllerId = `ZoneEntityController_${uniqueId}`; + + super(entity, controllerId); this._zoneEntity = entity; } @@ -87,17 +99,18 @@ export class ZoneEntityController extends EntityController { */ // eslint-disable-next-line class-methods-use-this public get componentType(): string { - return ZoneEntityController.typeName; + return this.id; // This will now return the unique "ZoneEntityController_XXX" ID } static get typeName(): string { - return "ZoneEntityController"; + return "ZoneEntityController"; // This remains the same for the class type } public onInitialize(): void { super.onInitialize(); this._zoneEntity.onShapeTypeChanged?.add(this._handleShapeTypeChanged.bind(this)); + this._zoneEntity.onCompoundShapeURLChanged?.add(this._handleCompoundShapeURLChanged.bind(this)); this._zoneEntity.onSkyboxPropertiesChanged?.add(this._updateSkybox.bind(this)); this._zoneEntity.onAmbientLightPropertiesChanged?.add(this.updateAmbientLight.bind(this)); this._zoneEntity.onKeyLightPropertiesChanged?.add(this.updateKeyLight.bind(this)); @@ -105,16 +118,16 @@ export class ZoneEntityController extends EntityController { this._zoneEntity.onUserDataChanged?.add(this._updateUserData.bind(this)); this._zoneEntity.onDimensionChanged?.add(this._updateDimensions.bind(this)); + this._zoneEntity.onPositionAndRotationChanged?.add(this._updatePositionAndRotation.bind(this)); } public onStart(): void { + super.onStart(); this._updateSkybox(); this.updateAmbientLight(); this.updateKeyLight(); this.updateHaze(); this._updateUserData(); - - super.onStart(); } // eslint-disable-next-line @typescript-eslint/no-empty-function, class-methods-use-this @@ -129,93 +142,101 @@ export class ZoneEntityController extends EntityController { } protected _handleShapeTypeChanged(): void { - // eslint-disable-next-line no-empty - if (this._gameObject) { - - } + console.log(`Zone shape type changed to: ${this._zoneEntity.shapeType}`); + // Here you could implement logic to change the zone's visual representation + // or behavior based on the new shape type, if needed in the future. } - protected _updateDimensions(): void { - if (!this._zoneEntity.skybox) { - return; - } - - // reload sky box mesh - if (this._skybox) { - this._skybox.load(this._zoneEntity.skybox, this._zoneEntity.dimensions, this._zoneEntity.id); - } - - this._updateSkybox(); + protected _handleCompoundShapeURLChanged(): void { + console.log(`Zone compound shape URL changed to: ${this._zoneEntity.compoundShapeURL}`); + // Here you could implement logic to load and apply the compound shape + // from the URL, if needed in the future. } - protected _updateSkybox(): void { - if (this._zoneEntity.skyboxMode === "enabled" && this._zoneEntity.skybox && this._gameObject) { - if (!this._skybox) { - this._skybox = new SkyboxComponent(); - this._gameObject.addComponent(this._skybox); - this._skybox.load(this._zoneEntity.skybox, this._zoneEntity.dimensions, this._zoneEntity.id); + protected _updateSkybox(): Promise { + return new Promise((resolve) => { + if (this._zoneEntity.skyboxMode === "enabled" && this._zoneEntity.skybox && this._scene.activeCamera) { + if (!this._skybox) { + this._skybox = new SkyboxComponent(); + // Create the skybox mesh and add it to the scene + this._skybox.load(this._zoneEntity.skybox, this._zoneEntity.id, this._scene.activeCamera); + // The mesh is now added to the scene in the load method + } + this._skybox.update(this._zoneEntity.skybox); + this._skybox.enable = true; + } else if (this._skybox) { + this._skybox.enable = false; } - this._skybox.update(this._zoneEntity.skybox); - this._skybox.enable = true; - } else if (this._skybox) { - this._skybox.enable = false; - } + resolve(); + }); } - protected updateAmbientLight(): void { - if (this._zoneEntity.ambientLightMode === "enabled" && this._zoneEntity.ambientLight && this._gameObject) { - if (!this._ambientLight) { - this._ambientLight = new AmbientLightComponent(); - this._ambientLight.light = new HemisphericLight("AmbientLight", Vector3.Up(), this._gameObject.getScene()); - this._gameObject.addComponent(this._ambientLight); - } + protected updateAmbientLight(): Promise { + return new Promise((resolve) => { + if (this._zoneEntity.ambientLightMode === "enabled" && this._zoneEntity.ambientLight && this._gameObject) { + if (!this._ambientLight) { + this._ambientLight = new AmbientLightComponent(); + this._ambientLight.light = new HemisphericLight("AmbientLight", Vector3.Up(), this._gameObject.getScene()); + this._gameObject.addComponent(this._ambientLight); + } - if (this._ambientLight.light) { - this._ambientLight.light.setEnabled(true); - if (typeof this._zoneEntity.ambientLight.ambientIntensity === "number") { - this._ambientLight.light.intensity = this._zoneEntity.ambientLight.ambientIntensity; - this._scene.environmentIntensity = this._zoneEntity.ambientLight.ambientIntensity; + if (this._ambientLight.light) { + this._ambientLight.light.setEnabled(true); + if (typeof this._zoneEntity.ambientLight.ambientIntensity === "number") { + this._ambientLight.light.intensity = this._zoneEntity.ambientLight.ambientIntensity; + this._scene.environmentIntensity = this._zoneEntity.ambientLight.ambientIntensity; + } } - } - } else if (this._ambientLight) { - this._ambientLight.light?.setEnabled(false); - } + } else if (this._ambientLight) { + this._ambientLight.light?.setEnabled(false); + } + resolve(); + }); } - protected updateKeyLight(): void { - if (this._zoneEntity.keyLightMode === "enabled" && this._zoneEntity.keyLight && this._gameObject) { - if (!this._keyLight) { - this._keyLight = new KeyLightComponent(this._zoneEntity.keyLight, this._gameObject.getScene()); - this._gameObject.addComponent(this._keyLight); + protected updateKeyLight(): Promise { + return new Promise((resolve) => { + if (this._zoneEntity.keyLightMode === "enabled" && this._zoneEntity.keyLight && this._gameObject) { + if (!this._keyLight) { + this._keyLight = new KeyLightComponent(this._zoneEntity.keyLight, this._gameObject.getScene()); + this._gameObject.addComponent(this._keyLight); + } + this._keyLight.update(this._zoneEntity.keyLight); + this._keyLight.enable = true; + } else if (this._keyLight) { + this._keyLight.enable = false; } - this._keyLight.update(this._zoneEntity.keyLight); - this._keyLight.enable = true; - } else if (this._keyLight) { - this._keyLight.enable = false; - } + resolve(); + }); } - protected updateHaze(): void { - if (this._zoneEntity.hazeMode === "enabled" && this._zoneEntity.haze && this._gameObject) { - if (!this._haze) { - this._haze = new HazeComponent(this._zoneEntity.haze, this._gameObject.getScene()); - this._gameObject.addComponent(this._haze); + protected updateHaze(): Promise { + return new Promise((resolve) => { + if (this._zoneEntity.hazeMode === "enabled" && this._zoneEntity.haze && this._gameObject) { + if (!this._haze) { + this._haze = new HazeComponent(this._zoneEntity.haze, this._gameObject.getScene()); + this._gameObject.addComponent(this._haze); + } + this._haze.update(this._zoneEntity.haze); + this._haze.enable = true; + } else if (this._haze) { + this._haze.enable = false; } - this._haze.update(this._zoneEntity.haze); - this._haze.enable = true; - } else if (this._haze) { - this._haze.enable = false; - } + resolve(); + }); } - protected _updateUserData(): void { - const userData = this._zoneEntity.userData - ? JSON.parse(this._zoneEntity.userData) as ZoneExtensions - : undefined; - this._updateEnvironment(userData); - this._updateDefaultRenderingPipeline(userData); - this._updateVolumetricLight(userData); + protected _updateUserData(): Promise { + return new Promise((resolve) => { + const userData = this._zoneEntity.userData + ? JSON.parse(this._zoneEntity.userData) as ZoneExtensions + : undefined; + this._updateEnvironment(userData); + this._updateDefaultRenderingPipeline(userData); + this._updateVolumetricLight(userData); + resolve(); + }); } protected _updateEnvironment(userData: ZoneExtensions | undefined): void { @@ -317,4 +338,156 @@ export class ZoneEntityController extends EntityController { this._vls.mesh.isVisible = false; } } -} + + public createZoneMesh(): void { + if (this._zoneMesh) { + console.warn(`Zone mesh already exists for zone ${this._zoneEntity.id}`); + return; + } + + if (this._zoneEntity.compoundShapeURL) { + this._createCompoundShapeMesh(); + } else if (this._zoneEntity.shapeType) { + this._createShapeTypeMesh(); + } else { + console.warn(`No shape type or compound shape URL specified for zone ${this._zoneEntity.id}`); + } + } + + private _createShapeTypeMesh(): void { + if (!this._zoneEntity.shapeType) { + console.warn(`No shape type specified for zone entity ${this._zoneEntity.id}`); + return; + } + + const meshName = `zoneMesh_${this._zoneEntity.id}`; + + switch (this._zoneEntity.shapeType) { + case ShapeType.Box: + this._zoneMesh = MeshBuilder.CreateBox(meshName, { size: 1 }, this._scene); + break; + case ShapeType.Sphere: + this._zoneMesh = MeshBuilder.CreateSphere(meshName, { diameter: 1 }, this._scene); + break; + // Add other shape types as needed + default: + console.warn(`Unsupported shape type: ${this._zoneEntity.shapeType} for zone ${this._zoneEntity.id}`); + return; + } + + this._setupZoneMesh(); + } + + private _createCompoundShapeMesh(): void { + const zoneId = this._zoneEntity.id; + const meshName = `zoneMesh_${zoneId}`; + const shapeURL = this._zoneEntity.compoundShapeURL || ''; + SceneLoader.ImportMesh("", shapeURL, "", this._scene, (meshes) => { + if (meshes.length > 0) { + // Find the mesh that represents the compound shape + const compoundMesh = meshes.find(mesh => mesh.name !== "__root__") || meshes[0]; + + if (compoundMesh instanceof AbstractMesh) { + this._zoneMesh = compoundMesh; + this._zoneMesh.name = meshName; + this._zoneMesh.id = meshName; // Set the ID to match the name + + // Remove any parent-child relationships + this._zoneMesh.parent = null; + this._zoneMesh.getChildMeshes().forEach(child => { + if (child instanceof AbstractMesh) { + child.parent = this._zoneMesh; + } + }); + } else { + console.warn(`Compound mesh for zone ${zoneId} is not an AbstractMesh instance`); + return; + } + + // Dispose of all other imported meshes + meshes.forEach(mesh => { + if (mesh !== this._zoneMesh && this._zoneMesh && !mesh.isDescendantOf(this._zoneMesh)) { + mesh.dispose(); + } + }); + + this._setupZoneMesh(); + } else { + console.error(`No meshes imported for zone ${zoneId}`); + } + }); + } + + private _setupZoneMesh(): void { + if (this._zoneMesh && this._zoneEntity && this._gameObject) { + this._zoneMesh.parent = this._gameObject; + this._zoneMesh.position = Vector3.Zero(); + this._zoneMesh.rotationQuaternion = Quaternion.Identity(); + + if (this._zoneEntity.dimensions) { + this._zoneMesh.scaling = new Vector3( + this._zoneEntity.dimensions.x, + this._zoneEntity.dimensions.y, + this._zoneEntity.dimensions.z + ); + } + + const material = new StandardMaterial(`zoneMaterial_${this._zoneEntity.id}`, this._scene); + material.alpha = 0.3; + material.diffuseColor = new Color3(1, 0, 0); + this._zoneMesh.material = material; + this._zoneMesh.isPickable = true; + + // Set metadata for the mesh + this._zoneMesh.metadata = { zoneController: this }; + + console.log(`Zone mesh setup complete for zone ${this._zoneEntity.id}.`); + console.log(`Zone mesh name: ${this._zoneMesh.name}`); + console.log(`Zone mesh ID: ${this._zoneMesh.id}`); + console.log(`Zone mesh parent: ${this._zoneMesh.parent?.name}`); + console.log(`Zone position: ${this._gameObject.position.toString()}`); + console.log(`Zone mesh local scaling: ${this._zoneMesh.scaling.toString()}`); + } else { + console.warn(`Zone mesh or zone entity is undefined in _setupZoneMesh for zone ${this._zoneEntity.id}`); + } + } + + // Remove or modify this method as it's no longer needed for skybox dimensions + protected _updateDimensions(): void { + // This method may still be needed for other zone-related updates, + // but remove any skybox-specific code + } + + public get zoneMesh(): AbstractMesh | null { + return this._zoneMesh; + } + + public activateZoneSettings(): void { + Promise.all([ + this._updateSkybox(), + this.updateAmbientLight(), + this.updateKeyLight(), + this.updateHaze(), + this._updateUserData() + ]).then(() => { + console.log(`Activated settings for zone ${this._zoneEntity.id}`); + }); + } + + public deactivateZoneSettings(): void { + if (this._skybox) { + this._skybox.enable = false; + } + if (this._ambientLight) { + this._ambientLight.light?.setEnabled(false); + } + if (this._keyLight) { + this._keyLight.enable = false; + } + if (this._haze) { + this._haze.enable = false; + } + // Revert any user data changes if necessary + console.log(`Deactivated settings for zone ${this._zoneEntity.id}`); + } +} \ No newline at end of file diff --git a/src/modules/entity/entities/Entity.ts b/src/modules/entity/entities/Entity.ts index 59db48ac..64d4db02 100644 --- a/src/modules/entity/entities/Entity.ts +++ b/src/modules/entity/entities/Entity.ts @@ -377,7 +377,7 @@ export abstract class Entity implements IEntity { }); } - public copyFormPacketData(props: EntityProperties): void { + public copyFromPacketData(props: EntityProperties): void { this.name = props.name; this.position = props.position; this.rotation = props.rotation; diff --git a/src/modules/entity/entities/ImageEntity.ts b/src/modules/entity/entities/ImageEntity.ts index b47319d4..73ec3224 100644 --- a/src/modules/entity/entities/ImageEntity.ts +++ b/src/modules/entity/entities/ImageEntity.ts @@ -108,8 +108,8 @@ export class ImageEntity extends Entity implements IImageEntity { return this._onImageURLChanged.observable; } - public copyFormPacketData(props: EntityProperties): void { - super.copyFormPacketData(props); + public copyFromPacketData(props: EntityProperties): void { + super.copyFromPacketData(props); const imageProps = props as ImageEntityProperties; this.imageURL = imageProps.imageURL; diff --git a/src/modules/entity/entities/LabelEntity.ts b/src/modules/entity/entities/LabelEntity.ts index 9cb9e0cf..e5e5551b 100644 --- a/src/modules/entity/entities/LabelEntity.ts +++ b/src/modules/entity/entities/LabelEntity.ts @@ -22,6 +22,7 @@ import { StandardMaterial, TransformNode, Vector3, + Engine, } from "@babylonjs/core"; import { DEFAULT_MESH_RENDER_GROUP_ID } from "@Modules/object"; import { Renderer } from "@Modules/scene"; @@ -157,6 +158,14 @@ export class LabelEntity { const tagBackgroundColorString = tagBackgroundColor.toHexString(); const memoName = `${name}${icon ? "-i" : ""}-${tagBackgroundColorString}`; + // Add these lines at the beginning of the method: + const enableBevels = false; // Set to false to disable bevels (large fps gain when false) + const enableAlpha = false; // Set to false to disable alpha transparency (minimal fps gain when false) + + // Declare corners and edges outside the conditional block + const corners: Mesh[] = [] + const edges: Mesh[] = [] + // Attempt to reuse a memoized mesh, if one exists. let mesh = meshMemo.get(memoName)?.clone("Label", object, false, false); @@ -187,7 +196,9 @@ export class LabelEntity { true, true ); - newForegroundTexture.getAlphaFromRGB = true; + if (enableAlpha) { + newForegroundTexture.getAlphaFromRGB = true; + } // Memoize the texture. foregroundTextureMemo.set(memoName, newForegroundTexture); foregroundTexture = newForegroundTexture; @@ -212,7 +223,9 @@ export class LabelEntity { true, true ); - newBackgroundTexture.getAlphaFromRGB = true; + if (enableAlpha) { + newBackgroundTexture.getAlphaFromRGB = true; + } // Memoize the texture. backgroundTextureMemo.set(tagBackgroundColorString, newBackgroundTexture); backgroundTexture = newBackgroundTexture; @@ -231,6 +244,11 @@ export class LabelEntity { newForegroundMaterial.specularTexture = foregroundTexture; newForegroundMaterial.emissiveTexture = foregroundTexture; newForegroundMaterial.disableLighting = true; + if (enableAlpha) { + newForegroundMaterial.useAlphaFromDiffuseTexture = true; + } else { + newForegroundMaterial.alphaMode = Engine.ALPHA_DISABLE; + } // Memoize the material. foregroundMaterialMemo.set(memoName, newForegroundMaterial); foregroundMaterial = newForegroundMaterial; @@ -248,6 +266,11 @@ export class LabelEntity { newBackgroundMaterial.specularTexture = backgroundTexture; newBackgroundMaterial.emissiveTexture = backgroundTexture; newBackgroundMaterial.disableLighting = true; + if (enableAlpha) { + newBackgroundMaterial.useAlphaFromDiffuseTexture = true; + } else { + newBackgroundMaterial.alphaMode = Engine.ALPHA_DISABLE; + } // Memoize the material. backgroundMaterialMemo.set(tagBackgroundColorString, newBackgroundMaterial); backgroundMaterial = newBackgroundMaterial; @@ -266,52 +289,52 @@ export class LabelEntity { ); plane.material = foregroundMaterial; - // Rounded corners. - const corners = new Array(); - const cornerPositions = [ - new Vector3(-tagWidth / 2, tagHeight / 2 - tagCornerRadius, 0), - new Vector3(tagWidth / 2, tagHeight / 2 - tagCornerRadius, 0), - new Vector3(tagWidth / 2, -tagHeight / 2 + tagCornerRadius, 0), - new Vector3(-tagWidth / 2, -tagHeight / 2 + tagCornerRadius, 0), - ]; - const sector = createSector( - "LabelCorner", - Vector3.Up(), - Vector3.Left(), - tagCornerRadius, - scene - ); - corners.push(sector); - corners.push(sector.clone("LabelCorner")); - corners.push(sector.clone("LabelCorner")); - corners.push(sector.clone("LabelCorner")); - let index = 0; - for (const cornerMesh of corners) { - cornerMesh.material = backgroundMaterial; - cornerMesh.position = cornerPositions[index]; - cornerMesh.rotate(new Vector3(0, 0, 1), -index * (Math.PI / 2)); - index += 1; - } - - // Left and right edges. - const edges = new Array(); - const edgeOptions = { - width: tagCornerRadius, - height: tagHeight - tagCornerRadius * 2, - sideOrientation: Mesh.FRONTSIDE, - updatable: false, - }; - const edgePositions = [ - new Vector3(-tagWidth / 2 - tagCornerRadius / 2, 0, 0), - new Vector3(tagWidth / 2 + tagCornerRadius / 2, 0, 0), - ]; - edges.push(MeshBuilder.CreatePlane("LabelLeftEdge", edgeOptions, scene)); - edges.push(MeshBuilder.CreatePlane("LabelRightEdge", edgeOptions, scene)); - index = 0; - for (const edgeMesh of edges) { - edgeMesh.material = backgroundMaterial; - edgeMesh.position = edgePositions[index]; - index += 1; + if (enableBevels) { + // Rounded corners. + const cornerPositions = [ + new Vector3(-tagWidth / 2, tagHeight / 2 - tagCornerRadius, 0), + new Vector3(tagWidth / 2, tagHeight / 2 - tagCornerRadius, 0), + new Vector3(tagWidth / 2, -tagHeight / 2 + tagCornerRadius, 0), + new Vector3(-tagWidth / 2, -tagHeight / 2 + tagCornerRadius, 0), + ]; + const sector = createSector( + "LabelCorner", + Vector3.Up(), + Vector3.Left(), + tagCornerRadius, + scene + ); + corners.push(sector); + corners.push(sector.clone("LabelCorner")); + corners.push(sector.clone("LabelCorner")); + corners.push(sector.clone("LabelCorner")); + let index = 0; + for (const cornerMesh of corners) { + cornerMesh.material = backgroundMaterial; + cornerMesh.position = cornerPositions[index]; + cornerMesh.rotate(new Vector3(0, 0, 1), -index * (Math.PI / 2)); + index += 1; + } + + // Left and right edges. + const edgeOptions = { + width: tagCornerRadius, + height: tagHeight - tagCornerRadius * 2, + sideOrientation: Mesh.FRONTSIDE, + updatable: false, + }; + const edgePositions = [ + new Vector3(-tagWidth / 2 - tagCornerRadius / 2, 0, 0), + new Vector3(tagWidth / 2 + tagCornerRadius / 2, 0, 0), + ]; + edges.push(MeshBuilder.CreatePlane("LabelLeftEdge", edgeOptions, scene)); + edges.push(MeshBuilder.CreatePlane("LabelRightEdge", edgeOptions, scene)); + index = 0; + for (const edgeMesh of edges) { + edgeMesh.material = backgroundMaterial; + edgeMesh.position = edgePositions[index]; + index += 1; + } } // Arrow mesh. @@ -330,9 +353,16 @@ export class LabelEntity { arrow.rotation.z = -Math.PI / 2; arrow.scaling.x = 0.5; + // Modify the mesh merging section: + const meshesToMerge = [plane]; + if (enableBevels) { + meshesToMerge.push(...corners, ...edges); + } + meshesToMerge.push(arrow); + // Merge the label meshes. const mergedMesh = Mesh.MergeMeshes( - [plane, ...corners, ...edges, arrow], + meshesToMerge, true, true, undefined, @@ -387,37 +417,34 @@ export class LabelEntity { mesh.renderingGroupId = DEFAULT_MESH_RENDER_GROUP_ID; mesh.setEnabled(true); - // Hide the label if it is too far from the avatar, - // or if `showLabels` has been turned off in the Store. - scene.registerBeforeRender(() => { + // Modify the scene.registerBeforeRender section: + const updateFunction = () => { if (!mesh) { return; } - // Update the label's position. if (heightHysteresis) { mesh.position.y = heightHysteresis.get(); } - // Update the label's opacity. const avatar = Renderer.getScene()?.getMyAvatar(); if (avatar) { const avatarPosition = avatar.getAbsolutePosition().clone(); const labelPosition = mesh.getAbsolutePosition(); - const distance = avatarPosition.subtract(labelPosition).length(); - // Clamp the opacity between 0 and 0.94. - // Max opacity of 0.94 reduces the chance that the label will be affected by bloom. - const opacity = Math.min( - Math.max(popDistance + 1 - distance, 0), - 0.94 - ); - mesh.visibility = - opacity * - Number( - userStore.avatar.showLabels && - (popOverride?.(distance) ?? true) - ); - mesh.isVisible = true; + const distance = Vector3.Distance(avatarPosition, labelPosition); + const isVisible = distance <= popDistance && + userStore.avatar.showLabels && + (popOverride?.(distance) ?? true); + + if (enableAlpha) { + const opacity = Math.min(Math.max(popDistance + 1 - distance, 0), 0.94); + mesh.visibility = opacity * Number(isVisible); + } else { + mesh.isVisible = isVisible; + } } - }); + }; + + // Always use onBeforeRenderObservable to ensure the update function is called + scene.onBeforeRenderObservable.add(updateFunction); return mesh; } @@ -444,4 +471,4 @@ export class LabelEntity { const labelMeshes = object.getChildMeshes(false, (node) => (/^Label/iu).test(node.name)); labelMeshes.forEach((labelMesh) => labelMesh.dispose(false, false)); } -} +} \ No newline at end of file diff --git a/src/modules/entity/entities/LightEntity.ts b/src/modules/entity/entities/LightEntity.ts index 721f34c9..2d02a3cb 100644 --- a/src/modules/entity/entities/LightEntity.ts +++ b/src/modules/entity/entities/LightEntity.ts @@ -107,8 +107,8 @@ export class LightEntity extends Entity implements ILightEntity { return this._onLightTypeChanged.observable; } - public copyFormPacketData(props: EntityProperties): void { - super.copyFormPacketData(props); + public copyFromPacketData(props: EntityProperties): void { + super.copyFromPacketData(props); const lightProps = props as LightEntityProperties; diff --git a/src/modules/entity/entities/ModelEntity.ts b/src/modules/entity/entities/ModelEntity.ts index bad2ccf1..9348d15b 100644 --- a/src/modules/entity/entities/ModelEntity.ts +++ b/src/modules/entity/entities/ModelEntity.ts @@ -98,8 +98,8 @@ export class ModelEntity extends Entity implements IModelEntity { return this._animation; } - public copyFormPacketData(props: EntityProperties): void { - super.copyFormPacketData(props); + public copyFromPacketData(props: EntityProperties): void { + super.copyFromPacketData(props); const modelProps = props as ModelEntityProperties; this.modelURL = modelProps.modelURL; diff --git a/src/modules/entity/entities/ShapeEntity.ts b/src/modules/entity/entities/ShapeEntity.ts index 554c7edb..01ada70f 100644 --- a/src/modules/entity/entities/ShapeEntity.ts +++ b/src/modules/entity/entities/ShapeEntity.ts @@ -75,8 +75,8 @@ export class ShapeEntity extends Entity implements IShapeEntity { return this._onColorChanged.observable; } - public copyFormPacketData(props: EntityProperties): void { - super.copyFormPacketData(props); + public copyFromPacketData(props: EntityProperties): void { + super.copyFromPacketData(props); const shapeProps = props as ShapeEntityProperties; this.shape = shapeProps.shape; diff --git a/src/modules/entity/entities/WebEntity.ts b/src/modules/entity/entities/WebEntity.ts index 6304e291..0fdbebb0 100644 --- a/src/modules/entity/entities/WebEntity.ts +++ b/src/modules/entity/entities/WebEntity.ts @@ -170,8 +170,8 @@ export class WebEntity extends Entity implements IWebEntity { return this._onWebPropertiesChanged.observable; } - public copyFormPacketData(props: EntityProperties): void { - super.copyFormPacketData(props); + public copyFromPacketData(props: EntityProperties): void { + super.copyFromPacketData(props); const webProps = props as WebEntityProperties; this.sourceUrl = webProps.sourceURL; diff --git a/src/modules/entity/entities/ZoneEntity.ts b/src/modules/entity/entities/ZoneEntity.ts index f9cdfa64..a3049048 100644 --- a/src/modules/entity/entities/ZoneEntity.ts +++ b/src/modules/entity/entities/ZoneEntity.ts @@ -1,227 +1,247 @@ -// -// ZoneEntity.ts -// -// Created by Nolan Huang on 18 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 { ShapeType, IAmbientLightProperty, IKeyLightProperty, - ISkyboxProperty, IHazeProperty, IBloomProperty, ComponentMode, - AvatarPriorityMode } from "../EntityProperties"; -import { IEntity, IZoneEntity } from "../EntityInterfaces"; -import { Entity, EntityPropertyChangeObservable } from "./Entity"; -import { EntityProperties, ZoneEntityProperties } from "@vircadia/web-sdk"; -import { Observable } from "@babylonjs/core"; -import { EntityMapper } from "../package"; - -/* eslint-disable prefer-object-spread */ -export class ZoneEntity extends Entity implements IZoneEntity { - private _shapeType: ShapeType | undefined; - private _compoundShapeURL: string | undefined; - private _keyLightMode: ComponentMode | undefined; - private _keyLight: IKeyLightProperty | undefined; - private _ambientLightMode: ComponentMode | undefined; - private _ambientLight: IAmbientLightProperty | undefined; - private _skyboxMode: ComponentMode | undefined; - private _skybox: ISkyboxProperty | undefined; - private _hazeMode: ComponentMode | undefined; - private _haze: IHazeProperty | undefined; - private _bloomMode?: ComponentMode | undefined; - private _bloom: IBloomProperty | undefined; - _flyingAllowed: boolean | undefined; - _ghostingAllowed: boolean | undefined; - _filterURL: string | undefined; - _avatarPriority: AvatarPriorityMode | undefined; - _screenshare: ComponentMode | undefined; - protected _onShapeTypeChanged: EntityPropertyChangeObservable; - protected _onAmbientLightPropertiesChanged: EntityPropertyChangeObservable; - protected _onKeyLightPropertiesChanged: EntityPropertyChangeObservable; - protected _onSkyboxPropertiesChanged: EntityPropertyChangeObservable; - protected _onHazePropertiesChanged: EntityPropertyChangeObservable; - protected _onBloomPropertiesChanged: EntityPropertyChangeObservable; - - constructor(id: string) { - super(id, "Zone"); - - this._onShapeTypeChanged = this.createPropertyChangeObservable(); - this._onAmbientLightPropertiesChanged = this.createPropertyChangeObservable(); - this._onKeyLightPropertiesChanged = this.createPropertyChangeObservable(); - this._onSkyboxPropertiesChanged = this.createPropertyChangeObservable(); - this._onHazePropertiesChanged = this.createPropertyChangeObservable(); - this._onBloomPropertiesChanged = this.createPropertyChangeObservable(); - } - - public get shapeType(): ShapeType | undefined { - return this._shapeType; - } - - public set shapeType(value: ShapeType | undefined) { - if (this._shapeType !== value) { - this._shapeType = value; - this._onShapeTypeChanged.isDirty = true; - } - } - - public get ambientLightMode(): ComponentMode | undefined { - return this._ambientLightMode; - } - - public set ambientLightMode(value: ComponentMode | undefined) { - if (value && value !== this._ambientLightMode) { - this._ambientLightMode = value; - this._onAmbientLightPropertiesChanged.isDirty = true; - } - } - - public get ambientLight(): IAmbientLightProperty | undefined { - return this._ambientLight; - } - - public set ambientLight(value: IAmbientLightProperty | undefined) { - if (value) { - this._ambientLight = value; - this._onAmbientLightPropertiesChanged.isDirty = true; - } - } - - public get onShapeTypeChanged(): Observable { - return this._onShapeTypeChanged.observable; - } - - public get onAmbientLightPropertiesChanged(): Observable { - return this._onAmbientLightPropertiesChanged.observable; - } - - public get onKeyLightPropertiesChanged(): Observable { - return this._onKeyLightPropertiesChanged.observable; - } - - public get onHazePropertiesChanged(): Observable { - return this._onHazePropertiesChanged.observable; - } - - public get onSkyboxPropertiesChanged(): Observable { - return this._onSkyboxPropertiesChanged.observable; - } - - public get onBloomPropertiesChanged(): Observable { - return this._onBloomPropertiesChanged.observable; - } - - public get keyLightMode(): ComponentMode | undefined { - return this._keyLightMode; - } - - public set keyLightMode(value: ComponentMode | undefined) { - if (value && value !== this._keyLightMode) { - this._keyLightMode = value; - this._onKeyLightPropertiesChanged.isDirty = true; - } - } - - public get keyLight(): IKeyLightProperty | undefined { - return this._keyLight; - } - - public set keyLight(value: IKeyLightProperty | undefined) { - if (value) { - this._keyLight = value; - this._onKeyLightPropertiesChanged.isDirty = true; - } - } - - public get skyboxMode(): ComponentMode | undefined { - return this._skyboxMode; - } - - public set skyboxMode(value: ComponentMode | undefined) { - if (value && value !== this._skyboxMode) { - this._skyboxMode = value; - this._onSkyboxPropertiesChanged.isDirty = true; - } - } - - public get skybox(): ISkyboxProperty | undefined { - return this._skybox; - } - - public set skybox(value: ISkyboxProperty | undefined) { - if (value) { - this._skybox = value; - this._onSkyboxPropertiesChanged.isDirty = true; - } - } - - public get hazeMode(): ComponentMode | undefined { - return this._hazeMode; - } - - public set hazeMode(value: ComponentMode | undefined) { - this._hazeMode = value; - - if (value && value !== this._hazeMode) { - this._hazeMode = value; - this._onHazePropertiesChanged.isDirty = true; - } - } - - public get haze(): IHazeProperty | undefined { - return this._haze; - } - - public set haze(value: IHazeProperty | undefined) { - if (value) { - this._haze = value; - this._onHazePropertiesChanged.isDirty = true; - } - } - - public get bloomMode(): ComponentMode | undefined { - return this._bloomMode; - } - - public set bloomMode(value: ComponentMode | undefined) { - if (value && value !== this._bloomMode) { - this._bloomMode = value; - this._onBloomPropertiesChanged.isDirty = true; - } - } - - public get bloom(): IBloomProperty | undefined { - return this._bloom; - } - - public set bloom(value: IBloomProperty | undefined) { - if (value) { - this._bloom = value; - this._onBloomPropertiesChanged.isDirty = true; - } - } - - public copyFormPacketData(props: EntityProperties): void { - super.copyFormPacketData(props); - - const zoneProps = props as ZoneEntityProperties; - - this.shapeType = EntityMapper.mapToShapeType(zoneProps.shapeType); - - this.ambientLightMode = EntityMapper.mapToComponentMode(zoneProps.ambientLightMode); - this.ambientLight = EntityMapper.mapToAmbientLightProperty(zoneProps.ambientLight); - - this.keyLightMode = EntityMapper.mapToComponentMode(zoneProps.keyLightMode); - this.keyLight = Object.assign({}, zoneProps.keyLight); - - this.skyboxMode = EntityMapper.mapToComponentMode(zoneProps.skyboxMode); - this.skybox = Object.assign({}, zoneProps.skybox); - - this.hazeMode = EntityMapper.mapToComponentMode(zoneProps.hazeMode); - this.haze = EntityMapper.mapToHazeProperty(zoneProps.haze); - - this.bloomMode = EntityMapper.mapToComponentMode(zoneProps.bloomMode); - this.bloom = EntityMapper.mapToBloomProperty(zoneProps.bloom); - } -} +// +// ZoneEntity.ts +// +// Created by Nolan Huang on 18 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 { + ShapeType, IAmbientLightProperty, IKeyLightProperty, + ISkyboxProperty, IHazeProperty, IBloomProperty, ComponentMode, + AvatarPriorityMode +} from "../EntityProperties"; +import { IEntity, IZoneEntity } from "../EntityInterfaces"; +import { Entity, EntityPropertyChangeObservable } from "./Entity"; +import { EntityProperties, ZoneEntityProperties } from "@vircadia/web-sdk"; +import { Observable } from "@babylonjs/core"; +import { EntityMapper } from "../package"; + +/* eslint-disable prefer-object-spread */ +export class ZoneEntity extends Entity implements IZoneEntity { + private _shapeType: ShapeType | undefined; + private _compoundShapeURL: string | undefined; + private _keyLightMode: ComponentMode | undefined; + private _keyLight: IKeyLightProperty | undefined; + private _ambientLightMode: ComponentMode | undefined; + private _ambientLight: IAmbientLightProperty | undefined; + private _skyboxMode: ComponentMode | undefined; + private _skybox: ISkyboxProperty | undefined; + private _hazeMode: ComponentMode | undefined; + private _haze: IHazeProperty | undefined; + private _bloomMode?: ComponentMode | undefined; + private _bloom: IBloomProperty | undefined; + _flyingAllowed: boolean | undefined; + _ghostingAllowed: boolean | undefined; + _filterURL: string | undefined; + _avatarPriority: AvatarPriorityMode | undefined; + _screenshare: ComponentMode | undefined; + protected _onShapeTypeChanged: EntityPropertyChangeObservable; + protected _onCompoundShapeURLChanged: EntityPropertyChangeObservable; + protected _onAmbientLightPropertiesChanged: EntityPropertyChangeObservable; + protected _onKeyLightPropertiesChanged: EntityPropertyChangeObservable; + protected _onSkyboxPropertiesChanged: EntityPropertyChangeObservable; + protected _onHazePropertiesChanged: EntityPropertyChangeObservable; + protected _onBloomPropertiesChanged: EntityPropertyChangeObservable; + + constructor(id: string) { + super(id, "Zone"); + + this._onShapeTypeChanged = this.createPropertyChangeObservable(); + this._onCompoundShapeURLChanged = this.createPropertyChangeObservable(); + this._onAmbientLightPropertiesChanged = this.createPropertyChangeObservable(); + this._onKeyLightPropertiesChanged = this.createPropertyChangeObservable(); + this._onSkyboxPropertiesChanged = this.createPropertyChangeObservable(); + this._onHazePropertiesChanged = this.createPropertyChangeObservable(); + this._onBloomPropertiesChanged = this.createPropertyChangeObservable(); + } + + public get shapeType(): ShapeType | undefined { + return this._shapeType; + } + + public set shapeType(value: ShapeType | undefined) { + if (this._shapeType !== value) { + this._shapeType = value; + this._onShapeTypeChanged.isDirty = true; + } + } + + public get compoundShapeURL(): string | undefined { + return this._compoundShapeURL; + } + + public set compoundShapeURL(value: string | undefined) { + if (this._compoundShapeURL !== value) { + this._compoundShapeURL = value; + this._onCompoundShapeURLChanged.isDirty = true; + } + } + + public get ambientLightMode(): ComponentMode | undefined { + return this._ambientLightMode; + } + + public set ambientLightMode(value: ComponentMode | undefined) { + if (value && value !== this._ambientLightMode) { + this._ambientLightMode = value; + this._onAmbientLightPropertiesChanged.isDirty = true; + } + } + + public get ambientLight(): IAmbientLightProperty | undefined { + return this._ambientLight; + } + + public set ambientLight(value: IAmbientLightProperty | undefined) { + if (value) { + this._ambientLight = value; + this._onAmbientLightPropertiesChanged.isDirty = true; + } + } + + public get onShapeTypeChanged(): Observable { + return this._onShapeTypeChanged.observable; + } + + public get onCompoundShapeURLChanged(): Observable { + return this._onCompoundShapeURLChanged.observable; + } + + public get onAmbientLightPropertiesChanged(): Observable { + return this._onAmbientLightPropertiesChanged.observable; + } + + public get onKeyLightPropertiesChanged(): Observable { + return this._onKeyLightPropertiesChanged.observable; + } + + public get onHazePropertiesChanged(): Observable { + return this._onHazePropertiesChanged.observable; + } + + public get onSkyboxPropertiesChanged(): Observable { + return this._onSkyboxPropertiesChanged.observable; + } + + public get onBloomPropertiesChanged(): Observable { + return this._onBloomPropertiesChanged.observable; + } + + public get keyLightMode(): ComponentMode | undefined { + return this._keyLightMode; + } + + public set keyLightMode(value: ComponentMode | undefined) { + if (value && value !== this._keyLightMode) { + this._keyLightMode = value; + this._onKeyLightPropertiesChanged.isDirty = true; + } + } + + public get keyLight(): IKeyLightProperty | undefined { + return this._keyLight; + } + + public set keyLight(value: IKeyLightProperty | undefined) { + if (value) { + this._keyLight = value; + this._onKeyLightPropertiesChanged.isDirty = true; + } + } + + public get skyboxMode(): ComponentMode | undefined { + return this._skyboxMode; + } + + public set skyboxMode(value: ComponentMode | undefined) { + if (value && value !== this._skyboxMode) { + this._skyboxMode = value; + this._onSkyboxPropertiesChanged.isDirty = true; + } + } + + public get skybox(): ISkyboxProperty | undefined { + return this._skybox; + } + + public set skybox(value: ISkyboxProperty | undefined) { + if (value) { + this._skybox = value; + this._onSkyboxPropertiesChanged.isDirty = true; + } + } + + public get hazeMode(): ComponentMode | undefined { + return this._hazeMode; + } + + public set hazeMode(value: ComponentMode | undefined) { + this._hazeMode = value; + + if (value && value !== this._hazeMode) { + this._hazeMode = value; + this._onHazePropertiesChanged.isDirty = true; + } + } + + public get haze(): IHazeProperty | undefined { + return this._haze; + } + + public set haze(value: IHazeProperty | undefined) { + if (value) { + this._haze = value; + this._onHazePropertiesChanged.isDirty = true; + } + } + + public get bloomMode(): ComponentMode | undefined { + return this._bloomMode; + } + + public set bloomMode(value: ComponentMode | undefined) { + if (value && value !== this._bloomMode) { + this._bloomMode = value; + this._onBloomPropertiesChanged.isDirty = true; + } + } + + public get bloom(): IBloomProperty | undefined { + return this._bloom; + } + + public set bloom(value: IBloomProperty | undefined) { + if (value) { + this._bloom = value; + this._onBloomPropertiesChanged.isDirty = true; + } + } + + public copyFromPacketData(props: EntityProperties): void { + super.copyFromPacketData(props); + + const zoneProps = props as ZoneEntityProperties; + + this.shapeType = EntityMapper.mapToShapeType(zoneProps.shapeType); + this.compoundShapeURL = zoneProps.compoundShapeURL; + + this.ambientLightMode = EntityMapper.mapToComponentMode(zoneProps.ambientLightMode); + this.ambientLight = EntityMapper.mapToAmbientLightProperty(zoneProps.ambientLight); + + this.keyLightMode = EntityMapper.mapToComponentMode(zoneProps.keyLightMode); + this.keyLight = Object.assign({}, zoneProps.keyLight); + + this.skyboxMode = EntityMapper.mapToComponentMode(zoneProps.skyboxMode); + this.skybox = Object.assign({}, zoneProps.skybox); + + this.hazeMode = EntityMapper.mapToComponentMode(zoneProps.hazeMode); + this.haze = EntityMapper.mapToHazeProperty(zoneProps.haze); + + this.bloomMode = EntityMapper.mapToComponentMode(zoneProps.bloomMode); + this.bloom = EntityMapper.mapToBloomProperty(zoneProps.bloom); + } +} diff --git a/src/modules/entity/entities/materialEntity.ts b/src/modules/entity/entities/materialEntity.ts index b5ce21aa..6f7dcae7 100644 --- a/src/modules/entity/entities/materialEntity.ts +++ b/src/modules/entity/entities/materialEntity.ts @@ -157,8 +157,8 @@ export class MaterialEntity extends Entity implements IMaterialEntity { } - public copyFormPacketData(props: EntityProperties): void { - super.copyFormPacketData(props); + public copyFromPacketData(props: EntityProperties): void { + super.copyFromPacketData(props); const materialProps = props as MaterialEntityProperties; diff --git a/src/modules/scene/ZoneManager.ts b/src/modules/scene/ZoneManager.ts new file mode 100644 index 00000000..ac259806 --- /dev/null +++ b/src/modules/scene/ZoneManager.ts @@ -0,0 +1,71 @@ +import { Scene, Vector3, AbstractMesh } from "@babylonjs/core"; +import { ZoneEntityController } from "../entity/components/controllers/ZoneEntityController"; +import Log from "@Modules/debugging/log"; + +export class ZoneManager { + private _scene: Scene; + private _currentZone: ZoneEntityController | null = null; + private _lastUpdateTime: number = 0; + private _updateInterval: number = 200; // Update every 200ms + + constructor(scene: Scene) { + this._scene = scene; + } + + public update(): void { + const currentTime = Date.now(); + // Only update if enough time has passed + if (currentTime - this._lastUpdateTime < this._updateInterval) { + return; + } + + this._lastUpdateTime = currentTime; + + const camera = this._scene.activeCamera; + if (!camera) return; + + const cameraPosition = camera.position; + let newZone: ZoneEntityController | null = null; + + this._scene.meshes.forEach(mesh => { + const zoneController = mesh.metadata?.zoneController as ZoneEntityController | undefined; + if (zoneController && this.isPointInside(cameraPosition, zoneController)) { + newZone = zoneController; + } + }); + + if (newZone !== this._currentZone) { + if (this._currentZone) { + Log.debug(Log.types.ENTITIES, `Exiting zone: ${this._currentZone.id}`); + this._currentZone.deactivateZoneSettings(); + } + if (newZone) { + Log.debug(Log.types.ENTITIES, `Entering zone: ${newZone.id}`); + newZone.activateZoneSettings(); + } else { + Log.debug(Log.types.ENTITIES, "Not in any zone"); + // Optionally, activate default scene settings here + } + this._currentZone = newZone; + } + } + + private isPointInside(point: Vector3, zoneController: ZoneEntityController): boolean { + const zoneMesh = zoneController.zoneMesh; + if (!zoneMesh) return false; + + // Check if the zoneMesh is a compound shape (has child meshes) + if (zoneMesh.getChildMeshes().length > 0) { + // For compound shapes, check if the point is inside any child mesh + return zoneMesh.getChildMeshes().some(childMesh => { + if (childMesh instanceof AbstractMesh) { + return childMesh.intersectsPoint(point); + } + return false; + }); + } else { + // For simple shapes or single mesh compounds, use intersectsPoint + return zoneMesh.intersectsPoint(point); + } + } +} diff --git a/src/modules/scene/controllers/sceneController.ts b/src/modules/scene/controllers/sceneController.ts index 0b5863ce..0962a7f4 100644 --- a/src/modules/scene/controllers/sceneController.ts +++ b/src/modules/scene/controllers/sceneController.ts @@ -15,17 +15,21 @@ import { Vector3, Ray } from "@babylonjs/core"; import { ScriptComponent } from "@Modules/script"; import type { VScene } from "@Modules/scene/vscene"; import Log from "@Modules/debugging/log"; +import { ZoneManager } from "../ZoneManager"; +import { ZoneEntityController } from "@Modules/entity/components/controllers/ZoneEntityController"; const DEFAULT_GRAVITY = 9.81; const GROUND_DETECTION_LENGTH = 5; // FIXME: This is not a good system for detecting the ground. export class SceneController extends ScriptComponent { private _vscene: VScene; + private _zoneManager: ZoneManager; public isGravityApplied = false; constructor(vscene: VScene) { super(SceneController.typeName); this._vscene = vscene; + this._zoneManager = new ZoneManager(this._scene); } public get componentType(): string { @@ -67,7 +71,7 @@ export class SceneController extends ScriptComponent { } // eslint-disable-next-line @typescript-eslint/no-empty-function - public onInitialize(): void {} + public onInitialize(): void { } public onUpdate(): void { if ( @@ -77,6 +81,9 @@ export class SceneController extends ScriptComponent { ) { this.applyGravity(); } + + // Update zone detection + this._zoneManager.update(); } public onSceneReady(): void { @@ -114,4 +121,24 @@ export class SceneController extends ScriptComponent { } return false; } + + private _checkZones(): void { + const avatar = this._vscene.getMyAvatar(); + if (avatar) { + const avatarPosition = avatar.position; + console.log(`Checking zones for avatar at position: ${avatarPosition.toString()}`); + this._scene.meshes.forEach(mesh => { + const zoneController = (mesh as any).zoneEntityController; + if (zoneController && zoneController.isInside(avatarPosition)) { + console.log(`Avatar is inside zone: ${zoneController.zoneEntity.id}`); + // Apply zone properties + this._applyZoneProperties(zoneController.zoneEntity); + } + }); + } + } + + private _applyZoneProperties(zoneEntity: any): void { + // Implement zone properties application logic here + } } diff --git a/src/modules/scene/renderer.ts b/src/modules/scene/renderer.ts index bd6b8e8a..24f46830 100644 --- a/src/modules/scene/renderer.ts +++ b/src/modules/scene/renderer.ts @@ -77,7 +77,7 @@ export class Renderer { * @returns A reference to the new scene. */ public static createScene(index = this._renderingScenes.length): VScene { - const scene = new VScene(this._engine, index); + const scene = new VScene(this._engine as Engine, index); this._renderingScenes[index] = scene; return scene; } diff --git a/src/modules/scene/vscene.ts b/src/modules/scene/vscene.ts index fdfa9e02..ebd45c58 100644 --- a/src/modules/scene/vscene.ts +++ b/src/modules/scene/vscene.ts @@ -392,9 +392,11 @@ export class VScene { meshComponent.mesh = result.mesh; meshComponent.skeleton = result.skeleton; // Get the bounding vectors of the avatar mesh. - const boundingMesh = meshComponent.mesh.refreshBoundingInfo( - Boolean(meshComponent.skeleton) - ); + const boundingMesh = meshComponent.mesh.refreshBoundingInfo({ + applySkeleton: Boolean(meshComponent.skeleton), + applyMorph: false, + updatePositionsArray: true + }); boundingVectors = boundingMesh.getHierarchyBoundingVectors(); meshComponent.mesh.position = Vector3.Zero(); this._myAvatar.addComponent(meshComponent); diff --git a/src/vircadia-world b/src/vircadia-world index d3e21d33..a48bbc58 160000 --- a/src/vircadia-world +++ b/src/vircadia-world @@ -1 +1 @@ -Subproject commit d3e21d334eb56c416a6a1f6e406b1b7e9b169fdd +Subproject commit a48bbc58c14e747bca3c714af951b7b54fb95ee4