diff --git a/packages/frontend/assets/room/objects/poster/poster.blend b/packages/frontend/assets/room/objects/poster/poster.blend index 608d038353..bc1d82b055 100644 Binary files a/packages/frontend/assets/room/objects/poster/poster.blend and b/packages/frontend/assets/room/objects/poster/poster.blend differ diff --git a/packages/frontend/assets/room/objects/poster/poster.glb b/packages/frontend/assets/room/objects/poster/poster.glb index 9fe6b411cf..9136fe27b7 100644 Binary files a/packages/frontend/assets/room/objects/poster/poster.glb and b/packages/frontend/assets/room/objects/poster/poster.glb differ diff --git a/packages/frontend/src/utility/room/engine.ts b/packages/frontend/src/utility/room/engine.ts index 5cfe1e007d..72c452b17c 100644 --- a/packages/frontend/src/utility/room/engine.ts +++ b/packages/frontend/src/utility/room/engine.ts @@ -17,12 +17,15 @@ * - 後からモデルを調整したくなった時に備え、モディファイアを駆使するなどして、なるべく非破壊的なモデリングを心がけることを推奨します。 * - モディファイアをapplyしないとならないシチュエーションでは、apply前の状態を複製して(非表示にした上で)残すことを推奨します。 * - 上記の非破壊的なモデリングの原則に反しない限り、なるべくscaleはapplyした状態で(=scaleが1, 1, 1の状態で)エクスポートすること。そうしないとbake前後で法線が変わるのかレンダリング結果が異なる現象が発生することがあります。 + * - 現在のMisskey RoomのGLB root除去システムの実装上、スケールに1以外の値を持つメッシュにシェイプキーを設定することはサポートされていません。 */ // TODO: 家具設置時のコリジョン判定(めりこんで設置されないようにする) // TODO: 近くのオブジェクトの端にスナップオプション // TODO: 近くのオブジェクトの原点に軸を揃えるオプション +const BAKE_TRANSFORM = false; // 実験的 + import * as BABYLON from '@babylonjs/core'; import { AxesViewer } from '@babylonjs/core/Debug/axesViewer'; import { registerBuiltInLoaders } from '@babylonjs/loaders/dynamic'; @@ -33,7 +36,7 @@ import { reactive, ref, shallowRef, triggerRef, watch } from 'vue'; import { genId } from '../id.js'; import { deepClone } from '../clone.js'; import { getObjectDef } from './object-defs.js'; -import { HorizontalCameraKeyboardMoveInput, applyMorphTargetsToMesh, camelToKebab, findMaterial } from './utility.js'; +import { HorizontalCameraKeyboardMoveInput, applyMorphTargetsToMesh, camelToKebab, findMaterial, scaleMorph } from './utility.js'; import * as sound from '@/utility/sound.js'; // babylonのドメイン知識は持たない @@ -1044,8 +1047,136 @@ export class RoomEngine { const loaderResult = await BABYLON.LoadAssetContainerAsync(`/client-assets/room/objects/${camelToKebab(args.type)}/${camelToKebab(args.type)}.glb`, this.scene); // babylonによって自動で追加される右手系変換用ノード - const subRoot = loaderResult.meshes[0]; - subRoot.scaling = subRoot.scaling.scale(WORLD_SCALE);// cmをmに + const subRoot = loaderResult.meshes[0] as BABYLON.Mesh; + + if (BAKE_TRANSFORM) { + subRoot.scaling = new BABYLON.Vector3(1, 1, 1); + subRoot.rotationQuaternion = null; + subRoot.rotation = new BABYLON.Vector3(0, 0, 0); + //subRoot.scaling = subRoot.scaling.scale(WORLD_SCALE);// cmをmに + //subRoot.bakeCurrentTransformIntoVertices(); + //subRoot.bakeTransformIntoVertices(BABYLON.Matrix.Scaling(WORLD_SCALE, WORLD_SCALE, WORLD_SCALE)); + + for (const m of loaderResult.transformNodes) { + if (m.name === '__root__') continue; + if (m.parent === subRoot) { + m.setParent(root); + //m.parent = root; + } + } + for (const m of loaderResult.meshes) { + if (m.name === '__root__') continue; + if (m.parent === subRoot) { + m.setParent(root); + //m.parent = root; + } + } + + const bakeTransformNode = (m: BABYLON.TransformNode) => { + m.position.x *= -WORLD_SCALE; + m.position.y *= WORLD_SCALE; + m.position.z *= WORLD_SCALE; + m.rotation = m.rotationQuaternion.toEulerAngles(); + m.rotationQuaternion = null; + //m.rotation.x = -m.rotation.x; + m.rotation.y = -m.rotation.y; + m.rotation.z = -m.rotation.z; + for (const child of m.getChildren()) { + if (child instanceof BABYLON.Mesh) { + //child.scaling = child.scaling.scale(WORLD_SCALE);// cmをmに + //child.position = child.position.scale(WORLD_SCALE); + + const pos = child.position.clone(); + const scaling = child.scaling.clone(); + child.scaling.x = -WORLD_SCALE; + child.scaling.y = WORLD_SCALE; + child.scaling.z = WORLD_SCALE; + + const rotation = child.rotationQuaternion ? child.rotationQuaternion.toEulerAngles() : child.rotation.clone(); + child.rotationQuaternion = null; + child.position = new BABYLON.Vector3(0, 0, 0); + + child.parent = root; + child.bakeCurrentTransformIntoVertices(); + child.parent = m; + child.scaling = scaling; + child.position.x = pos.x * -WORLD_SCALE; + child.position.y = pos.y * WORLD_SCALE; + child.position.z = pos.z * WORLD_SCALE; + + child.rotation = rotation; + + scaleMorph(child, [-WORLD_SCALE, WORLD_SCALE, WORLD_SCALE]); + + //const indices = child.getIndices(); + //const positions = child.getVerticesData(BABYLON.VertexBuffer.PositionKind); + //const normals = child.getVerticesData(BABYLON.VertexBuffer.NormalKind); + //BABYLON.VertexData.ComputeNormals(positions, indices, normals); + //child.updateVerticesData(BABYLON.VertexBuffer.NormalKind, normals, false, false); + } else if (child instanceof BABYLON.InstancedMesh) { + const pos = child.position.clone(); + child.position.x = pos.x * -WORLD_SCALE; + child.position.y = pos.y * WORLD_SCALE; + child.position.z = pos.z * WORLD_SCALE; + } else if (child instanceof BABYLON.TransformNode) { + bakeTransformNode(child); + } + } + }; + + const bakeChildren = (node: BABYLON.Node) => { + for (const m of node.getChildren(undefined, true)) { + if (m instanceof BABYLON.Mesh) { + const scaling = m.scaling.clone(); + m.scaling.x = -WORLD_SCALE; + m.scaling.y = WORLD_SCALE; + m.scaling.z = WORLD_SCALE; + //m.position.x *= -WORLD_SCALE; + //m.position.y *= WORLD_SCALE; + //m.position.z *= WORLD_SCALE; + const pos = m.position.clone(); + const rotation = m.rotationQuaternion.toEulerAngles(); + m.rotationQuaternion = null; + m.rotation = new BABYLON.Vector3(0, 0, 0); + m.position = new BABYLON.Vector3(0, 0, 0); + m.bakeCurrentTransformIntoVertices(); + m.scaling.x = scaling.x; + m.scaling.y = scaling.y; + m.scaling.z = scaling.z; + m.position.x = pos.x * -WORLD_SCALE; + m.position.y = pos.y * WORLD_SCALE; + m.position.z = pos.z * WORLD_SCALE; + m.rotation = rotation; + //m.rotation.x = -m.rotation.x; + m.rotation.y = -m.rotation.y; + m.rotation.z = -m.rotation.z; + + scaleMorph(m, [-WORLD_SCALE, WORLD_SCALE, WORLD_SCALE]); + } else if (m instanceof BABYLON.InstancedMesh) { + //const pos = m.position.clone(); + //m.position.x = pos.x * -WORLD_SCALE; + //m.position.y = pos.y * WORLD_SCALE; + //m.position.z = pos.z * WORLD_SCALE; + m.position.x *= -WORLD_SCALE; + m.position.y *= WORLD_SCALE; + m.position.z *= WORLD_SCALE; + m.rotation = m.rotationQuaternion.toEulerAngles(); + m.rotationQuaternion = null; + m.rotation.x = -m.rotation.x; + m.rotation.y = -m.rotation.y; + m.rotation.z = -m.rotation.z; + } else if (m instanceof BABYLON.TransformNode) { + bakeTransformNode(m); + } + } + }; + + bakeChildren(root); + + subRoot.dispose(); + } else { + subRoot.scaling = subRoot.scaling.scale(WORLD_SCALE);// cmをmに + } def.treatLoaderResult?.(loaderResult); @@ -1055,13 +1186,15 @@ export class RoomEngine { objectType: args.type, }; - root.addChild(subRoot); + if (!BAKE_TRANSFORM) { + root.addChild(subRoot); + } root.position = args.position.clone(); root.rotation = args.rotation.clone(); root.metadata = metadata; - const model = new ModelManager(subRoot, loaderResult.meshes.filter(m => m !== subRoot), (meshes) => { + const model = new ModelManager(BAKE_TRANSFORM ? root : subRoot, loaderResult.meshes.filter(m => m.name !== subRoot), (meshes) => { if (this.selected.value?.objectId === args.id) { this.highlightMeshes(meshes); } diff --git a/packages/frontend/src/utility/room/objects/allInOnePc.ts b/packages/frontend/src/utility/room/objects/allInOnePc.ts index 7245da586f..9c2f24fdb8 100644 --- a/packages/frontend/src/utility/room/objects/allInOnePc.ts +++ b/packages/frontend/src/utility/room/objects/allInOnePc.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject, WORLD_SCALE } from '../engine.js'; +import { defineObject } from '../engine.js'; import { createPlaneUvMapper } from '../utility.js'; export const allInOnePc = defineObject({ @@ -47,7 +47,11 @@ export const allInOnePc = defineObject({ }, placement: 'top', createInstance: async ({ room, scene, options, model }) => { - const light = new BABYLON.SpotLight('', new BABYLON.Vector3(0/*cm*/, 30/*cm*/ / WORLD_SCALE, 0), new BABYLON.Vector3(0, 0, 1), Math.PI / 1, 2, scene, room?.lightContainer != null); + const matrix = model.root.getWorldMatrix(true); + const scale = new BABYLON.Vector3(); + matrix.decompose(scale); + + const light = new BABYLON.SpotLight('', new BABYLON.Vector3(0/*cm*/, 30/*cm*/ / Math.abs(scale.y), 0), new BABYLON.Vector3(0, 0, 1), Math.PI / 1, 2, scene, room?.lightContainer != null); light.parent = model.root; light.diffuse = new BABYLON.Color3(1.0, 1.0, 1.0); light.range = 100/*cm*/; diff --git a/packages/frontend/src/utility/room/objects/blind.ts b/packages/frontend/src/utility/room/objects/blind.ts index f6816a187e..0619c499e6 100644 --- a/packages/frontend/src/utility/room/objects/blind.ts +++ b/packages/frontend/src/utility/room/objects/blind.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject, WORLD_SCALE } from '../engine.js'; +import { defineObject } from '../engine.js'; import { createOverridedStates } from '../utility.js'; export const blind = defineObject({ @@ -57,12 +57,16 @@ export const blind = defineObject({ } blades = []; + const matrix = blade.parent.getWorldMatrix(true); + const scale = new BABYLON.Vector3(); + matrix.decompose(scale); + for (let i = 0; i < options.blades; i++) { const b = blade.clone('blade_' + i); // createInstanceを使いたいが、削除するときになぜかエラーになる if (i / options.blades < temp.open) { - b.position.y -= (i * 4/*cm*/) / WORLD_SCALE; + b.position.y -= (i * 4/*cm*/) / Math.abs(scale.y); } else { - b.position.y -= (((options.blades - 1) * temp.open * 4/*cm*/) + (i * 0.3/*cm*/)) / WORLD_SCALE; + b.position.y -= (((options.blades - 1) * temp.open * 4/*cm*/) + (i * 0.3/*cm*/)) / Math.abs(scale.y); } blades.push(b); } diff --git a/packages/frontend/src/utility/room/objects/laptopPc.ts b/packages/frontend/src/utility/room/objects/laptopPc.ts index 35f85484e5..db05885028 100644 --- a/packages/frontend/src/utility/room/objects/laptopPc.ts +++ b/packages/frontend/src/utility/room/objects/laptopPc.ts @@ -4,7 +4,7 @@ */ import * as BABYLON from '@babylonjs/core'; -import { defineObject, WORLD_SCALE } from '../engine.js'; +import { defineObject } from '../engine.js'; import { createPlaneUvMapper } from '../utility.js'; export const laptopPc = defineObject({ @@ -55,10 +55,14 @@ export const laptopPc = defineObject({ }, placement: 'top', createInstance: async ({ room, scene, options, model }) => { + const matrix = model.root.getWorldMatrix(true); + const scale = new BABYLON.Vector3(); + matrix.decompose(scale); + const screenMesh = model.findMesh('__X_SCREEN__'); const hutaNode = model.findTransformNode('__X_HUTA__'); - const light = new BABYLON.SpotLight('', new BABYLON.Vector3(0/*cm*/, 10/*cm*/ / WORLD_SCALE, 0), new BABYLON.Vector3(0, 0, 1), Math.PI / 1, 2, scene, room?.lightContainer != null); + const light = new BABYLON.SpotLight('', new BABYLON.Vector3(0/*cm*/, 10/*cm*/ / Math.abs(scale.y), 0), new BABYLON.Vector3(0, 0, 1), Math.PI / 1, 2, scene, room?.lightContainer != null); light.parent = hutaNode; light.diffuse = new BABYLON.Color3(1.0, 1.0, 1.0); light.range = 100/*cm*/; diff --git a/packages/frontend/src/utility/room/utility.ts b/packages/frontend/src/utility/room/utility.ts index 6abd1ab3d6..d9399dc09f 100644 --- a/packages/frontend/src/utility/room/utility.ts +++ b/packages/frontend/src/utility/room/utility.ts @@ -491,6 +491,30 @@ export function findMaterial(rootMesh: BABYLON.AbstractMesh, keyword: string): B 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; + } + + 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;