1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-05-13 17:35:40 +02:00
This commit is contained in:
syuilo
2026-04-30 16:01:11 +09:00
parent c7c785ad2a
commit 6cf90fd714
9 changed files with 136 additions and 2 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

View File

@@ -17,6 +17,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<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 === 'string'">
<MkInput type="text" :modelValue="options[k]" @update:modelValue="v => emit('update', k, v)"></MkInput>
</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>

View File

@@ -31,6 +31,7 @@ import { djMixer } from './objects/djMixer.js';
import { djPlayer } from './objects/djPlayer.js';
import { ductRailSpotLights } from './objects/ductRailSpotLights.js';
import { ductTape } from './objects/ductTape.js';
import { electronicDisplayBoard } from './objects/electronicDisplayBoard.js';
import { emptyBento } from './objects/emptyBento.js';
import { energyDrink } from './objects/energyDrink.js';
import { envelope } from './objects/envelope.js';
@@ -128,6 +129,7 @@ export const OBJECT_DEFS = [
djPlayer,
ductRailSpotLights,
ductTape,
electronicDisplayBoard,
emptyBento,
energyDrink,
envelope,

View File

@@ -47,6 +47,11 @@ type BooleanOptionSchema = {
label: string;
};
type StringOptionSchema = {
type: 'string';
label: string;
};
type ColorOptionSchema = {
type: 'color';
label: string;
@@ -76,12 +81,13 @@ type SeedOptionSchema = {
label: string;
};
type OptionsSchema = Record<string, NumberOptionSchema | BooleanOptionSchema | ColorOptionSchema | EnumOptionSchema | RangeOptionSchema | ImageOptionSchema | SeedOptionSchema>;
type OptionsSchema = Record<string, NumberOptionSchema | BooleanOptionSchema | StringOptionSchema | ColorOptionSchema | EnumOptionSchema | RangeOptionSchema | ImageOptionSchema | SeedOptionSchema>;
type GetOptionsSchemaValues<T extends OptionsSchema> = {
[K in keyof T]:
T[K] extends NumberOptionSchema ? number :
T[K] extends BooleanOptionSchema ? boolean :
T[K] extends StringOptionSchema ? string :
T[K] extends ColorOptionSchema ? [number, number, number] :
T[K] extends EnumOptionSchema ? T[K]['enum'][number] :
T[K] extends RangeOptionSchema ? number :

View File

@@ -0,0 +1,122 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../object.js';
import { createPlaneUvMapper, RecyvlingTextGrid } from '../../utility.js';
export const electronicDisplayBoard = defineObject({
id: 'electronicDisplayBoard',
name: 'electronicDisplayBoard',
options: {
schema: {
text: {
type: 'string',
label: 'Text',
},
frameColor: {
type: 'color',
label: 'Frame color',
},
ledColor: {
type: 'color',
label: 'LED color',
},
},
default: {
text: 'Hello, Misskey!',
frameColor: [0.2, 0.2, 0.2],
ledColor: [1, 1, 1],
},
},
placement: 'side',
hasCollisions: false,
hasTexture: true,
createInstance: async ({ scene, options, model, timer }) => {
const frameMaterial = model.findMaterial('__X_BODY__');
const textMaterial = new BABYLON.PBRMaterial('textMaterial', scene);
textMaterial.albedoColor = new BABYLON.Color3(0, 0, 0);
textMaterial.roughness = 1;
const texLoading = Promise.withResolvers<void>();
const tex = new BABYLON.Texture('/client-assets/room/textures/dot-matrix-chars.png', scene, false, false, undefined, () => {
textMaterial.emissiveTexture = tex;
textMaterial.albedoTexture = tex;
textMaterial.disableLighting = true;
textMaterial.emissiveTexture.hasAlpha = true;
textMaterial.transparencyMode = BABYLON.Material.MATERIAL_ALPHABLEND;
textMaterial.useAlphaFromAlbedoTexture = true;
textMaterial.freeze();
texLoading.resolve();
}, (message, exception) => {
console.warn('Failed to load texture:', message, exception);
textMaterial.emissiveColor = new BABYLON.Color3(0, 1, 0);
textMaterial.emissiveTexture = null;
texLoading.resolve();
});
await texLoading.promise;
const maxChars = 6;
const displayMesh = model.findMesh('__X_DISPLAY__');
displayMesh.material = textMaterial;
const textManager = new RecyvlingTextGrid(displayMesh, maxChars, {
meshFlipped: true,
material: textMaterial,
});
model.bakeExcludeMeshes = [displayMesh];
const applyFrameColor = () => {
const [r, g, b] = options.frameColor;
frameMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyFrameColor();
const applyLedColor = () => {
const [r, g, b] = options.ledColor;
textMaterial.emissiveColor = new BABYLON.Color3(r, g, b);
};
applyLedColor();
let text = '';
const applyText = () => {
text = options.text + ' ';
};
applyText();
let textIndex = 0;
timer.setInterval(() => {
let displayText = '';
for (let i = 0; i < maxChars; i++) {
displayText += text[(textIndex + i) % text.length];
}
textManager.write(displayText);
textIndex = (textIndex + 1) % text.length;
}, 500);
return {
onInited: () => {
},
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'text': applyText(); break;
case 'frameColor': applyFrameColor(); break;
case 'ledColor': applyLedColor(); break;
}
},
interactions: {},
};
},
});

View File

@@ -384,6 +384,7 @@ export class RecyvlingText {
}
}
// 各面はUV展開時に全ての面が0~1の正方形に"reset"されていること。(全ての面がUV的に重なっている状態)
export class RecyvlingTextGrid {
public facesCount: number;
public mesh: BABYLON.Mesh;
@@ -487,7 +488,7 @@ export class RecyvlingTextGrid {
}
for (let i = 0; i < this.facesCount; i++) {
if (i + text.length >= (maxRepeat * text.length)) {
if (maxRepeat > 1 && (i + text.length >= (maxRepeat * text.length))) {
if (i >= this.facesCount - this.repeatSeparator.length) {
charIndexes.push(TEXT_TEXTURE_CHAR_MAP[this.repeatSeparator[(i - (this.facesCount - this.repeatSeparator.length)) % this.repeatSeparator.length]]);
} else {