mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-03-21 03:30:42 +00:00
wip
This commit is contained in:
@@ -26,14 +26,21 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<div v-if="engine != null && engine.isEditMode.value && engine.selected.value != null" class="_panel" :class="$style.overlayObjectInfoPanel">
|
||||
{{ engine.selected.value.objectDef.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="engine != null" class="_buttons" :class="$style.controls">
|
||||
<!--<MkButton v-for="action in actions" :key="action.key" @click="action.fn">{{ action.label }}{{ hotkeyToLabel(action.hotkey) }}</MkButton>-->
|
||||
<MkButton @click="toggleLight">Toggle Light</MkButton>
|
||||
<MkButton :primary="engine.isEditMode.value" @click="toggleEditMode">Edit mode: {{ engine.isEditMode.value ? 'on' : 'off' }}</MkButton>
|
||||
<MkButton @click="addObject">addObject</MkButton>
|
||||
<div v-for="[k, s] in Object.entries(engine.selected.value.objectDef.options.schema)" :key="k">
|
||||
<div>{{ s.label }}</div>
|
||||
<div v-if="s.type === 'color'">
|
||||
<MkInput :modelValue="getHex(engine.selected.value.objectState.options[k])" type="color" @update:modelValue="v => { const c = getRgb(v); if (c != null) engine.updateObjectOption(engine.selected.value.objectId, k, c); }"></MkInput>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="engine != null" class="_buttons" :class="$style.controls">
|
||||
<!--<MkButton v-for="action in actions" :key="action.key" @click="action.fn">{{ action.label }}{{ hotkeyToLabel(action.hotkey) }}</MkButton>-->
|
||||
<MkButton @click="toggleLight">Toggle Light</MkButton>
|
||||
<MkButton :primary="engine.isEditMode.value" @click="toggleEditMode">Edit mode: {{ engine.isEditMode.value ? 'on' : 'off' }}</MkButton>
|
||||
<MkButton @click="addObject">addObject</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -48,6 +55,7 @@ import { RoomEngine } from '@/utility/room/engine.js';
|
||||
import { getObjectDef, OBJECT_DEFS } from '@/utility/room/object-defs.js';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import * as os from '@/os.js';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
|
||||
const canvas = useTemplateRef('canvas');
|
||||
|
||||
@@ -516,6 +524,24 @@ function addObject(ev: PointerEvent) {
|
||||
})), ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
function getHex(c: [number, number, number]) {
|
||||
return `#${c.map(x => (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];
|
||||
}
|
||||
|
||||
definePage(() => ({
|
||||
title: 'Room',
|
||||
icon: 'ti ti-door',
|
||||
@@ -554,8 +580,8 @@ definePage(() => ({
|
||||
|
||||
.overlayObjectInfoPanel {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
@@ -52,17 +52,27 @@ type RoomState = {
|
||||
};
|
||||
|
||||
type RoomObjectInstance<Options> = {
|
||||
onInited?: (room: RoomEngine, o: RoomStateObject<Options>, rootNode: BABYLON.Mesh) => void;
|
||||
onInited?: () => void;
|
||||
onOptionsUpdated?: <K extends keyof Options, V extends Options[K]>(kv: [K, V]) => void;
|
||||
interactions: Record<string, {
|
||||
label: string;
|
||||
fn: () => void;
|
||||
}>;
|
||||
primaryInteraction?: string | null;
|
||||
resetTemporaryState?: () => void;
|
||||
dispose?: () => void;
|
||||
};
|
||||
|
||||
export const WORLD_SCALE = 100;
|
||||
|
||||
type NumberOptionSchema = {
|
||||
type: 'number';
|
||||
label: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
};
|
||||
|
||||
type ColorOptionSchema = {
|
||||
type: 'color';
|
||||
label: string;
|
||||
@@ -74,13 +84,13 @@ type SelectOptionSchema = {
|
||||
enum: string[];
|
||||
};
|
||||
|
||||
type OptionsSchema = Record<string, ColorOptionSchema | SelectOptionSchema>;
|
||||
type OptionsSchema = Record<string, NumberOptionSchema | ColorOptionSchema | SelectOptionSchema>;
|
||||
|
||||
type GetOptionsSchemaValues<T extends OptionsSchema> = {
|
||||
[K in keyof T]: T[K] extends ColorOptionSchema ? [number, number, number] : T[K] extends SelectOptionSchema ? T[K]['enum'][number] : never;
|
||||
[K in keyof T]: T[K] extends NumberOptionSchema ? number : T[K] extends ColorOptionSchema ? [number, number, number] : T[K] extends SelectOptionSchema ? T[K]['enum'][number] : never;
|
||||
};
|
||||
|
||||
type ObjectDef<OpSc extends OptionsSchema> = {
|
||||
type ObjectDef<OpSc extends OptionsSchema = OptionsSchema> = {
|
||||
id: string;
|
||||
name: string;
|
||||
options: {
|
||||
@@ -92,7 +102,7 @@ type ObjectDef<OpSc extends OptionsSchema> = {
|
||||
createInstance: (args: {
|
||||
room: RoomEngine;
|
||||
root: BABYLON.Mesh;
|
||||
options: GetOptionsSchemaValues<OpSc>;
|
||||
options: Readonly<GetOptionsSchemaValues<OpSc>>;
|
||||
loaderResult: BABYLON.ISceneLoaderAsyncResult;
|
||||
meshUpdated: () => void;
|
||||
}) => RoomObjectInstance<GetOptionsSchemaValues<OpSc>>;
|
||||
@@ -176,7 +186,7 @@ export class RoomEngine {
|
||||
objectMesh: BABYLON.Mesh;
|
||||
objectInstance: RoomObjectInstance<any>;
|
||||
objectState: RoomStateObject<any>;
|
||||
objectDef: ObjectDef<any>;
|
||||
objectDef: ObjectDef;
|
||||
} | null>(null);
|
||||
private time: 0 | 1 | 2 = 0; // 0: 昼, 1: 夕, 2: 夜
|
||||
private roomCollisionMeshes: BABYLON.AbstractMesh[] = [];
|
||||
@@ -207,7 +217,7 @@ export class RoomEngine {
|
||||
|
||||
registerBuiltInLoaders();
|
||||
|
||||
this.engine = new BABYLON.Engine(options.canvas, false, { alpha: false });
|
||||
this.engine = new BABYLON.Engine(options.canvas, false, { alpha: false, antialias: false });
|
||||
this.scene = new BABYLON.Scene(this.engine);
|
||||
//this.scene.useRightHandedSystem = true;
|
||||
|
||||
@@ -403,6 +413,14 @@ export class RoomEngine {
|
||||
this.zGridPreviewPlane.isPickable = false;
|
||||
this.zGridPreviewPlane.isVisible = false;
|
||||
|
||||
watch(this.isEditMode, (v) => {
|
||||
if (v) {
|
||||
for (const obji of this.objectInstances.values()) {
|
||||
obji.resetTemporaryState?.();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let isDragging = false;
|
||||
|
||||
this.canvas.addEventListener('pointerdown', (ev) => {
|
||||
@@ -1187,6 +1205,16 @@ export class RoomEngine {
|
||||
this.grabbingCtx.rotation += delta;
|
||||
}
|
||||
|
||||
public updateObjectOption(objectId: string, key: string, value: any) {
|
||||
const options = this.roomState.installedObjects.find(o => o.id === objectId)?.options;
|
||||
if (options == null) return;
|
||||
options[key] = value;
|
||||
|
||||
const obji = this.objectInstances.get(objectId);
|
||||
if (obji == null) return;
|
||||
obji.onOptionsUpdated?.([key, value]);
|
||||
}
|
||||
|
||||
public resize() {
|
||||
this.engine.resize();
|
||||
}
|
||||
|
||||
@@ -5,16 +5,47 @@
|
||||
|
||||
import * as BABYLON from '@babylonjs/core';
|
||||
import { defineObject, WORLD_SCALE } from '../engine.js';
|
||||
import { createOverridedStates } from '../utility.js';
|
||||
|
||||
export const blind = defineObject({
|
||||
id: 'blind',
|
||||
defaultOptions: {
|
||||
blades: 24,
|
||||
angle: 0,
|
||||
open: 1,
|
||||
name: 'Blind',
|
||||
options: {
|
||||
schema: {
|
||||
blades: {
|
||||
type: 'number',
|
||||
label: 'Number of blades',
|
||||
min: 1,
|
||||
max: 100,
|
||||
},
|
||||
angle: {
|
||||
type: 'number',
|
||||
label: 'Blade rotation angle (radian)',
|
||||
min: -Math.PI / 2,
|
||||
max: Math.PI / 2,
|
||||
step: 0.01,
|
||||
},
|
||||
open: {
|
||||
type: 'number',
|
||||
label: 'Opening state',
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
},
|
||||
},
|
||||
default: {
|
||||
blades: 24,
|
||||
angle: 0,
|
||||
open: 1,
|
||||
},
|
||||
},
|
||||
placement: 'bottom',
|
||||
createInstance: ({ options, loaderResult, meshUpdated }) => {
|
||||
const temp = createOverridedStates({
|
||||
angle: () => options.angle,
|
||||
open: () => options.open,
|
||||
});
|
||||
|
||||
const blade = loaderResult.meshes[0].getChildMeshes().find(m => m.name === 'Blade') as BABYLON.Mesh;
|
||||
blade.rotation = new BABYLON.Vector3(options.angle, 0, 0);
|
||||
|
||||
@@ -28,10 +59,10 @@ export const blind = defineObject({
|
||||
|
||||
for (let i = 0; i < options.blades; i++) {
|
||||
const b = blade.clone();
|
||||
if (i / options.blades < options.open) {
|
||||
if (i / options.blades < temp.open) {
|
||||
b.position.y -= (i * 4/*cm*/) / WORLD_SCALE;
|
||||
} else {
|
||||
b.position.y -= (((options.blades - 1) * options.open * 4/*cm*/) + (i * 0.3/*cm*/)) / WORLD_SCALE;
|
||||
b.position.y -= (((options.blades - 1) * temp.open * 4/*cm*/) + (i * 0.3/*cm*/)) / WORLD_SCALE;
|
||||
}
|
||||
blades.push(b);
|
||||
}
|
||||
@@ -41,7 +72,7 @@ export const blind = defineObject({
|
||||
|
||||
const applyAngle = () => {
|
||||
for (const b of [blade, ...blades]) {
|
||||
b.rotation.x = options.angle;
|
||||
b.rotation.x = temp.angle;
|
||||
b.rotation.x += Math.random() * 0.3 - 0.15;
|
||||
}
|
||||
};
|
||||
@@ -57,20 +88,25 @@ export const blind = defineObject({
|
||||
adjustBladeRotation: {
|
||||
label: 'Adjust blade rotation',
|
||||
fn: () => {
|
||||
options.angle += Math.PI / 8;
|
||||
if (options.angle >= Math.PI / 2) options.angle = -Math.PI / 2;
|
||||
temp.angle += Math.PI / 8;
|
||||
if (temp.angle >= Math.PI / 2) temp.angle = -Math.PI / 2;
|
||||
applyAngle();
|
||||
},
|
||||
},
|
||||
openClose: {
|
||||
label: 'Open/close',
|
||||
fn: () => {
|
||||
options.open -= 0.25;
|
||||
if (options.open < 0) options.open = 1;
|
||||
temp.open -= 0.25;
|
||||
if (temp.open < 0) temp.open = 1;
|
||||
applyOpeningState();
|
||||
},
|
||||
},
|
||||
},
|
||||
resetTemporaryState: () => {
|
||||
temp.$reset();
|
||||
applyAngle();
|
||||
applyOpeningState();
|
||||
},
|
||||
primaryInteraction: 'openClose',
|
||||
};
|
||||
},
|
||||
|
||||
@@ -29,16 +29,21 @@ export const tabletopDigitalClock = defineObject({
|
||||
},
|
||||
placement: 'top',
|
||||
createInstance: ({ room, options, root }) => {
|
||||
const applyBodyColor = () => {
|
||||
const bodyMesh = root.getChildMeshes().find(m => m.name.includes('__X_BODY__')) as BABYLON.Mesh;
|
||||
const bodyMaterial = bodyMesh.material as BABYLON.PBRMaterial;
|
||||
|
||||
if (options.bodyStyle === 'color') {
|
||||
const [r, g, b] = options.bodyColor;
|
||||
bodyMaterial.albedoColor = new BABYLON.Color3(r, g, b);
|
||||
} else {
|
||||
bodyMaterial.albedoTexture = room.scene.getTextureByName('tabletop_digital_clock_wood');
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
onInited: () => {
|
||||
const bodyMesh = root.getChildMeshes().find(m => m.name.includes('__X_BODY__')) as BABYLON.Mesh;
|
||||
|
||||
const bodyMaterial = bodyMesh.material as BABYLON.PBRMaterial;
|
||||
|
||||
if (options.bodyStyle === 'color') {
|
||||
const [r, g, b] = options.bodyColor;
|
||||
bodyMaterial.albedoColor = new BABYLON.Color3(r, g, b);
|
||||
}
|
||||
applyBodyColor();
|
||||
|
||||
const segmentMeshes = {
|
||||
'1a': root.getChildMeshes().find(m => m.name.includes('__TIME_7SEG_1A__')),
|
||||
@@ -85,6 +90,11 @@ export const tabletopDigitalClock = defineObject({
|
||||
}
|
||||
}, 1000));
|
||||
},
|
||||
onOptionsUpdated: ([k, v]) => {
|
||||
if (k === 'bodyColor') {
|
||||
applyBodyColor();
|
||||
}
|
||||
},
|
||||
interactions: {},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -213,3 +213,28 @@ export function get7segMeshesOfCurrentTime(meshes: {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createOverridedStates<T extends Record<string, (() => any)>>(stateDefs: T): { [K in keyof T]: ReturnType<T[K]>; } & { $reset: () => void } {
|
||||
const overridedStates = {} as { [K in keyof T]: ReturnType<T[K]>; };
|
||||
const result = {} as { [K in keyof T]: ReturnType<T[K]>; } & { $reset: () => void };
|
||||
|
||||
for (const k in stateDefs) {
|
||||
Object.defineProperty(result, k, {
|
||||
get() {
|
||||
return overridedStates[k] ?? stateDefs[k]();
|
||||
},
|
||||
set(value) {
|
||||
overridedStates[k] = value;
|
||||
},
|
||||
enumerable: true,
|
||||
});
|
||||
}
|
||||
|
||||
result.$reset = () => {
|
||||
for (const k in stateDefs) {
|
||||
overridedStates[k] = stateDefs[k]();
|
||||
}
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user