diff --git a/packages/frontend/src/pages/room.add-object-dialog.vue b/packages/frontend/src/pages/room.add-object-dialog.vue index c6847e0fcb..121f943dc7 100644 --- a/packages/frontend/src/pages/room.add-object-dialog.vue +++ b/packages/frontend/src/pages/room.add-object-dialog.vue @@ -40,7 +40,7 @@ import { i18n } from '@/i18n.js'; import MkModalWindow from '@/components/MkModalWindow.vue'; import * as os from '@/os.js'; import { OBJECT_DEFS } from '@/world/room/object-defs.js'; -import { createRoomObjectPreviewEngine, RoomObjectPreviewEngine } from '@/world/room/engine.js'; +import { createRoomObjectPreviewEngine, RoomObjectPreviewEngine } from '@/world/room/previewEngine.js'; const emit = defineEmits<{ (ev: 'ok', id: string): void; diff --git a/packages/frontend/src/pages/world.vue b/packages/frontend/src/pages/world.vue new file mode 100644 index 0000000000..dbd40aff3c --- /dev/null +++ b/packages/frontend/src/pages/world.vue @@ -0,0 +1,421 @@ + + + + + + + diff --git a/packages/frontend/src/router.definition.ts b/packages/frontend/src/router.definition.ts index 4241f79dea..db1006dbcc 100644 --- a/packages/frontend/src/router.definition.ts +++ b/packages/frontend/src/router.definition.ts @@ -594,6 +594,9 @@ export const ROUTE_DEF = [{ path: '/qr', component: page(() => import('@/pages/qr.vue')), loginRequired: true, +}, { + path: '/world', + component: page(() => import('@/pages/world.vue')), }, { path: '/room', component: page(() => import('@/pages/room.vue')), diff --git a/packages/frontend/src/world/engine.ts b/packages/frontend/src/world/engine.ts new file mode 100644 index 0000000000..e98e2e56b7 --- /dev/null +++ b/packages/frontend/src/world/engine.ts @@ -0,0 +1,267 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +// TODO: 家具設置時のコリジョン判定(めりこんで設置されないようにする) +// TODO: 近くのオブジェクトの端にスナップオプション +// TODO: 近くのオブジェクトの原点に軸を揃えるオプション +// TODO: glbを事前に最適化(なるべくメッシュをマージするなど)するツールもしくはMisskeyビルド時処理。ついでにカタログ用スクショも自動生成したい + +import * as BABYLON from '@babylonjs/core'; +import { AxesViewer } from '@babylonjs/core/Debug/axesViewer'; +import { registerBuiltInLoaders } from '@babylonjs/loaders/dynamic'; +import { EventEmitter } from 'eventemitter3'; +import { HorizontalCameraKeyboardMoveInput, camelToKebab, cm } from './utility.js'; +import { TIME_MAP } from './utility.js'; +import { genId } from '@/utility/id.js'; +import { deepClone } from '@/utility/clone.js'; + +const SNAPSHOT_RENDERING = true; // 実験的 +const USE_GLOW = true; // ドローコールが増えて重い +const IN_WEB_WORKER = typeof window === 'undefined'; + +export type WorldEngineEvents = { + 'playSfxUrl': (ctx: { + url: string; + options: { + volume: number; + playbackRate: number; + }; + }) => void; + 'loadingProgress': (ctx: { progress: number }) => void; +}; + +export class WorldEngine extends EventEmitter { + private canvas: HTMLCanvasElement; + private engine: BABYLON.WebGPUEngine; + public scene: BABYLON.Scene; + private shadowGeneratorForSunLight: BABYLON.ShadowGenerator; + public camera: BABYLON.UniversalCamera; + public intervalIds: number[] = []; + public timeoutIds: number[] = []; + private time: 0 | 1 | 2 = 0; // 0: 昼, 1: 夕, 2: 夜 + private envMapOutdoor: BABYLON.CubeTexture; + public lightContainer: BABYLON.ClusteredLightContainer; + public sr: BABYLON.SnapshotRenderingHelper; + + public isSitting = false; + private fps: number | null = null; + private disposed = false; + + public domEvents: EventEmitter<{ + 'click': (event: { offsetX: number; offsetY: number; }) => void; + 'keydown': (event: { code: string; shiftKey: boolean; }) => void; + 'keyup': (event: { code: string; shiftKey: boolean; }) => void; + 'wheel': (event: { deltaY: number; }) => void; + }> = new EventEmitter(); + + constructor(options: { + canvas: HTMLCanvasElement; + engine: BABYLON.WebGPUEngine; + }) { + super(); + + this.canvas = options.canvas; + + registerBuiltInLoaders(); + + this.engine = options.engine; + this.scene = new BABYLON.Scene(this.engine); + // なんかレンダリングがおかしくなるときがあるのでコメントアウト + // オブジェクトを選択し、後ろを向いて別のオブジェクトを選択した後、最初のオブジェクトに振り返ると消えているなど + //this.scene.performancePriority = BABYLON.ScenePerformancePriority.Intermediate; + this.scene.autoClear = false; + //this.scene.autoClearDepthAndStencil = false; + this.scene.skipPointerMovePicking = true; + this.scene.skipFrustumClipping = true; // snapshot renderingでは全てのメッシュがアクティブになっている必要があるため + + this.sr = new BABYLON.SnapshotRenderingHelper(this.scene); + + const skybox = BABYLON.MeshBuilder.CreateBox('skybox', { size: cm(1000) }, this.scene); + const skyboxMat = new BABYLON.StandardMaterial('skyboxMat', this.scene); + skyboxMat.backFaceCulling = false; + skyboxMat.disableLighting = true; + skybox.material = skyboxMat; + skybox.infiniteDistance = true; + + this.time = TIME_MAP[new Date().getHours() as keyof typeof TIME_MAP]; + + if (this.time === 0) { + skyboxMat.emissiveColor = new BABYLON.Color3(0.7, 0.9, 1.0); + } else if (this.time === 1) { + skyboxMat.emissiveColor = new BABYLON.Color3(0.8, 0.5, 0.3); + } else { + skyboxMat.emissiveColor = new BABYLON.Color3(0.05, 0.05, 0.2); + } + this.scene.ambientColor = new BABYLON.Color3(1.0, 0.9, 0.8); + + this.envMapOutdoor = BABYLON.CubeTexture.CreateFromPrefilteredData(this.time === 2 ? '/client-assets/room/outdoor-night.env' : '/client-assets/room/outdoor-day.env', this.scene); + this.envMapOutdoor.level = this.time === 0 ? 0.5 : this.time === 1 ? 0.3 : 0.1; + + this.scene.collisionsEnabled = true; + + this.camera = new BABYLON.UniversalCamera('camera', new BABYLON.Vector3(0, cm(130), cm(0)), this.scene); + this.camera.inputs.removeByType('FreeCameraKeyboardMoveInput'); + this.camera.inputs.add(new HorizontalCameraKeyboardMoveInput(this.camera)); + this.camera.attachControl(this.canvas); + this.camera.minZ = cm(1); + this.camera.maxZ = cm(2000); + this.camera.fov = 1; + this.camera.ellipsoid = new BABYLON.Vector3(cm(15), cm(65), cm(15)); + this.camera.checkCollisions = true; + this.camera.applyGravity = true; + this.camera.needMoveForGravity = true; + + //this.scene.activeCamera = this.camera; + + const ambientLight = new BABYLON.HemisphericLight('ambientLight', new BABYLON.Vector3(0, 1, -0.5), this.scene); + ambientLight.diffuse = new BABYLON.Color3(1.0, 1.0, 1.0); + ambientLight.intensity = 0.5; + //ambientLight.intensity = 0; + + const sunLight = new BABYLON.DirectionalLight('sunLight', new BABYLON.Vector3(0.2, -1, -1), this.scene); + sunLight.position = new BABYLON.Vector3(cm(-20), cm(1000), cm(1000)); + sunLight.diffuse = this.time === 0 ? new BABYLON.Color3(1.0, 0.9, 0.8) : this.time === 1 ? new BABYLON.Color3(1.0, 0.8, 0.6) : new BABYLON.Color3(0.6, 0.8, 1.0); + sunLight.intensity = this.time === 0 ? 3 : this.time === 1 ? 1 : 0.25; + sunLight.shadowMinZ = cm(1000); + sunLight.shadowMaxZ = cm(2000); + + this.shadowGeneratorForSunLight = new BABYLON.ShadowGenerator(2048, sunLight); + this.shadowGeneratorForSunLight.forceBackFacesOnly = true; + this.shadowGeneratorForSunLight.bias = 0.0001; + this.shadowGeneratorForSunLight.usePercentageCloserFiltering = true; + this.shadowGeneratorForSunLight.usePoissonSampling = true; + if (!SNAPSHOT_RENDERING) this.shadowGeneratorForSunLight.getShadowMap().refreshRate = 60; // snapshot renderingではrefreshRateが設定されているとなぜかクラッシュする + + this.lightContainer = new BABYLON.ClusteredLightContainer('clustered', [], this.scene); + + if (USE_GLOW) { + const gl = new BABYLON.GlowLayer('glow', this.scene, { + //mainTextureFixedSize: 512, + blurKernelSize: 64, + }); + gl.intensity = 0.5; + this.scene.setRenderingAutoClearDepthStencil(gl.renderingGroupId, false); + + if (SNAPSHOT_RENDERING) { + this.sr.updateMeshesForEffectLayer(gl); + } + } + + if (_DEV_) { + // snapshot renderingかつglow layerが有効だとなんかクラッシュする + if (!(SNAPSHOT_RENDERING && USE_GLOW)) { + const axes = new AxesViewer(this.scene, 30); + axes.xAxis.position = new BABYLON.Vector3(0, 30, 0); + axes.yAxis.position = new BABYLON.Vector3(0, 30, 0); + axes.zAxis.position = new BABYLON.Vector3(0, 30, 0); + } + + if (!IN_WEB_WORKER) { + (window as any).showBabylonInspector = () => { + import('@babylonjs/inspector').then(({ ShowInspector }) => { + ShowInspector(this.scene); + }); + }; + } + } + } + + public async init() { + this.scene.blockMaterialDirtyMechanism = true; + + if (SNAPSHOT_RENDERING) { + this.sr.enableSnapshotRendering(); + } + + if (this.fps == null) { + this.engine.runRenderLoop(() => { + this.scene.render(); + }); + } else { + let then = 0; + const interval = 1000 / this.fps; + + const renderLoop = (timeStamp: number) => { + if (this.disposed) return; + + window.requestAnimationFrame(renderLoop); + + const delta = timeStamp - then; + if (delta <= interval) return; + then = timeStamp - (delta % interval); + + this.engine.beginFrame(); + this.scene.render(); + this.engine.endFrame(); + }; + + window.requestAnimationFrame(renderLoop); + } + + this.domEvents.on('keydown', (ev) => { + }); + + this.domEvents.on('wheel', (ev) => { + this.camera.fov += ev.deltaY * 0.001; + this.camera.fov = Math.max(0.25, Math.min(1, this.camera.fov)); + }); + + this.domEvents.on('click', (ev) => { + + }); + } + + private async loadEnvModel() { + const envObj = await BABYLON.ImportMeshAsync('/client-assets/room/env.glb', this.scene); + envObj.meshes[0].scaling = envObj.meshes[0].scaling.scale(WORLD_SCALE); + envObj.meshes[0].bakeCurrentTransformIntoVertices(); + envObj.meshes[0].position = new BABYLON.Vector3(0, cm(-900), 0); // 4階くらいの想定 + envObj.meshes[0].rotation = new BABYLON.Vector3(0, -Math.PI, 0); + for (const mesh of envObj.meshes) { + mesh.isPickable = false; + mesh.checkCollisions = false; + + //if (mesh.name === '__root__') continue; + mesh.receiveShadows = false; + if (mesh.material) (mesh.material as BABYLON.PBRMaterial).reflectionTexture = this.envMapOutdoor; + } + } + + public sitChair(objectId: string) { + this.isSitting = true; + this.fixedCamera.parent = this.objectMeshs.get(objectId); + this.fixedCamera.position = new BABYLON.Vector3(0, cm(120), 0); + this.fixedCamera.rotation = new BABYLON.Vector3(0, 0, 0); + this.scene.activeCamera = this.fixedCamera; + this.selectObject(null); + } + + public standUp() { + this.isSitting = false; + this.scene.activeCamera = this.camera; + this.fixedCamera.parent = null; + } + + private playSfxUrl(url: string, options: { volume: number; playbackRate: number }) { + this.emit('playSfxUrl', { url, options }); + } + + public resize() { + this.engine.resize(); + } + + public destroy() { + for (const id of this.intervalIds) { + window.clearInterval(id); + } + for (const id of this.timeoutIds) { + window.clearTimeout(id); + } + this.intervalIds = []; + this.timeoutIds = []; + this.engine.dispose(); + this.disposed = true; + } +} diff --git a/packages/frontend/src/world/room/controller.ts b/packages/frontend/src/world/room/controller.ts index c052d9abbd..e624668252 100644 --- a/packages/frontend/src/world/room/controller.ts +++ b/packages/frontend/src/world/room/controller.ts @@ -199,6 +199,7 @@ export class RoomController { } public addObject(type: string) { + console.log(type); if (this.worker != null) { this.worker.postMessage({ type: 'addObject', objectType: type }); } else if (this.engine != null) { diff --git a/packages/frontend/src/world/room/engine.ts b/packages/frontend/src/world/room/engine.ts index 6ca0d62c32..7986a7f6f0 100644 --- a/packages/frontend/src/world/room/engine.ts +++ b/packages/frontend/src/world/room/engine.ts @@ -14,59 +14,19 @@ import { registerBuiltInLoaders } from '@babylonjs/loaders/dynamic'; import { BoundingBoxRenderer } from '@babylonjs/core/Rendering/boundingBoxRenderer'; import { GridMaterial } from '@babylonjs/materials'; import { EventEmitter } from 'eventemitter3'; +import { TIME_MAP, scaleMorph, HorizontalCameraKeyboardMoveInput, camelToKebab, cm, WORLD_SCALE, getMeshesBoundingBox } from '../utility.js'; import { getObjectDef } from './object-defs.js'; -import { HorizontalCameraKeyboardMoveInput, applyMorphTargetsToMesh, camelToKebab, cm, findMaterial, scaleMorph } from './utility.js'; +import { findMaterial, ModelManager, SYSTEM_MESH_NAMES } from './utility.js'; +import type { ObjectDef, RoomObjectInstance, RoomStateObject } from './object.js'; import { genId } from '@/utility/id.js'; import { deepClone } from '@/utility/clone.js'; const BAKE_TRANSFORM = false; // 実験的 const SNAPSHOT_RENDERING = true; // 実験的 const IGNORE_OBJECTS: string[] = []; // for debug -const SYSTEM_MESH_NAMES = ['__TOP__', '__SIDE__', '__PICK__', '__COLLISION__']; const USE_GLOW = true; // ドローコールが増えて重い const IN_WEB_WORKER = typeof window === 'undefined'; -const TIME_MAP = { - 0: 2, - 1: 2, - 2: 2, - 3: 2, - 4: 2, - 5: 1, - 6: 1, - 7: 0, - 8: 0, - 9: 0, - 10: 0, - 11: 0, - 12: 0, - 13: 0, - 14: 0, - 15: 0, - 16: 1, - 17: 1, - 18: 2, - 19: 2, - 20: 2, - 21: 2, - 22: 2, - 23: 2, -} as const; - -// babylonのドメイン知識は持たない -export type RoomStateObject = { - id: string; - type: string; - position: [number, number, number]; - rotation: [number, number, number]; - options: Options; - - /** - * 別のオブジェクトのID - */ - sticky?: string | null; -}; - type SimpleHeyaWallBase = { material: null | 'wood' | 'concrete'; color: [number, number, number]; @@ -99,221 +59,6 @@ export type RoomState = { installedObjects: RoomStateObject[]; }; -type RoomObjectInstance = { - onInited?: () => void; - onOptionsUpdated?: (kv: [K, V]) => void; - interactions: Record void; - }>; - primaryInteraction?: string | null; - resetTemporaryState?: () => void; - dispose?: () => void; -}; - -export const WORLD_SCALE = 100; - -type NumberOptionSchema = { - type: 'number'; - label: string; - min?: number; - max?: number; - step?: number; -}; - -type BooleanOptionSchema = { - type: 'boolean'; - label: string; -}; - -type ColorOptionSchema = { - type: 'color'; - label: string; -}; - -type EnumOptionSchema = { - type: 'enum'; - label: string; - enum: string[]; -}; - -type RangeOptionSchema = { - type: 'range'; - label: string; - min: number; - max: number; - step?: number; -}; - -type ImageOptionSchema = { - type: 'image'; - label: string; -}; - -type OptionsSchema = Record; - -type GetOptionsSchemaValues = { - [K in keyof T]: - T[K] extends NumberOptionSchema ? number : - T[K] extends BooleanOptionSchema ? boolean : - T[K] extends ColorOptionSchema ? [number, number, number] : - T[K] extends EnumOptionSchema ? T[K]['enum'][number] : - T[K] extends RangeOptionSchema ? number : - T[K] extends ImageOptionSchema ? string | null : - never; -}; - -class ModelManager { - public root: BABYLON.Mesh; - public bakedCallback: (() => void) | null = null; - public bakeExcludeMeshes: BABYLON.Mesh[] = []; - private originalMeshes: BABYLON.Mesh[] = []; - private bakedMeshes: BABYLON.Mesh[] = []; - private hasTexture: boolean; - - constructor(root: BABYLON.Mesh, originalMeshes: BABYLON.Mesh[], hasTexture: boolean, bakedCallback: (() => void) | null = null) { - this.root = root; - this.originalMeshes = originalMeshes; - this.hasTexture = hasTexture; - this.bakedCallback = bakedCallback; - } - - public findMesh(keyword: string) { - const mesh = this.root.getChildMeshes().find(m => m.name.includes(keyword)); - if (mesh == null) { - throw new Error(`Mesh with keyword "${keyword}" not found for object ${this.root.metadata?.objectType}`); - } - return mesh as BABYLON.Mesh; - } - - public findMeshes(keyword: string) { - const meshes = this.root.getChildMeshes().filter(m => m.name.includes(keyword)); - return meshes as BABYLON.Mesh[]; - } - - public findMaterial(keyword: string) { - return findMaterial(this.root, keyword); - } - - public findTransformNode(keyword: string) { - const node = this.root.getChildTransformNodes().find(n => n.name.includes(keyword)); - if (node == null) { - throw new Error(`TransformNode with keyword "${keyword}" not found for object ${this.root.metadata?.objectType}`); - } - return node; - } - - public updated() { - } - - public bakeMesh() { - for (const m of this.bakedMeshes) { - m.dispose(); - } - this.bakedMeshes = []; - - const excludeMeshes = [...this.bakeExcludeMeshes, ...this.root.getChildMeshes().filter(m => SYSTEM_MESH_NAMES.some(s => m.name.includes(s)))]; - - const childMeshes = this.root.getChildMeshes().filter(m => !excludeMeshes.some(x => x === m) && m.isVisible && !m.isDisposed()); - - if (childMeshes.length <= 1) { - this.bakedCallback?.([...childMeshes, ...excludeMeshes]); - return; - } - - const _toMerge = [] as BABYLON.Mesh[]; - for (const mesh of childMeshes) { - let fixedMesh = mesh; - fixedMesh.setEnabled(false); - - if (mesh instanceof BABYLON.InstancedMesh) { - const sourceMesh = mesh.sourceMesh; - const realizedMesh = sourceMesh.clone(mesh.name + '_realized', null, true); - realizedMesh.getScene().removeMesh(realizedMesh); - - 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; - realizedMesh.setEnabled(false); - - fixedMesh = realizedMesh; - } - - _toMerge.push(fixedMesh); - } - - const toMerge = [] as BABYLON.Mesh[]; - for (const mesh of _toMerge) { - const newMesh = mesh.name.endsWith('_realized') ? mesh : mesh.clone(mesh.name + '_bakeMerged', null, true); - newMesh.makeGeometryUnique(); - applyMorphTargetsToMesh(newMesh); - if (newMesh.parent === this.root) { - newMesh.parent = null; - } else { - newMesh.setParent(this.root); - //newMesh.bakeCurrentTransformIntoVertices(); - newMesh.parent = null; - } - //newMesh.bakeCurrentTransformIntoVertices(); - - if (this.hasTexture) { - if (newMesh.getVerticesData(BABYLON.VertexBuffer.UVKind) == null) { - const vertexCount = newMesh.getTotalVertices(); - const uvs = new Array(vertexCount * 2).fill(0); - newMesh.setVerticesData(BABYLON.VertexBuffer.UVKind, uvs, false, 2); - } - if (newMesh.getVerticesData(BABYLON.VertexBuffer.UV2Kind) == null) { - const vertexCount = newMesh.getTotalVertices(); - const uvs = new Array(vertexCount * 2).fill(0); - newMesh.setVerticesData(BABYLON.VertexBuffer.UV2Kind, uvs, false, 2); - } - } - - toMerge.push(newMesh); - } - - if (toMerge.length === 0) { - this.bakedCallback?.([...childMeshes, ...excludeMeshes]); - return; - } - - const merged = BABYLON.Mesh.MergeMeshes(toMerge, true, false, undefined, false, true); - merged.parent = this.root; - merged.material.freeze(); - if (merged.material instanceof BABYLON.MultiMaterial) { - for (const subMat of merged.material.subMaterials) { - (subMat as BABYLON.PBRMaterial).freeze(); - } - } - merged.freezeWorldMatrix(); - merged.metadata = { ...this.root.metadata }; - if (!this.hasTexture) merged.convertToUnIndexedMesh(); - this.bakedMeshes = [merged]; - - this.bakedCallback?.([...this.bakedMeshes, ...excludeMeshes]); - } - - public unbakeMesh() { - for (const m of this.bakedMeshes) { - m.dispose(); - } - this.bakedMeshes = []; - - const childMeshes = this.root.getChildMeshes(); - - for (const mesh of childMeshes) { - mesh.setEnabled(true); - } - - this.bakedCallback?.(this.root.getChildMeshes()); - } -} - function mergeMeshes(meshes: BABYLON.Mesh[], root: BABYLON.Mesh, hasTexture: boolean) { const excludeMeshes = root.getChildMeshes().filter(m => SYSTEM_MESH_NAMES.some(s => m.name.includes(s))); @@ -352,58 +97,6 @@ function mergeMeshes(meshes: BABYLON.Mesh[], root: BABYLON.Mesh, hasTexture: boo return merged; } -export type ObjectDef = { - id: string; - name: string; - path?: string; - options: { - schema: OpSc; - default: GetOptionsSchemaValues; - }; - placement: 'top' | 'side' | 'bottom' | 'wall' | 'ceiling' | 'floor'; - hasCollisions?: boolean; - hasTexture?: boolean; - canPreMeshesMerging?: boolean; - //groupingMeshes: string[]; // multi-materialなメッシュは複数のメッシュに分割されるが、それだと不便な場合に追加の親メッシュでグルーピングするための指定 - isChair?: boolean; - treatLoaderResult?: (loaderResult: BABYLON.AssetContainer) => void; - createInstance: (args: { - room?: RoomEngine | null; - scene: BABYLON.Scene; - root: BABYLON.Mesh; - options: Readonly>; - model: ModelManager; - id: string; - stickyMarkerMeshUpdated?: (mesh: BABYLON.Mesh) => void; - }) => RoomObjectInstance> | Promise>>; // TODO: createInstanceをasyncにするのではなく、別にreadyみたいなものを返させる -}; - -export function defineObject(def: ObjectDef): ObjectDef { - return def; -} - -export function defineObjectClass(baseDef: Partial>): { - extend: (childDef: Partial>) => ObjectDef; -} { - return { - extend: (childDef) => ({ ...baseDef, ...childDef }) as ObjectDef, - }; -} - -// この実装方法だとマイナスの座標をうまく処理できず結果がおかしくなるので応急処置で全体を+10000cmオフセットしてから計算している -function getMeshesBoundingBox(meshes: BABYLON.Mesh[]): BABYLON.BoundingBox { - let min = new BABYLON.Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE); - let max = new BABYLON.Vector3(Number.MIN_VALUE, Number.MIN_VALUE, Number.MIN_VALUE); - - for (const mesh of meshes) { - const boundingInfo = mesh.getBoundingInfo(); - min = BABYLON.Vector3.Minimize(min, boundingInfo.boundingBox.minimumWorld.add(new BABYLON.Vector3(10000, 10000, 10000))); - max = BABYLON.Vector3.Maximize(max, boundingInfo.boundingBox.maximumWorld.add(new BABYLON.Vector3(10000, 10000, 10000))); - } - - return new BABYLON.BoundingBox(min.subtract(new BABYLON.Vector3(10000, 10000, 10000)), max.subtract(new BABYLON.Vector3(10000, 10000, 10000))); -} - function enableObjectCollision(meshes: BABYLON.Mesh[]) { for (const mesh of meshes) { if (mesh.name.includes('__COLLISION__')) { @@ -444,8 +137,6 @@ export class RoomEngine extends EventEmitter { private shadowGeneratorForRoomLight: BABYLON.ShadowGenerator; private shadowGeneratorForSunLight: BABYLON.ShadowGenerator; public camera: BABYLON.UniversalCamera; - private fixedCamera: BABYLON.UniversalCamera; - private birdeyeCamera: BABYLON.ArcRotateCamera; public intervalIds: number[] = []; public timeoutIds: number[] = []; public objectEntities: Map { } } - { - //const postProcess = new BABYLON.ImageProcessingPostProcess('processing', 1.0, this.camera); - //postProcess.exposure = 1.1; - //postProcess.contrast = 0.9; - - //const curve = new BABYLON.ColorCurves(); - //curve.highlightsHue = 40; - //curve.highlightsDensity = 50; - //curve.highlightsSaturation = 40; - //curve.shadowsHue = 200; - //curve.shadowsDensity = 100; - //curve.shadowsSaturation = 40; - //postProcess.colorCurvesEnabled = true; - //postProcess.colorCurves = curve; - - //const postProcess2 = new BABYLON.ImageProcessingPostProcess('processing2', 1.0, this.birdeyeCamera); - //postProcess2.exposure = 2; - //postProcess2.contrast = 0.9; - - //const ssao = new BABYLON.SSAORenderingPipeline('ssao', this.scene, { - // ssaoRatio: 4, - // combineRatio: 1, - //}); - //ssao.radius = 0.0001; - //ssao.totalStrength = 0.8; - //this.scene.postProcessRenderPipelineManager.attachCamerasToRenderPipeline('ssao', this.camera); - - //const lensEffect = new BABYLON.LensRenderingPipeline('lens', { - // edge_blur: 1.0, - // distortion: 0.5, - // dof_focus_distance: cm(90), - // dof_aperture: 6.0, - // dof_pentagon: true, - // dof_gain: 2.0, - // dof_threshold: 1.0, - // dof_darken: 0, - //}, this.scene, 1, [this.camera]); - } - this.putParticleSystem = new BABYLON.ParticleSystem('', 64, this.scene); this.putParticleSystem.particleTexture = new BABYLON.Texture('/client-assets/room/steam.png'); this.putParticleSystem.createCylinderEmitter(cm(5), cm(1), cm(5)); @@ -1952,14 +1604,6 @@ export class RoomEngine extends EventEmitter { } } - public showBoundingBox() { - for (const mesh of this.objectMeshs.values()) { - for (const m of mesh.getChildMeshes()) { - m.showBoundingBox = true; - } - } - } - private playSfxUrl(url: string, options: { volume: number; playbackRate: number }) { this.emit('playSfxUrl', { url, options }); } @@ -1981,239 +1625,3 @@ export class RoomEngine extends EventEmitter { this.disposed = true; } } - -export async function createRoomObjectPreviewEngine(canvas: HTMLCanvasElement) { - //const babylonEngine = new BABYLON.WebGPUEngine(canvas); - //babylonEngine.compatibilityMode = false; - //await babylonEngine.initAsync(); - const babylonEngine = new BABYLON.Engine(canvas, false, { alpha: false, antialias: false }); - return new RoomObjectPreviewEngine({ canvas, engine: babylonEngine }); -} - -export class RoomObjectPreviewEngine { - private canvas: HTMLCanvasElement; - private engine: BABYLON.WebGPUEngine; - private scene: BABYLON.Scene; - private shadowGenerator1: BABYLON.ShadowGenerator; - private camera: BABYLON.ArcRotateCamera; - private objectMesh: BABYLON.Mesh | null = null; - private objectInstance: RoomObjectInstance | null = null; - private envMapIndoor: BABYLON.CubeTexture; - private roomLight: BABYLON.SpotLight; - private zGridPreviewPlane: BABYLON.Mesh; - private fps = 60; - - constructor(options: { - canvas: HTMLCanvasElement; - engine: BABYLON.WebGPUEngine; - }) { - this.canvas = options.canvas; - - registerBuiltInLoaders(); - - this.engine = options.engine; - this.scene = new BABYLON.Scene(this.engine); - - this.scene.ambientColor = new BABYLON.Color3(1.0, 0.9, 0.8); - - this.envMapIndoor = BABYLON.CubeTexture.CreateFromPrefilteredData('/client-assets/room/indoor.env', this.scene); - this.envMapIndoor.boundingBoxSize = new BABYLON.Vector3(cm(500), cm(500), cm(500)); - - this.camera = new BABYLON.ArcRotateCamera('camera', -Math.PI / 2, Math.PI / 2.5, cm(300), new BABYLON.Vector3(0, cm(90), 0), this.scene); - this.camera.attachControl(this.canvas); - this.camera.minZ = cm(1); - this.camera.maxZ = cm(100000); - this.camera.fov = 0.5; - this.camera.lowerBetaLimit = 0; - this.camera.upperBetaLimit = (Math.PI / 2) + 0.1; - this.camera.lowerRadiusLimit = cm(50); - this.camera.upperRadiusLimit = cm(1000); - this.camera.useAutoRotationBehavior = true; - this.camera.autoRotationBehavior!.idleRotationSpeed = 0.3; - //this.camera.mode = BABYLON.Camera.ORTHOGRAPHIC_CAMERA; - this.scene.activeCamera = this.camera; - - const ambientLight = new BABYLON.HemisphericLight('ambientLight', new BABYLON.Vector3(0, 1, -0.5), this.scene); - ambientLight.diffuse = new BABYLON.Color3(1.0, 1.0, 1.0); - ambientLight.intensity = 0.5; - //ambientLight.intensity = 0; - - this.roomLight = new BABYLON.SpotLight('roomLight', new BABYLON.Vector3(0, cm(249), 0), new BABYLON.Vector3(0, -1, 0), 16, 8, this.scene); - this.roomLight.diffuse = new BABYLON.Color3(1.0, 0.9, 0.8); - this.roomLight.shadowMinZ = cm(10); - this.roomLight.shadowMaxZ = cm(300); - - this.shadowGenerator1 = new BABYLON.ShadowGenerator(4096, this.roomLight); - this.shadowGenerator1.forceBackFacesOnly = true; - this.shadowGenerator1.bias = 0.0001; - this.shadowGenerator1.usePercentageCloserFiltering = true; - this.shadowGenerator1.filteringQuality = BABYLON.ShadowGenerator.QUALITY_HIGH; - //this.shadowGenerator1.useContactHardeningShadow = true; - - const gridMaterial = new GridMaterial('grid', this.scene); - gridMaterial.lineColor = new BABYLON.Color3(0.5, 0.5, 0.5); - gridMaterial.mainColor = new BABYLON.Color3(0, 0, 0); - gridMaterial.minorUnitVisibility = 1; - gridMaterial.opacity = 0.5; - gridMaterial.gridRatio = cm(10); - - //this.zGridPreviewPlane = BABYLON.MeshBuilder.CreatePlane('zGridPreviewPlane', { width: cm(1000), height: cm(1000) }, this.scene); - //this.zGridPreviewPlane.material = gridMaterial; - //this.zGridPreviewPlane.rotation = new BABYLON.Vector3(Math.PI / 2, 0, 0); - - //this.scene.fogMode = BABYLON.Scene.FOGMODE_LINEAR; - //this.scene.fogStart = cm(100); - //this.scene.fogEnd = cm(110); - //this.scene.fogColor = new BABYLON.Color3(0.0, 0.0, 0.0); - } - - public async init() { - const frameInterval = 1000 / this.fps; - let lastTime = performance.now(); - - this.engine.runRenderLoop(() => { - const currentTime = performance.now(); - const delta = currentTime - lastTime; - - if (delta >= frameInterval) { - this.scene.render(); - lastTime = currentTime - (delta % frameInterval); - } - }); - } - - public async load(type: string) { - if (this.objectInstance != null) { - this.objectInstance.dispose?.(); - this.objectInstance = null; - this.objectMesh!.dispose(); - } - - // reset camera rotation - this.camera.setPosition(new BABYLON.Vector3(0, cm(90), cm(300))); - - const def = getObjectDef(type); - - const options = deepClone(def.options.default); - - const id = genId(); - - await this.loadObject({ - type, - options, - id, - }); - - // なぜかちょっと待たないとbounding boxのサイズが正しくない - window.setTimeout(() => { - const boundingInfo = getMeshesBoundingBox(this.objectMesh!.getChildMeshes().filter(m => m.isEnabled() && m.isVisible)); - this.camera.setTarget(new BABYLON.Vector3(0, boundingInfo.center.y, 0)); - - // zoom to fit - const size = boundingInfo.extendSize; - const distance = Math.max(size.x, size.y, size.z) * 2; - this.camera.radius = distance * 3; - //this.camera.orthoLeft = -distance; - //this.camera.orthoRight = distance; - //this.camera.orthoTop = distance; - //this.camera.orthoBottom = -distance; - }, 10); - } - - // TODO: RoomEngineのものとほぼ同じだからいい感じに共通化 - private async loadObject(args: { - type: string; - options: any; - id: string; - }) { - const def = getObjectDef(args.type); - - const root = new BABYLON.Mesh(`object_${args.type}`, this.scene); - - const filePath = def.path != null ? `/client-assets/room/objects/${def.path}.glb` : `/client-assets/room/objects/${camelToKebab(args.type)}/${camelToKebab(args.type)}.glb`; - const loaderResult = await BABYLON.LoadAssetContainerAsync(filePath, this.scene); - - // 不要なUVを掃除 - if (!def.hasTexture) { - for (const m of loaderResult.meshes) { - if (m.geometry != null) { - m.geometry.removeVerticesData(BABYLON.VertexBuffer.UVKind); - m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV2Kind); - m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV3Kind); - m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV4Kind); - m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV5Kind); - m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV6Kind); - } - } - } - - // babylonによって自動で追加される右手系変換用ノード - const subRoot = loaderResult.meshes[0]; - subRoot.scaling = subRoot.scaling.scale(WORLD_SCALE);// cmをmに - - def.treatLoaderResult?.(loaderResult); - - root.addChild(subRoot); - - const model = new ModelManager(subRoot, loaderResult.meshes.filter(m => !m.isDisposed() && m !== subRoot), def.hasTexture, (meshes) => { - for (const m of meshes) { - const mesh = m; - - // シェイプキー(morph)を考慮してbounding boxを更新するために必要 - mesh.refreshBoundingInfo({ applyMorph: true }); - - if (SYSTEM_MESH_NAMES.some(n => mesh.name.includes(n))) { - mesh.receiveShadows = false; - mesh.isVisible = false; - } else { - if (def.receiveShadows !== false) mesh.receiveShadows = true; - if (def.castShadows !== false) { - this.shadowGenerator1.addShadowCaster(mesh); - } - - if (mesh.material) { - if (mesh.material instanceof BABYLON.MultiMaterial) { - for (const subMat of mesh.material.subMaterials) { - (subMat as BABYLON.PBRMaterial).reflectionTexture = this.envMapIndoor; - (subMat as BABYLON.PBRMaterial).useGLTFLightFalloff = true; // Clustered Lightingではphysical falloffを持つマテリアルはアーチファクトが発生する https://doc.babylonjs.com/features/featuresDeepDive/lights/clusteredLighting/#materials-with-a-physical-falloff-may-cause-artefacts - } - } else { - (mesh.material as BABYLON.PBRMaterial).reflectionTexture = this.envMapIndoor; - (mesh.material as BABYLON.PBRMaterial).useGLTFLightFalloff = true; // Clustered Lightingではphysical falloffを持つマテリアルはアーチファクトが発生する https://doc.babylonjs.com/features/featuresDeepDive/lights/clusteredLighting/#materials-with-a-physical-falloff-may-cause-artefacts - } - } - } - - if (!this.scene.meshes.includes(mesh)) this.scene.addMesh(mesh); - } - }); - - const objectInstance = await def.createInstance({ - room: null, - scene: this.scene, - root, - options: args.options, - model, - id: args.id, - }); - - objectInstance.onInited?.(); - - model.bakeMesh(); - - this.objectInstance = objectInstance; - this.objectMesh = root; - } - - public updateObjectOption(key: string, value: any) { - this.objectInstance?.onOptionsUpdated?.([key, value]); - } - - public resize() { - this.engine.resize(); - } - - public destroy() { - this.engine.dispose(); - } -} diff --git a/packages/frontend/src/world/room/object.ts b/packages/frontend/src/world/room/object.ts new file mode 100644 index 0000000000..9f2717c7d5 --- /dev/null +++ b/packages/frontend/src/world/room/object.ts @@ -0,0 +1,122 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as BABYLON from '@babylonjs/core'; +import type { RoomEngine } from './engine.js'; +import type { ModelManager } from './utility.js'; + +// babylonのドメイン知識は持たない +export type RoomStateObject = { + id: string; + type: string; + position: [number, number, number]; + rotation: [number, number, number]; + options: Options; + + /** + * 別のオブジェクトのID + */ + sticky?: string | null; +}; + +export type RoomObjectInstance = { + onInited?: () => void; + onOptionsUpdated?: (kv: [K, V]) => void; + interactions: Record void; + }>; + primaryInteraction?: string | null; + resetTemporaryState?: () => void; + dispose?: () => void; +}; + +type NumberOptionSchema = { + type: 'number'; + label: string; + min?: number; + max?: number; + step?: number; +}; + +type BooleanOptionSchema = { + type: 'boolean'; + label: string; +}; + +type ColorOptionSchema = { + type: 'color'; + label: string; +}; + +type EnumOptionSchema = { + type: 'enum'; + label: string; + enum: string[]; +}; + +type RangeOptionSchema = { + type: 'range'; + label: string; + min: number; + max: number; + step?: number; +}; + +type ImageOptionSchema = { + type: 'image'; + label: string; +}; + +type OptionsSchema = Record; + +type GetOptionsSchemaValues = { + [K in keyof T]: + T[K] extends NumberOptionSchema ? number : + T[K] extends BooleanOptionSchema ? boolean : + T[K] extends ColorOptionSchema ? [number, number, number] : + T[K] extends EnumOptionSchema ? T[K]['enum'][number] : + T[K] extends RangeOptionSchema ? number : + T[K] extends ImageOptionSchema ? string | null : + never; +}; + +export type ObjectDef = { + id: string; + name: string; + path?: string; + options: { + schema: OpSc; + default: GetOptionsSchemaValues; + }; + placement: 'top' | 'side' | 'bottom' | 'wall' | 'ceiling' | 'floor'; + hasCollisions?: boolean; + hasTexture?: boolean; + canPreMeshesMerging?: boolean; + //groupingMeshes: string[]; // multi-materialなメッシュは複数のメッシュに分割されるが、それだと不便な場合に追加の親メッシュでグルーピングするための指定 + isChair?: boolean; + treatLoaderResult?: (loaderResult: BABYLON.AssetContainer) => void; + createInstance: (args: { + room?: RoomEngine | null; + scene: BABYLON.Scene; + root: BABYLON.Mesh; + options: Readonly>; + model: ModelManager; + id: string; + stickyMarkerMeshUpdated?: (mesh: BABYLON.Mesh) => void; + }) => RoomObjectInstance> | Promise>>; // TODO: createInstanceをasyncにするのではなく、別にreadyみたいなものを返させる +}; + +export function defineObject(def: ObjectDef): ObjectDef { + return def; +} + +export function defineObjectClass(baseDef: Partial>): { + extend: (childDef: Partial>) => ObjectDef; +} { + return { + extend: (childDef) => ({ ...baseDef, ...childDef }) as ObjectDef, + }; +} diff --git a/packages/frontend/src/world/room/objects/a4Case.ts b/packages/frontend/src/world/room/objects/a4Case.ts index 3b5c89808b..256a2cf33b 100644 --- a/packages/frontend/src/world/room/objects/a4Case.ts +++ b/packages/frontend/src/world/room/objects/a4Case.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const a4Case = defineObject({ id: 'a4Case', diff --git a/packages/frontend/src/world/room/objects/aircon.ts b/packages/frontend/src/world/room/objects/aircon.ts index 12044454a7..9c225129a5 100644 --- a/packages/frontend/src/world/room/objects/aircon.ts +++ b/packages/frontend/src/world/room/objects/aircon.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const aircon = defineObject({ id: 'aircon', diff --git a/packages/frontend/src/world/room/objects/allInOnePc.ts b/packages/frontend/src/world/room/objects/allInOnePc.ts index 372834e3b3..3c2b57fcbb 100644 --- a/packages/frontend/src/world/room/objects/allInOnePc.ts +++ b/packages/frontend/src/world/room/objects/allInOnePc.ts @@ -4,8 +4,8 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject, WORLD_SCALE } from '../engine.js'; -import { cm, createPlaneUvMapper } from '../utility.js'; +import { defineObject } from '../object.js'; +import { cm, WORLD_SCALE, createPlaneUvMapper } from '../../utility.js'; export const allInOnePc = defineObject({ id: 'allInOnePc', diff --git a/packages/frontend/src/world/room/objects/aquarium.ts b/packages/frontend/src/world/room/objects/aquarium.ts index 4f9c861edf..702d1c6e03 100644 --- a/packages/frontend/src/world/room/objects/aquarium.ts +++ b/packages/frontend/src/world/room/objects/aquarium.ts @@ -4,8 +4,8 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; -import { cm } from '../utility.js'; +import { defineObject } from '../object.js'; +import { cm } from '../../utility.js'; export const aquarium = defineObject({ id: 'aquarium', diff --git a/packages/frontend/src/world/room/objects/aromaReedDiffuser.ts b/packages/frontend/src/world/room/objects/aromaReedDiffuser.ts index fa1f941b4c..da5c18ea0a 100644 --- a/packages/frontend/src/world/room/objects/aromaReedDiffuser.ts +++ b/packages/frontend/src/world/room/objects/aromaReedDiffuser.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const aromaReedDiffuser = defineObject({ id: 'aromaReedDiffuser', diff --git a/packages/frontend/src/world/room/objects/banknote.ts b/packages/frontend/src/world/room/objects/banknote.ts index 75cc5d5c44..bcab3bc3cd 100644 --- a/packages/frontend/src/world/room/objects/banknote.ts +++ b/packages/frontend/src/world/room/objects/banknote.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const banknote = defineObject({ id: 'banknote', diff --git a/packages/frontend/src/world/room/objects/beamLamp.ts b/packages/frontend/src/world/room/objects/beamLamp.ts index 85b29fd6b1..a42a09445c 100644 --- a/packages/frontend/src/world/room/objects/beamLamp.ts +++ b/packages/frontend/src/world/room/objects/beamLamp.ts @@ -4,8 +4,8 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject, WORLD_SCALE } from '../engine.js'; -import { cm } from '../utility.js'; +import { defineObject } from '../object.js'; +import { cm, WORLD_SCALE } from '../../utility.js'; export const beamLamp = defineObject({ id: 'beamLamp', diff --git a/packages/frontend/src/world/room/objects/bed.ts b/packages/frontend/src/world/room/objects/bed.ts index 8c1eeec1b0..02f709dd28 100644 --- a/packages/frontend/src/world/room/objects/bed.ts +++ b/packages/frontend/src/world/room/objects/bed.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const bed = defineObject({ id: 'bed', diff --git a/packages/frontend/src/world/room/objects/blind.ts b/packages/frontend/src/world/room/objects/blind.ts index c0e7351b52..e052ceb42e 100644 --- a/packages/frontend/src/world/room/objects/blind.ts +++ b/packages/frontend/src/world/room/objects/blind.ts @@ -4,8 +4,9 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; -import { cm, createOverridedStates } from '../utility.js'; +import { defineObject } from '../object.js'; +import { cm } from '../../utility.js'; +import { createOverridedStates } from '../utility.js'; export const blind = defineObject({ id: 'blind', diff --git a/packages/frontend/src/world/room/objects/book.ts b/packages/frontend/src/world/room/objects/book.ts index 51a9813a6e..c2b9387312 100644 --- a/packages/frontend/src/world/room/objects/book.ts +++ b/packages/frontend/src/world/room/objects/book.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const book = defineObject({ id: 'book', diff --git a/packages/frontend/src/world/room/objects/books.ts b/packages/frontend/src/world/room/objects/books.ts index e3942ed0f4..4232254cdd 100644 --- a/packages/frontend/src/world/room/objects/books.ts +++ b/packages/frontend/src/world/room/objects/books.ts @@ -4,8 +4,8 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; -import { cm } from '../utility.js'; +import { defineObject } from '../object.js'; +import { cm } from '../../utility.js'; export const books = defineObject({ id: 'books', diff --git a/packages/frontend/src/world/room/objects/cactusS.ts b/packages/frontend/src/world/room/objects/cactusS.ts index e16bb4fdb1..fbebf2e66f 100644 --- a/packages/frontend/src/world/room/objects/cactusS.ts +++ b/packages/frontend/src/world/room/objects/cactusS.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const cactusS = defineObject({ id: 'cactusS', diff --git a/packages/frontend/src/world/room/objects/cardboardBox.ts b/packages/frontend/src/world/room/objects/cardboardBox.ts index dfff5fcc8e..75ca2427c1 100644 --- a/packages/frontend/src/world/room/objects/cardboardBox.ts +++ b/packages/frontend/src/world/room/objects/cardboardBox.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const cardboardBox = defineObject({ id: 'cardboardBox', diff --git a/packages/frontend/src/world/room/objects/ceilingFanLight.ts b/packages/frontend/src/world/room/objects/ceilingFanLight.ts index ed178a187f..4e8cfd1431 100644 --- a/packages/frontend/src/world/room/objects/ceilingFanLight.ts +++ b/packages/frontend/src/world/room/objects/ceilingFanLight.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const ceilingFanLight = defineObject({ id: 'ceilingFanLight', diff --git a/packages/frontend/src/world/room/objects/chair.ts b/packages/frontend/src/world/room/objects/chair.ts index 1ade6539b7..f409fd5fba 100644 --- a/packages/frontend/src/world/room/objects/chair.ts +++ b/packages/frontend/src/world/room/objects/chair.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const chair = defineObject({ id: 'chair', diff --git a/packages/frontend/src/world/room/objects/coffeeCup.ts b/packages/frontend/src/world/room/objects/coffeeCup.ts index a38cd8d1a7..6eea0a0ac6 100644 --- a/packages/frontend/src/world/room/objects/coffeeCup.ts +++ b/packages/frontend/src/world/room/objects/coffeeCup.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const coffeeCup = defineObject({ id: 'coffeeCup', diff --git a/packages/frontend/src/world/room/objects/colorBox.ts b/packages/frontend/src/world/room/objects/colorBox.ts index 821a7938ee..660174bbed 100644 --- a/packages/frontend/src/world/room/objects/colorBox.ts +++ b/packages/frontend/src/world/room/objects/colorBox.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const colorBox = defineObject({ id: 'colorBox', diff --git a/packages/frontend/src/world/room/objects/cuboid.ts b/packages/frontend/src/world/room/objects/cuboid.ts index d2f1ee7b9c..dadbec43e4 100644 --- a/packages/frontend/src/world/room/objects/cuboid.ts +++ b/packages/frontend/src/world/room/objects/cuboid.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const cuboid = defineObject({ id: 'cuboid', diff --git a/packages/frontend/src/world/room/objects/cupNoodle.ts b/packages/frontend/src/world/room/objects/cupNoodle.ts index a3cb80486d..277cfd1f29 100644 --- a/packages/frontend/src/world/room/objects/cupNoodle.ts +++ b/packages/frontend/src/world/room/objects/cupNoodle.ts @@ -4,8 +4,9 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; -import { cm, yuge } from '../utility.js'; +import { defineObject } from '../object.js'; +import { cm } from '../../utility.js'; +import { yuge } from '../utility.js'; export const cupNoodle = defineObject({ id: 'cupNoodle', diff --git a/packages/frontend/src/world/room/objects/custardPudding.ts b/packages/frontend/src/world/room/objects/custardPudding.ts index dfd007fcf5..ca540559b1 100644 --- a/packages/frontend/src/world/room/objects/custardPudding.ts +++ b/packages/frontend/src/world/room/objects/custardPudding.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const custardPudding = defineObject({ id: 'custardPudding', diff --git a/packages/frontend/src/world/room/objects/debugHipoly.ts b/packages/frontend/src/world/room/objects/debugHipoly.ts index 2df52ba401..c8539eb071 100644 --- a/packages/frontend/src/world/room/objects/debugHipoly.ts +++ b/packages/frontend/src/world/room/objects/debugHipoly.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const debugHipoly = defineObject({ id: 'debugHipoly', diff --git a/packages/frontend/src/world/room/objects/desk.ts b/packages/frontend/src/world/room/objects/desk.ts index a166d96c9d..34cb6f8aaf 100644 --- a/packages/frontend/src/world/room/objects/desk.ts +++ b/packages/frontend/src/world/room/objects/desk.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const desk = defineObject({ id: 'desk', diff --git a/packages/frontend/src/world/room/objects/desktopPc.ts b/packages/frontend/src/world/room/objects/desktopPc.ts index 5cb59ed075..ac953db11e 100644 --- a/packages/frontend/src/world/room/objects/desktopPc.ts +++ b/packages/frontend/src/world/room/objects/desktopPc.ts @@ -4,8 +4,8 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject, WORLD_SCALE } from '../engine.js'; -import { cm } from '../utility.js'; +import { defineObject } from '../object.js'; +import { cm, WORLD_SCALE } from '../../utility.js'; export const desktopPc = defineObject({ id: 'desktopPc', diff --git a/packages/frontend/src/world/room/objects/djMixer.ts b/packages/frontend/src/world/room/objects/djMixer.ts index 51ec3734aa..f15a533ff9 100644 --- a/packages/frontend/src/world/room/objects/djMixer.ts +++ b/packages/frontend/src/world/room/objects/djMixer.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const djMixer = defineObject({ id: 'djMixer', diff --git a/packages/frontend/src/world/room/objects/djPlayer.ts b/packages/frontend/src/world/room/objects/djPlayer.ts index 2c312e12a2..d19f4d1788 100644 --- a/packages/frontend/src/world/room/objects/djPlayer.ts +++ b/packages/frontend/src/world/room/objects/djPlayer.ts @@ -4,8 +4,8 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; -import { createPlaneUvMapper, normalizeUvToSquare } from '../utility.js'; +import { defineObject } from '../object.js'; +import { createPlaneUvMapper, normalizeUvToSquare } from '../../utility.js'; export const djPlayer = defineObject({ id: 'djPlayer', diff --git a/packages/frontend/src/world/room/objects/ductTape.ts b/packages/frontend/src/world/room/objects/ductTape.ts index 967c9f532a..bf5e313644 100644 --- a/packages/frontend/src/world/room/objects/ductTape.ts +++ b/packages/frontend/src/world/room/objects/ductTape.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const ductTape = defineObject({ id: 'ductTape', diff --git a/packages/frontend/src/world/room/objects/emptyBento.ts b/packages/frontend/src/world/room/objects/emptyBento.ts index 51b176c6c5..3de101c44b 100644 --- a/packages/frontend/src/world/room/objects/emptyBento.ts +++ b/packages/frontend/src/world/room/objects/emptyBento.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const emptyBento = defineObject({ id: 'emptyBento', diff --git a/packages/frontend/src/world/room/objects/energyDrink.ts b/packages/frontend/src/world/room/objects/energyDrink.ts index 4b4e1c28a3..1e856b14a9 100644 --- a/packages/frontend/src/world/room/objects/energyDrink.ts +++ b/packages/frontend/src/world/room/objects/energyDrink.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const energyDrink = defineObject({ id: 'energyDrink', diff --git a/packages/frontend/src/world/room/objects/envelope.ts b/packages/frontend/src/world/room/objects/envelope.ts index 60a97b33da..352976901f 100644 --- a/packages/frontend/src/world/room/objects/envelope.ts +++ b/packages/frontend/src/world/room/objects/envelope.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const envelope = defineObject({ id: 'envelope', diff --git a/packages/frontend/src/world/room/objects/facialTissue.ts b/packages/frontend/src/world/room/objects/facialTissue.ts index 5285bb828c..447d742eb3 100644 --- a/packages/frontend/src/world/room/objects/facialTissue.ts +++ b/packages/frontend/src/world/room/objects/facialTissue.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const facialTissue = defineObject({ id: 'facialTissue', diff --git a/packages/frontend/src/world/room/objects/hangingTShirt.ts b/packages/frontend/src/world/room/objects/hangingTShirt.ts index 822afcc081..1c2dedef95 100644 --- a/packages/frontend/src/world/room/objects/hangingTShirt.ts +++ b/packages/frontend/src/world/room/objects/hangingTShirt.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const hangingTShirt = defineObject({ id: 'hangingTShirt', diff --git a/packages/frontend/src/world/room/objects/icosahedron.ts b/packages/frontend/src/world/room/objects/icosahedron.ts index 69bb62b1b0..4db072a3af 100644 --- a/packages/frontend/src/world/room/objects/icosahedron.ts +++ b/packages/frontend/src/world/room/objects/icosahedron.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const icosahedron = defineObject({ id: 'icosahedron', diff --git a/packages/frontend/src/world/room/objects/ironFrameShelf.ts b/packages/frontend/src/world/room/objects/ironFrameShelf.ts index 0df184942a..fe03eeefab 100644 --- a/packages/frontend/src/world/room/objects/ironFrameShelf.ts +++ b/packages/frontend/src/world/room/objects/ironFrameShelf.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject, defineObjectClass } from '../engine.js'; +import { defineObject, defineObjectClass } from '../object.js'; const base = defineObjectClass({ options: { diff --git a/packages/frontend/src/world/room/objects/ironFrameTable.ts b/packages/frontend/src/world/room/objects/ironFrameTable.ts index 0a32d685ce..abb749045e 100644 --- a/packages/frontend/src/world/room/objects/ironFrameTable.ts +++ b/packages/frontend/src/world/room/objects/ironFrameTable.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const ironFrameTable = defineObject({ id: 'ironFrameTable', diff --git a/packages/frontend/src/world/room/objects/keyboard.ts b/packages/frontend/src/world/room/objects/keyboard.ts index cbd860a884..412a5d9e18 100644 --- a/packages/frontend/src/world/room/objects/keyboard.ts +++ b/packages/frontend/src/world/room/objects/keyboard.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const keyboard = defineObject({ id: 'keyboard', diff --git a/packages/frontend/src/world/room/objects/laptopPc.ts b/packages/frontend/src/world/room/objects/laptopPc.ts index 43283af136..3168402354 100644 --- a/packages/frontend/src/world/room/objects/laptopPc.ts +++ b/packages/frontend/src/world/room/objects/laptopPc.ts @@ -4,8 +4,8 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject, WORLD_SCALE } from '../engine.js'; -import { cm, createPlaneUvMapper } from '../utility.js'; +import { defineObject } from '../object.js'; +import { cm, WORLD_SCALE, createPlaneUvMapper } from '../../utility.js'; export const laptopPc = defineObject({ id: 'laptopPc', diff --git a/packages/frontend/src/world/room/objects/lavaLamp.ts b/packages/frontend/src/world/room/objects/lavaLamp.ts index a36538ef6f..ddc619ed0e 100644 --- a/packages/frontend/src/world/room/objects/lavaLamp.ts +++ b/packages/frontend/src/world/room/objects/lavaLamp.ts @@ -3,8 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ import * as BABYLON from '@babylonjs/core'; -import { defineObject, WORLD_SCALE } from '../engine.js'; -import { cm } from '../utility.js'; +import { defineObject } from '../object.js'; +import { cm, WORLD_SCALE } from '../../utility.js'; export const lavaLamp = defineObject({ id: 'lavaLamp', diff --git a/packages/frontend/src/world/room/objects/letterCase.ts b/packages/frontend/src/world/room/objects/letterCase.ts index 3d3a6eab71..90f60444b1 100644 --- a/packages/frontend/src/world/room/objects/letterCase.ts +++ b/packages/frontend/src/world/room/objects/letterCase.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const letterCase = defineObject({ id: 'letterCase', diff --git a/packages/frontend/src/world/room/objects/mi-objet.ts b/packages/frontend/src/world/room/objects/mi-objet.ts index 923eeeefd0..6a77c08b8f 100644 --- a/packages/frontend/src/world/room/objects/mi-objet.ts +++ b/packages/frontend/src/world/room/objects/mi-objet.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const miObjet = defineObject({ id: 'miObjet', diff --git a/packages/frontend/src/world/room/objects/miPlate.ts b/packages/frontend/src/world/room/objects/miPlate.ts index d2c10053fb..1902ec4b23 100644 --- a/packages/frontend/src/world/room/objects/miPlate.ts +++ b/packages/frontend/src/world/room/objects/miPlate.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const miPlate = defineObject({ id: 'miPlate', diff --git a/packages/frontend/src/world/room/objects/miPlateDisplayed.ts b/packages/frontend/src/world/room/objects/miPlateDisplayed.ts index d1bdfc926a..9b14fcb9e7 100644 --- a/packages/frontend/src/world/room/objects/miPlateDisplayed.ts +++ b/packages/frontend/src/world/room/objects/miPlateDisplayed.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const miPlateDisplayed = defineObject({ id: 'miPlateDisplayed', diff --git a/packages/frontend/src/world/room/objects/milk.ts b/packages/frontend/src/world/room/objects/milk.ts index 2096e854b1..1d19845a4f 100644 --- a/packages/frontend/src/world/room/objects/milk.ts +++ b/packages/frontend/src/world/room/objects/milk.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const milk = defineObject({ id: 'milk', diff --git a/packages/frontend/src/world/room/objects/mixer.ts b/packages/frontend/src/world/room/objects/mixer.ts index bb58c055db..5ef459b3ce 100644 --- a/packages/frontend/src/world/room/objects/mixer.ts +++ b/packages/frontend/src/world/room/objects/mixer.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const mixer = defineObject({ id: 'mixer', diff --git a/packages/frontend/src/world/room/objects/monitor.ts b/packages/frontend/src/world/room/objects/monitor.ts index 166e0dfe25..56b79b5421 100644 --- a/packages/frontend/src/world/room/objects/monitor.ts +++ b/packages/frontend/src/world/room/objects/monitor.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const monitor = defineObject({ id: 'monitor', diff --git a/packages/frontend/src/world/room/objects/monitorSpeaker.ts b/packages/frontend/src/world/room/objects/monitorSpeaker.ts index 3cb8a589cb..8d718a91dd 100644 --- a/packages/frontend/src/world/room/objects/monitorSpeaker.ts +++ b/packages/frontend/src/world/room/objects/monitorSpeaker.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const monitorSpeaker = defineObject({ id: 'monitorSpeaker', diff --git a/packages/frontend/src/world/room/objects/monstera.ts b/packages/frontend/src/world/room/objects/monstera.ts index ae0520580b..7b502aa4d1 100644 --- a/packages/frontend/src/world/room/objects/monstera.ts +++ b/packages/frontend/src/world/room/objects/monstera.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const monstera = defineObject({ id: 'monstera', diff --git a/packages/frontend/src/world/room/objects/mug.ts b/packages/frontend/src/world/room/objects/mug.ts index b6a0f3d4c4..2f4d090d66 100644 --- a/packages/frontend/src/world/room/objects/mug.ts +++ b/packages/frontend/src/world/room/objects/mug.ts @@ -4,8 +4,9 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; -import { cm, yuge } from '../utility.js'; +import { defineObject } from '../object.js'; +import { cm } from '../../utility.js'; +import { yuge } from '../utility.js'; export const mug = defineObject({ id: 'mug', diff --git a/packages/frontend/src/world/room/objects/newtonsCradle.ts b/packages/frontend/src/world/room/objects/newtonsCradle.ts index 9d08c2f61a..ece48fca14 100644 --- a/packages/frontend/src/world/room/objects/newtonsCradle.ts +++ b/packages/frontend/src/world/room/objects/newtonsCradle.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const newtonsCradle = defineObject({ id: 'newtonsCradle', diff --git a/packages/frontend/src/world/room/objects/openedCardboardBox.ts b/packages/frontend/src/world/room/objects/openedCardboardBox.ts index ad7492dcd4..7bf377f967 100644 --- a/packages/frontend/src/world/room/objects/openedCardboardBox.ts +++ b/packages/frontend/src/world/room/objects/openedCardboardBox.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const openedCardboardBox = defineObject({ id: 'openedCardboardBox', diff --git a/packages/frontend/src/world/room/objects/pachira.ts b/packages/frontend/src/world/room/objects/pachira.ts index 4f5f191b05..be0c1a91eb 100644 --- a/packages/frontend/src/world/room/objects/pachira.ts +++ b/packages/frontend/src/world/room/objects/pachira.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const pachira = defineObject({ id: 'pachira', diff --git a/packages/frontend/src/world/room/objects/pc.ts b/packages/frontend/src/world/room/objects/pc.ts index 2be88baea0..d667e936ec 100644 --- a/packages/frontend/src/world/room/objects/pc.ts +++ b/packages/frontend/src/world/room/objects/pc.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const pc = defineObject({ id: 'pc', diff --git a/packages/frontend/src/world/room/objects/petBottle.ts b/packages/frontend/src/world/room/objects/petBottle.ts index 084483d074..d3587b15cd 100644 --- a/packages/frontend/src/world/room/objects/petBottle.ts +++ b/packages/frontend/src/world/room/objects/petBottle.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const petBottle = defineObject({ id: 'petBottle', diff --git a/packages/frontend/src/world/room/objects/piano.ts b/packages/frontend/src/world/room/objects/piano.ts index da3875c04f..4e797d6e78 100644 --- a/packages/frontend/src/world/room/objects/piano.ts +++ b/packages/frontend/src/world/room/objects/piano.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const piano = defineObject({ id: 'piano', diff --git a/packages/frontend/src/world/room/objects/pictureFrame.ts b/packages/frontend/src/world/room/objects/pictureFrame.ts index d42f6ea675..43cd12257c 100644 --- a/packages/frontend/src/world/room/objects/pictureFrame.ts +++ b/packages/frontend/src/world/room/objects/pictureFrame.ts @@ -4,8 +4,8 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; -import { createPlaneUvMapper } from '../utility.js'; +import { defineObject } from '../object.js'; +import { createPlaneUvMapper } from '../../utility.js'; // NOTE: シェイプキーのnormalのエクスポートは無効にしないとmatを大きくしたときに面のレンダリングがグリッチする diff --git a/packages/frontend/src/world/room/objects/pizza.ts b/packages/frontend/src/world/room/objects/pizza.ts index 2e93d58f89..5f79965070 100644 --- a/packages/frontend/src/world/room/objects/pizza.ts +++ b/packages/frontend/src/world/room/objects/pizza.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const pizza = defineObject({ id: 'pizza', diff --git a/packages/frontend/src/world/room/objects/plant.ts b/packages/frontend/src/world/room/objects/plant.ts index ba064e07fc..4163919007 100644 --- a/packages/frontend/src/world/room/objects/plant.ts +++ b/packages/frontend/src/world/room/objects/plant.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const plant = defineObject({ id: 'plant', diff --git a/packages/frontend/src/world/room/objects/plant2.ts b/packages/frontend/src/world/room/objects/plant2.ts index 3497329039..c29f48f417 100644 --- a/packages/frontend/src/world/room/objects/plant2.ts +++ b/packages/frontend/src/world/room/objects/plant2.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const plant2 = defineObject({ id: 'plant2', diff --git a/packages/frontend/src/world/room/objects/poster.ts b/packages/frontend/src/world/room/objects/poster.ts index 1be19f7124..11cc760634 100644 --- a/packages/frontend/src/world/room/objects/poster.ts +++ b/packages/frontend/src/world/room/objects/poster.ts @@ -4,8 +4,8 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; -import { createPlaneUvMapper, getPlaneUvIndexes } from '../utility.js'; +import { defineObject } from '../object.js'; +import { createPlaneUvMapper, getPlaneUvIndexes } from '../../utility.js'; const remap = (value: number, fromMin: number, fromMax: number, toMin: number, toMax: number) => { return toMin + ((value - fromMin) / (fromMax - fromMin)) * (toMax - toMin); diff --git a/packages/frontend/src/world/room/objects/powerStrip.ts b/packages/frontend/src/world/room/objects/powerStrip.ts index 275bddfaf8..133d8c9af1 100644 --- a/packages/frontend/src/world/room/objects/powerStrip.ts +++ b/packages/frontend/src/world/room/objects/powerStrip.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const powerStrip = defineObject({ id: 'powerStrip', diff --git a/packages/frontend/src/world/room/objects/radiometer.ts b/packages/frontend/src/world/room/objects/radiometer.ts index 60a689bf7e..0ccee9abe4 100644 --- a/packages/frontend/src/world/room/objects/radiometer.ts +++ b/packages/frontend/src/world/room/objects/radiometer.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const radiometer = defineObject({ id: 'radiometer', diff --git a/packages/frontend/src/world/room/objects/randomBooks.ts b/packages/frontend/src/world/room/objects/randomBooks.ts index be1ba8d842..375a2dfe62 100644 --- a/packages/frontend/src/world/room/objects/randomBooks.ts +++ b/packages/frontend/src/world/room/objects/randomBooks.ts @@ -5,7 +5,8 @@ import * as BABYLON from '@babylonjs/core'; import seedrandom from 'seedrandom'; -import { defineObject, WORLD_SCALE } from '../engine.js'; +import { defineObject } from '../object.js'; +import { WORLD_SCALE } from '@/world/utility.js'; const remap = (value: number, fromMin: number, fromMax: number, toMin: number, toMax: number) => { return toMin + ((value - fromMin) / (fromMax - fromMin)) * (toMax - toMin); diff --git a/packages/frontend/src/world/room/objects/rolledUpPoster.ts b/packages/frontend/src/world/room/objects/rolledUpPoster.ts index df077abfd6..3a91c3fbda 100644 --- a/packages/frontend/src/world/room/objects/rolledUpPoster.ts +++ b/packages/frontend/src/world/room/objects/rolledUpPoster.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const rolledUpPoster = defineObject({ id: 'rolledUpPoster', diff --git a/packages/frontend/src/world/room/objects/roundRug.ts b/packages/frontend/src/world/room/objects/roundRug.ts index 1fcac48119..93df0e36bb 100644 --- a/packages/frontend/src/world/room/objects/roundRug.ts +++ b/packages/frontend/src/world/room/objects/roundRug.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const roundRug = defineObject({ id: 'roundRug', diff --git a/packages/frontend/src/world/room/objects/router.ts b/packages/frontend/src/world/room/objects/router.ts index 80545e5469..e3b3244e65 100644 --- a/packages/frontend/src/world/room/objects/router.ts +++ b/packages/frontend/src/world/room/objects/router.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const router = defineObject({ id: 'router', diff --git a/packages/frontend/src/world/room/objects/siphon.ts b/packages/frontend/src/world/room/objects/siphon.ts index 929ee0f3ca..f204a90088 100644 --- a/packages/frontend/src/world/room/objects/siphon.ts +++ b/packages/frontend/src/world/room/objects/siphon.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const siphon = defineObject({ id: 'siphon', diff --git a/packages/frontend/src/world/room/objects/snakeplant.ts b/packages/frontend/src/world/room/objects/snakeplant.ts index 4e84cbffd7..be9bacaeed 100644 --- a/packages/frontend/src/world/room/objects/snakeplant.ts +++ b/packages/frontend/src/world/room/objects/snakeplant.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const snakeplant = defineObject({ id: 'snakeplant', diff --git a/packages/frontend/src/world/room/objects/speaker.ts b/packages/frontend/src/world/room/objects/speaker.ts index 369bb8e85a..861157b2ef 100644 --- a/packages/frontend/src/world/room/objects/speaker.ts +++ b/packages/frontend/src/world/room/objects/speaker.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const speaker = defineObject({ id: 'speaker', diff --git a/packages/frontend/src/world/room/objects/sprayer.ts b/packages/frontend/src/world/room/objects/sprayer.ts index 4e02c049df..11c77a003d 100644 --- a/packages/frontend/src/world/room/objects/sprayer.ts +++ b/packages/frontend/src/world/room/objects/sprayer.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const sprayer = defineObject({ id: 'sprayer', diff --git a/packages/frontend/src/world/room/objects/steelRack.ts b/packages/frontend/src/world/room/objects/steelRack.ts index 0514e0417e..0df243423a 100644 --- a/packages/frontend/src/world/room/objects/steelRack.ts +++ b/packages/frontend/src/world/room/objects/steelRack.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const steelRack = defineObject({ id: 'steelRack', diff --git a/packages/frontend/src/world/room/objects/tabletopCalendar.ts b/packages/frontend/src/world/room/objects/tabletopCalendar.ts index a3ee36f77c..c5ffae5827 100644 --- a/packages/frontend/src/world/room/objects/tabletopCalendar.ts +++ b/packages/frontend/src/world/room/objects/tabletopCalendar.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const tabletopCalendar = defineObject({ id: 'tabletopCalendar', diff --git a/packages/frontend/src/world/room/objects/tabletopDigitalClock.ts b/packages/frontend/src/world/room/objects/tabletopDigitalClock.ts index 5fddcf8e12..fb260b0da6 100644 --- a/packages/frontend/src/world/room/objects/tabletopDigitalClock.ts +++ b/packages/frontend/src/world/room/objects/tabletopDigitalClock.ts @@ -4,8 +4,8 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject, WORLD_SCALE } from '../engine.js'; -import { cm, get7segMeshesOfCurrentTime } from '../utility.js'; +import { defineObject } from '../object.js'; +import { cm, get7segMeshesOfCurrentTime, WORLD_SCALE } from '@/world/utility.js'; export const tabletopDigitalClock = defineObject({ id: 'tabletopDigitalClock', diff --git a/packages/frontend/src/world/room/objects/tabletopFlag.ts b/packages/frontend/src/world/room/objects/tabletopFlag.ts index b1b7279f49..86d2227fa7 100644 --- a/packages/frontend/src/world/room/objects/tabletopFlag.ts +++ b/packages/frontend/src/world/room/objects/tabletopFlag.ts @@ -4,8 +4,8 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; -import { createPlaneUvMapper } from '../utility.js'; +import { defineObject } from '../object.js'; +import { createPlaneUvMapper } from '../../utility.js'; export const tabletopFlag = defineObject({ id: 'tabletopFlag', diff --git a/packages/frontend/src/world/room/objects/tabletopGlassPictureFrame.ts b/packages/frontend/src/world/room/objects/tabletopGlassPictureFrame.ts index 6cc8b33ddd..36e1ab6dfa 100644 --- a/packages/frontend/src/world/room/objects/tabletopGlassPictureFrame.ts +++ b/packages/frontend/src/world/room/objects/tabletopGlassPictureFrame.ts @@ -4,8 +4,8 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; -import { createPlaneUvMapper, getPlaneUvIndexes } from '../utility.js'; +import { defineObject } from '../object.js'; +import { createPlaneUvMapper, getPlaneUvIndexes } from '../../utility.js'; const remap = (value: number, fromMin: number, fromMax: number, toMin: number, toMax: number) => { return toMin + ((value - fromMin) / (fromMax - fromMin)) * (toMax - toMin); diff --git a/packages/frontend/src/world/room/objects/tabletopIronFrameStand.ts b/packages/frontend/src/world/room/objects/tabletopIronFrameStand.ts index 6e4ec3904e..baa74ebfc0 100644 --- a/packages/frontend/src/world/room/objects/tabletopIronFrameStand.ts +++ b/packages/frontend/src/world/room/objects/tabletopIronFrameStand.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const tabletopIronFrameStand = defineObject({ id: 'tabletopIronFrameStand', diff --git a/packages/frontend/src/world/room/objects/tabletopPictureFrame.ts b/packages/frontend/src/world/room/objects/tabletopPictureFrame.ts index 6716c5d4b0..9f546f29cd 100644 --- a/packages/frontend/src/world/room/objects/tabletopPictureFrame.ts +++ b/packages/frontend/src/world/room/objects/tabletopPictureFrame.ts @@ -4,8 +4,8 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; -import { createPlaneUvMapper } from '../utility.js'; +import { defineObject } from '../object.js'; +import { createPlaneUvMapper } from '../../utility.js'; // NOTE: シェイプキーのnormalのエクスポートは無効にしないとmatを大きくしたときに面のレンダリングがグリッチする diff --git a/packages/frontend/src/world/room/objects/tapestry.ts b/packages/frontend/src/world/room/objects/tapestry.ts index d6254429e0..e727e4501e 100644 --- a/packages/frontend/src/world/room/objects/tapestry.ts +++ b/packages/frontend/src/world/room/objects/tapestry.ts @@ -4,8 +4,8 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; -import { createPlaneUvMapper, getPlaneUvIndexes } from '../utility.js'; +import { defineObject } from '../object.js'; +import { createPlaneUvMapper, getPlaneUvIndexes } from '../../utility.js'; const remap = (value: number, fromMin: number, fromMax: number, toMin: number, toMax: number) => { return toMin + ((value - fromMin) / (fromMax - fromMin)) * (toMax - toMin); diff --git a/packages/frontend/src/world/room/objects/tetrapod.ts b/packages/frontend/src/world/room/objects/tetrapod.ts index a9b4c7ca73..7ae4a77c88 100644 --- a/packages/frontend/src/world/room/objects/tetrapod.ts +++ b/packages/frontend/src/world/room/objects/tetrapod.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const tetrapod = defineObject({ id: 'tetrapod', diff --git a/packages/frontend/src/world/room/objects/tv.ts b/packages/frontend/src/world/room/objects/tv.ts index 1271cee71f..5c728ff7e1 100644 --- a/packages/frontend/src/world/room/objects/tv.ts +++ b/packages/frontend/src/world/room/objects/tv.ts @@ -4,8 +4,9 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject, WORLD_SCALE } from '../engine.js'; -import { cm, createPlaneUvMapper, initTv } from '../utility.js'; +import { defineObject } from '../object.js'; +import { initTv } from '../utility.js'; +import { cm, WORLD_SCALE } from '@/world/utility.js'; export const tv = defineObject({ id: 'tv', diff --git a/packages/frontend/src/world/room/objects/twistedCubeObjet.ts b/packages/frontend/src/world/room/objects/twistedCubeObjet.ts index 66f3236902..5134ce0e74 100644 --- a/packages/frontend/src/world/room/objects/twistedCubeObjet.ts +++ b/packages/frontend/src/world/room/objects/twistedCubeObjet.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const twistedCubeObjet = defineObject({ id: 'twistedCubeObjet', diff --git a/packages/frontend/src/world/room/objects/usedTissue.ts b/packages/frontend/src/world/room/objects/usedTissue.ts index 870391d891..6a79512f98 100644 --- a/packages/frontend/src/world/room/objects/usedTissue.ts +++ b/packages/frontend/src/world/room/objects/usedTissue.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const usedTissue = defineObject({ id: 'usedTissue', diff --git a/packages/frontend/src/world/room/objects/wallCanvas.ts b/packages/frontend/src/world/room/objects/wallCanvas.ts index 431a18a0bc..daa1d8f01d 100644 --- a/packages/frontend/src/world/room/objects/wallCanvas.ts +++ b/packages/frontend/src/world/room/objects/wallCanvas.ts @@ -4,8 +4,8 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; -import { createPlaneUvMapper, getPlaneUvIndexes } from '../utility.js'; +import { defineObject } from '../object.js'; +import { createPlaneUvMapper, getPlaneUvIndexes } from '../../utility.js'; export const wallCanvas = defineObject({ id: 'wallCanvas', diff --git a/packages/frontend/src/world/room/objects/wallClock.ts b/packages/frontend/src/world/room/objects/wallClock.ts index 306543fecc..08294771e9 100644 --- a/packages/frontend/src/world/room/objects/wallClock.ts +++ b/packages/frontend/src/world/room/objects/wallClock.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const wallClock = defineObject({ id: 'wallClock', diff --git a/packages/frontend/src/world/room/objects/wallGlassPictureFrame.ts b/packages/frontend/src/world/room/objects/wallGlassPictureFrame.ts index 6acb75e6d2..1def8b83bc 100644 --- a/packages/frontend/src/world/room/objects/wallGlassPictureFrame.ts +++ b/packages/frontend/src/world/room/objects/wallGlassPictureFrame.ts @@ -4,8 +4,8 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; -import { createPlaneUvMapper, getPlaneUvIndexes } from '../utility.js'; +import { defineObject } from '../object.js'; +import { createPlaneUvMapper, getPlaneUvIndexes } from '../../utility.js'; const remap = (value: number, fromMin: number, fromMax: number, toMin: number, toMax: number) => { return toMin + ((value - fromMin) / (fromMax - fromMin)) * (toMax - toMin); diff --git a/packages/frontend/src/world/room/objects/wallMirror.ts b/packages/frontend/src/world/room/objects/wallMirror.ts index 26756c53ee..e9df24f491 100644 --- a/packages/frontend/src/world/room/objects/wallMirror.ts +++ b/packages/frontend/src/world/room/objects/wallMirror.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const wallMirror = defineObject({ id: 'wallMirror', diff --git a/packages/frontend/src/world/room/objects/wallShelf.ts b/packages/frontend/src/world/room/objects/wallShelf.ts index 6ef3c4c3ff..9c7465098d 100644 --- a/packages/frontend/src/world/room/objects/wallShelf.ts +++ b/packages/frontend/src/world/room/objects/wallShelf.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const wallShelf = defineObject({ id: 'wallShelf', diff --git a/packages/frontend/src/world/room/objects/woodRingFloorLamp.ts b/packages/frontend/src/world/room/objects/woodRingFloorLamp.ts index de5c0ab6e1..5fd5621bcc 100644 --- a/packages/frontend/src/world/room/objects/woodRingFloorLamp.ts +++ b/packages/frontend/src/world/room/objects/woodRingFloorLamp.ts @@ -4,8 +4,8 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject, WORLD_SCALE } from '../engine.js'; -import { cm } from '../utility.js'; +import { defineObject } from '../object.js'; +import { cm, WORLD_SCALE } from '@/world/utility.js'; export const woodRingFloorLamp = defineObject({ id: 'woodRingFloorLamp', diff --git a/packages/frontend/src/world/room/objects/woodRingsPendantLight.ts b/packages/frontend/src/world/room/objects/woodRingsPendantLight.ts index 6c41f8a6b2..e5d45801d5 100644 --- a/packages/frontend/src/world/room/objects/woodRingsPendantLight.ts +++ b/packages/frontend/src/world/room/objects/woodRingsPendantLight.ts @@ -4,8 +4,8 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject, WORLD_SCALE } from '../engine.js'; -import { cm } from '../utility.js'; +import { defineObject } from '../object.js'; +import { cm, WORLD_SCALE } from '@/world/utility.js'; const remap = (value: number, fromMin: number, fromMax: number, toMin: number, toMax: number) => { return toMin + ((value - fromMin) / (fromMax - fromMin)) * (toMax - toMin); diff --git a/packages/frontend/src/world/room/objects/woodSoundAbsorbingPanel.ts b/packages/frontend/src/world/room/objects/woodSoundAbsorbingPanel.ts index f9381a64f2..0fc884d7e2 100644 --- a/packages/frontend/src/world/room/objects/woodSoundAbsorbingPanel.ts +++ b/packages/frontend/src/world/room/objects/woodSoundAbsorbingPanel.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineObject } from '../engine.js'; +import { defineObject } from '../object.js'; export const woodSoundAbsorbingPanel = defineObject({ id: 'woodSoundAbsorbingPanel', diff --git a/packages/frontend/src/world/room/previewEngine.ts b/packages/frontend/src/world/room/previewEngine.ts new file mode 100644 index 0000000000..b286213eb6 --- /dev/null +++ b/packages/frontend/src/world/room/previewEngine.ts @@ -0,0 +1,250 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as BABYLON from '@babylonjs/core'; +import { registerBuiltInLoaders } from '@babylonjs/loaders/dynamic.js'; +import { GridMaterial } from '@babylonjs/materials'; +import { camelToKebab, WORLD_SCALE, cm, getMeshesBoundingBox } from '../utility.js'; +import { getObjectDef } from './object-defs.js'; +import { SYSTEM_MESH_NAMES, ModelManager } from './utility.js'; +import type { RoomObjectInstance } from './object.js'; +import { genId } from '@/utility/id.js'; +import { deepClone } from '@/utility/clone.js'; + +export async function createRoomObjectPreviewEngine(canvas: HTMLCanvasElement) { + //const babylonEngine = new BABYLON.WebGPUEngine(canvas); + //babylonEngine.compatibilityMode = false; + //await babylonEngine.initAsync(); + const babylonEngine = new BABYLON.Engine(canvas, false, { alpha: false, antialias: false }); + return new RoomObjectPreviewEngine({ canvas, engine: babylonEngine }); +} + +export class RoomObjectPreviewEngine { + private canvas: HTMLCanvasElement; + private engine: BABYLON.WebGPUEngine; + private scene: BABYLON.Scene; + private shadowGenerator1: BABYLON.ShadowGenerator; + private camera: BABYLON.ArcRotateCamera; + private objectMesh: BABYLON.Mesh | null = null; + private objectInstance: RoomObjectInstance | null = null; + private envMapIndoor: BABYLON.CubeTexture; + private roomLight: BABYLON.SpotLight; + private zGridPreviewPlane: BABYLON.Mesh; + private fps = 60; + + constructor(options: { + canvas: HTMLCanvasElement; + engine: BABYLON.WebGPUEngine; + }) { + this.canvas = options.canvas; + + registerBuiltInLoaders(); + + this.engine = options.engine; + this.scene = new BABYLON.Scene(this.engine); + + this.scene.ambientColor = new BABYLON.Color3(1.0, 0.9, 0.8); + + this.envMapIndoor = BABYLON.CubeTexture.CreateFromPrefilteredData('/client-assets/room/indoor.env', this.scene); + this.envMapIndoor.boundingBoxSize = new BABYLON.Vector3(cm(500), cm(500), cm(500)); + + this.camera = new BABYLON.ArcRotateCamera('camera', -Math.PI / 2, Math.PI / 2.5, cm(300), new BABYLON.Vector3(0, cm(90), 0), this.scene); + this.camera.attachControl(this.canvas); + this.camera.minZ = cm(1); + this.camera.maxZ = cm(100000); + this.camera.fov = 0.5; + this.camera.lowerBetaLimit = 0; + this.camera.upperBetaLimit = (Math.PI / 2) + 0.1; + this.camera.lowerRadiusLimit = cm(50); + this.camera.upperRadiusLimit = cm(1000); + this.camera.useAutoRotationBehavior = true; + this.camera.autoRotationBehavior!.idleRotationSpeed = 0.3; + //this.camera.mode = BABYLON.Camera.ORTHOGRAPHIC_CAMERA; + this.scene.activeCamera = this.camera; + + const ambientLight = new BABYLON.HemisphericLight('ambientLight', new BABYLON.Vector3(0, 1, -0.5), this.scene); + ambientLight.diffuse = new BABYLON.Color3(1.0, 1.0, 1.0); + ambientLight.intensity = 0.5; + //ambientLight.intensity = 0; + + this.roomLight = new BABYLON.SpotLight('roomLight', new BABYLON.Vector3(0, cm(249), 0), new BABYLON.Vector3(0, -1, 0), 16, 8, this.scene); + this.roomLight.diffuse = new BABYLON.Color3(1.0, 0.9, 0.8); + this.roomLight.shadowMinZ = cm(10); + this.roomLight.shadowMaxZ = cm(300); + + this.shadowGenerator1 = new BABYLON.ShadowGenerator(4096, this.roomLight); + this.shadowGenerator1.forceBackFacesOnly = true; + this.shadowGenerator1.bias = 0.0001; + this.shadowGenerator1.usePercentageCloserFiltering = true; + this.shadowGenerator1.filteringQuality = BABYLON.ShadowGenerator.QUALITY_HIGH; + //this.shadowGenerator1.useContactHardeningShadow = true; + + const gridMaterial = new GridMaterial('grid', this.scene); + gridMaterial.lineColor = new BABYLON.Color3(0.5, 0.5, 0.5); + gridMaterial.mainColor = new BABYLON.Color3(0, 0, 0); + gridMaterial.minorUnitVisibility = 1; + gridMaterial.opacity = 0.5; + gridMaterial.gridRatio = cm(10); + + //this.zGridPreviewPlane = BABYLON.MeshBuilder.CreatePlane('zGridPreviewPlane', { width: cm(1000), height: cm(1000) }, this.scene); + //this.zGridPreviewPlane.material = gridMaterial; + //this.zGridPreviewPlane.rotation = new BABYLON.Vector3(Math.PI / 2, 0, 0); + + //this.scene.fogMode = BABYLON.Scene.FOGMODE_LINEAR; + //this.scene.fogStart = cm(100); + //this.scene.fogEnd = cm(110); + //this.scene.fogColor = new BABYLON.Color3(0.0, 0.0, 0.0); + } + + public async init() { + const frameInterval = 1000 / this.fps; + let lastTime = performance.now(); + + this.engine.runRenderLoop(() => { + const currentTime = performance.now(); + const delta = currentTime - lastTime; + + if (delta >= frameInterval) { + this.scene.render(); + lastTime = currentTime - (delta % frameInterval); + } + }); + } + + public async load(type: string) { + if (this.objectInstance != null) { + this.objectInstance.dispose?.(); + this.objectInstance = null; + this.objectMesh!.dispose(); + } + + // reset camera rotation + this.camera.setPosition(new BABYLON.Vector3(0, cm(90), cm(300))); + + const def = getObjectDef(type); + + const options = deepClone(def.options.default); + + const id = genId(); + + await this.loadObject({ + type, + options, + id, + }); + + // なぜかちょっと待たないとbounding boxのサイズが正しくない + window.setTimeout(() => { + const boundingInfo = getMeshesBoundingBox(this.objectMesh!.getChildMeshes().filter(m => m.isEnabled() && m.isVisible)); + this.camera.setTarget(new BABYLON.Vector3(0, boundingInfo.center.y, 0)); + + // zoom to fit + const size = boundingInfo.extendSize; + const distance = Math.max(size.x, size.y, size.z) * 2; + this.camera.radius = distance * 3; + //this.camera.orthoLeft = -distance; + //this.camera.orthoRight = distance; + //this.camera.orthoTop = distance; + //this.camera.orthoBottom = -distance; + }, 10); + } + + // TODO: RoomEngineのものとほぼ同じだからいい感じに共通化 + private async loadObject(args: { + type: string; + options: any; + id: string; + }) { + const def = getObjectDef(args.type); + + const root = new BABYLON.Mesh(`object_${args.type}`, this.scene); + + const filePath = def.path != null ? `/client-assets/room/objects/${def.path}.glb` : `/client-assets/room/objects/${camelToKebab(args.type)}/${camelToKebab(args.type)}.glb`; + const loaderResult = await BABYLON.LoadAssetContainerAsync(filePath, this.scene); + + // 不要なUVを掃除 + if (!def.hasTexture) { + for (const m of loaderResult.meshes) { + if (m.geometry != null) { + m.geometry.removeVerticesData(BABYLON.VertexBuffer.UVKind); + m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV2Kind); + m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV3Kind); + m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV4Kind); + m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV5Kind); + m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV6Kind); + } + } + } + + // babylonによって自動で追加される右手系変換用ノード + const subRoot = loaderResult.meshes[0]; + subRoot.scaling = subRoot.scaling.scale(WORLD_SCALE);// cmをmに + + def.treatLoaderResult?.(loaderResult); + + root.addChild(subRoot); + + const model = new ModelManager(subRoot, loaderResult.meshes.filter(m => !m.isDisposed() && m !== subRoot), def.hasTexture, (meshes) => { + for (const m of meshes) { + const mesh = m; + + // シェイプキー(morph)を考慮してbounding boxを更新するために必要 + mesh.refreshBoundingInfo({ applyMorph: true }); + + if (SYSTEM_MESH_NAMES.some(n => mesh.name.includes(n))) { + mesh.receiveShadows = false; + mesh.isVisible = false; + } else { + if (def.receiveShadows !== false) mesh.receiveShadows = true; + if (def.castShadows !== false) { + this.shadowGenerator1.addShadowCaster(mesh); + } + + if (mesh.material) { + if (mesh.material instanceof BABYLON.MultiMaterial) { + for (const subMat of mesh.material.subMaterials) { + (subMat as BABYLON.PBRMaterial).reflectionTexture = this.envMapIndoor; + (subMat as BABYLON.PBRMaterial).useGLTFLightFalloff = true; // Clustered Lightingではphysical falloffを持つマテリアルはアーチファクトが発生する https://doc.babylonjs.com/features/featuresDeepDive/lights/clusteredLighting/#materials-with-a-physical-falloff-may-cause-artefacts + } + } else { + (mesh.material as BABYLON.PBRMaterial).reflectionTexture = this.envMapIndoor; + (mesh.material as BABYLON.PBRMaterial).useGLTFLightFalloff = true; // Clustered Lightingではphysical falloffを持つマテリアルはアーチファクトが発生する https://doc.babylonjs.com/features/featuresDeepDive/lights/clusteredLighting/#materials-with-a-physical-falloff-may-cause-artefacts + } + } + } + + if (!this.scene.meshes.includes(mesh)) this.scene.addMesh(mesh); + } + }); + + const objectInstance = await def.createInstance({ + room: null, + scene: this.scene, + root, + options: args.options, + model, + id: args.id, + }); + + objectInstance.onInited?.(); + + model.bakeMesh(); + + this.objectInstance = objectInstance; + this.objectMesh = root; + } + + public updateObjectOption(key: string, value: any) { + this.objectInstance?.onOptionsUpdated?.([key, value]); + } + + public resize() { + this.engine.resize(); + } + + public destroy() { + this.engine.dispose(); + } +} diff --git a/packages/frontend/src/world/room/utility.ts b/packages/frontend/src/world/room/utility.ts index 475178121c..640fff8298 100644 --- a/packages/frontend/src/world/room/utility.ts +++ b/packages/frontend/src/world/room/utility.ts @@ -4,10 +4,10 @@ */ import * as BABYLON from '@babylonjs/core'; +import { applyMorphTargetsToMesh, cm, getPlaneUvIndexes } from '../utility.js'; import type { RoomEngine } from './engine.js'; -//export const cm = (value: number) => value / 100; -export const cm = (value: number) => value; +export const SYSTEM_MESH_NAMES = ['__TOP__', '__SIDE__', '__PICK__', '__COLLISION__']; export function yuge(scene: BABYLON.Scene, mesh: BABYLON.Mesh, offset: BABYLON.Vector3) { const emitter = new BABYLON.TransformNode('emitter', scene); @@ -41,182 +41,6 @@ export function yuge(scene: BABYLON.Scene, mesh: BABYLON.Mesh, offset: BABYLON.V }; } -export class HorizontalCameraKeyboardMoveInput extends BABYLON.BaseCameraPointersInput { - public camera: BABYLON.FreeCamera; - private engine: BABYLON.AbstractEngine; - private scene: BABYLON.Scene; - preShift = false; - codes = []; - codesUp = ['KeyW']; - codesDown = ['KeyS']; - codesLeft = ['KeyA']; - codesRight = ['KeyD']; - onCanvasBlurObserver = null; - onKeyboardObserver = null; - public canMove = true; - - constructor(camera: BABYLON.UniversalCamera) { - super(); - this.camera = camera; - this.scene = this.camera.getScene(); - this.engine = this.scene.getEngine(); - } - - attachControl() { - if (this.onCanvasBlurObserver) { - return; - } - - this.onCanvasBlurObserver = this.engine.onCanvasBlurObservable.add(() => { - this.codes = []; - }); - - this.onKeyboardObserver = this.scene.onKeyboardObservable.add(({ event, type }) => { - const { code, shiftKey } = event; - this.preShift = shiftKey; - - if (type === BABYLON.KeyboardEventTypes.KEYDOWN) { - if (this.codesUp.indexOf(code) >= 0 || - this.codesDown.indexOf(code) >= 0 || - this.codesLeft.indexOf(code) >= 0 || - this.codesRight.indexOf(code) >= 0) { - const index = this.codes.findIndex(v => v.code === code); - if (index < 0) { // 存在しなかったら追加する - this.codes.push({ code }); - } - } - } else { - if (this.codesUp.indexOf(code) >= 0 || - this.codesDown.indexOf(code) >= 0 || - this.codesLeft.indexOf(code) >= 0 || - this.codesRight.indexOf(code) >= 0) { - const index = this.codes.findIndex(v => v.code === code); - if (index >= 0) { // 存在したら削除する - this.codes.splice(index, 1); - } - } - } - }); - } - - detachControl() { - this.codes = []; - - if (this.onKeyboardObserver) this.scene.onKeyboardObservable.remove(this.onKeyboardObserver); - if (this.onCanvasBlurObserver) this.engine.onCanvasBlurObservable.remove(this.onCanvasBlurObserver); - this.onKeyboardObserver = null; - this.onCanvasBlurObserver = null; - } - - checkInputs() { - if (!this.onKeyboardObserver) { - return; - } - for (let index = 0; index < this.codes.length; index++) { - const { code } = this.codes[index]; - - const local = new BABYLON.Vector3(); - if (this.codesLeft.indexOf(code) >= 0) { - local.x += -1; - } else if (this.codesUp.indexOf(code) >= 0) { - local.z += this.scene.useRightHandedSystem ? -1 : 1; - } else if (this.codesRight.indexOf(code) >= 0) { - local.x += 1; - } else if (this.codesDown.indexOf(code) >= 0) { - local.z += this.scene.useRightHandedSystem ? 1 : -1; - } - - if (local.length() === 0) { - continue; - } - - const dir = this.camera.getDirection(local.normalize()); - dir.y = 0; - dir.normalize(); - const rate = this.preShift ? 3 : 1; - const moveSpeed = 0.1 * this.scene.getAnimationRatio(); - const move = dir.scale(moveSpeed * rate); - - if (this.canMove) { - this.camera.cameraDirection.addInPlace(move); - } - } - } - - getClassName() { - return 'HorizontalCameraKeyboardMoveInput'; - } - - getSimpleName() { - return 'horizontalkeyboard'; - } -} - -const nanasegNumberMap = [ - ['a', 'b', 'c', 'd', 'e', 'f'], // 0 - ['b', 'c'], // 1 - ['a', 'b', 'd', 'e', 'g'], // 2 - ['a', 'b', 'c', 'd', 'g'], // 3 - ['b', 'c', 'f', 'g'], // 4 - ['a', 'c', 'd', 'f', 'g'], // 5 - ['a', 'c', 'd', 'e', 'f', 'g'], // 6 - ['a', 'b', 'c'], // 7 - ['a', 'b', 'c', 'd', 'e', 'f', 'g'], // 8 - ['a', 'b', 'c', 'd', 'f', 'g'], // 9 -]; - -export function get7segMeshesOfCurrentTime(meshes: { - '1a'?: BABYLON.AbstractMesh; - '1b'?: BABYLON.AbstractMesh; - '1c'?: BABYLON.AbstractMesh; - '1d'?: BABYLON.AbstractMesh; - '1e'?: BABYLON.AbstractMesh; - '1f'?: BABYLON.AbstractMesh; - '1g'?: BABYLON.AbstractMesh; - '2a'?: BABYLON.AbstractMesh; - '2b'?: BABYLON.AbstractMesh; - '2c'?: BABYLON.AbstractMesh; - '2d'?: BABYLON.AbstractMesh; - '2e'?: BABYLON.AbstractMesh; - '2f'?: BABYLON.AbstractMesh; - '2g'?: BABYLON.AbstractMesh; - '3a'?: BABYLON.AbstractMesh; - '3b'?: BABYLON.AbstractMesh; - '3c'?: BABYLON.AbstractMesh; - '3d'?: BABYLON.AbstractMesh; - '3e'?: BABYLON.AbstractMesh; - '3f'?: BABYLON.AbstractMesh; - '3g'?: BABYLON.AbstractMesh; - '4a'?: BABYLON.AbstractMesh; - '4b'?: BABYLON.AbstractMesh; - '4c'?: BABYLON.AbstractMesh; - '4d'?: BABYLON.AbstractMesh; - '4e'?: BABYLON.AbstractMesh; - '4f'?: BABYLON.AbstractMesh; - '4g'?: BABYLON.AbstractMesh; -}) { - const now = new Date(); - const h = now.getHours(); - const m = now.getMinutes(); - - const chars = [Math.floor(h / 10), h % 10, Math.floor(m / 10), m % 10]; - - const result: BABYLON.AbstractMesh[] = []; - - for (let i = 0; i < chars.length; i++) { - const char = chars[i]; - const segs = nanasegNumberMap[char]; - for (const seg of segs) { - const mesh = meshes[`${i + 1}${seg}`]; - if (mesh) { - result.push(mesh); - } - } - } - - return result; -} - export function createOverridedStates any)>>(stateDefs: T): { [K in keyof T]: ReturnType; } & { $reset: () => void } { const overridedStates = {} as { [K in keyof T]: ReturnType; }; const result = {} as { [K in keyof T]: ReturnType; } & { $reset: () => void }; @@ -337,86 +161,6 @@ export function initTv(room: RoomEngine, screenMesh: BABYLON.Mesh) { }; } -/** - * 0 1 - * 0 a(x,y) --- b(x,y) - * | | - * 1 c(x,y) --- d(x,y) - */ -export function getPlaneUvIndexes(mesh: BABYLON.Mesh) { - const uvs = mesh.getVerticesData(BABYLON.VertexBuffer.UVKind); - if (uvs == null) { - throw new Error('Mesh does not have UV data'); - } - - let aIndex = 0; - let bIndex = 0; - let cIndex = 0; - let dIndex = 0; - - for (let i = 0; i < 8; i += 2) { - const x = uvs[i]; - const y = uvs[i + 1]; - - // 多少ずれがあってもいいように(例えばblenderではUV展開時にデフォルトでわずかなマージンを追加する)、中心より大きいか/小さいかで判定する - if (x < 0.5 && y < 0.5) { - aIndex = i; - } else if (x > 0.5 && y < 0.5) { - bIndex = i; - } else if (x < 0.5 && y > 0.5) { - cIndex = i; - } else if (x > 0.5 && y > 0.5) { - dIndex = i; - } - } - - return [aIndex, bIndex, cIndex, dIndex]; -} - -export function normalizeUvToSquare(mesh: BABYLON.Mesh) { - const uvs = mesh.getVerticesData(BABYLON.VertexBuffer.UVKind)!; - const uvIndexes = getPlaneUvIndexes(mesh); - uvs[uvIndexes[0]] = 0; - uvs[uvIndexes[0] + 1] = 0; - uvs[uvIndexes[1]] = 1; - uvs[uvIndexes[1] + 1] = 0; - uvs[uvIndexes[2]] = 0; - uvs[uvIndexes[2] + 1] = 1; - uvs[uvIndexes[3]] = 1; - uvs[uvIndexes[3] + 1] = 1; - mesh.updateVerticesData(BABYLON.VertexBuffer.UVKind, uvs); -} - -export function createPlaneUvMapper(mesh: BABYLON.Mesh) { - mesh.markVerticesDataAsUpdatable(BABYLON.VertexBuffer.UVKind, true); - - const uvs = mesh.getVerticesData(BABYLON.VertexBuffer.UVKind)!; - const originalUvs = uvs.slice(); - - return (srcAspect: number, targetAspect: number, method: 'cover' | 'contain' | 'stretch') => { - let uScale = 1; - let vScale = 1; - const ratio = targetAspect / srcAspect; - if (method === 'cover') { - uScale = ratio < 1 ? ratio : 1; - vScale = ratio < 1 ? 1 : 1 / ratio; - } else if (method === 'contain') { - uScale = ratio > 1 ? ratio : 1; - vScale = ratio > 1 ? 1 : 1 / ratio; - } else if (method === 'stretch') { - // nop - } - - // (0,0)を隅ではなく中心として扱いたいので0.5引いて計算してから最後に0.5足す - for (let i = 0; i < uvs.length; i += 2) { - uvs[i] = ((originalUvs[i] - 0.5) * uScale) + 0.5; - uvs[i + 1] = ((originalUvs[i + 1] - 0.5) * vScale) + 0.5; - } - - mesh.updateVerticesData(BABYLON.VertexBuffer.UVKind, uvs); - }; -} - export function findMaterial(rootMesh: BABYLON.AbstractMesh, keyword: string, allowMultiMaterial = false): BABYLON.PBRMaterial { for (const m of rootMesh.getChildMeshes()) { if (m.material == null) continue; @@ -442,87 +186,153 @@ export function findMaterial(rootMesh: BABYLON.AbstractMesh, keyword: string, al throw new Error(`Material with keyword "${keyword}" not found`); } -export function scaleMorph(mesh: BABYLON.Mesh, scale: [number, number, number], offset: [number, number, number] = [0, 0, 0]) { - if (!mesh.morphTargetManager) { - return; +export class ModelManager { + public root: BABYLON.Mesh; + public bakedCallback: (() => void) | null = null; + public bakeExcludeMeshes: BABYLON.Mesh[] = []; + private originalMeshes: BABYLON.Mesh[] = []; + private bakedMeshes: BABYLON.Mesh[] = []; + private hasTexture: boolean; + + constructor(root: BABYLON.Mesh, originalMeshes: BABYLON.Mesh[], hasTexture: boolean, bakedCallback: (() => void) | null = null) { + this.root = root; + this.originalMeshes = originalMeshes; + this.hasTexture = hasTexture; + this.bakedCallback = bakedCallback; } - const morphTargetManager = mesh.morphTargetManager; - - for (let targetIndex = 0; targetIndex < morphTargetManager.numTargets; targetIndex++) { - const target = morphTargetManager.getTarget(targetIndex); - const newPos = target.getPositions(); - for (let i = 0; i < newPos.length; i += 3) { - newPos[i] = (newPos[i] + offset[0]) * scale[0]; - newPos[i + 1] = (newPos[i + 1] + offset[1]) * scale[1]; - newPos[i + 2] = (newPos[i + 2] + offset[2]) * scale[2]; + public findMesh(keyword: string) { + const mesh = this.root.getChildMeshes().find(m => m.name.includes(keyword)); + if (mesh == null) { + throw new Error(`Mesh with keyword "${keyword}" not found for object ${this.root.metadata?.objectType}`); } - target.setPositions(newPos); + return mesh as BABYLON.Mesh; } - morphTargetManager.synchronize(); + public findMeshes(keyword: string) { + const meshes = this.root.getChildMeshes().filter(m => m.name.includes(keyword)); + return meshes as BABYLON.Mesh[]; + } - mesh.refreshBoundingInfo(); - mesh.computeWorldMatrix(true); + public findMaterial(keyword: string) { + return findMaterial(this.root, keyword); + } + + public findTransformNode(keyword: string) { + const node = this.root.getChildTransformNodes().find(n => n.name.includes(keyword)); + if (node == null) { + throw new Error(`TransformNode with keyword "${keyword}" not found for object ${this.root.metadata?.objectType}`); + } + return node; + } + + public updated() { + } + + public bakeMesh() { + for (const m of this.bakedMeshes) { + m.dispose(); + } + this.bakedMeshes = []; + + const excludeMeshes = [...this.bakeExcludeMeshes, ...this.root.getChildMeshes().filter(m => SYSTEM_MESH_NAMES.some(s => m.name.includes(s)))]; + + const childMeshes = this.root.getChildMeshes().filter(m => !excludeMeshes.some(x => x === m) && m.isVisible && !m.isDisposed()); + + if (childMeshes.length <= 1) { + this.bakedCallback?.([...childMeshes, ...excludeMeshes]); + return; + } + + const _toMerge = [] as BABYLON.Mesh[]; + for (const mesh of childMeshes) { + let fixedMesh = mesh; + fixedMesh.setEnabled(false); + + if (mesh instanceof BABYLON.InstancedMesh) { + const sourceMesh = mesh.sourceMesh; + const realizedMesh = sourceMesh.clone(mesh.name + '_realized', null, true); + realizedMesh.getScene().removeMesh(realizedMesh); + + 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; + realizedMesh.setEnabled(false); + + fixedMesh = realizedMesh; + } + + _toMerge.push(fixedMesh); + } + + const toMerge = [] as BABYLON.Mesh[]; + for (const mesh of _toMerge) { + const newMesh = mesh.name.endsWith('_realized') ? mesh : mesh.clone(mesh.name + '_bakeMerged', null, true); + newMesh.makeGeometryUnique(); + applyMorphTargetsToMesh(newMesh); + if (newMesh.parent === this.root) { + newMesh.parent = null; + } else { + newMesh.setParent(this.root); + //newMesh.bakeCurrentTransformIntoVertices(); + newMesh.parent = null; + } + //newMesh.bakeCurrentTransformIntoVertices(); + + if (this.hasTexture) { + if (newMesh.getVerticesData(BABYLON.VertexBuffer.UVKind) == null) { + const vertexCount = newMesh.getTotalVertices(); + const uvs = new Array(vertexCount * 2).fill(0); + newMesh.setVerticesData(BABYLON.VertexBuffer.UVKind, uvs, false, 2); + } + if (newMesh.getVerticesData(BABYLON.VertexBuffer.UV2Kind) == null) { + const vertexCount = newMesh.getTotalVertices(); + const uvs = new Array(vertexCount * 2).fill(0); + newMesh.setVerticesData(BABYLON.VertexBuffer.UV2Kind, uvs, false, 2); + } + } + + toMerge.push(newMesh); + } + + if (toMerge.length === 0) { + this.bakedCallback?.([...childMeshes, ...excludeMeshes]); + return; + } + + const merged = BABYLON.Mesh.MergeMeshes(toMerge, true, false, undefined, false, true); + merged.parent = this.root; + merged.material.freeze(); + if (merged.material instanceof BABYLON.MultiMaterial) { + for (const subMat of merged.material.subMaterials) { + (subMat as BABYLON.PBRMaterial).freeze(); + } + } + merged.freezeWorldMatrix(); + merged.metadata = { ...this.root.metadata }; + if (!this.hasTexture) merged.convertToUnIndexedMesh(); + this.bakedMeshes = [merged]; + + this.bakedCallback?.([...this.bakedMeshes, ...excludeMeshes]); + } + + public unbakeMesh() { + for (const m of this.bakedMeshes) { + m.dispose(); + } + this.bakedMeshes = []; + + const childMeshes = this.root.getChildMeshes(); + + for (const mesh of childMeshes) { + mesh.setEnabled(true); + } + + this.bakedCallback?.(this.root.getChildMeshes()); + } } - -export function applyMorphTargetsToMesh(mesh: BABYLON.Mesh) { - if (!mesh.morphTargetManager) { - return; - } - - const morphTargetManager = mesh.morphTargetManager; - const positions = mesh.getVerticesData(BABYLON.VertexBuffer.PositionKind); - - if (!positions) { - return; - } - - // Create a copy of the original positions to work with - const finalPositions = positions.slice(); - - // Apply each morph target - for (let targetIndex = 0; targetIndex < morphTargetManager.numTargets; targetIndex++) { - const target = morphTargetManager.getTarget(targetIndex); - const influence = target.influence; - - if (influence === 0) { - continue; - } - - // Get the morph target positions - const targetPositions = target.getPositions(); - - if (!targetPositions || targetPositions.length !== positions.length) { - console.warn(`Morph target ${targetIndex} has invalid position data`); - continue; - } - - // Apply the morph target influence to each vertex - for (let i = 0; i < positions.length; i++) { - finalPositions[i] += (targetPositions[i] - positions[i]) * influence; - } - } - - // Update the mesh with the morphed positions - mesh.setVerticesData(BABYLON.VertexBuffer.PositionKind, finalPositions); - - //// Update normals if available - //const normals = mesh.getVerticesData(BABYLON.VertexBuffer.NormalKind); - //if (normals) { - // // For simplicity, we'll just recompute the normals - // mesh.createNormals(true); - //} - - // Refresh the mesh - mesh.refreshBoundingInfo(); - mesh.computeWorldMatrix(true); -} - -// ex) hangingTShirt -> hanging-t-shirt -export const camelToKebab = (s: string) => { - return s - .replace(/([a-z0-9])([A-Z])/g, '$1-$2') - .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2') - .toLowerCase(); -}; diff --git a/packages/frontend/src/world/utility.ts b/packages/frontend/src/world/utility.ts new file mode 100644 index 0000000000..3807be351f --- /dev/null +++ b/packages/frontend/src/world/utility.ts @@ -0,0 +1,393 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as BABYLON from '@babylonjs/core'; + +export const WORLD_SCALE = 100; + +//export const cm = (value: number) => value / 100; +export const cm = (value: number) => value; + +export const TIME_MAP = { + 0: 2, + 1: 2, + 2: 2, + 3: 2, + 4: 2, + 5: 1, + 6: 1, + 7: 0, + 8: 0, + 9: 0, + 10: 0, + 11: 0, + 12: 0, + 13: 0, + 14: 0, + 15: 0, + 16: 1, + 17: 1, + 18: 2, + 19: 2, + 20: 2, + 21: 2, + 22: 2, + 23: 2, +} as const; + +export class HorizontalCameraKeyboardMoveInput extends BABYLON.BaseCameraPointersInput { + public camera: BABYLON.FreeCamera; + private engine: BABYLON.AbstractEngine; + private scene: BABYLON.Scene; + preShift = false; + codes = []; + codesUp = ['KeyW']; + codesDown = ['KeyS']; + codesLeft = ['KeyA']; + codesRight = ['KeyD']; + onCanvasBlurObserver = null; + onKeyboardObserver = null; + public canMove = true; + + constructor(camera: BABYLON.UniversalCamera) { + super(); + this.camera = camera; + this.scene = this.camera.getScene(); + this.engine = this.scene.getEngine(); + } + + attachControl() { + if (this.onCanvasBlurObserver) { + return; + } + + this.onCanvasBlurObserver = this.engine.onCanvasBlurObservable.add(() => { + this.codes = []; + }); + + this.onKeyboardObserver = this.scene.onKeyboardObservable.add(({ event, type }) => { + const { code, shiftKey } = event; + this.preShift = shiftKey; + + if (type === BABYLON.KeyboardEventTypes.KEYDOWN) { + if (this.codesUp.indexOf(code) >= 0 || + this.codesDown.indexOf(code) >= 0 || + this.codesLeft.indexOf(code) >= 0 || + this.codesRight.indexOf(code) >= 0) { + const index = this.codes.findIndex(v => v.code === code); + if (index < 0) { // 存在しなかったら追加する + this.codes.push({ code }); + } + } + } else { + if (this.codesUp.indexOf(code) >= 0 || + this.codesDown.indexOf(code) >= 0 || + this.codesLeft.indexOf(code) >= 0 || + this.codesRight.indexOf(code) >= 0) { + const index = this.codes.findIndex(v => v.code === code); + if (index >= 0) { // 存在したら削除する + this.codes.splice(index, 1); + } + } + } + }); + } + + detachControl() { + this.codes = []; + + if (this.onKeyboardObserver) this.scene.onKeyboardObservable.remove(this.onKeyboardObserver); + if (this.onCanvasBlurObserver) this.engine.onCanvasBlurObservable.remove(this.onCanvasBlurObserver); + this.onKeyboardObserver = null; + this.onCanvasBlurObserver = null; + } + + checkInputs() { + if (!this.onKeyboardObserver) { + return; + } + for (let index = 0; index < this.codes.length; index++) { + const { code } = this.codes[index]; + + const local = new BABYLON.Vector3(); + if (this.codesLeft.indexOf(code) >= 0) { + local.x += -1; + } else if (this.codesUp.indexOf(code) >= 0) { + local.z += this.scene.useRightHandedSystem ? -1 : 1; + } else if (this.codesRight.indexOf(code) >= 0) { + local.x += 1; + } else if (this.codesDown.indexOf(code) >= 0) { + local.z += this.scene.useRightHandedSystem ? 1 : -1; + } + + if (local.length() === 0) { + continue; + } + + const dir = this.camera.getDirection(local.normalize()); + dir.y = 0; + dir.normalize(); + const rate = this.preShift ? 3 : 1; + const moveSpeed = 0.1 * this.scene.getAnimationRatio(); + const move = dir.scale(moveSpeed * rate); + + if (this.canMove) { + this.camera.cameraDirection.addInPlace(move); + } + } + } + + getClassName() { + return 'HorizontalCameraKeyboardMoveInput'; + } + + getSimpleName() { + return 'horizontalkeyboard'; + } +} + +const nanasegNumberMap = [ + ['a', 'b', 'c', 'd', 'e', 'f'], // 0 + ['b', 'c'], // 1 + ['a', 'b', 'd', 'e', 'g'], // 2 + ['a', 'b', 'c', 'd', 'g'], // 3 + ['b', 'c', 'f', 'g'], // 4 + ['a', 'c', 'd', 'f', 'g'], // 5 + ['a', 'c', 'd', 'e', 'f', 'g'], // 6 + ['a', 'b', 'c'], // 7 + ['a', 'b', 'c', 'd', 'e', 'f', 'g'], // 8 + ['a', 'b', 'c', 'd', 'f', 'g'], // 9 +]; + +export function get7segMeshesOfCurrentTime(meshes: { + '1a'?: BABYLON.AbstractMesh; + '1b'?: BABYLON.AbstractMesh; + '1c'?: BABYLON.AbstractMesh; + '1d'?: BABYLON.AbstractMesh; + '1e'?: BABYLON.AbstractMesh; + '1f'?: BABYLON.AbstractMesh; + '1g'?: BABYLON.AbstractMesh; + '2a'?: BABYLON.AbstractMesh; + '2b'?: BABYLON.AbstractMesh; + '2c'?: BABYLON.AbstractMesh; + '2d'?: BABYLON.AbstractMesh; + '2e'?: BABYLON.AbstractMesh; + '2f'?: BABYLON.AbstractMesh; + '2g'?: BABYLON.AbstractMesh; + '3a'?: BABYLON.AbstractMesh; + '3b'?: BABYLON.AbstractMesh; + '3c'?: BABYLON.AbstractMesh; + '3d'?: BABYLON.AbstractMesh; + '3e'?: BABYLON.AbstractMesh; + '3f'?: BABYLON.AbstractMesh; + '3g'?: BABYLON.AbstractMesh; + '4a'?: BABYLON.AbstractMesh; + '4b'?: BABYLON.AbstractMesh; + '4c'?: BABYLON.AbstractMesh; + '4d'?: BABYLON.AbstractMesh; + '4e'?: BABYLON.AbstractMesh; + '4f'?: BABYLON.AbstractMesh; + '4g'?: BABYLON.AbstractMesh; +}) { + const now = new Date(); + const h = now.getHours(); + const m = now.getMinutes(); + + const chars = [Math.floor(h / 10), h % 10, Math.floor(m / 10), m % 10]; + + const result: BABYLON.AbstractMesh[] = []; + + for (let i = 0; i < chars.length; i++) { + const char = chars[i]; + const segs = nanasegNumberMap[char]; + for (const seg of segs) { + const mesh = meshes[`${i + 1}${seg}`]; + if (mesh) { + result.push(mesh); + } + } + } + + return result; +} + +/** + * 0 1 + * 0 a(x,y) --- b(x,y) + * | | + * 1 c(x,y) --- d(x,y) + */ +export function getPlaneUvIndexes(mesh: BABYLON.Mesh) { + const uvs = mesh.getVerticesData(BABYLON.VertexBuffer.UVKind); + if (uvs == null) { + throw new Error('Mesh does not have UV data'); + } + + let aIndex = 0; + let bIndex = 0; + let cIndex = 0; + let dIndex = 0; + + for (let i = 0; i < 8; i += 2) { + const x = uvs[i]; + const y = uvs[i + 1]; + + // 多少ずれがあってもいいように(例えばblenderではUV展開時にデフォルトでわずかなマージンを追加する)、中心より大きいか/小さいかで判定する + if (x < 0.5 && y < 0.5) { + aIndex = i; + } else if (x > 0.5 && y < 0.5) { + bIndex = i; + } else if (x < 0.5 && y > 0.5) { + cIndex = i; + } else if (x > 0.5 && y > 0.5) { + dIndex = i; + } + } + + return [aIndex, bIndex, cIndex, dIndex]; +} + +export function normalizeUvToSquare(mesh: BABYLON.Mesh) { + const uvs = mesh.getVerticesData(BABYLON.VertexBuffer.UVKind)!; + const uvIndexes = getPlaneUvIndexes(mesh); + uvs[uvIndexes[0]] = 0; + uvs[uvIndexes[0] + 1] = 0; + uvs[uvIndexes[1]] = 1; + uvs[uvIndexes[1] + 1] = 0; + uvs[uvIndexes[2]] = 0; + uvs[uvIndexes[2] + 1] = 1; + uvs[uvIndexes[3]] = 1; + uvs[uvIndexes[3] + 1] = 1; + mesh.updateVerticesData(BABYLON.VertexBuffer.UVKind, uvs); +} + +export function createPlaneUvMapper(mesh: BABYLON.Mesh) { + mesh.markVerticesDataAsUpdatable(BABYLON.VertexBuffer.UVKind, true); + + const uvs = mesh.getVerticesData(BABYLON.VertexBuffer.UVKind)!; + const originalUvs = uvs.slice(); + + return (srcAspect: number, targetAspect: number, method: 'cover' | 'contain' | 'stretch') => { + let uScale = 1; + let vScale = 1; + const ratio = targetAspect / srcAspect; + if (method === 'cover') { + uScale = ratio < 1 ? ratio : 1; + vScale = ratio < 1 ? 1 : 1 / ratio; + } else if (method === 'contain') { + uScale = ratio > 1 ? ratio : 1; + vScale = ratio > 1 ? 1 : 1 / ratio; + } else if (method === 'stretch') { + // nop + } + + // (0,0)を隅ではなく中心として扱いたいので0.5引いて計算してから最後に0.5足す + for (let i = 0; i < uvs.length; i += 2) { + uvs[i] = ((originalUvs[i] - 0.5) * uScale) + 0.5; + uvs[i + 1] = ((originalUvs[i + 1] - 0.5) * vScale) + 0.5; + } + + mesh.updateVerticesData(BABYLON.VertexBuffer.UVKind, uvs); + }; +} + +export function scaleMorph(mesh: BABYLON.Mesh, scale: [number, number, number], offset: [number, number, number] = [0, 0, 0]) { + if (!mesh.morphTargetManager) { + return; + } + + const morphTargetManager = mesh.morphTargetManager; + + for (let targetIndex = 0; targetIndex < morphTargetManager.numTargets; targetIndex++) { + const target = morphTargetManager.getTarget(targetIndex); + const newPos = target.getPositions(); + for (let i = 0; i < newPos.length; i += 3) { + newPos[i] = (newPos[i] + offset[0]) * scale[0]; + newPos[i + 1] = (newPos[i + 1] + offset[1]) * scale[1]; + newPos[i + 2] = (newPos[i + 2] + offset[2]) * scale[2]; + } + target.setPositions(newPos); + } + + morphTargetManager.synchronize(); + + mesh.refreshBoundingInfo(); + mesh.computeWorldMatrix(true); +} + +export function applyMorphTargetsToMesh(mesh: BABYLON.Mesh) { + if (!mesh.morphTargetManager) { + return; + } + + const morphTargetManager = mesh.morphTargetManager; + const positions = mesh.getVerticesData(BABYLON.VertexBuffer.PositionKind); + + if (!positions) { + return; + } + + // Create a copy of the original positions to work with + const finalPositions = positions.slice(); + + // Apply each morph target + for (let targetIndex = 0; targetIndex < morphTargetManager.numTargets; targetIndex++) { + const target = morphTargetManager.getTarget(targetIndex); + const influence = target.influence; + + if (influence === 0) { + continue; + } + + // Get the morph target positions + const targetPositions = target.getPositions(); + + if (!targetPositions || targetPositions.length !== positions.length) { + console.warn(`Morph target ${targetIndex} has invalid position data`); + continue; + } + + // Apply the morph target influence to each vertex + for (let i = 0; i < positions.length; i++) { + finalPositions[i] += (targetPositions[i] - positions[i]) * influence; + } + } + + // Update the mesh with the morphed positions + mesh.setVerticesData(BABYLON.VertexBuffer.PositionKind, finalPositions); + + //// Update normals if available + //const normals = mesh.getVerticesData(BABYLON.VertexBuffer.NormalKind); + //if (normals) { + // // For simplicity, we'll just recompute the normals + // mesh.createNormals(true); + //} + + // Refresh the mesh + mesh.refreshBoundingInfo(); + mesh.computeWorldMatrix(true); +} + +// ex) hangingTShirt -> hanging-t-shirt +export const camelToKebab = (s: string) => { + return s + .replace(/([a-z0-9])([A-Z])/g, '$1-$2') + .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2') + .toLowerCase(); +}; + +// この実装方法だとマイナスの座標をうまく処理できず結果がおかしくなるので応急処置で全体を+10000cmオフセットしてから計算している +export function getMeshesBoundingBox(meshes: BABYLON.Mesh[]): BABYLON.BoundingBox { + let min = new BABYLON.Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE); + let max = new BABYLON.Vector3(Number.MIN_VALUE, Number.MIN_VALUE, Number.MIN_VALUE); + + for (const mesh of meshes) { + const boundingInfo = mesh.getBoundingInfo(); + min = BABYLON.Vector3.Minimize(min, boundingInfo.boundingBox.minimumWorld.add(new BABYLON.Vector3(10000, 10000, 10000))); + max = BABYLON.Vector3.Maximize(max, boundingInfo.boundingBox.maximumWorld.add(new BABYLON.Vector3(10000, 10000, 10000))); + } + + return new BABYLON.BoundingBox(min.subtract(new BABYLON.Vector3(10000, 10000, 10000)), max.subtract(new BABYLON.Vector3(10000, 10000, 10000))); +}