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