diff --git a/packages/frontend/assets/room/envs/japanese/ceiling.png b/packages/frontend/assets/room/envs/japanese/ceiling.png new file mode 100644 index 0000000000..83d10363f2 Binary files /dev/null and b/packages/frontend/assets/room/envs/japanese/ceiling.png differ diff --git a/packages/frontend/assets/room/envs/japanese/japanese.blend b/packages/frontend/assets/room/envs/japanese/japanese.blend new file mode 100644 index 0000000000..83a130296a Binary files /dev/null and b/packages/frontend/assets/room/envs/japanese/japanese.blend differ diff --git a/packages/frontend/assets/room/envs/japanese/japanese.glb b/packages/frontend/assets/room/envs/japanese/japanese.glb new file mode 100644 index 0000000000..1112043e56 Binary files /dev/null and b/packages/frontend/assets/room/envs/japanese/japanese.glb differ diff --git a/packages/frontend/assets/room/envs/japanese/tatami.png b/packages/frontend/assets/room/envs/japanese/tatami.png new file mode 100644 index 0000000000..2561378160 Binary files /dev/null and b/packages/frontend/assets/room/envs/japanese/tatami.png differ diff --git a/packages/frontend/assets/room/envs/museum/museum.blend b/packages/frontend/assets/room/envs/museum/museum.blend index 5ed0f38fc3..42885bdd7e 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 f1bf907311..8ca78cef80 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/assets/room/objects/duct-rail-spot-lights/duct-rail-spot-lights.blend b/packages/frontend/assets/room/objects/duct-rail-spot-lights/duct-rail-spot-lights.blend index c2753d8cb3..73dda218ee 100644 Binary files a/packages/frontend/assets/room/objects/duct-rail-spot-lights/duct-rail-spot-lights.blend and b/packages/frontend/assets/room/objects/duct-rail-spot-lights/duct-rail-spot-lights.blend differ diff --git a/packages/frontend/assets/room/objects/duct-rail-spot-lights/duct-rail-spot-lights.glb b/packages/frontend/assets/room/objects/duct-rail-spot-lights/duct-rail-spot-lights.glb index f3a3aa1609..909a1a4311 100644 Binary files a/packages/frontend/assets/room/objects/duct-rail-spot-lights/duct-rail-spot-lights.glb and b/packages/frontend/assets/room/objects/duct-rail-spot-lights/duct-rail-spot-lights.glb differ diff --git a/packages/frontend/assets/room/objects/hanging-duct-rail/hanging-duct-rail.blend b/packages/frontend/assets/room/objects/hanging-duct-rail/hanging-duct-rail.blend new file mode 100644 index 0000000000..73dda218ee Binary files /dev/null and b/packages/frontend/assets/room/objects/hanging-duct-rail/hanging-duct-rail.blend differ diff --git a/packages/frontend/src/pages/room.env-options.vue b/packages/frontend/src/pages/room.env-options.vue index 1b23c1af3d..10c6aff1d4 100644 --- a/packages/frontend/src/pages/room.env-options.vue +++ b/packages/frontend/src/pages/room.env-options.vue @@ -9,6 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/world/room/engine.ts b/packages/frontend/src/world/room/engine.ts index a4a6235d6f..49b84f507a 100644 --- a/packages/frontend/src/world/room/engine.ts +++ b/packages/frontend/src/world/room/engine.ts @@ -18,7 +18,7 @@ import { EventEmitter } from 'eventemitter3'; import { TIME_MAP, scaleMorph, camelToKebab, cm, WORLD_SCALE, getMeshesBoundingBox, Timer, getYRotationDirection, FreeCameraManualInput, remap } from '../utility.js'; import { getObjectDef } from './object-defs.js'; import { findMaterial, GRAPHICS_QUALITY, ModelManager, SYSTEM_HEYA_MESH_NAMES, SYSTEM_MESH_NAMES } from './utility.js'; -import { MuseumEnvManager, SimpleEnvManager } from './env.js'; +import { JapaneseEnvManager, MuseumEnvManager, SimpleEnvManager } from './env.js'; import type { GridMaterial } from '@babylonjs/materials'; import type { EnvManager, JapaneseEnvOptions, SimpleEnvOptions } from './env.js'; import type { ObjectDef, RoomObjectInstance, RoomStateObject } from './object.js'; @@ -313,7 +313,7 @@ export class RoomEngine extends EventEmitter { this.gizmoManager = new BABYLON.GizmoManager(this.scene); this.gizmoManager.positionGizmoEnabled = false; - this.gridPlane = BABYLON.MeshBuilder.CreatePlane('gridPlane', { width: cm(1000), height: cm(1000) }, this.scene); + this.gridPlane = BABYLON.MeshBuilder.CreatePlane('gridPlane', { width: cm(10000), height: cm(10000) }, this.scene); this.gridPlane.isPickable = false; this.gridPlane.isVisible = false; this.gridPlane.setEnabled(false); @@ -564,7 +564,7 @@ export class RoomEngine extends EventEmitter { if (this.roomState.env.type === 'simple') { envManager = new SimpleEnvManager(this, onMeshUpdatedCallback); } else if (this.roomState.env.type === 'japanese') { - // TODO + envManager = new JapaneseEnvManager(this, onMeshUpdatedCallback); } else if (this.roomState.env.type === 'museum') { envManager = new MuseumEnvManager(this, onMeshUpdatedCallback); } diff --git a/packages/frontend/src/world/room/env.ts b/packages/frontend/src/world/room/env.ts index 8baa24336f..dbb72a13f9 100644 --- a/packages/frontend/src/world/room/env.ts +++ b/packages/frontend/src/world/room/env.ts @@ -80,10 +80,6 @@ export type SimpleEnvOptions = { }; }; -export type JapaneseEnvOptions = { - window: 'none' | 'kosidakamado' | 'demado' | 'hakidasimado'; -}; - // TODO: マテリアルは必要になるまで作成しないようにする export class SimpleEnvManager extends EnvManager { @@ -440,16 +436,6 @@ export class SimpleEnvManager extends EnvManager { this.floorMaterial.freeze(); } - for (const mesh of this.meshes) { - if (mesh.material != null) { - if ((mesh.material as BABYLON.PBRMaterial).metadata?.disableEnvMap) { - (mesh.material as BABYLON.PBRMaterial).ambientColor = new BABYLON.Color3(0.5, 0.5, 0.5); - } else { - (mesh.material as BABYLON.PBRMaterial).reflectionTexture = this.envMapIndoor; - } - } - } - this.onMeshUpdatedCallback?.(this.meshes); } @@ -483,6 +469,187 @@ export class SimpleEnvManager extends EnvManager { } } +export type JapaneseEnvOptions = { + window: 'none' | 'kosidakamado' | 'demado' | 'hakidasimado'; +}; + +export class JapaneseEnvManager extends EnvManager { + private loaderResult: BABYLON.ISceneLoaderAsyncResult | null = null; + private meshes: BABYLON.Mesh[] = []; + private skybox: BABYLON.Mesh | null = null; + private skyboxMat: BABYLON.StandardMaterial | null = null; + private roomLight: BABYLON.SpotLight | null = null; + private sunLight: BABYLON.DirectionalLight | null = null; + public envMapIndoor: BABYLON.CubeTexture | null = null; + public maxCameraZ = cm(1000); + + constructor(engine: RoomEngine, onMeshUpdatedCallback?: ((meshes: BABYLON.AbstractMesh[]) => void) | null) { + super(engine, onMeshUpdatedCallback); + } + + public async load(options: JapaneseEnvOptions) { + 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('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 (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.0005; + shadowGeneratorForRoomLight.usePercentageCloserFiltering = true; + shadowGeneratorForRoomLight.filteringQuality = BABYLON.ShadowGenerator.QUALITY_HIGH; + if (this.engine.graphicsQuality <= GRAPHICS_QUALITY.MEDIUM) { + shadowGeneratorForRoomLight.getShadowMap().refreshRate = 60; + } + //shadowGeneratorForRoomLight.useContactHardeningShadow = true; + //shadowGeneratorForRoomLight.contactHardeningLightSizeUVRatio = 0.01; + this.shadowGenerators.push(shadowGeneratorForRoomLight); + } + + 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(this.engine.graphicsQuality <= GRAPHICS_QUALITY.MEDIUM ? 1024 : 2048, this.sunLight); + shadowGeneratorForSunLight.forceBackFacesOnly = true; + shadowGeneratorForSunLight.bias = 0.00001; + shadowGeneratorForSunLight.usePercentageCloserFiltering = true; + shadowGeneratorForSunLight.usePoissonSampling = true; + if (this.engine.graphicsQuality <= GRAPHICS_QUALITY.MEDIUM) { + shadowGeneratorForSunLight.getShadowMap().refreshRate = 60; + } + this.shadowGenerators.push(shadowGeneratorForSunLight); + } + + this.loaderResult = await BABYLON.ImportMeshAsync('/client-assets/room/envs/japanese/japanese.glb', this.engine.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); + this.meshes[0].scaling = this.meshes[0].scaling.scale(WORLD_SCALE); + this.meshes[0].rotationQuaternion = null; + this.meshes[0].rotation = new BABYLON.Vector3(0, 0, 0); + + // instanced mesh を通常の mesh に変換 (そうしないとマテリアルが共有される) + for (const mesh of this.loaderResult.meshes) { + if (mesh instanceof BABYLON.InstancedMesh) { + const realizedMesh = mesh.sourceMesh.clone(mesh.name, null, true); + + realizedMesh.position = mesh.position.clone(); + if (mesh.rotationQuaternion) { + realizedMesh.rotationQuaternion = mesh.rotationQuaternion.clone(); + } else { + realizedMesh.rotation = mesh.rotation.clone(); + } + realizedMesh.scaling = mesh.scaling.clone(); + realizedMesh.parent = mesh.parent; + + mesh.dispose(); + this.engine.scene.removeMesh(mesh); + this.meshes.push(realizedMesh); + } + } + + 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) { + if (this.skyboxMat == null) return; + + if (time === 0) { + this.skyboxMat.emissiveColor = new BABYLON.Color3(0.7, 0.9, 1.0); + } else if (time === 1) { + this.skyboxMat.emissiveColor = new BABYLON.Color3(0.8, 0.5, 0.3); + } else { + this.skyboxMat.emissiveColor = new BABYLON.Color3(0.05, 0.05, 0.2); + } + + if (this.sunLight != null) { + this.sunLight.diffuse = time === 0 ? new BABYLON.Color3(1.0, 0.9, 0.8) : time === 1 ? new BABYLON.Color3(1.0, 0.8, 0.6) : new BABYLON.Color3(0.6, 0.8, 1.0); + this.sunLight.intensity = time === 0 ? 3 : time === 1 ? 1 : 0.25; + } + } + + public updateRoomLightColor(color: BABYLON.Color3): void { + if (this.roomLight == null) return; + this.roomLight.diffuse = color; + } + + 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) { + this.onMeshUpdatedCallback?.(this.meshes); + } + + public dispose() { + for (const m of this.meshes) { + m.dispose(false, true); + } + this.skybox?.dispose(); + this.skyboxMat?.dispose(); + 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(); + } +} + export type MuseumEnvOptions = any; export class MuseumEnvManager extends EnvManager { @@ -550,10 +717,11 @@ export class MuseumEnvManager extends EnvManager { } 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); + const light = new BABYLON.SpotLight('museumEnv:SubRoomLight', node.position, 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); + light.parent = this.meshes[0]; this.engine.lightContainer.addLight(light); this.subRoomLights.push(light); } @@ -591,7 +759,7 @@ export class MuseumEnvManager extends EnvManager { 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; + subLight.intensity = 20 * WORLD_SCALE * WORLD_SCALE; } if (this.envMapIndoor != null) this.envMapIndoor.level = 0.2; for (const m of this.engine.scene.materials) {