mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-13 15:15:45 +02:00
wip
This commit is contained in:
@@ -64,6 +64,23 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isRoomSettingsOpen" class="_panel" :class="$style.overlayObjectInfoPanel">
|
||||
<div class="_gaps">
|
||||
Room options
|
||||
|
||||
<MkSelect
|
||||
:items="[
|
||||
{ label: 'Simple', value: 'simple' },
|
||||
]" :modelValue="controller.roomState.value.heya.type" @update:modelValue="v => controller.changeHeyaType(v)"
|
||||
>
|
||||
<template #label>Heya type</template>
|
||||
</MkSelect>
|
||||
|
||||
<template v-if="controller.roomState.value.heya.type === 'simple'">
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -75,6 +92,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkButton v-if="controller.isEditMode.value" @click="exitEditMode">Exit edit mode</MkButton>
|
||||
<MkButton v-if="!controller.isEditMode.value" @click="enterEditMode">Edit mode</MkButton>
|
||||
<MkButton v-if="controller.isEditMode.value" @click="addObject">addObject</MkButton>
|
||||
<MkButton v-if="controller.isEditMode.value" @click="() => isRoomSettingsOpen = !isRoomSettingsOpen">roomSettings</MkButton>
|
||||
<MkButton @click="expor">Export</MkButton>
|
||||
<MkButton @click="impor">Import</MkButton>
|
||||
</div>
|
||||
@@ -109,6 +127,7 @@ function resize() {
|
||||
}
|
||||
|
||||
const isZenMode = ref(false);
|
||||
const isRoomSettingsOpen = ref(false);
|
||||
|
||||
const data = localStorage.getItem('roomData') != null ? { ...JSON.parse(localStorage.getItem('roomData')!), ...{
|
||||
heya: {
|
||||
@@ -177,6 +196,8 @@ const data = localStorage.getItem('roomData') != null ? { ...JSON.parse(localSto
|
||||
installedObjects: [],
|
||||
};
|
||||
|
||||
console.log(data);
|
||||
|
||||
const controller = new RoomController(data);
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
@@ -182,6 +182,22 @@ export class RoomController {
|
||||
}
|
||||
}
|
||||
|
||||
public changeHeyaType(type: RoomState['heya']['type']) {
|
||||
if (this.worker != null) {
|
||||
this.worker.postMessage({ type: 'changeHeyaType', heyaType: type });
|
||||
} else if (this.engine != null) {
|
||||
this.engine.changeHeyaType(type);
|
||||
}
|
||||
}
|
||||
|
||||
public updateHeyaOptions(options: RoomState['heya']['options']) {
|
||||
if (this.worker != null) {
|
||||
this.worker.postMessage({ type: 'updateHeyaOptions', heyaOptions: options });
|
||||
} else if (this.engine != null) {
|
||||
this.engine.updateHeyaOptions(options);
|
||||
}
|
||||
}
|
||||
|
||||
public beginSelectedInstalledObjectGrabbing() {
|
||||
if (this.worker != null) {
|
||||
this.worker.postMessage({ type: 'beginSelectedInstalledObjectGrabbing' });
|
||||
|
||||
@@ -16,7 +16,9 @@ import { GridMaterial } from '@babylonjs/materials';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { TIME_MAP, scaleMorph, HorizontalCameraKeyboardMoveInput, camelToKebab, cm, WORLD_SCALE, getMeshesBoundingBox, Timer } from '../utility.js';
|
||||
import { getObjectDef } from './object-defs.js';
|
||||
import { findMaterial, ModelManager, SYSTEM_MESH_NAMES } from './utility.js';
|
||||
import { findMaterial, ModelManager, SYSTEM_HEYA_MESH_NAMES, SYSTEM_MESH_NAMES } from './utility.js';
|
||||
import { SimpleHeyaManager } from './heya.js';
|
||||
import type { HeyaManager, JapaneseHeyaOptions, SimpleHeyaOptions } from './heya.js';
|
||||
import type { ObjectDef, RoomObjectInstance, RoomStateObject } from './object.js';
|
||||
import { genId } from '@/utility/id.js';
|
||||
import { deepClone } from '@/utility/clone.js';
|
||||
@@ -27,35 +29,14 @@ const IGNORE_OBJECTS: string[] = []; // for debug
|
||||
const USE_GLOW = true; // ドローコールが増えて重い
|
||||
const IN_WEB_WORKER = typeof window === 'undefined';
|
||||
|
||||
type SimpleHeyaWallBase = {
|
||||
material: null | 'wood' | 'concrete';
|
||||
color: [number, number, number];
|
||||
};
|
||||
|
||||
type Heya = {
|
||||
type: 'simple';
|
||||
options: {
|
||||
dimension: [number, number];
|
||||
window: 'none' | 'kosidakamado' | 'demado' | 'hakidasimado';
|
||||
wallN: SimpleHeyaWallBase;
|
||||
wallE: SimpleHeyaWallBase;
|
||||
wallS: SimpleHeyaWallBase;
|
||||
wallW: SimpleHeyaWallBase;
|
||||
flooring: {
|
||||
material: null | 'wood' | 'concrete';
|
||||
color: [number, number, number];
|
||||
};
|
||||
ceiling: {
|
||||
material: null | 'wood' | 'concrete';
|
||||
color: [number, number, number];
|
||||
};
|
||||
};
|
||||
} | {
|
||||
type: 'japanese';
|
||||
};
|
||||
|
||||
export type RoomState = {
|
||||
heya: Heya;
|
||||
heya: {
|
||||
type: 'simple';
|
||||
options: SimpleHeyaOptions;
|
||||
} | {
|
||||
type: 'japanese';
|
||||
options: JapaneseHeyaOptions;
|
||||
};
|
||||
installedObjects: RoomStateObject<any>[];
|
||||
};
|
||||
|
||||
@@ -142,6 +123,7 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
|
||||
instance: RoomObjectInstance<any>;
|
||||
model: ModelManager;
|
||||
}> = new Map();
|
||||
private heyaManager: HeyaManager | null = null;
|
||||
|
||||
// TODO: たぶんオブジェクト内の値のmutateはsetで検知できないので、そのような操作を実際に行うようになった & それを検知する必要性が出てきたら専用の設定関数などを新設してそれを使わせる
|
||||
private _grabbingCtx: {
|
||||
@@ -425,7 +407,7 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
|
||||
}
|
||||
|
||||
public async init() {
|
||||
await this.loadRoomModel();
|
||||
await this.loadHeya();
|
||||
//await this.loadEnvModel();
|
||||
|
||||
const objects = this.roomState.installedObjects.filter(o => !IGNORE_OBJECTS.includes(o.type));
|
||||
@@ -742,77 +724,49 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
|
||||
}
|
||||
}
|
||||
|
||||
private async loadRoomModel() {
|
||||
//await BABYLON.InitializeCSG2Async();
|
||||
public async changeHeyaType(type: RoomState['heya']['type']) {
|
||||
this.roomState.heya.type = type;
|
||||
|
||||
//const box = BABYLON.MeshBuilder.CreateBox('box', { size: cm(50) }, this.scene);
|
||||
//const boxCsg = BABYLON.CSG2.FromMesh(box);
|
||||
if (this.heyaManager != null) {
|
||||
this.heyaManager.dispose();
|
||||
}
|
||||
|
||||
const meshes: BABYLON.Mesh[] = [];
|
||||
const onMeshUpdatedCallback = (meshes: BABYLON.AbstractMesh[]) => {
|
||||
for (const m of meshes) {
|
||||
if (SYSTEM_HEYA_MESH_NAMES.some(name => m.name.includes(name))) {
|
||||
m.isPickable = false;
|
||||
m.receiveShadows = false;
|
||||
m.isVisible = false;
|
||||
m.checkCollisions = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
m.isPickable = false;
|
||||
m.checkCollisions = false;
|
||||
m.receiveShadows = true;
|
||||
this.shadowGeneratorForRoomLight.addShadowCaster(m);
|
||||
this.shadowGeneratorForSunLight.addShadowCaster(m);
|
||||
//if (m.material) (m.material as BABYLON.PBRMaterial).ambientColor = new BABYLON.Color3(1, 1, 1);
|
||||
if (m.material) {
|
||||
(m.material as BABYLON.PBRMaterial).reflectionTexture = this.envMapIndoor;
|
||||
(m.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.roomState.heya.type === 'simple') {
|
||||
const loaderResult = await BABYLON.ImportMeshAsync('/client-assets/room/rooms/default/300.glb', this.scene);
|
||||
loaderResult.meshes[0].scaling = loaderResult.meshes[0].scaling.scale(WORLD_SCALE);
|
||||
loaderResult.meshes[0].rotationQuaternion = null;
|
||||
loaderResult.meshes[0].rotation = new BABYLON.Vector3(0, 0, 0);
|
||||
|
||||
const wallNRoot = loaderResult.transformNodes.find(t => t.name.includes('__WALL_N__'));
|
||||
const wallSRoot = loaderResult.transformNodes.find(t => t.name.includes('__WALL_S__'));
|
||||
const wallWRoot = loaderResult.transformNodes.find(t => t.name.includes('__WALL_W__'));
|
||||
const wallERoot = loaderResult.transformNodes.find(t => t.name.includes('__WALL_E__'));
|
||||
|
||||
const wallMaterial = findMaterial(loaderResult.meshes[0], '__X_WALL__');
|
||||
|
||||
const wallNMaterial = wallMaterial.clone('wallNMaterial');
|
||||
wallNMaterial.albedoColor = new BABYLON.Color3(...this.roomState.heya.options.wallN.color);
|
||||
|
||||
const wallSMaterial = wallMaterial.clone('wallSMaterial');
|
||||
wallSMaterial.albedoColor = new BABYLON.Color3(...this.roomState.heya.options.wallS.color);
|
||||
|
||||
const wallWMaterial = wallMaterial.clone('wallWMaterial');
|
||||
wallWMaterial.albedoColor = new BABYLON.Color3(...this.roomState.heya.options.wallW.color);
|
||||
|
||||
const wallEMaterial = wallMaterial.clone('wallEMaterial');
|
||||
wallEMaterial.albedoColor = new BABYLON.Color3(...this.roomState.heya.options.wallE.color);
|
||||
|
||||
// TODO: wall meshが一部instanced meshになっているせいでマテリアルが共有されるのを直す
|
||||
|
||||
for (const m of wallNRoot.getChildMeshes().filter(m => m.material === wallMaterial)) {
|
||||
m.material = wallNMaterial;
|
||||
}
|
||||
for (const m of wallSRoot.getChildMeshes().filter(m => m.material === wallMaterial)) {
|
||||
m.material = wallSMaterial;
|
||||
}
|
||||
for (const m of wallWRoot.getChildMeshes().filter(m => m.material === wallMaterial)) {
|
||||
m.material = wallWMaterial;
|
||||
}
|
||||
for (const m of wallERoot.getChildMeshes().filter(m => m.material === wallMaterial)) {
|
||||
m.material = wallEMaterial;
|
||||
}
|
||||
|
||||
meshes.push(...loaderResult.meshes);
|
||||
const heyaManager = new SimpleHeyaManager(onMeshUpdatedCallback);
|
||||
await heyaManager.load(this.roomState.heya.options, this.scene);
|
||||
this.heyaManager = heyaManager;
|
||||
} else if (this.roomState.heya.type === 'japanese') {
|
||||
// TODO
|
||||
}
|
||||
|
||||
for (const m of meshes) {
|
||||
if (m.name.includes('__ROOM_WALL__') || m.name.includes('__ROOM_SIDE__') || m.name.includes('__ROOM_FLOOR__') || m.name.includes('__ROOM_CEILING__') || m.name.includes('__ROOM_TOP__') || m.name.includes('__ROOM_BOTTOM__')) {
|
||||
m.isPickable = false;
|
||||
m.receiveShadows = false;
|
||||
m.isVisible = false;
|
||||
m.checkCollisions = false;
|
||||
continue;
|
||||
}
|
||||
this.emit('changeRoomState', { roomState: this.roomState });
|
||||
}
|
||||
|
||||
m.isPickable = false;
|
||||
m.checkCollisions = false;
|
||||
m.receiveShadows = true;
|
||||
this.shadowGeneratorForRoomLight.addShadowCaster(m);
|
||||
this.shadowGeneratorForSunLight.addShadowCaster(m);
|
||||
//if (m.material) (m.material as BABYLON.PBRMaterial).ambientColor = new BABYLON.Color3(1, 1, 1);
|
||||
if (m.material) {
|
||||
(m.material as BABYLON.PBRMaterial).reflectionTexture = this.envMapIndoor;
|
||||
(m.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
|
||||
}
|
||||
}
|
||||
private async loadHeya() {
|
||||
await this.changeHeyaType(this.roomState.heya.type);
|
||||
}
|
||||
|
||||
private async loadObject(args: {
|
||||
@@ -1592,11 +1546,11 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
|
||||
const entity = this.objectEntities.get(objectId);
|
||||
if (entity == null) return;
|
||||
entity.instance.onOptionsUpdated?.([key, value]);
|
||||
}
|
||||
|
||||
if (this.selected?.objectId === objectId) {
|
||||
// TODO
|
||||
//triggerRef(this.selected);
|
||||
}
|
||||
public updateHeyaOptions(options: RoomState['heya']['options']) {
|
||||
this.heyaManager.applyOptions(options);
|
||||
this.emit('changeRoomState', { roomState: this.roomState });
|
||||
}
|
||||
|
||||
private playSfxUrl(url: string, options: { volume: number; playbackRate: number }) {
|
||||
|
||||
128
packages/frontend/src/world/room/heya.ts
Normal file
128
packages/frontend/src/world/room/heya.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import * as BABYLON from '@babylonjs/core';
|
||||
import { WORLD_SCALE } from '../utility.js';
|
||||
import { findMaterial } from './utility.js';
|
||||
|
||||
//export interface HeyaManager<T = any> {
|
||||
// constructor(onMeshUpdatedCallback?: ((meshes: BABYLON.AbstractMesh[]) => void) | null): void;
|
||||
// load: (options: T, scene: BABYLON.Scene) => Promise<void>;
|
||||
// applyOptions: (options: T) => void;
|
||||
// dispose: () => void;
|
||||
//}
|
||||
|
||||
export abstract class HeyaManager<T = any> {
|
||||
protected onMeshUpdatedCallback: ((meshes: BABYLON.AbstractMesh[]) => void) | null = null;
|
||||
|
||||
constructor(onMeshUpdatedCallback?: ((meshes: BABYLON.AbstractMesh[]) => void) | null) {
|
||||
this.onMeshUpdatedCallback = onMeshUpdatedCallback ?? null;
|
||||
}
|
||||
|
||||
abstract load(options: T, scene: BABYLON.Scene): Promise<void>;
|
||||
abstract applyOptions(options: T): void;
|
||||
abstract dispose(): void;
|
||||
}
|
||||
|
||||
type SimpleHeyaWallBase = {
|
||||
material: null | 'wood' | 'concrete';
|
||||
color: [number, number, number];
|
||||
};
|
||||
|
||||
export type SimpleHeyaOptions = {
|
||||
dimension: [number, number];
|
||||
window: 'none' | 'kosidakamado' | 'demado' | 'hakidasimado';
|
||||
wallN: SimpleHeyaWallBase;
|
||||
wallE: SimpleHeyaWallBase;
|
||||
wallS: SimpleHeyaWallBase;
|
||||
wallW: SimpleHeyaWallBase;
|
||||
flooring: {
|
||||
material: null | 'wood' | 'concrete';
|
||||
color: [number, number, number];
|
||||
};
|
||||
ceiling: {
|
||||
material: null | 'wood' | 'concrete';
|
||||
color: [number, number, number];
|
||||
};
|
||||
};
|
||||
|
||||
export type JapaneseHeyaOptions = {
|
||||
window: 'none' | 'kosidakamado' | 'demado' | 'hakidasimado';
|
||||
};
|
||||
|
||||
export class SimpleHeyaManager extends HeyaManager<SimpleHeyaOptions> {
|
||||
private loaderResult: BABYLON.ISceneLoaderAsyncResult | null = null;
|
||||
private wallNRoot: BABYLON.TransformNode | null = null;
|
||||
private wallSRoot: BABYLON.TransformNode | null = null;
|
||||
private wallWRoot: BABYLON.TransformNode | null = null;
|
||||
private wallERoot: BABYLON.TransformNode | null = null;
|
||||
private wallNMaterial: BABYLON.Material | null = null;
|
||||
private wallSMaterial: BABYLON.Material | null = null;
|
||||
private wallWMaterial: BABYLON.Material | null = null;
|
||||
private wallEMaterial: BABYLON.Material | null = null;
|
||||
|
||||
constructor(onMeshUpdatedCallback?: ((meshes: BABYLON.AbstractMesh[]) => void) | null) {
|
||||
super(onMeshUpdatedCallback);
|
||||
}
|
||||
|
||||
public async load(options: SimpleHeyaOptions, scene: BABYLON.Scene) {
|
||||
this.loaderResult = await BABYLON.ImportMeshAsync('/client-assets/room/rooms/default/300.glb', scene);
|
||||
this.loaderResult.meshes[0].scaling = this.loaderResult.meshes[0].scaling.scale(WORLD_SCALE);
|
||||
this.loaderResult.meshes[0].rotationQuaternion = null;
|
||||
this.loaderResult.meshes[0].rotation = new BABYLON.Vector3(0, 0, 0);
|
||||
|
||||
this.wallNRoot = this.loaderResult.transformNodes.find(t => t.name.includes('__WALL_N__'))!;
|
||||
this.wallSRoot = this.loaderResult.transformNodes.find(t => t.name.includes('__WALL_S__'))!;
|
||||
this.wallWRoot = this.loaderResult.transformNodes.find(t => t.name.includes('__WALL_W__'))!;
|
||||
this.wallERoot = this.loaderResult.transformNodes.find(t => t.name.includes('__WALL_E__'))!;
|
||||
|
||||
const wallMaterial = findMaterial(this.loaderResult.meshes[0], '__X_WALL__');
|
||||
this.wallNMaterial = wallMaterial.clone('wallNMaterial');
|
||||
this.wallSMaterial = wallMaterial.clone('wallSMaterial');
|
||||
this.wallWMaterial = wallMaterial.clone('wallWMaterial');
|
||||
this.wallEMaterial = wallMaterial.clone('wallEMaterial');
|
||||
|
||||
// TODO: wall meshが一部instanced meshになっているせいでマテリアルが共有されるのを直す
|
||||
|
||||
for (const m of this.wallNRoot.getChildMeshes().filter(m => m.material === wallMaterial)) {
|
||||
m.material = this.wallNMaterial;
|
||||
}
|
||||
for (const m of this.wallSRoot.getChildMeshes().filter(m => m.material === wallMaterial)) {
|
||||
m.material = this.wallSMaterial;
|
||||
}
|
||||
for (const m of this.wallWRoot.getChildMeshes().filter(m => m.material === wallMaterial)) {
|
||||
m.material = this.wallWMaterial;
|
||||
}
|
||||
for (const m of this.wallERoot.getChildMeshes().filter(m => m.material === wallMaterial)) {
|
||||
m.material = this.wallEMaterial;
|
||||
}
|
||||
|
||||
this.applyOptions(options);
|
||||
}
|
||||
|
||||
public applyOptions(options: SimpleHeyaOptions) {
|
||||
this.wallNMaterial.albedoColor = new BABYLON.Color3(...options.wallN.color);
|
||||
this.wallSMaterial.albedoColor = new BABYLON.Color3(...options.wallS.color);
|
||||
this.wallWMaterial.albedoColor = new BABYLON.Color3(...options.wallW.color);
|
||||
this.wallEMaterial.albedoColor = new BABYLON.Color3(...options.wallE.color);
|
||||
|
||||
this.onMeshUpdatedCallback?.(this.loaderResult.meshes);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
if (this.loaderResult != null) {
|
||||
for (const m of this.loaderResult.meshes) {
|
||||
m.dispose();
|
||||
}
|
||||
for (const t of this.loaderResult.transformNodes) {
|
||||
t.dispose();
|
||||
}
|
||||
}
|
||||
this.wallNMaterial?.dispose();
|
||||
this.wallSMaterial?.dispose();
|
||||
this.wallWMaterial?.dispose();
|
||||
this.wallEMaterial?.dispose();
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { applyMorphTargetsToMesh, cm, getPlaneUvIndexes } from '../utility.js';
|
||||
import type { RoomEngine } from './engine.js';
|
||||
|
||||
export const SYSTEM_MESH_NAMES = ['__TOP__', '__SIDE__', '__PICK__', '__COLLISION__'];
|
||||
export const SYSTEM_HEYA_MESH_NAMES = ['__ROOM_WALL__', '__ROOM_SIDE__', '__ROOM_FLOOR__', '__ROOM_CEILING__', '__ROOM_TOP__', '__ROOM_BOTTOM__'];
|
||||
|
||||
export function yuge(scene: BABYLON.Scene, mesh: BABYLON.Mesh, offset: BABYLON.Vector3) {
|
||||
const emitter = new BABYLON.TransformNode('emitter', scene);
|
||||
|
||||
Reference in New Issue
Block a user