mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-06-04 13:34:10 +02:00
wip
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user