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