mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-21 15:45:41 +02:00
wip
This commit is contained in:
@@ -31,23 +31,35 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
<div v-show="selectedId != null" :class="$style.preview" class="_panel">
|
<div v-show="selectedId != null" :class="$style.preview" class="_panel">
|
||||||
<canvas ref="canvas" :class="$style.canvas"></canvas>
|
<canvas ref="canvas" :class="$style.canvas"></canvas>
|
||||||
<button :class="$style.unselectButton" @click="selectedId = null">x</button>
|
<MkButton :class="$style.unselectButton" small iconOnly @click="selectedId = null"><i class="ti ti-x"></i></MkButton>
|
||||||
|
<MkButton :class="$style.customizeButton" small iconOnly @click=""><i class="ti ti-settings"></i></MkButton>
|
||||||
|
<div v-if="selectedObjectDef != null && selectedInstanceId != null" :class="$style.customize">
|
||||||
|
<XObjectCustomizeForm :schema="selectedObjectDef.options.schema" :options="selectedObjectOptionsState" @update="(k, v) => { engine.updateObjectOption(k, v); triggerRef(selectedObjectOptionsState) }"></XObjectCustomizeForm>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MkModalWindow>
|
</MkModalWindow>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive, nextTick, shallowRef } from 'vue';
|
import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive, nextTick, shallowRef, computed, triggerRef } from 'vue';
|
||||||
|
import XObjectCustomizeForm from './room.object-customize-form.vue';
|
||||||
|
import type { RoomObjectInstance, RoomStateObject } from '@/world/room/object.js';
|
||||||
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 '@/world/room/object-defs.js';
|
import { OBJECT_DEFS } from '@/world/room/object-defs.js';
|
||||||
import { createRoomObjectPreviewEngine, RoomObjectPreviewEngine } from '@/world/room/previewEngine.js';
|
import { createRoomObjectPreviewEngine, RoomObjectPreviewEngine } from '@/world/room/previewEngine.js';
|
||||||
import { camelToKebab } from '@/world/utility.js';
|
import { camelToKebab } from '@/world/utility.js';
|
||||||
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
|
||||||
|
// TODO: instanceのidと紛らわしいのでid -> typeにする
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'ok', id: string): void;
|
(ev: 'ok', ctx: {
|
||||||
|
id: string;
|
||||||
|
options: any;
|
||||||
|
}): void;
|
||||||
(ev: 'cancel'): void;
|
(ev: 'cancel'): void;
|
||||||
(ev: 'closed'): void;
|
(ev: 'closed'): void;
|
||||||
}>();
|
}>();
|
||||||
@@ -55,6 +67,9 @@ 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 selectedInstanceId = ref<string | null>(null);
|
||||||
|
const selectedObjectOptionsState = shallowRef<RoomStateObject | null>(null);
|
||||||
|
const selectedObjectDef = computed(() => OBJECT_DEFS.find(def => def.id === selectedId.value) ?? null);
|
||||||
const engine = shallowRef<RoomObjectPreviewEngine | null>(null);
|
const engine = shallowRef<RoomObjectPreviewEngine | null>(null);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -70,17 +85,27 @@ onUnmounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
watch(selectedId, (newId) => {
|
watch(selectedId, (newId) => {
|
||||||
if (newId == null) return;
|
if (newId == null) {
|
||||||
|
engine.value!.clear();
|
||||||
nextTick(() => {
|
selectedInstanceId.value = null;
|
||||||
engine.value!.load(newId);
|
selectedObjectOptionsState.value = null;
|
||||||
engine.value!.resize();
|
} else {
|
||||||
});
|
nextTick(() => {
|
||||||
|
engine.value!.load(newId).then(res => {
|
||||||
|
selectedInstanceId.value = res.id;
|
||||||
|
selectedObjectOptionsState.value = res.options;
|
||||||
|
engine.value!.resize();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function ok() {
|
function ok() {
|
||||||
if (selectedId.value == null) return;
|
if (selectedId.value == null) return;
|
||||||
emit('ok', selectedId.value);
|
emit('ok', {
|
||||||
|
id: selectedId.value,
|
||||||
|
options: selectedObjectOptionsState.value,
|
||||||
|
});
|
||||||
dialog.value?.close();
|
dialog.value?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,8 +171,20 @@ async function cancel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.unselectButton {
|
.unselectButton {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customizeButton {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.customize {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
59
packages/frontend/src/pages/room.object-customize-form.vue
Normal file
59
packages/frontend/src/pages/room.object-customize-form.vue
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="$style.root">
|
||||||
|
<div class="_gaps">
|
||||||
|
<div v-for="[k, s] in Object.entries(schema)" :key="k">
|
||||||
|
<div>{{ s.label }}</div>
|
||||||
|
<div v-if="s.type === 'color'">
|
||||||
|
<MkInput :modelValue="getHex(options[k])" type="color" @update:modelValue="v => { const c = getRgb(v); if (c != null) emit('update', k, c); }"></MkInput>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="s.type === 'boolean'">
|
||||||
|
<MkSwitch :modelValue="options[k]" @update:modelValue="v => emit('update', k, v)"></MkSwitch>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="s.type === 'enum'">
|
||||||
|
<MkSelect :items="s.enum.map(e => ({ label: e, value: e }))" :modelValue="options[k]" @update:modelValue="v => emit('update', k, v)"></MkSelect>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="s.type === 'range'">
|
||||||
|
<MkRange :continuousUpdate="true" :min="s.min" :max="s.max" :step="s.step" :modelValue="options[k]" @update:modelValue="v => emit('update', k, v)"></MkRange>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="s.type === 'image'">
|
||||||
|
<MkInput type="text" :modelValue="options[k]" @update:modelValue="v => emit('update', k, v)"></MkInput>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="s.type === 'seed'">
|
||||||
|
<MkRange :continuousUpdate="true" :min="0" :max="1000" :step="1" :modelValue="options[k]" @update:modelValue="v => emit('update', k, v)"></MkRange>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, defineAsyncComponent, nextTick, onMounted, onUnmounted, ref, shallowRef, useTemplateRef, watch } from 'vue';
|
||||||
|
import type { ObjectDef } from '@/world/room/object.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
|
import * as os from '@/os.js';
|
||||||
|
import MkInput from '@/components/MkInput.vue';
|
||||||
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
|
import MkRange from '@/components/MkRange.vue';
|
||||||
|
import { getHex, getRgb } from '@/world/utility.js';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
schema: ObjectDef['options']['schema'];
|
||||||
|
options: any;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'update', k: string, v: any): void;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.root {
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -44,26 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<div v-if="controller.isReady.value && controller.isEditMode.value && controller.selected.value != null && !controller.grabbing.value" :key="controller.selected.value.objectId" class="_panel" :class="$style.overlayObjectInfoPanel">
|
<div v-if="controller.isReady.value && controller.isEditMode.value && controller.selected.value != null && !controller.grabbing.value" :key="controller.selected.value.objectId" class="_panel" :class="$style.overlayObjectInfoPanel">
|
||||||
{{ controller.selected.value.objectDef.name }}
|
{{ controller.selected.value.objectDef.name }}
|
||||||
|
|
||||||
<div class="_gaps">
|
<XObjectCustomizeForm :schema="controller.selected.value.objectDef.options.schema" :options="controller.selected.value.objectState.options" @update="(k, v) => controller.updateObjectOption(controller.selected.value.objectId, k, v)"></XObjectCustomizeForm>
|
||||||
<div v-for="[k, s] in Object.entries(controller.selected.value.objectDef.options.schema)" :key="k">
|
|
||||||
<div>{{ s.label }}</div>
|
|
||||||
<div v-if="s.type === 'color'">
|
|
||||||
<MkInput :modelValue="getHex(controller.selected.value.objectState.options[k])" type="color" @update:modelValue="v => { const c = getRgb(v); if (c != null) controller.updateObjectOption(controller.selected.value.objectId, k, c); }"></MkInput>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="s.type === 'boolean'">
|
|
||||||
<MkSwitch :modelValue="controller.selected.value.objectState.options[k]" @update:modelValue="v => controller.updateObjectOption(controller.selected.value.objectId, k, v)"></MkSwitch>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="s.type === 'enum'">
|
|
||||||
<MkSelect :items="s.enum.map(e => ({ label: e, value: e }))" :modelValue="controller.selected.value.objectState.options[k]" @update:modelValue="v => controller.updateObjectOption(controller.selected.value.objectId, k, v)"></MkSelect>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="s.type === 'range'">
|
|
||||||
<MkRange :continuousUpdate="true" :min="s.min" :max="s.max" :step="s.step" :modelValue="controller.selected.value.objectState.options[k]" @update:modelValue="v => controller.updateObjectOption(controller.selected.value.objectId, k, v)"></MkRange>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="s.type === 'image'">
|
|
||||||
<MkInput type="text" :modelValue="controller.selected.value.objectState.options[k]" @update:modelValue="v => controller.updateObjectOption(controller.selected.value.objectId, k, v)"></MkInput>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="isRoomSettingsOpen" class="_panel" :class="$style.overlayObjectInfoPanel">
|
<div v-if="isRoomSettingsOpen" class="_panel" :class="$style.overlayObjectInfoPanel">
|
||||||
@@ -119,6 +100,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, defineAsyncComponent, nextTick, onMounted, onUnmounted, ref, shallowRef, useTemplateRef, watch } from 'vue';
|
import { computed, defineAsyncComponent, nextTick, onMounted, onUnmounted, ref, shallowRef, useTemplateRef, watch } from 'vue';
|
||||||
|
import XObjectCustomizeForm from './room.object-customize-form.vue';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { ensureSignin } from '@/i';
|
import { ensureSignin } from '@/i';
|
||||||
@@ -129,7 +111,7 @@ import MkInput from '@/components/MkInput.vue';
|
|||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import MkRange from '@/components/MkRange.vue';
|
import MkRange from '@/components/MkRange.vue';
|
||||||
import { RoomController } from '@/world/room/controller.js';
|
import { RoomController } from '@/world/room/controller.js';
|
||||||
import { cm } from '@/world/utility.js';
|
import { cm, getHex, getRgb } from '@/world/utility.js';
|
||||||
import { deepClone } from '@/utility/clone.js';
|
import { deepClone } from '@/utility/clone.js';
|
||||||
|
|
||||||
const canvas = useTemplateRef('canvas');
|
const canvas = useTemplateRef('canvas');
|
||||||
@@ -283,7 +265,7 @@ async function addObject(ev: PointerEvent) {
|
|||||||
const { dispose } = await os.popupAsyncWithDialog(import('./room.add-object-dialog.vue').then(x => x.default), {
|
const { dispose } = await os.popupAsyncWithDialog(import('./room.add-object-dialog.vue').then(x => x.default), {
|
||||||
}, {
|
}, {
|
||||||
ok: async (res) => {
|
ok: async (res) => {
|
||||||
controller.addObject(res);
|
controller.addObject(res.id, res.options);
|
||||||
canvas.value!.focus();
|
canvas.value!.focus();
|
||||||
},
|
},
|
||||||
closed: () => {
|
closed: () => {
|
||||||
@@ -306,24 +288,6 @@ function exitEditMode() {
|
|||||||
controller.exitEditMode();
|
controller.exitEditMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHex(c: [number, number, number]) {
|
|
||||||
return `#${c.map(x => Math.round(x * 255).toString(16).padStart(2, '0')).join('')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRgb(hex: string | number): [number, number, number] | null {
|
|
||||||
if (
|
|
||||||
typeof hex === 'number' ||
|
|
||||||
typeof hex !== 'string' ||
|
|
||||||
!/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(hex)
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const m = hex.slice(1).match(/[0-9a-fA-F]{2}/g);
|
|
||||||
if (m == null) return [0, 0, 0];
|
|
||||||
return m.map(x => parseInt(x, 16) / 255) as [number, number, number];
|
|
||||||
}
|
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
latestData = deepClone(controller.roomState.value);
|
latestData = deepClone(controller.roomState.value);
|
||||||
localStorage.setItem('roomData', JSON.stringify(latestData));
|
localStorage.setItem('roomData', JSON.stringify(latestData));
|
||||||
|
|||||||
@@ -255,11 +255,11 @@ export class RoomController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public addObject(type: string) {
|
public addObject(type: string, options: any) {
|
||||||
if (this.worker != null) {
|
if (this.worker != null) {
|
||||||
this.worker.postMessage({ type: 'addObject', objectType: type });
|
this.worker.postMessage({ type: 'addObject', objectType: type, objectOptions: options });
|
||||||
} else if (this.engine != null) {
|
} else if (this.engine != null) {
|
||||||
this.engine.addObject(type);
|
this.engine.addObject(type, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
|
|||||||
public camera: BABYLON.UniversalCamera;
|
public camera: BABYLON.UniversalCamera;
|
||||||
public objectEntities: Map<string, {
|
public objectEntities: Map<string, {
|
||||||
rootMesh: BABYLON.Mesh;
|
rootMesh: BABYLON.Mesh;
|
||||||
instance: RoomObjectInstance<any>;
|
instance: RoomObjectInstance;
|
||||||
model: ModelManager;
|
model: ModelManager;
|
||||||
}> = new Map();
|
}> = new Map();
|
||||||
private heyaManager: HeyaManager | null = null;
|
private heyaManager: HeyaManager | null = null;
|
||||||
@@ -1433,7 +1433,7 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
|
|||||||
return ghost;
|
return ghost;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async addObject(type: string) {
|
public async addObject(type: string, _options?: any) {
|
||||||
if (!this.isEditMode) return;
|
if (!this.isEditMode) return;
|
||||||
if (this.grabbingCtx != null) return;
|
if (this.grabbingCtx != null) return;
|
||||||
this.selectObject(null);
|
this.selectObject(null);
|
||||||
@@ -1445,7 +1445,7 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
|
|||||||
|
|
||||||
const def = getObjectDef(type);
|
const def = getObjectDef(type);
|
||||||
|
|
||||||
const options = deepClone(def.options.default);
|
const options = _options != null ? deepClone(_options) : deepClone(def.options.default);
|
||||||
|
|
||||||
const { root } = await this.loadObject({
|
const { root } = await this.loadObject({
|
||||||
id: id,
|
id: id,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export type RoomStateObject<Options = any> = {
|
|||||||
sticky?: string | null;
|
sticky?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RoomObjectInstance<Options> = {
|
export type RoomObjectInstance<Options = any> = {
|
||||||
onInited?: () => void;
|
onInited?: () => void;
|
||||||
onOptionsUpdated?: <K extends keyof Options, V extends Options[K]>(kv: [K, V]) => void;
|
onOptionsUpdated?: <K extends keyof Options, V extends Options[K]>(kv: [K, V]) => void;
|
||||||
interactions: Record<string, {
|
interactions: Record<string, {
|
||||||
@@ -71,7 +71,12 @@ type ImageOptionSchema = {
|
|||||||
label: string;
|
label: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type OptionsSchema = Record<string, NumberOptionSchema | BooleanOptionSchema | ColorOptionSchema | EnumOptionSchema | RangeOptionSchema | ImageOptionSchema>;
|
type SeedOptionSchema = {
|
||||||
|
type: 'seed';
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type OptionsSchema = Record<string, NumberOptionSchema | BooleanOptionSchema | ColorOptionSchema | EnumOptionSchema | RangeOptionSchema | ImageOptionSchema | SeedOptionSchema>;
|
||||||
|
|
||||||
type GetOptionsSchemaValues<T extends OptionsSchema> = {
|
type GetOptionsSchemaValues<T extends OptionsSchema> = {
|
||||||
[K in keyof T]:
|
[K in keyof T]:
|
||||||
@@ -81,6 +86,7 @@ type GetOptionsSchemaValues<T extends OptionsSchema> = {
|
|||||||
T[K] extends EnumOptionSchema ? T[K]['enum'][number] :
|
T[K] extends EnumOptionSchema ? T[K]['enum'][number] :
|
||||||
T[K] extends RangeOptionSchema ? number :
|
T[K] extends RangeOptionSchema ? number :
|
||||||
T[K] extends ImageOptionSchema ? string | null :
|
T[K] extends ImageOptionSchema ? string | null :
|
||||||
|
T[K] extends SeedOptionSchema ? number :
|
||||||
never;
|
never;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -33,11 +33,8 @@ export const randomBooks = defineObject({
|
|||||||
label: 'Stack vertically',
|
label: 'Stack vertically',
|
||||||
},
|
},
|
||||||
seed: {
|
seed: {
|
||||||
type: 'range',
|
type: 'seed',
|
||||||
label: 'Seed',
|
label: 'Seed',
|
||||||
min: 0,
|
|
||||||
max: 1000,
|
|
||||||
step: 1,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
default: {
|
default: {
|
||||||
@@ -51,7 +48,7 @@ export const randomBooks = defineObject({
|
|||||||
hasCollisions: false,
|
hasCollisions: false,
|
||||||
hasTexture: true,
|
hasTexture: true,
|
||||||
canPreMeshesMerging: false,
|
canPreMeshesMerging: false,
|
||||||
createInstance: ({ options, model, scene, id }) => {
|
createInstance: ({ options, model, scene }) => {
|
||||||
const bodyMesh = model.findMesh('__X_BODY__');
|
const bodyMesh = model.findMesh('__X_BODY__');
|
||||||
const tex = new BABYLON.Texture('/client-assets/room/objects/random-books/texture.png', scene, {
|
const tex = new BABYLON.Texture('/client-assets/room/objects/random-books/texture.png', scene, {
|
||||||
invertY: false,
|
invertY: false,
|
||||||
@@ -69,7 +66,7 @@ export const randomBooks = defineObject({
|
|||||||
}
|
}
|
||||||
bookMeshes = [];
|
bookMeshes = [];
|
||||||
|
|
||||||
const rng = seedrandom(options.seed === 0 ? id : options.seed.toString());
|
const rng = seedrandom(options.seed.toString());
|
||||||
const randomRange = (min: number, max: number) => rng() * (max - min) + min;
|
const randomRange = (min: number, max: number) => rng() * (max - min) + min;
|
||||||
|
|
||||||
let accumulatedPos = 0;
|
let accumulatedPos = 0;
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ export class RoomObjectPreviewEngine {
|
|||||||
private shadowGenerator: BABYLON.ShadowGenerator;
|
private shadowGenerator: BABYLON.ShadowGenerator;
|
||||||
private camera: BABYLON.ArcRotateCamera;
|
private camera: BABYLON.ArcRotateCamera;
|
||||||
private objectMesh: BABYLON.Mesh | null = null;
|
private objectMesh: BABYLON.Mesh | null = null;
|
||||||
private objectInstance: RoomObjectInstance<any> | null = null;
|
private objectInstance: RoomObjectInstance | null = null;
|
||||||
|
private objectOptions: any = null;
|
||||||
private objectType: string | null = null;
|
private objectType: string | null = null;
|
||||||
private envMapIndoor: BABYLON.CubeTexture;
|
private envMapIndoor: BABYLON.CubeTexture;
|
||||||
private roomLight: BABYLON.SpotLight;
|
private roomLight: BABYLON.SpotLight;
|
||||||
@@ -152,21 +153,20 @@ export class RoomObjectPreviewEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async load(type: string) {
|
public async load(type: string) {
|
||||||
if (this.objectInstance != null) {
|
this.clear();
|
||||||
this.objectInstance.dispose?.();
|
|
||||||
this.objectInstance = null;
|
|
||||||
this.objectMesh!.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
const def = getObjectDef(type);
|
|
||||||
|
|
||||||
const options = deepClone(def.options.default);
|
|
||||||
|
|
||||||
const id = genId();
|
const id = genId();
|
||||||
|
const def = getObjectDef(type);
|
||||||
|
this.objectOptions = deepClone(def.options.default);
|
||||||
|
for (const [key, value] of Object.entries(def.options.schema)) {
|
||||||
|
if (value.type === 'seed') {
|
||||||
|
this.objectOptions[key] = Math.floor(Math.random() * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await this.loadObject({
|
await this.loadObject({
|
||||||
type,
|
type,
|
||||||
options,
|
options: this.objectOptions,
|
||||||
id,
|
id,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -214,6 +214,12 @@ export class RoomObjectPreviewEngine {
|
|||||||
//this.camera.orthoTop = distance;
|
//this.camera.orthoTop = distance;
|
||||||
//this.camera.orthoBottom = -distance;
|
//this.camera.orthoBottom = -distance;
|
||||||
}, 10);
|
}, 10);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
objectInstance: this.objectInstance,
|
||||||
|
options: this.objectOptions,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadObject(args: {
|
private async loadObject(args: {
|
||||||
@@ -236,10 +242,8 @@ export class RoomObjectPreviewEngine {
|
|||||||
|
|
||||||
root.addChild(subRoot);
|
root.addChild(subRoot);
|
||||||
|
|
||||||
const model = new ModelManager(subRoot, loaderResult.meshes.filter(m => !m.isDisposed() && m !== subRoot), def.hasTexture, (meshes) => {
|
const updateMeshes = (meshes: BABYLON.AbstractMesh[]) => {
|
||||||
for (const m of meshes) {
|
for (const mesh of meshes) {
|
||||||
const mesh = m;
|
|
||||||
|
|
||||||
// シェイプキー(morph)を考慮してbounding boxを更新するために必要
|
// シェイプキー(morph)を考慮してbounding boxを更新するために必要
|
||||||
mesh.refreshBoundingInfo({ applyMorph: true });
|
mesh.refreshBoundingInfo({ applyMorph: true });
|
||||||
|
|
||||||
@@ -269,11 +273,14 @@ export class RoomObjectPreviewEngine {
|
|||||||
|
|
||||||
if (!this.scene.meshes.includes(mesh)) this.scene.addMesh(mesh);
|
if (!this.scene.meshes.includes(mesh)) this.scene.addMesh(mesh);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const model = new ModelManager(subRoot, loaderResult.meshes.filter(m => !m.isDisposed() && m !== subRoot), def.hasTexture, (meshes) => {
|
||||||
|
updateMeshes(meshes);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.timerForEachObject != null) {
|
updateMeshes(subRoot.getChildMeshes());
|
||||||
this.timerForEachObject.dispose();
|
|
||||||
}
|
|
||||||
this.timerForEachObject = new Timer();
|
this.timerForEachObject = new Timer();
|
||||||
|
|
||||||
const objectInstance = await def.createInstance({
|
const objectInstance = await def.createInstance({
|
||||||
@@ -288,17 +295,31 @@ export class RoomObjectPreviewEngine {
|
|||||||
|
|
||||||
objectInstance.onInited?.();
|
objectInstance.onInited?.();
|
||||||
|
|
||||||
model.bakeMesh();
|
|
||||||
|
|
||||||
this.objectType = args.type;
|
this.objectType = args.type;
|
||||||
this.objectInstance = objectInstance;
|
this.objectInstance = objectInstance;
|
||||||
this.objectMesh = root;
|
this.objectMesh = root;
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateObjectOption(key: string, value: any) {
|
public updateObjectOption(key: string, value: any) {
|
||||||
|
this.objectOptions[key] = value;
|
||||||
this.objectInstance?.onOptionsUpdated?.([key, value]);
|
this.objectInstance?.onOptionsUpdated?.([key, value]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public clear() {
|
||||||
|
if (this.timerForEachObject != null) {
|
||||||
|
this.timerForEachObject.dispose();
|
||||||
|
this.timerForEachObject = null;
|
||||||
|
}
|
||||||
|
if (this.objectInstance != null) {
|
||||||
|
this.objectInstance.dispose?.();
|
||||||
|
this.objectInstance = null;
|
||||||
|
this.objectOptions = null;
|
||||||
|
this.objectMesh!.dispose();
|
||||||
|
this.objectMesh = null;
|
||||||
|
this.objectType = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public resize() {
|
public resize() {
|
||||||
this.engine.resize();
|
this.engine.resize();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -589,3 +589,21 @@ export function getYRotationDirection(rotationY: number): '+x' | '+z' | '-x' | '
|
|||||||
return '+x';
|
return '+x';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getHex(c: [number, number, number]) {
|
||||||
|
return `#${c.map(x => Math.round(x * 255).toString(16).padStart(2, '0')).join('')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRgb(hex: string | number): [number, number, number] | null {
|
||||||
|
if (
|
||||||
|
typeof hex === 'number' ||
|
||||||
|
typeof hex !== 'string' ||
|
||||||
|
!/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(hex)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const m = hex.slice(1).match(/[0-9a-fA-F]{2}/g);
|
||||||
|
if (m == null) return [0, 0, 0];
|
||||||
|
return m.map(x => parseInt(x, 16) / 255) as [number, number, number];
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user