1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-06-04 13:34:10 +02:00
This commit is contained in:
syuilo
2026-03-26 21:56:05 +09:00
parent 5458ee016d
commit c558f93a0e
2 changed files with 82 additions and 49 deletions

View File

@@ -16,8 +16,9 @@ SPDX-License-Identifier: AGPL-3.0-only
> >
<template #header><i class="ti ti-box"></i> カタログ</template> <template #header><i class="ti ti-box"></i> カタログ</template>
<div :class="$style.root">
<div :class="$style.container"> <div :class="$style.container">
<div> <div :class="$style.menu">
<div <div
v-for="def in OBJECT_DEFS" v-for="def in OBJECT_DEFS"
:key="def.id" :key="def.id"
@@ -29,15 +30,17 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<canvas ref="canvas" :class="$style.canvas"></canvas> <canvas ref="canvas" :class="$style.canvas"></canvas>
</div> </div>
</div>
</MkModalWindow> </MkModalWindow>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive, nextTick } from 'vue'; import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive, nextTick, shallowRef } from 'vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import MkModalWindow from '@/components/MkModalWindow.vue'; import MkModalWindow from '@/components/MkModalWindow.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { OBJECT_DEFS } from '@/utility/room/object-defs.js'; import { OBJECT_DEFS } from '@/utility/room/object-defs.js';
import { RoomObjectPreviewEngine } from '@/utility/room/engine.js';
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'ok', id: string): void; (ev: 'ok', id: string): void;
@@ -48,6 +51,26 @@ const emit = defineEmits<{
const dialog = useTemplateRef('dialog'); const dialog = useTemplateRef('dialog');
const canvas = useTemplateRef('canvas'); const canvas = useTemplateRef('canvas');
const selectedId = ref<string | null>(null); const selectedId = ref<string | null>(null);
const engine = shallowRef<RoomObjectPreviewEngine | null>(null);
onMounted(() => {
engine.value = new RoomObjectPreviewEngine({
canvas: canvas.value!,
});
engine.value.init();
canvas.value!.focus();
});
onUnmounted(() => {
engine.value.destroy();
});
watch(selectedId, (newId) => {
if (newId == null) return;
engine.value!.load(newId);
});
function ok() { function ok() {
if (selectedId.value == null) return; if (selectedId.value == null) return;
@@ -62,10 +85,19 @@ async function cancel() {
</script> </script>
<style module> <style module>
.root {
container-type: inline-size;
height: 100%;
}
.container { .container {
height: 100%; height: 100%;
display: grid; display: grid;
grid-template-columns: 1fr 400px; grid-template-columns: 400px 1fr;
}
.menu {
overflow-y: scroll;
} }
.catalogItem { .catalogItem {
@@ -75,8 +107,8 @@ async function cancel() {
cursor: pointer; cursor: pointer;
} }
.selected { .selected {
background-color: var(--accent-color); color: var(--MI_THEME-accent);
color: var(--accent-text-color); background-color: var(--MI_THEME-accentedBg);
} }
.canvas { .canvas {

View File

@@ -945,7 +945,7 @@ export class RoomEngine {
findMesh: (keyword) => { findMesh: (keyword) => {
const mesh = root.getChildMeshes().find(m => m.name.includes(keyword)); const mesh = root.getChildMeshes().find(m => m.name.includes(keyword));
if (mesh == null) { if (mesh == null) {
throw new Error(`Mesh with keyword "${keyword}" not found for object ${args.type} (${args.id})`); throw new Error(`Mesh with keyword "${keyword}" not found for object ${args.type}`);
} }
return mesh as BABYLON.Mesh; return mesh as BABYLON.Mesh;
}, },
@@ -959,7 +959,7 @@ export class RoomEngine {
findTransformNode: (keyword) => { findTransformNode: (keyword) => {
const node = root.getChildTransformNodes().find(n => n.name.includes(keyword)); const node = root.getChildTransformNodes().find(n => n.name.includes(keyword));
if (node == null) { if (node == null) {
throw new Error(`TransformNode with keyword "${keyword}" not found for object ${args.type} (${args.id})`); throw new Error(`TransformNode with keyword "${keyword}" not found for object ${args.type}`);
} }
return node; return node;
}, },
@@ -1340,16 +1340,16 @@ export class RoomEngine {
export class RoomObjectPreviewEngine { export class RoomObjectPreviewEngine {
private canvas: HTMLCanvasElement; private canvas: HTMLCanvasElement;
private engine: BABYLON.Engine; private engine: BABYLON.Engine;
public scene: BABYLON.Scene; private scene: BABYLON.Scene;
private shadowGenerator1: BABYLON.ShadowGenerator; private shadowGenerator1: BABYLON.ShadowGenerator;
private camera: BABYLON.ArcRotateCamera; private camera: BABYLON.ArcRotateCamera;
private objectMeshs: Map<string, BABYLON.Mesh> = new Map(); private objectMesh: BABYLON.Mesh | null = null;
public objectInstance: RoomObjectInstance<any> | null = null; private objectInstance: RoomObjectInstance<any> | null = null;
private envMapIndoor: BABYLON.CubeTexture; private envMapIndoor: BABYLON.CubeTexture;
private roomLight: BABYLON.SpotLight; private roomLight: BABYLON.SpotLight;
private zGridPreviewPlane: BABYLON.Mesh; private zGridPreviewPlane: BABYLON.Mesh;
constructor(roomState: RoomState, options: { constructor(options: {
canvas: HTMLCanvasElement; canvas: HTMLCanvasElement;
}) { }) {
this.canvas = options.canvas; this.canvas = options.canvas;
@@ -1373,6 +1373,8 @@ export class RoomObjectPreviewEngine {
this.camera.upperBetaLimit = (Math.PI / 2) + 0.1; this.camera.upperBetaLimit = (Math.PI / 2) + 0.1;
this.camera.lowerRadiusLimit = 50/*cm*/; this.camera.lowerRadiusLimit = 50/*cm*/;
this.camera.upperRadiusLimit = 1000/*cm*/; this.camera.upperRadiusLimit = 1000/*cm*/;
this.camera.useAutoRotationBehavior = true;
this.camera.autoRotationBehavior!.idleRotationSpeed = 0.2;
this.scene.activeCamera = this.camera; this.scene.activeCamera = this.camera;
const ambientLight = new BABYLON.HemisphericLight('ambientLight', new BABYLON.Vector3(0, 1, -0.5), this.scene); const ambientLight = new BABYLON.HemisphericLight('ambientLight', new BABYLON.Vector3(0, 1, -0.5), this.scene);
@@ -1401,15 +1403,7 @@ export class RoomObjectPreviewEngine {
this.zGridPreviewPlane = BABYLON.MeshBuilder.CreatePlane('zGridPreviewPlane', { width: 1000/*cm*/, height: 1000/*cm*/ }, this.scene); this.zGridPreviewPlane = BABYLON.MeshBuilder.CreatePlane('zGridPreviewPlane', { width: 1000/*cm*/, height: 1000/*cm*/ }, this.scene);
this.zGridPreviewPlane.material = gridMaterial; this.zGridPreviewPlane.material = gridMaterial;
this.zGridPreviewPlane.isPickable = false; this.zGridPreviewPlane.rotation = new BABYLON.Vector3(Math.PI / 2, 0, 0);
this.zGridPreviewPlane.isVisible = false;
if (_DEV_) {
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);
}
} }
public async init() { public async init() {
@@ -1418,12 +1412,32 @@ export class RoomObjectPreviewEngine {
}); });
} }
public async load(type: string) {
if (this.objectInstance != null) {
this.objectInstance.dispose?.();
this.objectInstance = null;
this.objectMesh!.dispose();
}
const def = getObjectDef(type);
const options = deepClone(def.options.default);
await this.loadObject({
type,
options,
});
// なぜかちょっと待たないとbounding boxのサイズが正しくない
window.setTimeout(() => {
const boundingInfo = getMeshesBoundingBox(this.objectMesh!.getChildMeshes());
this.camera.setTarget(new BABYLON.Vector3(0, boundingInfo.center.y, 0));
}, 10);
}
// TODO: RoomEngineのものとほぼ同じだからいい感じに共通化 // TODO: RoomEngineのものとほぼ同じだからいい感じに共通化
private async loadObject(args: { private async loadObject(args: {
type: string; type: string;
id: string;
position: BABYLON.Vector3;
rotation: BABYLON.Vector3;
options: any; options: any;
}) { }) {
const def = getObjectDef(args.type); const def = getObjectDef(args.type);
@@ -1436,7 +1450,7 @@ export class RoomObjectPreviewEngine {
.toLowerCase(); .toLowerCase();
}; };
const root = new BABYLON.Mesh(`object_${args.id}_${args.type}`, this.scene); const root = new BABYLON.Mesh(`object_${args.type}`, this.scene);
const loaderResult = await BABYLON.ImportMeshAsync(`/client-assets/room/objects/${camelToKebab(args.type)}/${camelToKebab(args.type)}.glb`, this.scene); const loaderResult = await BABYLON.ImportMeshAsync(`/client-assets/room/objects/${camelToKebab(args.type)}/${camelToKebab(args.type)}.glb`, this.scene);
@@ -1462,19 +1476,8 @@ export class RoomObjectPreviewEngine {
} }
} }
const metadata = {
isObject: true,
objectId: args.id,
objectType: args.type,
isCollision: !hasCollisionMesh,
};
root.addChild(subRoot); root.addChild(subRoot);
root.position = args.position.clone();
root.rotation = args.rotation.clone();
root.metadata = metadata;
const meshUpdated = (meshes: BABYLON.Mesh[]) => { const meshUpdated = (meshes: BABYLON.Mesh[]) => {
for (const m of meshes) { for (const m of meshes) {
const mesh = m; const mesh = m;
@@ -1482,13 +1485,11 @@ export class RoomObjectPreviewEngine {
// シェイプキー(morph)を考慮してbounding boxを更新するために必要 // シェイプキー(morph)を考慮してbounding boxを更新するために必要
mesh.refreshBoundingInfo({ applyMorph: true }); mesh.refreshBoundingInfo({ applyMorph: true });
mesh.metadata = metadata;
mesh.checkCollisions = !hasCollisionMesh; mesh.checkCollisions = !hasCollisionMesh;
if (mesh.name.includes('__COLLISION__')) { if (mesh.name.includes('__COLLISION__')) {
mesh.receiveShadows = false; mesh.receiveShadows = false;
mesh.isVisible = false; mesh.isVisible = false;
mesh.metadata.isCollision = true;
mesh.checkCollisions = true; mesh.checkCollisions = true;
} else if (mesh.name.includes('__TOP__') || mesh.name.includes('__SIDE__')) { } else if (mesh.name.includes('__TOP__') || mesh.name.includes('__SIDE__')) {
mesh.receiveShadows = false; mesh.receiveShadows = false;
@@ -1497,7 +1498,6 @@ export class RoomObjectPreviewEngine {
if (def.receiveShadows !== false) mesh.receiveShadows = true; if (def.receiveShadows !== false) mesh.receiveShadows = true;
if (def.castShadows !== false) { if (def.castShadows !== false) {
this.shadowGenerator1.addShadowCaster(mesh); this.shadowGenerator1.addShadowCaster(mesh);
this.shadowGenerator2.addShadowCaster(mesh);
} }
if (mesh.material) { if (mesh.material) {
@@ -1521,7 +1521,7 @@ export class RoomObjectPreviewEngine {
findMesh: (keyword) => { findMesh: (keyword) => {
const mesh = root.getChildMeshes().find(m => m.name.includes(keyword)); const mesh = root.getChildMeshes().find(m => m.name.includes(keyword));
if (mesh == null) { if (mesh == null) {
throw new Error(`Mesh with keyword "${keyword}" not found for object ${args.type} (${args.id})`); throw new Error(`Mesh with keyword "${keyword}" not found for object ${args.type}`);
} }
return mesh as BABYLON.Mesh; return mesh as BABYLON.Mesh;
}, },
@@ -1535,7 +1535,7 @@ export class RoomObjectPreviewEngine {
findTransformNode: (keyword) => { findTransformNode: (keyword) => {
const node = root.getChildTransformNodes().find(n => n.name.includes(keyword)); const node = root.getChildTransformNodes().find(n => n.name.includes(keyword));
if (node == null) { if (node == null) {
throw new Error(`TransformNode with keyword "${keyword}" not found for object ${args.type} (${args.id})`); throw new Error(`TransformNode with keyword "${keyword}" not found for object ${args.type}`);
} }
return node; return node;
}, },
@@ -1544,6 +1544,7 @@ export class RoomObjectPreviewEngine {
objectInstance.onInited?.(); objectInstance.onInited?.();
this.objectInstance = objectInstance; this.objectInstance = objectInstance;
this.objectMesh = root;
} }
public updateObjectOption(key: string, value: any) { public updateObjectOption(key: string, value: any) {