diff --git a/packages/frontend/assets/room/envs/museum/museum.blend b/packages/frontend/assets/room/envs/museum/museum.blend index e5ae22a648..5ed0f38fc3 100644 Binary files a/packages/frontend/assets/room/envs/museum/museum.blend and b/packages/frontend/assets/room/envs/museum/museum.blend differ diff --git a/packages/frontend/assets/room/envs/museum/museum.glb b/packages/frontend/assets/room/envs/museum/museum.glb index 0a6364e5df..f1bf907311 100644 Binary files a/packages/frontend/assets/room/envs/museum/museum.glb and b/packages/frontend/assets/room/envs/museum/museum.glb differ diff --git a/packages/frontend/src/world/room/engine.ts b/packages/frontend/src/world/room/engine.ts index 14d4e7fca7..d34c67baf4 100644 --- a/packages/frontend/src/world/room/engine.ts +++ b/packages/frontend/src/world/room/engine.ts @@ -533,6 +533,7 @@ export class RoomEngine extends EventEmitter { this.startRenderLoop(); } + // TODO: 初回以外で呼び出すとエンジンがクラッシュするのを修正 public async changeEnvType(type: RoomState['env']['type'], forInit = false) { this.roomState.env.type = type; @@ -565,14 +566,14 @@ export class RoomEngine extends EventEmitter { let envManager: EnvManager; if (this.roomState.env.type === 'simple') { - envManager = new SimpleEnvManager(onMeshUpdatedCallback); + envManager = new SimpleEnvManager(this, onMeshUpdatedCallback); } else if (this.roomState.env.type === 'japanese') { // TODO } else if (this.roomState.env.type === 'museum') { - envManager = new MuseumEnvManager(onMeshUpdatedCallback); + envManager = new MuseumEnvManager(this, onMeshUpdatedCallback); } - await envManager.load(this.roomState.env.options, this.scene, this); + await envManager.load(this.roomState.env.options); envManager.setTime(this.time); for (const mat of this.scene.materials) { @@ -1386,12 +1387,6 @@ export class RoomEngine extends EventEmitter { public turnOnRoomLight(forInit = false) { if (!forInit) this.sr.disableSnapshotRendering(); // このメソッドは参照カウント方式な点に留意 this.envManager.turnOnRoomLight(); - if (this.envManager?.envMapIndoor != null) this.envManager.envMapIndoor.level = 0.6; - for (const m of this.scene.materials) { - if (m.metadata?.disableEnvMap) { - m.ambientColor = new BABYLON.Color3(0.5, 0.5, 0.5); - } - } if (!forInit) { // workerで実行される可能性がある // eslint-disable-next-line no-restricted-globals @@ -1404,12 +1399,6 @@ export class RoomEngine extends EventEmitter { public turnOffRoomLight() { this.sr.disableSnapshotRendering(); // このメソッドは参照カウント方式な点に留意 this.envManager.turnOffRoomLight(); - if (this.envManager?.envMapIndoor != null) this.envManager.envMapIndoor.level = 0.025; - for (const m of this.scene.materials) { - if (m.metadata?.disableEnvMap) { - m.ambientColor = new BABYLON.Color3(0.025, 0.025, 0.025); - } - } // workerで実行される可能性がある // eslint-disable-next-line no-restricted-globals setTimeout(() => { @@ -1729,12 +1718,14 @@ export class RoomEngine extends EventEmitter { } public destroy() { + this.engine.stopRenderLoop(); if (this.currentRafId != null) { // workerで実行される可能性がある cancelAnimationFrame(this.currentRafId); this.currentRafId = null; } this.timer.dispose(); + this.envManager.dispose(); this.engine.dispose(); this.scene.dispose(); this.disposed = true; diff --git a/packages/frontend/src/world/room/env.ts b/packages/frontend/src/world/room/env.ts index 5ca9e11257..a86e2b7db5 100644 --- a/packages/frontend/src/world/room/env.ts +++ b/packages/frontend/src/world/room/env.ts @@ -18,12 +18,14 @@ import type { RoomEngine } from './engine.js'; //} export abstract class EnvManager { + protected engine: RoomEngine; protected onMeshUpdatedCallback: ((meshes: BABYLON.AbstractMesh[]) => void) | null = null; public abstract envMapIndoor: BABYLON.CubeTexture | null; public abstract maxCameraZ: number; protected shadowGenerators: BABYLON.ShadowGenerator[] = []; - constructor(onMeshUpdatedCallback?: ((meshes: BABYLON.AbstractMesh[]) => void) | null) { + constructor(engine: RoomEngine, onMeshUpdatedCallback?: ((meshes: BABYLON.AbstractMesh[]) => void) | null) { + this.engine = engine; this.onMeshUpdatedCallback = onMeshUpdatedCallback ?? null; } @@ -102,58 +104,58 @@ export class SimpleEnvManager extends EnvManager { public envMapIndoor: BABYLON.CubeTexture | null = null; public maxCameraZ = cm(1000); - constructor(onMeshUpdatedCallback?: ((meshes: BABYLON.AbstractMesh[]) => void) | null) { - super(onMeshUpdatedCallback); + constructor(engine: RoomEngine, onMeshUpdatedCallback?: ((meshes: BABYLON.AbstractMesh[]) => void) | null) { + super(engine, onMeshUpdatedCallback); } - public async load(options: SimpleEnvOptions, scene: BABYLON.Scene, engine: RoomEngine) { - this.skybox = BABYLON.MeshBuilder.CreateBox('skybox', { size: cm(1000) }, scene); - this.skyboxMat = new BABYLON.StandardMaterial('skyboxMat', scene); + public async load(options: SimpleEnvOptions) { + this.skybox = BABYLON.MeshBuilder.CreateBox('skybox', { size: cm(1000) }, this.engine.scene); + this.skyboxMat = new BABYLON.StandardMaterial('skyboxMat', this.engine.scene); this.skyboxMat.backFaceCulling = false; this.skyboxMat.disableLighting = true; this.skybox.material = this.skyboxMat; this.skybox.infiniteDistance = true; - this.roomLight = new BABYLON.SpotLight('roomLight', new BABYLON.Vector3(0, cm(249), 0), new BABYLON.Vector3(0, -1, 0), 16, 8, scene); - this.roomLight.diffuse = new BABYLON.Color3(...engine.roomState.roomLightColor); + this.roomLight = new BABYLON.SpotLight('simpleEnv:RoomLight', new BABYLON.Vector3(0, cm(249), 0), new BABYLON.Vector3(0, -1, 0), 16, 8, this.engine.scene); + this.roomLight.diffuse = new BABYLON.Color3(...this.engine.roomState.roomLightColor); this.roomLight.shadowMinZ = cm(10); this.roomLight.shadowMaxZ = cm(300); this.roomLight.radius = cm(30); - if (engine.graphicsQuality >= GRAPHICS_QUALITY_MEDIUM) { - const shadowGeneratorForRoomLight = new BABYLON.ShadowGenerator(engine.graphicsQuality <= GRAPHICS_QUALITY_MEDIUM ? 1024 : 2048, this.roomLight); + if (this.engine.graphicsQuality >= GRAPHICS_QUALITY_MEDIUM) { + const shadowGeneratorForRoomLight = new BABYLON.ShadowGenerator(this.engine.graphicsQuality <= GRAPHICS_QUALITY_MEDIUM ? 1024 : 2048, this.roomLight); shadowGeneratorForRoomLight.forceBackFacesOnly = true; shadowGeneratorForRoomLight.bias = 0.00001; shadowGeneratorForRoomLight.normalBias = 0.005; shadowGeneratorForRoomLight.usePercentageCloserFiltering = true; shadowGeneratorForRoomLight.filteringQuality = BABYLON.ShadowGenerator.QUALITY_HIGH; - if (engine.graphicsQuality <= GRAPHICS_QUALITY_MEDIUM) { + if (this.engine.graphicsQuality <= GRAPHICS_QUALITY_MEDIUM) { shadowGeneratorForRoomLight.getShadowMap().refreshRate = 60; } //this.shadowGeneratorForRoomLight.useContactHardeningShadow = true; this.shadowGenerators.push(shadowGeneratorForRoomLight); } - if (engine.graphicsQuality >= GRAPHICS_QUALITY_MEDIUM) { - this.sunLight = new BABYLON.DirectionalLight('sunLight', new BABYLON.Vector3(0.2, -1, -1), scene); + if (this.engine.graphicsQuality >= GRAPHICS_QUALITY_MEDIUM) { + this.sunLight = new BABYLON.DirectionalLight('simpleEnv:SunLight', new BABYLON.Vector3(0.2, -1, -1), this.engine.scene); this.sunLight.position = new BABYLON.Vector3(cm(-20), cm(1000), cm(1000)); this.sunLight.shadowMinZ = cm(1000); this.sunLight.shadowMaxZ = cm(2000); - const shadowGeneratorForSunLight = new BABYLON.ShadowGenerator(engine.graphicsQuality <= GRAPHICS_QUALITY_MEDIUM ? 1024 : 2048, this.sunLight); + const shadowGeneratorForSunLight = new BABYLON.ShadowGenerator(this.engine.graphicsQuality <= GRAPHICS_QUALITY_MEDIUM ? 1024 : 2048, this.sunLight); shadowGeneratorForSunLight.forceBackFacesOnly = true; shadowGeneratorForSunLight.bias = 0.00001; shadowGeneratorForSunLight.usePercentageCloserFiltering = true; shadowGeneratorForSunLight.usePoissonSampling = true; - if (engine.graphicsQuality <= GRAPHICS_QUALITY_MEDIUM) { + if (this.engine.graphicsQuality <= GRAPHICS_QUALITY_MEDIUM) { shadowGeneratorForSunLight.getShadowMap().refreshRate = 60; } this.shadowGenerators.push(shadowGeneratorForSunLight); } - this.loaderResult = await BABYLON.ImportMeshAsync('/client-assets/room/envs/default/300.glb', scene); + this.loaderResult = await BABYLON.ImportMeshAsync('/client-assets/room/envs/default/300.glb', this.engine.scene); - this.envMapIndoor = BABYLON.CubeTexture.CreateFromPrefilteredData('/client-assets/room/indoor.env', scene); + this.envMapIndoor = BABYLON.CubeTexture.CreateFromPrefilteredData('/client-assets/room/indoor.env', this.engine.scene); this.envMapIndoor.boundingBoxSize = new BABYLON.Vector3(cm(500), cm(500), cm(500)); this.meshes = this.loaderResult.meshes.filter(m => m instanceof BABYLON.Mesh); @@ -176,7 +178,7 @@ export class SimpleEnvManager extends EnvManager { realizedMesh.parent = mesh.parent; mesh.dispose(); - scene.removeMesh(mesh); + this.engine.scene.removeMesh(mesh); this.meshes.push(realizedMesh); } } @@ -251,12 +253,23 @@ export class SimpleEnvManager extends EnvManager { if (mesh.material !== this.floorMaterial) { // 床は他の何にも影を落とさないことが確定している this.addShadowCaster(mesh); } + + const mat = mesh.material; + if (mat instanceof BABYLON.MultiMaterial) { + for (const subMat of mat.subMaterials) { + subMat.reflectionTexture = this.envMapIndoor; + } + } else if (mat instanceof BABYLON.PBRMaterial) { + mat.reflectionTexture = this.envMapIndoor; + } } await this.applyOptions(options); } public setTime(time: number) { + if (this.skyboxMat == null) return; + if (time === 0) { this.skyboxMat.emissiveColor = new BABYLON.Color3(0.7, 0.9, 1.0); } else if (time === 1) { @@ -279,11 +292,23 @@ export class SimpleEnvManager extends EnvManager { public turnOnRoomLight(): void { if (this.roomLight == null) return; this.roomLight.intensity = 18 * WORLD_SCALE * WORLD_SCALE; + if (this.envMapIndoor != null) this.envMapIndoor.level = 0.6; + for (const m of this.engine.scene.materials) { + if (m.metadata?.disableEnvMap) { + m.ambientColor = new BABYLON.Color3(0.5, 0.5, 0.5); + } + } } public turnOffRoomLight(): void { if (this.roomLight == null) return; this.roomLight.intensity = 0; + if (this.envMapIndoor != null) this.envMapIndoor.level = 0.025; + for (const m of this.engine.scene.materials) { + if (m.metadata?.disableEnvMap) { + m.ambientColor = new BABYLON.Color3(0.025, 0.025, 0.025); + } + } } public applyOptions(options: SimpleEnvOptions) { @@ -430,14 +455,6 @@ export class SimpleEnvManager extends EnvManager { } public dispose() { - if (this.loaderResult != null) { - for (const m of this.loaderResult.meshes) { - m.dispose(false, true); - } - for (const t of this.loaderResult.transformNodes) { - t.dispose(false, true); - } - } for (const m of this.meshes) { m.dispose(false, true); } @@ -455,6 +472,14 @@ export class SimpleEnvManager extends EnvManager { this.envMapIndoor?.dispose(); this.roomLight?.dispose(); this.sunLight?.dispose(); + if (this.loaderResult != null) { + for (const m of this.loaderResult.meshes) { + m.dispose(false, true); + } + for (const t of this.loaderResult.transformNodes) { + t.dispose(false, true); + } + } super.dispose(); } } @@ -464,18 +489,20 @@ export type MuseumEnvOptions = any; export class MuseumEnvManager extends EnvManager { private loaderResult: BABYLON.ISceneLoaderAsyncResult | null = null; private meshes: BABYLON.Mesh[] = []; + private roomLight: BABYLON.DirectionalLight | null = null; + private subRoomLights: BABYLON.SpotLight[] = []; public envMapIndoor: BABYLON.CubeTexture | null = null; - public maxCameraZ = cm(5000); + public maxCameraZ = cm(3000); - constructor(onMeshUpdatedCallback?: ((meshes: BABYLON.AbstractMesh[]) => void) | null) { - super(onMeshUpdatedCallback); + constructor(engine: RoomEngine, onMeshUpdatedCallback?: ((meshes: BABYLON.AbstractMesh[]) => void) | null) { + super(engine, onMeshUpdatedCallback); } - public async load(options: MuseumEnvOptions, scene: BABYLON.Scene) { - this.loaderResult = await BABYLON.ImportMeshAsync('/client-assets/room/envs/museum/museum.glb', scene); + public async load(options: MuseumEnvOptions) { + this.loaderResult = await BABYLON.ImportMeshAsync('/client-assets/room/envs/museum/museum.glb', this.engine.scene); - this.envMapIndoor = BABYLON.CubeTexture.CreateFromPrefilteredData('/client-assets/room/indoor.env', scene); - this.envMapIndoor.boundingBoxSize = new BABYLON.Vector3(cm(500), cm(500), cm(500)); + this.envMapIndoor = BABYLON.CubeTexture.CreateFromPrefilteredData('/client-assets/room/indoor.env', this.engine.scene); + this.envMapIndoor.boundingBoxSize = new BABYLON.Vector3(cm(2000), cm(500), cm(2000)); this.meshes = this.loaderResult.meshes.filter(m => m instanceof BABYLON.Mesh); this.meshes[0].scaling = this.meshes[0].scaling.scale(WORLD_SCALE); @@ -497,22 +524,108 @@ export class MuseumEnvManager extends EnvManager { realizedMesh.parent = mesh.parent; mesh.dispose(); - scene.removeMesh(mesh); + this.engine.scene.removeMesh(mesh); this.meshes.push(realizedMesh); } } + this.roomLight = new BABYLON.DirectionalLight('museumEnv:RoomLight', new BABYLON.Vector3(0, -1, 0), this.engine.scene); + this.roomLight.position = new BABYLON.Vector3(0, cm(300), 0); + this.roomLight.diffuse = new BABYLON.Color3(...this.engine.roomState.roomLightColor); + this.roomLight.shadowMinZ = cm(10); + this.roomLight.shadowMaxZ = cm(500); + this.roomLight.radius = cm(30); + + if (this.engine.graphicsQuality >= GRAPHICS_QUALITY_MEDIUM) { + const shadowGeneratorForRoomLight = new BABYLON.ShadowGenerator(this.engine.graphicsQuality <= GRAPHICS_QUALITY_MEDIUM ? 1024 : 2048, this.roomLight); + shadowGeneratorForRoomLight.forceBackFacesOnly = true; + shadowGeneratorForRoomLight.bias = 0.00001; + shadowGeneratorForRoomLight.normalBias = 0.005; + shadowGeneratorForRoomLight.usePercentageCloserFiltering = true; + shadowGeneratorForRoomLight.filteringQuality = BABYLON.ShadowGenerator.QUALITY_HIGH; + if (this.engine.graphicsQuality <= GRAPHICS_QUALITY_MEDIUM) { + shadowGeneratorForRoomLight.getShadowMap().refreshRate = 60; + } + //this.shadowGeneratorForRoomLight.useContactHardeningShadow = true; + this.shadowGenerators.push(shadowGeneratorForRoomLight); + } + + //for (const node of this.loaderResult.transformNodes.filter(node => node.name.includes('__LIGHT__'))) { + // const light = new BABYLON.SpotLight('museumEnv:SubRoomLight', node.position.scale(WORLD_SCALE), new BABYLON.Vector3(0, -1, 0), 16, 8, this.engine.scene, true); + // light.diffuse = new BABYLON.Color3(...this.engine.roomState.roomLightColor); + // light.range = cm(500); + // light.radius = cm(15); + // this.engine.lightContainer.addLight(light); + // this.subRoomLights.push(light); + //} + + for (const mesh of this.meshes) { + if (SYSTEM_HEYA_MESH_NAMES.some(name => mesh.name.includes(name))) continue; + mesh.receiveShadows = true; + //this.addShadowCaster(mesh); + + const mat = mesh.material; + if (mat instanceof BABYLON.MultiMaterial) { + for (const subMat of mat.subMaterials) { + subMat.reflectionTexture = this.envMapIndoor; + } + } else if (mat instanceof BABYLON.PBRMaterial) { + mat.reflectionTexture = this.envMapIndoor; + } + } + await this.applyOptions(options); } public setTime(time: number) { } + public updateRoomLightColor(color: BABYLON.Color3): void { + if (this.roomLight == null) return; + this.roomLight.diffuse = color; + for (const subLight of this.subRoomLights) { + subLight.diffuse = color; + } + } + + public turnOnRoomLight(): void { + if (this.roomLight == null) return; + this.roomLight.intensity = 0.00005 * WORLD_SCALE * WORLD_SCALE; + for (const subLight of this.subRoomLights) { + subLight.intensity = 10 * WORLD_SCALE * WORLD_SCALE; + } + if (this.envMapIndoor != null) this.envMapIndoor.level = 0.2; + for (const m of this.engine.scene.materials) { + if (m.metadata?.disableEnvMap) { + m.ambientColor = new BABYLON.Color3(0.5, 0.5, 0.5); + } + } + } + + public turnOffRoomLight(): void { + if (this.roomLight == null) return; + this.roomLight.intensity = 0; + for (const subLight of this.subRoomLights) { + subLight.intensity = 0; + } + if (this.envMapIndoor != null) this.envMapIndoor.level = 0.025; + for (const m of this.engine.scene.materials) { + if (m.metadata?.disableEnvMap) { + m.ambientColor = new BABYLON.Color3(0.025, 0.025, 0.025); + } + } + } + public applyOptions(options: MuseumEnvOptions) { this.onMeshUpdatedCallback?.(this.meshes); } public dispose() { + this.envMapIndoor?.dispose(); + this.roomLight?.dispose(); + for (const subLight of this.subRoomLights) { + subLight.dispose(); + } if (this.loaderResult != null) { for (const m of this.loaderResult.meshes) { m.dispose(false, true); @@ -524,7 +637,6 @@ export class MuseumEnvManager extends EnvManager { for (const m of this.meshes) { m.dispose(false, true); } - this.envMapIndoor?.dispose(); super.dispose(); } }