1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-06-06 04:04:18 +02:00
This commit is contained in:
syuilo
2026-02-19 17:23:33 +09:00
parent 411c4ef3ae
commit 6a08231591
5 changed files with 79 additions and 35 deletions

View File

@@ -5,22 +5,28 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div :class="$style.root" class="_pageScrollable"> <div :class="$style.root" class="_pageScrollable">
<canvas ref="canvas" :class="$style.canvas" @keypress="onKeypress" @wheel="onWheel"></canvas> <div :class="$style.screen">
<div v-if="engine != null" class="_buttons" :class="$style.controls"> <canvas ref="canvas" :class="$style.canvas" @keydown="onKeydown" @wheel="onWheel"></canvas>
<!--<MkButton v-for="action in actions" :key="action.key" @click="action.fn">{{ action.label }}{{ hotkeyToLabel(action.hotkey) }}</MkButton>--> <div v-if="engine != null" class="_buttonsCenter" :class="$style.overlayControls">
<MkButton @click="toggleLight">Toggle Light</MkButton>
<MkButton :primary="engine.isEditMode.value" @click="toggleEditMode">Edit mode: {{ engine.isEditMode.value ? 'on' : 'off' }}</MkButton>
<template v-if="engine.isEditMode.value"> <template v-if="engine.isEditMode.value">
<MkButton v-if="engine.ui.isGrabbing" @click="endGrabbing">Put (E)</MkButton> <MkButton v-if="engine.ui.isGrabbing" @click="endGrabbing">Put (E)</MkButton>
<MkButton v-else-if="engine.ui.isGrabbingForInstall" @click="endGrabbing">Install (E)</MkButton> <MkButton v-else-if="engine.ui.isGrabbingForInstall" @click="endGrabbing">Install (E)</MkButton>
<MkButton v-else @click="beginSelectedInstalledObjectGrabbing">Grab (E)</MkButton> <MkButton v-else @click="beginSelectedInstalledObjectGrabbing">Grab (E)</MkButton>
<MkButton v-if="engine.ui.isGrabbing || engine.ui.isGrabbingForInstall" @click="rotate">Rotate (R)</MkButton>
<MkButton :primary="engine.enableGridSnapping.value" @click="showSnappingMenu">Grid Snap: {{ engine.enableGridSnapping.value ? 'on' : 'off' }}</MkButton> <MkButton :primary="engine.enableGridSnapping.value" @click="showSnappingMenu">Grid Snap: {{ engine.enableGridSnapping.value ? 'on' : 'off' }}</MkButton>
</template> </template>
<MkButton v-if="engine.isSitting.value" @click="engine.standUp()">降りる (Q)</MkButton> <MkButton v-if="engine.isSitting.value" @click="engine.standUp()">降りる (Q)</MkButton>
<template v-for="interaction in interacions" :key="interaction.id"> <template v-for="interaction in interacions" :key="interaction.id">
<MkButton @click="interaction.fn()">{{ interaction.label }}</MkButton> <MkButton inline @click="interaction.fn()">{{ interaction.label }}{{ interaction.isPrimary ? ' (E)' : '' }}</MkButton>
</template> </template>
</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> <MkButton @click="addObject">addObject</MkButton>
</div> </div>
</div> </div>
@@ -44,6 +50,7 @@ const engine = shallowRef<RoomEngine | null>(null);
const interacions = shallowRef<{ const interacions = shallowRef<{
id: string; id: string;
label: string; label: string;
isPrimary: boolean;
fn: () => void; fn: () => void;
}[]>([]); }[]>([]);
@@ -97,36 +104,38 @@ const actions = computed<Action[]>(() => {
return actions; return actions;
}); });
function onKeypress(ev: KeyboardEvent) { function onKeydown(ev: KeyboardEvent) {
if (engine.value == null) return; if (engine.value == null) return;
/* todo
if (ev.code === 'KeyE') { if (ev.code === 'KeyE') {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
if (engine.value.isEditMode.value) { if (engine.value.isEditMode.value) {
if (engine.value.ui.isGrabbing || engine.value.ui.isGrabbingForInstall) { if (engine.value.ui.isGrabbing || engine.value.ui.isGrabbingForInstall) {
this.endGrabbing(); endGrabbing();
} else { } else {
this.beginSelectedInstalledObjectGrabbing(); beginSelectedInstalledObjectGrabbing();
} }
} else if (this.selectedObjectId.value != null) { } else if (engine.value.selectedObjectId.value != null) {
this.interact(this.selectedObjectId.value); engine.value.interact(engine.value.selectedObjectId.value);
} }
} else if (ev.code === 'KeyR') { } else if (ev.code === 'KeyR') {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
if (this.grabbingCtx != null) { if (engine.value.ui.isGrabbing || engine.value.ui.isGrabbingForInstall) {
this.grabbingCtx.rotation += Math.PI / 8; rotate();
} }
} else if (ev.code === 'KeyQ') { } else if (ev.code === 'KeyQ') {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
if (this.isSitting.value) { if (engine.value.isSitting.value) {
this.standUp(); engine.value.standUp();
} }
} else if (ev.code === 'Tab') {
ev.preventDefault();
ev.stopPropagation();
toggleEditMode();
} }
*/
} }
function onWheel(ev: WheelEvent) { function onWheel(ev: WheelEvent) {
@@ -217,7 +226,12 @@ onMounted(() => {
type: 'tabletopDigitalClock', type: 'tabletopDigitalClock',
position: [-35, 90, 175], position: [-35, 90, 175],
rotation: [0, Math.PI, 0], rotation: [0, Math.PI, 0],
options: {}, options: {
bodyStyle: {
type: 'color',
value: [0.45, 0.8, 1],
},
},
}, { }, {
id: 'f3', id: 'f3',
type: 'snakeplant', type: 'snakeplant',
@@ -421,6 +435,7 @@ onMounted(() => {
interacions.value = Object.entries(obji.interactions).map(([interactionId, interactionInfo]) => ({ interacions.value = Object.entries(obji.interactions).map(([interactionId, interactionInfo]) => ({
id: interactionId, id: interactionId,
label: interactionInfo.label, label: interactionInfo.label,
isPrimary: obji.primaryInteraction === interactionId,
fn: interactionInfo.fn, fn: interactionInfo.fn,
})); }));
} }
@@ -477,8 +492,8 @@ function showSnappingMenu(ev: PointerEvent) {
}], ev.currentTarget ?? ev.target); }], ev.currentTarget ?? ev.target);
} }
function toggleGridSnapping() { function rotate() {
engine.value.enableGridSnapping.value = !engine.value.enableGridSnapping.value; engine.value.changeGrabbingRotationY(Math.PI / 8);
canvas.value!.focus(); canvas.value!.focus();
} }
@@ -510,6 +525,12 @@ definePage(() => ({
height: 100%; height: 100%;
} }
.screen {
position: relative;
width: 100%;
height: 90cqh;
}
.canvas { .canvas {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -518,9 +539,13 @@ definePage(() => ({
} }
.controls { .controls {
}
.overlayControls {
position: absolute; position: absolute;
bottom: 16px; bottom: 0;
left: 0; left: 0;
z-index: 1;
width: 100%; width: 100%;
} }
</style> </style>

View File

@@ -854,7 +854,7 @@ export class RoomEngine {
const objectInstance = def.createInstance({ const objectInstance = def.createInstance({
room: this, room: this,
root, root,
options: args.options, options: args.options, // todo: merge with default options
loaderResult: loaderResult, loaderResult: loaderResult,
meshUpdated: () => { meshUpdated: () => {
meshUpdated(this.objectMeshs.get(args.id)!.getChildMeshes() as BABYLON.Mesh[]); meshUpdated(this.objectMeshs.get(args.id)!.getChildMeshes() as BABYLON.Mesh[]);
@@ -998,7 +998,7 @@ export class RoomEngine {
this.zGridPreviewPlane.isVisible = false; this.zGridPreviewPlane.isVisible = false;
} }
private interact(oid: string) { public interact(oid: string) {
const o = this.roomState.installedObjects.find(o => o.id === oid)!; const o = this.roomState.installedObjects.find(o => o.id === oid)!;
const mesh = this.objectMeshs.get(o.id)!; const mesh = this.objectMeshs.get(o.id)!;
const objDef = getObjectDef(o.type); const objDef = getObjectDef(o.type);
@@ -1150,6 +1150,11 @@ export class RoomEngine {
if (this.grabbingCtx.distance < 5/*cm*/) this.grabbingCtx.distance = 5/*cm*/; if (this.grabbingCtx.distance < 5/*cm*/) this.grabbingCtx.distance = 5/*cm*/;
} }
public changeGrabbingRotationY(delta: number) {
if (this.grabbingCtx == null) return;
this.grabbingCtx.rotation += delta;
}
public resize() { public resize() {
this.engine.resize(); this.engine.resize();
} }

View File

@@ -5,16 +5,30 @@
import * as BABYLON from '@babylonjs/core'; import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js'; import { defineObject } from '../engine.js';
import { get7segMeshesOfCurrentTime, yuge } from '../utility.js'; import { get7segMeshesOfCurrentTime } from '../utility.js';
export const tabletopDigitalClock = defineObject({ export const tabletopDigitalClock = defineObject({
id: 'tabletopDigitalClock', id: 'tabletopDigitalClock',
defaultOptions: {}, defaultOptions: {
bodyStyle: {
type: 'color',
value: [0.45, 0.8, 0],
} as { type: 'color'; value: [number, number, number] } | { type: 'wood'; } | null,
},
placement: 'top', placement: 'top',
createInstance: ({ room, root }) => { createInstance: ({ room, options, root }) => {
return { return {
onInited: () => { onInited: () => {
const meshes = { const bodyMesh = root.getChildMeshes().find(m => m.name.includes('__X_BODY__')) as BABYLON.Mesh;
const bodyMaterial = bodyMesh.material as BABYLON.PBRMaterial;
if (options.bodyStyle?.type === 'color') {
const [r, g, b] = options.bodyStyle.value;
bodyMaterial.albedoColor = new BABYLON.Color3(r, g, b);
}
const segmentMeshes = {
'1a': root.getChildMeshes().find(m => m.name.includes('__TIME_7SEG_1A__')), '1a': root.getChildMeshes().find(m => m.name.includes('__TIME_7SEG_1A__')),
'1b': root.getChildMeshes().find(m => m.name.includes('__TIME_7SEG_1B__')), '1b': root.getChildMeshes().find(m => m.name.includes('__TIME_7SEG_1B__')),
'1c': root.getChildMeshes().find(m => m.name.includes('__TIME_7SEG_1C__')), '1c': root.getChildMeshes().find(m => m.name.includes('__TIME_7SEG_1C__')),
@@ -48,9 +62,9 @@ export const tabletopDigitalClock = defineObject({
const colonMeshes = root.getChildMeshes().filter(m => m.name.includes('__TIME_7SEG_COLON__')); const colonMeshes = root.getChildMeshes().filter(m => m.name.includes('__TIME_7SEG_COLON__'));
room.intervalIds.push(window.setInterval(() => { room.intervalIds.push(window.setInterval(() => {
const onMeshes = get7segMeshesOfCurrentTime(meshes); const onMeshes = get7segMeshesOfCurrentTime(segmentMeshes);
for (const mesh of Object.values(meshes)) { for (const mesh of Object.values(segmentMeshes)) {
mesh.isVisible = onMeshes.includes(mesh); mesh.isVisible = onMeshes.includes(mesh);
} }