1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-05-22 01:05:40 +02:00

update directory structure

This commit is contained in:
syuilo
2026-04-17 10:16:05 +09:00
parent ae463cde5e
commit ebdf627b19
96 changed files with 5 additions and 5 deletions

View File

@@ -1,32 +0,0 @@
# Misskey Room
## オブジェクトの仕様
- Blenderで設計すること。必ず.blendファイルも含めること。
- 単位はセンチメートルで設計すること。
- それを置いたときに底になる縦軸座標(blenderならz)が0になるように設計すること。
- BlenderならYがマイナスになる方向が手前。(=スザンヌを設置してデフォルトで顔が向いている方向が手前)
- 壁面設置の場合は壁面に接する面のY軸座標が0になるように設計すること。
- ポリゴン数は、極端にハイポリでない限りはパフォーマンスに影響を及ぼすことは少ないです(必要ドローコール数の多寡の方が重要)。とはいえ、モデリングのし易さ・見た目の統一感を考えると(極端に見た目が不自然にならない程度に)低ポリゴンを推奨します。
目安として、例えば円柱を考えると、直径20cm程度以下なら頂点数16、直径2cm程度以下なら頂点数8、0.5cm程度以下なら頂点数4程度を推奨します。(2の乗数を推奨) なおここでいう頂点数とは、メッシュ全体の頂点数ではなく、円柱を作成するときのオプション項目にある"Vertices"の値のことです。
具体例を挙げると、大きめの円形の壁掛け時計であれば32、コップであれば16、ストローであれば4~8 です。
イメージとしてはPS2とか3DSくらいのポリゴン感です。Misskey Roomは3DCG作品ではなくゲームなので、多少のローポリ感はあってもいいです。
- メッシュ名を `__TOP__` で始めると、その面の上にモノを置けることを示す。当該メッシュはレンダリングでは表示されません。
- メッシュ名を `__SIDE__` で始めると、その面にモノを貼り付けられることを示す。当該メッシュはレンダリングでは表示されません。
- なお、現状 `__TOP__` / `__SIDE__` メッシュは単一の面でなければなりません。つまりArray Modifierなどを適用した状態では正しく動作しません。
- メッシュ名を `__COLLISION__` で始めると、コリジョン用メッシュとして扱われます。当該メッシュはレンダリングでは表示されません。
- メッシュ名を `__PICK__` で始めると、レイのヒットチェック用メッシュとして扱われます。当該メッシュはレンダリングでは表示されません。
- `__PICK__`が無い場合、すべてのメッシュをヒットチェックメッシュとして扱いますが、例えば網目のようなメッシュではレイが隙間を通り抜けて後ろにあるオブジェクトにヒットしてしまうなどの問題が発生します。
- 後からモデルを調整したくなった時に備え、モディファイアを駆使するなどして、なるべく非破壊的なモデリングを心がけることを推奨します。
- パーツ的に分かれていることが自然なメッシュについても、できるだけマージせす、別々のメッシュのままにしてください。
- モディファイアをapplyしないとならないシチュエーションでは、apply前の状態を複製して(非表示にした上で)残すことを推奨します。
- 位置や回転についても、不必要にapplyしないでください。
- 現在のMisskey RoomのGLB root除去システムの実装上、スケールに1以外の値を持つメッシュにシェイプキーを設定することはサポートされていません。
- マテリアルは基本的にPrincipled BSDFを使ってください。
- パフォーマンスに影響を及ぼすため、不必要にUVを作成しないでください。
- メッシュ名やマテリアル名などは英語であると嬉しいです。
### 開発者向けメモ
- シェイプキーを使用する場合、normalのエクスポートが有効だと面のレンダリングがおかしくなる場合があります。その場合は無効化してください。
- 非破壊的なモデリングの原則に反しない限り、なるべくscaleはapplyした状態で(=scaleが1, 1, 1の状態で)エクスポートすること。そうしないとbake前後で法線が変わるのかレンダリング結果が異なる現象が発生することがあります。
- 位置や回転についてはapplyする必要はありません。むしろなるべくしないでください。
- パフォーマンスに影響を及ぼすため、UV情報が必要ない場合はエクスポート時にUVのチェックを外すこと。

View File

@@ -1,246 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { reactive, ref, shallowRef, triggerRef, watch } from 'vue';
import * as BABYLON from '@babylonjs/core';
import RoomWorker from './worker?worker';
import { RoomEngine } from './engine.js';
import type { ShallowRef } from 'vue';
import type { ObjectDef, RoomState, RoomStateObject } from './engine.js';
import * as sound from '@/utility/sound.js';
// 抽象化レイヤー
export class RoomController {
private worker: Worker | null = null;
private engine: RoomEngine | null = null;
private canvas: HTMLCanvasElement | null = null;
public isReady = ref(false);
public isSitting = ref(false);
public isEditMode = ref(false);
public grabbing = ref<{ forInstall: boolean } | null>(null);
public gridSnapping = ref({ enabled: true, scale: 4 });
public selected = ref<{
objectId: string;
objectState: RoomStateObject;
objectDef: ObjectDef;
} | null>(null);
public roomState: ShallowRef<RoomState>;
public initializeProgress = ref(0);
constructor(roomState: RoomState) {
this.roomState = shallowRef(roomState);
}
public async init(canvas: HTMLCanvasElement, workerMode = false) {
this.canvas = canvas;
this.canvas.width = canvas.clientWidth;
this.canvas.height = canvas.clientHeight;
if (workerMode) {
const offscreen = canvas.transferControlToOffscreen();
this.worker = new RoomWorker();
this.worker.postMessage({ type: 'init', canvas: offscreen, roomState: this.roomState.value }, [offscreen]);
this.isReady.value = true;
} else {
const babylonEngine = new BABYLON.WebGPUEngine(canvas, { doNotHandleContextLost: true });
babylonEngine.compatibilityMode = false;
await babylonEngine.initAsync();
this.engine = new RoomEngine(this.roomState.value, { canvas, engine: babylonEngine });
this.engine.on('loadingProgress', ({ progress }) => {
this.initializeProgress.value = progress;
});
await this.engine.init();
this.initializeProgress.value = 1;
this.isReady.value = true;
this.engine.on('changeGrabbingState', ({ grabbing }) => {
this.grabbing.value = grabbing;
});
this.engine.on('changeEditMode', ({ isEditMode }) => {
this.isEditMode.value = isEditMode;
});
this.engine.on('changeGridSnapping', ({ gridSnapping }) => {
this.gridSnapping.value = gridSnapping;
});
this.engine.on('changeSelectedState', ({ selected }) => {
this.selected.value = selected;
});
this.engine.on('changeRoomState', ({ roomState }) => {
this.roomState.value = roomState;
triggerRef(this.selected);
});
this.engine.on('playSfxUrl', ({ url, options }) => {
sound.playUrl(url, options);
});
}
this.canvas.addEventListener('keydown', (ev) => {
if (this.worker != null) {
this.worker.postMessage({ type: 'dom:keydown', ev: { code: ev.code, shiftKey: ev.shiftKey } });
} else if (this.engine != null) {
this.engine.domEvents.emit('keydown', { code: ev.code, shiftKey: ev.shiftKey });
}
ev.preventDefault();
ev.stopPropagation();
return false;
});
this.canvas.addEventListener('keyup', (ev) => {
if (this.worker != null) {
this.worker.postMessage({ type: 'dom:keyup', ev: { code: ev.code, shiftKey: ev.shiftKey } });
} else if (this.engine != null) {
this.engine.domEvents.emit('keyup', { code: ev.code, shiftKey: ev.shiftKey });
}
ev.preventDefault();
ev.stopPropagation();
return false;
});
this.canvas.addEventListener('pointerdown', (ev) => {
// todo
});
this.canvas.addEventListener('wheel', (ev) => {
if (this.worker != null) {
this.worker.postMessage({ type: 'dom:wheel', ev: { deltaY: ev.deltaY } });
} else if (this.engine != null) {
this.engine.domEvents.emit('wheel', { deltaY: ev.deltaY });
}
ev.preventDefault();
ev.stopPropagation();
return false;
});
let isDragging = false;
this.canvas.addEventListener('pointerdown', (ev) => {
this.canvas.setPointerCapture(ev.pointerId);
});
this.canvas.addEventListener('pointermove', (ev) => {
if (this.canvas.hasPointerCapture(ev.pointerId)) {
isDragging = true;
}
});
this.canvas.addEventListener('pointerup', (ev) => {
window.setTimeout(() => {
isDragging = false;
this.canvas.releasePointerCapture(ev.pointerId);
}, 0);
});
this.canvas.addEventListener('click', (ev) => {
if (isDragging) return;
if (this.worker != null) {
this.worker.postMessage({ type: 'dom:click', ev: { offsetX: ev.offsetX, offsetY: ev.offsetY } });
} else if (this.engine != null) {
this.engine.domEvents.emit('click', { offsetX: ev.offsetX, offsetY: ev.offsetY });
}
ev.preventDefault();
ev.stopPropagation();
return false;
});
}
public enterEditMode() {
if (this.worker != null) {
this.worker.postMessage({ type: 'enterEditMode' });
} else if (this.engine != null) {
this.engine.enterEditMode();
}
}
public exitEditMode() {
if (this.worker != null) {
this.worker.postMessage({ type: 'exitEditMode' });
} else if (this.engine != null) {
this.engine.exitEditMode();
}
}
public setGridSnapping(gridSnapping: { enabled: boolean; scale: number }) {
if (this.worker != null) {
this.worker.postMessage({ type: 'setGridSnapping', gridSnapping });
} else if (this.engine != null) {
this.engine.gridSnapping = gridSnapping;
}
}
public updateObjectOption(objectId: string, key: string, value: any) {
if (this.worker != null) {
this.worker.postMessage({ type: 'updateObjectOption', objectId, key, value });
} else if (this.engine != null) {
this.engine.updateObjectOption(objectId, key, value);
}
}
public beginSelectedInstalledObjectGrabbing() {
if (this.worker != null) {
this.worker.postMessage({ type: 'beginSelectedInstalledObjectGrabbing' });
} else if (this.engine != null) {
this.engine.beginSelectedInstalledObjectGrabbing();
}
}
public removeSelectedObject() {
if (this.worker != null) {
this.worker.postMessage({ type: 'removeSelectedObject' });
} else if (this.engine != null) {
this.engine.removeSelectedObject();
}
}
public addObject(type: string) {
if (this.worker != null) {
this.worker.postMessage({ type: 'addObject', objectType: type });
} else if (this.engine != null) {
this.engine.addObject(type);
}
}
public endGrabbing() {
if (this.worker != null) {
this.worker.postMessage({ type: 'endGrabbing' });
} else if (this.engine != null) {
this.engine.endGrabbing();
}
}
public toggleRoomLight() {
if (this.worker != null) {
this.worker.postMessage({ type: 'toggleRoomLight' });
} else if (this.engine != null) {
this.engine.toggleRoomLight();
}
}
public resize() {
if (this.canvas == null) return;
const width = this.canvas.clientWidth;
const height = this.canvas.clientHeight;
if (this.worker != null) {
this.worker.postMessage({ type: 'resize', width, height });
} else if (this.engine != null) {
this.engine.resize();
}
}
public destroy() {
if (this.worker != null) {
this.worker.terminate();
this.worker = null;
}
if (this.engine != null) {
this.engine.destroy();
this.engine = null;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,194 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { a4Case } from './objects/a4Case.js';
import { aircon } from './objects/aircon.js';
import { allInOnePc } from './objects/allInOnePc.js';
import { aquarium } from './objects/aquarium.js';
import { aromaReedDiffuser } from './objects/aromaReedDiffuser.js';
import { banknote } from './objects/banknote.js';
import { beamLamp } from './objects/beamLamp.js';
import { bed } from './objects/bed.js';
import { blind } from './objects/blind.js';
import { book } from './objects/book.js';
import { books } from './objects/books.js';
import { cactusS } from './objects/cactusS.js';
import { cardboardBox } from './objects/cardboardBox.js';
import { ceilingFanLight } from './objects/ceilingFanLight.js';
import { chair } from './objects/chair.js';
import { coffeeCup } from './objects/coffeeCup.js';
import { colorBox } from './objects/colorBox.js';
import { cuboid } from './objects/cuboid.js';
import { cupNoodle } from './objects/cupNoodle.js';
import { custardPudding } from './objects/custardPudding.js';
import { debugHipoly } from './objects/debugHipoly.js';
import { desk } from './objects/desk.js';
import { desktopPc } from './objects/desktopPc.js';
import { djMixer } from './objects/djMixer.js';
import { djPlayer } from './objects/djPlayer.js';
import { ductTape } from './objects/ductTape.js';
import { emptyBento } from './objects/emptyBento.js';
import { energyDrink } from './objects/energyDrink.js';
import { envelope } from './objects/envelope.js';
import { facialTissue } from './objects/facialTissue.js';
import { hangingTShirt } from './objects/hangingTShirt.js';
import { icosahedron } from './objects/icosahedron.js';
import { ironFrameShelf5, ironFrameShelf4, ironFrameShelf3 } from './objects/ironFrameShelf.js';
import { ironFrameTable } from './objects/ironFrameTable.js';
import { keyboard } from './objects/keyboard.js';
import { laptopPc } from './objects/laptopPc.js';
import { lavaLamp } from './objects/lavaLamp.js';
import { letterCase } from './objects/letterCase.js';
import { miObjet } from './objects/mi-objet.js';
import { milk } from './objects/milk.js';
import { miPlate } from './objects/miPlate.js';
import { miPlateDisplayed } from './objects/miPlateDisplayed.js';
import { mixer } from './objects/mixer.js';
import { monitor } from './objects/monitor.js';
import { monitorSpeaker } from './objects/monitorSpeaker.js';
import { monstera } from './objects/monstera.js';
import { mug } from './objects/mug.js';
import { newtonsCradle } from './objects/newtonsCradle.js';
import { openedCardboardBox } from './objects/openedCardboardBox.js';
import { pachira } from './objects/pachira.js';
import { pc } from './objects/pc.js';
import { petBottle } from './objects/petBottle.js';
import { piano } from './objects/piano.js';
import { pictureFrame } from './objects/pictureFrame.js';
import { pizza } from './objects/pizza.js';
import { plant } from './objects/plant.js';
import { plant2 } from './objects/plant2.js';
import { poster } from './objects/poster.js';
import { powerStrip } from './objects/powerStrip.js';
import { radiometer } from './objects/radiometer.js';
import { randomBooks } from './objects/randomBooks.js';
import { rolledUpPoster } from './objects/rolledUpPoster.js';
import { roundRug } from './objects/roundRug.js';
import { router } from './objects/router.js';
import { siphon } from './objects/siphon.js';
import { snakeplant } from './objects/snakeplant.js';
import { speaker } from './objects/speaker.js';
import { sprayer } from './objects/sprayer.js';
import { steelRack } from './objects/steelRack.js';
import { tabletopCalendar } from './objects/tabletopCalendar.js';
import { tabletopDigitalClock } from './objects/tabletopDigitalClock.js';
import { tabletopFlag } from './objects/tabletopFlag.js';
import { tabletopGlassPictureFrame } from './objects/tabletopGlassPictureFrame.js';
import { tabletopIronFrameStand } from './objects/tabletopIronFrameStand.js';
import { tabletopPictureFrame } from './objects/tabletopPictureFrame.js';
import { tapestry } from './objects/tapestry.js';
import { tetrapod } from './objects/tetrapod.js';
import { tv } from './objects/tv.js';
import { twistedCubeObjet } from './objects/twistedCubeObjet.js';
import { usedTissue } from './objects/usedTissue.js';
import { wallCanvas } from './objects/wallCanvas.js';
import { wallClock } from './objects/wallClock.js';
import { wallGlassPictureFrame } from './objects/wallGlassPictureFrame.js';
import { wallMirror } from './objects/wallMirror.js';
import { wallShelf } from './objects/wallShelf.js';
import { woodRingFloorLamp } from './objects/woodRingFloorLamp.js';
import { woodRingsPendantLight } from './objects/woodRingsPendantLight.js';
import { woodSoundAbsorbingPanel } from './objects/woodSoundAbsorbingPanel.js';
export const OBJECT_DEFS = [
a4Case,
aircon,
allInOnePc,
aquarium,
aromaReedDiffuser,
banknote,
beamLamp,
bed,
blind,
book,
books,
cactusS,
cardboardBox,
ceilingFanLight,
chair,
coffeeCup,
colorBox,
cuboid,
cupNoodle,
custardPudding,
desk,
desktopPc,
djMixer,
djPlayer,
ductTape,
emptyBento,
energyDrink,
envelope,
facialTissue,
hangingTShirt,
icosahedron,
ironFrameShelf5,
ironFrameShelf4,
ironFrameShelf3,
ironFrameTable,
keyboard,
laptopPc,
lavaLamp,
letterCase,
miObjet,
milk,
miPlate,
miPlateDisplayed,
mixer,
monitor,
monitorSpeaker,
monstera,
mug,
newtonsCradle,
openedCardboardBox,
pachira,
pc,
petBottle,
piano,
pictureFrame,
pizza,
plant,
plant2,
poster,
powerStrip,
radiometer,
randomBooks,
rolledUpPoster,
roundRug,
router,
siphon,
snakeplant,
speaker,
sprayer,
steelRack,
tabletopCalendar,
tabletopDigitalClock,
tabletopFlag,
tabletopGlassPictureFrame,
tabletopIronFrameStand,
tabletopPictureFrame,
tapestry,
tetrapod,
tv,
twistedCubeObjet,
usedTissue,
wallCanvas,
wallClock,
wallGlassPictureFrame,
wallMirror,
wallShelf,
woodRingFloorLamp,
woodRingsPendantLight,
woodSoundAbsorbingPanel,
debugHipoly,
];
export function getObjectDef(type: string): typeof OBJECT_DEFS[number] {
const def = OBJECT_DEFS.find(x => x.id === type);
if (def == null) {
throw new Error(`Unrecognized object type: ${type}`);
}
return def;
}

View File

@@ -1,43 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const a4Case = defineObject({
id: 'a4Case',
name: 'A4 Case',
options: {
schema: {
color: {
type: 'color',
label: 'Color',
},
},
default: {
color: [0.9, 0.9, 0.9],
},
},
placement: 'top',
hasCollisions: false,
createInstance: ({ options, model }) => {
const bodyMesh = model.findMesh('__X_BODY__');
const bodyMaterial = bodyMesh.material as BABYLON.PBRMaterial;
const applyColor = () => {
const [r, g, b] = options.color;
bodyMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyColor();
return {
onOptionsUpdated: ([k, v]) => {
applyColor();
},
interactions: {},
};
},
});

View File

@@ -1,23 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const aircon = defineObject({
id: 'aircon',
name: 'Air Conditioner',
options: {
schema: {},
default: {},
},
placement: 'wall',
hasCollisions: false,
canPreMeshesMerging: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,142 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject, WORLD_SCALE } from '../engine.js';
import { cm, createPlaneUvMapper } from '../utility.js';
export const allInOnePc = defineObject({
id: 'allInOnePc',
name: 'All-in-One PC',
options: {
schema: {
bodyColor: {
type: 'color',
label: 'Body color',
},
bezelColor: {
type: 'color',
label: 'Bezel color',
},
screenBrightness: {
type: 'range',
label: 'Screen brightness',
min: 0,
max: 1,
step: 0.01,
},
customPicture: {
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
bodyColor: [1, 1, 1],
bezelColor: [0, 0, 0],
screenBrightness: 0.35,
customPicture: null,
fit: 'cover',
},
},
placement: 'top',
hasTexture: true,
createInstance: async ({ room, scene, options, model }) => {
const matrix = model.root.getWorldMatrix(true);
const scale = new BABYLON.Vector3();
matrix.decompose(scale);
const light = new BABYLON.SpotLight('', new BABYLON.Vector3(cm(0), cm(30) / Math.abs(scale.y), 0), new BABYLON.Vector3(0, 0, 1), Math.PI / 1, 2, scene, room?.lightContainer != null);
light.parent = model.root;
light.diffuse = new BABYLON.Color3(1.0, 1.0, 1.0);
light.range = cm(100);
if (room?.lightContainer != null) room.lightContainer.addLight(light);
const screenMesh = model.findMesh('__X_SCREEN__');
const bodyMaterial = model.findMaterial('__X_BODY__');
const bezelMaterial = model.findMaterial('__X_BEZEL__');
const screenMaterial = model.findMaterial('__X_SCREEN__');
screenMaterial.ambientColor = new BABYLON.Color3(0, 0, 0);
screenMaterial.albedoColor = new BABYLON.Color3(0, 0, 0);
const updateUv = createPlaneUvMapper(screenMesh);
const applyFit = () => {
const tex = screenMaterial.emissiveTexture;
if (tex == null) return;
const srcAspect = tex.getSize().width / tex.getSize().height;
const targetAspect = 50 / 27.5;
updateUv(srcAspect, targetAspect, options.fit);
model.updated();
};
applyFit();
const applyCustomPicture = () => new Promise<void>((resolve) => {
if (options.customPicture != null) {
const tex = new BABYLON.Texture(options.customPicture, scene, false, false);
tex.wrapU = BABYLON.Texture.MIRROR_ADDRESSMODE;
tex.wrapV = BABYLON.Texture.MIRROR_ADDRESSMODE;
tex.level = 0.5;
screenMaterial.unfreeze();
screenMaterial.emissiveTexture = tex;
tex.onLoadObservable.addOnce(() => {
applyFit();
resolve();
});
} else {
screenMaterial.emissiveTexture = null;
resolve();
}
});
await applyCustomPicture();
const applyScreenBrightness = () => {
const b = options.screenBrightness;
screenMaterial.emissiveColor = new BABYLON.Color3(b, b, b);
light.intensity = (5 * b) * WORLD_SCALE * WORLD_SCALE;
};
applyScreenBrightness();
const applyBodyColor = () => {
const [r, g, b] = options.bodyColor;
bodyMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
const applyBezelColor = () => {
const [r, g, b] = options.bezelColor;
bezelMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyBodyColor();
applyBezelColor();
return {
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'bodyColor': applyBodyColor(); break;
case 'bezelColor': applyBezelColor(); break;
case 'screenBrightness': applyScreenBrightness(); break;
case 'customPicture': applyCustomPicture(); break;
case 'fit': applyFit(); break;
}
},
interactions: {},
};
},
});

View File

@@ -1,58 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
import { cm } from '../utility.js';
export const aquarium = defineObject({
id: 'aquarium',
name: 'Aquarium',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
createInstance: ({ scene, root }) => {
return {
onInited: () => {
const noiseTexture = new BABYLON.NoiseProceduralTexture('perlin', 256, scene);
noiseTexture.animationSpeedFactor = 70;
noiseTexture.persistence = 10;
noiseTexture.brightness = 0.5;
noiseTexture.octaves = 5;
const emitter = new BABYLON.TransformNode('emitter', scene);
emitter.parent = root;
emitter.position = new BABYLON.Vector3(cm(17), cm(7), cm(-9));
const ps = new BABYLON.ParticleSystem('', 128, scene);
ps.particleTexture = new BABYLON.Texture('/client-assets/room/objects/lava-lamp/bubble.png');
ps.emitter = emitter;
ps.isLocal = true;
ps.minEmitBox = new BABYLON.Vector3(cm(-2), 0, cm(-2));
ps.maxEmitBox = new BABYLON.Vector3(cm(2), 0, cm(2));
ps.minEmitPower = 40;
ps.maxEmitPower = 60;
ps.minLifeTime = 0.5;
ps.maxLifeTime = 0.5;
ps.minSize = cm(0.1);
ps.maxSize = cm(1);
ps.direction1 = new BABYLON.Vector3(0, 1, 0);
ps.direction2 = new BABYLON.Vector3(0, 1, 0);
ps.noiseTexture = noiseTexture;
ps.noiseStrength = new BABYLON.Vector3(500, 0, 500);
ps.emitRate = 32;
ps.blendMode = BABYLON.ParticleSystem.BLENDMODE_ADD;
//ps.color1 = new BABYLON.Color4(1, 1, 1, 0.3);
//ps.color2 = new BABYLON.Color4(1, 1, 1, 0.2);
//ps.colorDead = new BABYLON.Color4(1, 1, 1, 0);
ps.preWarmCycles = Math.random() * 1000;
ps.start();
},
interactions: {},
};
},
});

View File

@@ -1,57 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const aromaReedDiffuser = defineObject({
id: 'aromaReedDiffuser',
name: 'Aroma Reed Diffuser',
options: {
schema: {
bottleColor: {
type: 'color',
label: 'Bottle Color',
},
oilColor: {
type: 'color',
label: 'Oil Color',
},
},
default: {
bottleColor: [1, 0.83, 0.48],
oilColor: [1, 0.4, 0],
},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
canPreMeshesMerging: true,
createInstance: ({ options, model }) => {
const bottleMaterial = model.findMaterial('__X_BOTTLE__');
const oilMaterial = model.findMaterial('__X_OIL__');
const applyBottleColor = () => {
const [r, g, b] = options.bottleColor;
bottleMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
const applyOilColor = () => {
const [r, g, b] = options.oilColor;
oilMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyBottleColor();
applyOilColor();
return {
onOptionsUpdated: ([k, v]) => {
applyBottleColor();
applyOilColor();
},
interactions: {},
};
},
});

View File

@@ -1,24 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const banknote = defineObject({
id: 'banknote',
name: 'Banknote',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
canPreMeshesMerging: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,33 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject, WORLD_SCALE } from '../engine.js';
import { cm } from '../utility.js';
export const beamLamp = defineObject({
id: 'beamLamp',
name: 'Beam Lamp',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
canPreMeshesMerging: true,
createInstance: ({ room, root, scene }) => {
return {
onInited: () => {
const light = new BABYLON.PointLight('beamLampLight', new BABYLON.Vector3(0, cm(10), 0), scene, room?.lightContainer != null);
light.parent = root;
light.diffuse = new BABYLON.Color3(1.0, 0.5, 0.2);
light.intensity = 0.03 * WORLD_SCALE * WORLD_SCALE;
light.range = cm(100);
if (room?.lightContainer != null) room.lightContainer.addLight(light);
},
interactions: {},
};
},
});

View File

@@ -1,44 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const bed = defineObject({
id: 'bed',
name: 'Bed',
options: {
schema: {
color: {
type: 'color',
label: 'Color',
},
},
default: {
color: [0.2, 0.1, 0.02],
},
},
placement: 'floor',
hasCollisions: true,
hasTexture: true,
createInstance: ({ options, model }) => {
const bodyMesh = model.findMesh('__X_BODY__');
const bodyMaterial = bodyMesh.material as BABYLON.PBRMaterial;
const applyColor = () => {
const [r, g, b] = options.color;
bodyMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyColor();
return {
onOptionsUpdated: ([k, v]) => {
applyColor();
},
interactions: {},
};
},
});

View File

@@ -1,126 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
import { cm, createOverridedStates } from '../utility.js';
export const blind = defineObject({
id: 'blind',
name: 'Blind',
options: {
schema: {
blades: {
type: 'range',
label: 'Number of blades',
min: 1,
max: 100,
},
angle: {
type: 'range',
label: 'Blade rotation angle (radian)',
min: -Math.PI / 2,
max: Math.PI / 2,
step: 0.01,
},
open: {
type: 'range',
label: 'Opening state',
min: 0,
max: 1,
step: 0.01,
},
},
default: {
blades: 24,
angle: 0,
open: 1,
},
},
placement: 'bottom',
hasCollisions: false,
createInstance: ({ options, model }) => {
const temp = createOverridedStates({
angle: () => options.angle,
open: () => options.open,
});
const blade = model.findMesh('Blade');
blade.rotation = new BABYLON.Vector3(options.angle, 0, 0);
let blades = [] as BABYLON.InstancedMesh[];
const applyOpeningState = () => {
for (const b of blades) {
b.dispose();
}
blades = [];
const matrix = blade.parent.getWorldMatrix(true);
const scale = new BABYLON.Vector3();
matrix.decompose(scale);
for (let i = 0; i < options.blades; i++) {
const b = blade.clone('blade_' + i); // createInstanceを使いたいが、削除するときになぜかエラーになる
if (i / options.blades < temp.open) {
b.position.y -= (i * cm(4)) / Math.abs(scale.y);
} else {
b.position.y -= (((options.blades - 1) * temp.open * cm(4)) + (i * cm(0.3))) / Math.abs(scale.y);
}
blades.push(b);
}
model.updated();
};
const applyAngle = () => {
for (const b of [blade, ...blades]) {
b.rotation.x = temp.angle;
b.rotation.x += Math.random() * 0.3 - 0.15;
}
};
applyOpeningState();
applyAngle();
return {
onInited: () => {
},
interactions: {
adjustBladeRotation: {
label: 'Adjust blade rotation',
fn: () => {
temp.angle += Math.PI / 8;
if (temp.angle >= Math.PI / 2) temp.angle = -Math.PI / 2;
applyAngle();
},
},
openClose: {
label: 'Open/close',
fn: () => {
temp.open -= 0.25;
if (temp.open < 0) temp.open = 1;
applyOpeningState();
},
},
},
onOptionsUpdated: ([k, v]) => {
temp.$reset();
switch (k) {
case 'angle': applyAngle(); break;
case 'open': applyOpeningState(); break;
case 'blades': applyOpeningState(); break;
}
},
resetTemporaryState: () => {
temp.$reset();
applyAngle();
applyOpeningState();
},
primaryInteraction: 'openClose',
};
},
});

View File

@@ -1,77 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const book = defineObject({
id: 'book',
name: 'Book',
options: {
schema: {
variation: {
type: 'enum',
label: 'Variation',
enum: [0, 1],
},
width: {
type: 'range',
label: 'Width',
min: 0,
max: 1,
step: 0.01,
},
height: {
type: 'range',
label: 'Height',
min: 0,
max: 1,
step: 0.01,
},
thickness: {
type: 'range',
label: 'thickness',
min: 0,
max: 1,
step: 0.01,
},
},
default: {
variation: 0,
width: 0.07,
height: 0.07,
thickness: 0.1,
},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
createInstance: ({ options, model }) => {
const bodyMesh = model.findMesh('__X_BODY__');
const applySize = () => {
bodyMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
bodyMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
bodyMesh.morphTargetManager!.getTargetByName('Thickness')!.influence = options.thickness;
model.updated();
};
applySize();
return {
onInited: () => {
},
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'width':
case 'height':
case 'thickness':
applySize();
break;
}
},
interactions: {},
};
},
});

View File

@@ -1,72 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
import { cm } from '../utility.js';
export const books = defineObject({
id: 'books',
name: 'Books',
options: {
schema: {
variation: {
type: 'enum',
label: 'Variation',
enum: ['A', 'B', 'C', 'D', 'E'],
},
},
default: {
variation: 'A',
},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
createInstance: ({ scene, options, model }) => {
const coverMaterial = model.findMaterial('__X_COVER__');
const applyVariation = () => {
const coverTexture =
options.variation === 'A' ? new BABYLON.Texture('/client-assets/room/objects/books/textures/a.png', scene, false, false) :
options.variation === 'B' ? new BABYLON.Texture('/client-assets/room/objects/books/textures/b.png', scene, false, false) :
options.variation === 'C' ? new BABYLON.Texture('/client-assets/room/objects/books/textures/c.png', scene, false, false) :
options.variation === 'D' ? new BABYLON.Texture('/client-assets/room/objects/books/textures/d.png', scene, false, false) :
new BABYLON.Texture('/client-assets/room/objects/books/textures/e.png', scene, false, false);
coverMaterial.albedoTexture = coverTexture;
};
applyVariation();
const bookMeshes = [
model.findMeshes('__X_BOOK_1__'),
model.findMeshes('__X_BOOK_2__'),
model.findMeshes('__X_BOOK_3__'),
model.findMeshes('__X_BOOK_4__'),
model.findMeshes('__X_BOOK_5__'),
model.findMeshes('__X_BOOK_6__'),
model.findMeshes('__X_BOOK_7__'),
model.findMeshes('__X_BOOK_8__'),
model.findMeshes('__X_BOOK_9__'),
model.findMeshes('__X_BOOK_10__'),
];
for (const meshes of bookMeshes) {
const z = Math.random() * cm(0.005);
const y = Math.random() * cm(0.0025);
for (const mesh of meshes) {
mesh.position.z -= z;
mesh.position.y += y;
}
}
return {
onOptionsUpdated: ([k, v]) => {
applyVariation();
},
interactions: {},
};
},
});

View File

@@ -1,44 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const cactusS = defineObject({
id: 'cactusS',
name: 'Cactus S',
options: {
schema: {
potColor: {
type: 'color',
label: 'Pot color',
},
},
default: {
potColor: [0.45, 0.45, 0.45],
},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
canPreMeshesMerging: true,
createInstance: ({ options, model }) => {
const potMaterial = model.findMaterial('__X_POT__');
const applyPotColor = () => {
const [r, g, b] = options.potColor;
potMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyPotColor();
return {
onOptionsUpdated: ([k, v]) => {
applyPotColor();
},
interactions: {},
};
},
});

View File

@@ -1,44 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const cardboardBox = defineObject({
id: 'cardboardBox',
name: 'Cardboard Box',
options: {
schema: {
variation: {
type: 'enum',
label: 'Variation',
enum: ['default', 'mikan', 'aizon'],
},
},
default: {
variation: 'default',
},
},
placement: 'top',
hasCollisions: true,
hasTexture: true,
createInstance: ({ scene, options, model }) => {
return {
onInited: () => {
const material = model.findMaterial('__X_BODY__');
if (options.variation === 'mikan') {
const tex = new BABYLON.Texture('/client-assets/room/objects/cardboard-box/textures/mikan.png', scene, false, false);
material.albedoTexture = tex;
material.albedoColor = new BABYLON.Color3(1, 1, 1);
} else if (options.variation === 'aizon') {
const tex = new BABYLON.Texture('/client-assets/room/objects/cardboard-box/textures/aizon.png', scene, false, false);
material.albedoTexture = tex;
material.albedoColor = new BABYLON.Color3(1, 1, 1);
}
},
interactions: {},
};
},
});

View File

@@ -1,41 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const ceilingFanLight = defineObject({
id: 'ceilingFanLight',
name: 'Ceiling Fan Light',
options: {
schema: {},
default: {},
},
placement: 'ceiling',
hasCollisions: false,
receiveShadows: false,
castShadows: false,
createInstance: ({ room, scene, model }) => {
const rotor = model.findMesh('Rotor');
model.bakeExcludeMeshes = [rotor, ...rotor.getChildMeshes()];
return {
onInited: () => {
rotor.rotation = rotor.rotationQuaternion != null ? rotor.rotationQuaternion.toEulerAngles() : rotor.rotation;
const anim = new BABYLON.Animation('', 'rotation.y', 60, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
anim.setKeys([
{ frame: 0, value: 0 },
{ frame: 100, value: Math.PI * 2 },
]);
rotor.animations = [anim];
scene.onAfterAnimationsObservable.add(() => {
room?.sr.updateMesh([rotor, ...rotor.getChildMeshes()]);
});
scene.beginAnimation(rotor, 0, 100, true);
},
interactions: {},
};
},
});

View File

@@ -1,57 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const chair = defineObject({
id: 'chair',
name: 'Chair',
options: {
schema: {
primaryColor: {
type: 'color',
label: 'Primay Color',
},
secondaryColor: {
type: 'color',
label: 'Secondary Color',
},
},
default: {
primaryColor: [0.44, 0.6, 0],
secondaryColor: [0, 0, 0],
},
},
placement: 'floor',
hasCollisions: true,
isChair: true,
canPreMeshesMerging: true,
createInstance: ({ model, options }) => {
const primaryMaterial = model.findMaterial('__X_PRIMARY__');
const secondaryMaterial = model.findMaterial('__X_SECONDARY__');
const applyPrimaryColor = () => {
const [r, g, b] = options.primaryColor;
primaryMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
const applySecondaryColor = () => {
const [r, g, b] = options.secondaryColor;
secondaryMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyPrimaryColor();
applySecondaryColor();
return {
onOptionsUpdated: ([k, v]) => {
applyPrimaryColor();
applySecondaryColor();
},
interactions: {},
};
},
});

View File

@@ -1,24 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const coffeeCup = defineObject({
id: 'coffeeCup',
name: 'Coffee Cup',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
canPreMeshesMerging: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,44 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const colorBox = defineObject({
id: 'colorBox',
name: 'Color Box',
options: {
schema: {
color: {
type: 'color',
label: 'Color',
},
},
default: {
color: [0.75, 0.75, 0.75],
},
},
placement: 'floor',
hasCollisions: true,
hasTexture: true,
createInstance: ({ options, model }) => {
const bodyMesh = model.findMesh('__X_BODY__');
const bodyMaterial = bodyMesh.material as BABYLON.PBRMaterial;
const applyColor = () => {
const [r, g, b] = options.color;
bodyMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyColor();
return {
onOptionsUpdated: ([k, v]) => {
applyColor();
},
interactions: {},
};
},
});

View File

@@ -1,87 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const cuboid = defineObject({
id: 'cuboid',
name: 'cuboid',
options: {
schema: {
x: {
type: 'range',
label: 'X',
min: 0,
max: 1,
step: 0.01,
},
y: {
type: 'range',
label: 'Y',
min: 0,
max: 1,
step: 0.01,
},
z: {
type: 'range',
label: 'Z',
min: 0,
max: 1,
step: 0.01,
},
color: {
type: 'color',
label: 'Color',
},
},
default: {
x: 0.01,
y: 0.01,
z: 0.01,
color: [1, 1, 1],
},
},
placement: 'top',
createInstance: async ({ scene, options, model }) => {
const mesh = model.findMesh('__X_BODY__');
const mat = model.findMaterial('__X_BODY__');
const applySize = () => {
mesh.morphTargetManager!.getTargetByName('X')!.influence = options.x;
mesh.morphTargetManager!.getTargetByName('Y')!.influence = options.y;
mesh.morphTargetManager!.getTargetByName('Z')!.influence = options.z;
model.updated();
};
applySize();
const applyColor = () => {
const [r, g, b] = options.color;
mat.albedoColor = new BABYLON.Color3(r, g, b);
};
applyColor();
return {
onInited: () => {
},
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'color':
applyColor();
break;
case 'x':
case 'y':
case 'z':
applySize();
break;
}
},
interactions: {},
};
},
});

View File

@@ -1,34 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
import { cm, yuge } from '../utility.js';
export const cupNoodle = defineObject({
id: 'cupNoodle',
name: 'Cup Noodle',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
canPreMeshesMerging: true,
createInstance: ({ scene, root }) => {
let yugeDispose: (() => void) | null = null;
return {
onInited: () => {
yugeDispose = yuge(scene, root, new BABYLON.Vector3(0, cm(10), 0));
},
interactions: {},
dispose: () => {
yugeDispose?.();
},
};
},
});

View File

@@ -1,23 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const custardPudding = defineObject({
id: 'custardPudding',
name: 'custardPudding',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
canPreMeshesMerging: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,21 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const debugHipoly = defineObject({
id: 'debugHipoly',
name: 'debugHipoly',
options: {
schema: {},
default: {},
},
placement: 'top',
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,90 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const desk = defineObject({
id: 'desk',
name: 'Desk',
options: {
schema: {
frameColor: {
type: 'color',
label: 'Frame color',
},
boardColor: {
type: 'color',
label: 'Board color',
},
width: {
type: 'range',
label: 'Width',
min: 0,
max: 1,
step: 0.01,
},
depth: {
type: 'range',
label: 'Depth',
min: 0,
max: 1,
step: 0.01,
},
},
default: {
frameColor: [0.8, 0.8, 0.8],
boardColor: [0.8, 0.4, 0.1],
width: 0.28,
depth: 0.26,
},
},
placement: 'floor',
hasCollisions: true,
createInstance: ({ options, model }) => {
const frameMaterial = model.findMaterial('__X_FRAME__');
const boardMaterial = model.findMaterial('__X_BOARD__');
const applyFrameColor = () => {
const [r, g, b] = options.frameColor;
frameMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyFrameColor();
const applyBoardColor = () => {
const [r, g, b] = options.boardColor;
boardMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyBoardColor();
const applySize = () => {
for (const mesh of model.root.getChildMeshes()) {
if (mesh.morphTargetManager != null && mesh.morphTargetManager.getTargetByName('W') != null) {
mesh.morphTargetManager.getTargetByName('W').influence = options.width;
}
if (mesh.morphTargetManager != null && mesh.morphTargetManager.getTargetByName('D') != null) {
mesh.morphTargetManager.getTargetByName('D').influence = options.depth;
}
}
model.updated();
};
applySize();
return {
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'frameColor': applyFrameColor(); break;
case 'boardColor': applyBoardColor(); break;
case 'width': applySize(); break;
case 'depth': applySize(); break;
}
},
interactions: {},
};
},
});

View File

@@ -1,130 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject, WORLD_SCALE } from '../engine.js';
import { cm } from '../utility.js';
export const desktopPc = defineObject({
id: 'desktopPc',
name: 'Desktop PC',
options: {
schema: {
bodyColor: {
type: 'color',
label: 'Body color',
},
coverColor: {
type: 'color',
label: 'Cover color',
},
inner1Color: {
type: 'color',
label: 'Inner color 1',
},
inner2Color: {
type: 'color',
label: 'Inner color 2',
},
inner3Color: {
type: 'color',
label: 'Inner color 3',
},
ledColor: {
type: 'color',
label: 'LED color',
},
},
default: {
bodyColor: [0.05, 0.05, 0.05],
coverColor: [0.9, 0.9, 0.9],
inner1Color: [1, 1, 1],
inner2Color: [1, 1, 1],
inner3Color: [0.1, 0.1, 0.1],
ledColor: [0.5, 0.9, 0],
},
},
placement: 'top',
hasCollisions: true,
canPreMeshesMerging: true,
createInstance: ({ options, model, root, scene, room }) => {
const light1 = new BABYLON.SpotLight('', new BABYLON.Vector3(0, cm(10), cm(22)), new BABYLON.Vector3(0, 0, 1), Math.PI / 1, 2, scene, room?.lightContainer != null);
light1.parent = root;
light1.intensity = 0.05 * WORLD_SCALE * WORLD_SCALE;
light1.range = cm(30);
if (room?.lightContainer != null) room.lightContainer.addLight(light1);
const light2 = new BABYLON.SpotLight('', new BABYLON.Vector3(cm(-5), cm(33), cm(-9)), new BABYLON.Vector3(1, 0, 0), Math.PI / 1, 2, scene, room?.lightContainer != null);
light2.parent = root;
light2.intensity = 0.05 * WORLD_SCALE * WORLD_SCALE;
light2.range = cm(30);
if (room?.lightContainer != null) room.lightContainer.addLight(light2);
const bodyMaterial = model.findMaterial('__X_BODY__');
const coverMaterial = model.findMaterial('__X_COVER__');
const inner1Material = model.findMaterial('__X_INNER__');
const inner2Material = model.findMaterial('__X_INNER2__');
const inner3Material = model.findMaterial('__X_TUBE__');
const ledMaterial = model.findMaterial('__X_LED__');
ledMaterial.emissiveIntensity = 1;
const applyBodyColor = () => {
const [r, g, b] = options.bodyColor;
bodyMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyBodyColor();
const applyCoverColor = () => {
const [r, g, b] = options.coverColor;
coverMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyCoverColor();
const applyInner1Color = () => {
const [r, g, b] = options.inner1Color;
inner1Material.albedoColor = new BABYLON.Color3(r, g, b);
};
applyInner1Color();
const applyInner2Color = () => {
const [r, g, b] = options.inner2Color;
inner2Material.albedoColor = new BABYLON.Color3(r, g, b);
};
applyInner2Color();
const applyInner3Color = () => {
const [r, g, b] = options.inner3Color;
inner3Material.albedoColor = new BABYLON.Color3(r, g, b);
};
applyInner3Color();
const applyLedColor = () => {
const [r, g, b] = options.ledColor;
ledMaterial.emissiveColor = new BABYLON.Color3(r, g, b);
light1.diffuse = new BABYLON.Color3(r, g, b);
light2.diffuse = new BABYLON.Color3(r, g, b);
};
applyLedColor();
return {
onOptionsUpdated: ([k, v]) => {
applyBodyColor();
applyCoverColor();
applyInner1Color();
applyInner2Color();
applyInner3Color();
applyLedColor();
},
interactions: {},
};
},
});

View File

@@ -1,24 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const djMixer = defineObject({
id: 'djMixer',
name: 'djMixer',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
canPreMeshesMerging: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,104 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
import { createPlaneUvMapper, normalizeUvToSquare } from '../utility.js';
export const djPlayer = defineObject({
id: 'djPlayer',
name: 'djPlayer',
options: {
schema: {
screenBrightness: {
type: 'range',
label: 'Screen brightness',
min: 0,
max: 1,
step: 0.01,
},
customPicture: {
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
screenBrightness: 0.35,
customPicture: null,
fit: 'cover',
},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
createInstance: async ({ model, options, scene }) => {
const screenMesh = model.findMesh('__X_SCREEN__');
const screenMaterial = model.findMaterial('__X_SCREEN__');
const defaultScreenTexture = screenMaterial.emissiveTexture;
normalizeUvToSquare(screenMesh);
const updateUv = createPlaneUvMapper(screenMesh);
const applyFit = () => {
const tex = screenMaterial.emissiveTexture;
if (tex == null) return;
const srcAspect = tex.getSize().width / tex.getSize().height;
const targetAspect = 15.6 / 8.33;
updateUv(srcAspect, targetAspect, options.fit);
model.updated();
};
applyFit();
const applyCustomPicture = () => new Promise<void>((resolve) => {
if (options.customPicture != null && options.customPicture !== '') {
const tex = new BABYLON.Texture(options.customPicture, scene, false, false);
tex.wrapU = BABYLON.Texture.MIRROR_ADDRESSMODE;
tex.wrapV = BABYLON.Texture.MIRROR_ADDRESSMODE;
tex.level = 0.5;
screenMaterial.unfreeze();
screenMaterial.emissiveTexture = tex;
tex.onLoadObservable.addOnce(() => {
applyFit();
resolve();
});
} else {
screenMaterial.emissiveTexture = defaultScreenTexture;
applyFit();
resolve();
}
});
await applyCustomPicture();
const applyScreenBrightness = () => {
const b = options.screenBrightness;
screenMaterial.emissiveColor = new BABYLON.Color3(b, b, b);
};
applyScreenBrightness();
return {
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'screenBrightness': applyScreenBrightness(); break;
case 'customPicture': applyCustomPicture(); break;
case 'fit': applyFit(); break;
}
},
interactions: {},
};
},
});

View File

@@ -1,23 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const ductTape = defineObject({
id: 'ductTape',
name: 'Duct Tape',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const emptyBento = defineObject({
id: 'emptyBento',
name: 'Empty Bento',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,23 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const energyDrink = defineObject({
id: 'energyDrink',
name: 'Energy Drink',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,23 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const envelope = defineObject({
id: 'envelope',
name: 'Envelope',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,24 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const facialTissue = defineObject({
id: 'facialTissue',
name: 'Facial Tissue',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
canPreMeshesMerging: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,24 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const hangingTShirt = defineObject({
id: 'hangingTShirt',
name: 'Hanging T-Shirt',
options: {
schema: {},
default: {},
},
placement: 'side',
hasCollisions: false,
hasTexture: true,
canPreMeshesMerging: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const icosahedron = defineObject({
id: 'icosahedron',
name: 'icosahedron',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,94 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject, defineObjectClass } from '../engine.js';
const base = defineObjectClass({
options: {
schema: {
frameColor: {
type: 'color',
label: 'Frame color',
},
boardColor: {
type: 'color',
label: 'Board color',
},
width: {
type: 'range',
label: 'Width',
min: 0,
max: 1,
step: 0.01,
},
},
default: {
frameColor: [0.2, 0.2, 0.2],
boardColor: [0.8, 0.4, 0.1],
width: 0.2,
},
},
placement: 'floor',
hasCollisions: true,
createInstance: ({ options, model }) => {
const frameMaterial = model.findMaterial('__X_FRAME__');
const boardMaterial = model.findMaterial('__X_BOARD__');
const applyFrameColor = () => {
const [r, g, b] = options.frameColor;
frameMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyFrameColor();
const applyBoardColor = () => {
const [r, g, b] = options.boardColor;
boardMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyBoardColor();
const applySize = () => {
for (const mesh of model.root.getChildMeshes()) {
if (mesh.morphTargetManager != null && mesh.morphTargetManager.getTargetByName('Width') != null) {
mesh.morphTargetManager.getTargetByName('Width').influence = options.width;
}
}
model.updated();
};
applySize();
return {
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'frameColor': applyFrameColor(); break;
case 'boardColor': applyBoardColor(); break;
case 'width': applySize(); break;
}
},
interactions: {},
};
},
});
export const ironFrameShelf5 = base.extend({
id: 'ironFrameShelf5',
name: 'ironFrameShelf 5',
path: 'iron-frame-shelf/iron-frame-shelf-5',
});
export const ironFrameShelf4 = base.extend({
id: 'ironFrameShelf4',
name: 'ironFrameShelf 4',
path: 'iron-frame-shelf/iron-frame-shelf-4',
});
export const ironFrameShelf3 = base.extend({
id: 'ironFrameShelf3',
name: 'ironFrameShelf 3',
path: 'iron-frame-shelf/iron-frame-shelf-3',
});

View File

@@ -1,104 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const ironFrameTable = defineObject({
id: 'ironFrameTable',
name: 'ironFrameTable',
options: {
schema: {
frameColor: {
type: 'color',
label: 'Frame color',
},
boardColor: {
type: 'color',
label: 'Board color',
},
width: {
type: 'range',
label: 'Width',
min: 0,
max: 1,
step: 0.01,
},
depth: {
type: 'range',
label: 'Depth',
min: 0,
max: 1,
step: 0.01,
},
height: {
type: 'range',
label: 'Height',
min: 0,
max: 1,
step: 0.01,
},
},
default: {
frameColor: [0.8, 0.8, 0.8],
boardColor: [0.8, 0.4, 0.1],
width: 0.28,
depth: 0.25,
height: 0.35,
},
},
placement: 'top',
hasCollisions: true,
createInstance: ({ options, model, stickyMarkerMeshUpdated }) => {
const frameMaterial = model.findMaterial('__X_FRAME__');
const boardMaterial = model.findMaterial('__X_BOARD__');
const applyFrameColor = () => {
const [r, g, b] = options.frameColor;
frameMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyFrameColor();
const applyBoardColor = () => {
const [r, g, b] = options.boardColor;
boardMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyBoardColor();
const topMesh = model.findMesh('__TOP__');
const applySize = () => {
for (const mesh of model.root.getChildMeshes()) {
if (mesh.morphTargetManager != null && mesh.morphTargetManager.getTargetByName('W') != null) {
mesh.morphTargetManager.getTargetByName('W').influence = options.width;
}
if (mesh.morphTargetManager != null && mesh.morphTargetManager.getTargetByName('D') != null) {
mesh.morphTargetManager.getTargetByName('D').influence = options.depth;
}
if (mesh.morphTargetManager != null && mesh.morphTargetManager.getTargetByName('H') != null) {
mesh.morphTargetManager.getTargetByName('H').influence = options.height;
}
}
model.updated();
};
applySize();
return {
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'frameColor': applyFrameColor(); break;
case 'boardColor': applyBoardColor(); break;
case 'width': applySize(); stickyMarkerMeshUpdated?.(topMesh); break;
case 'depth': applySize(); stickyMarkerMeshUpdated?.(topMesh); break;
case 'height': applySize(); stickyMarkerMeshUpdated?.(topMesh); break;
}
},
interactions: {},
};
},
});

View File

@@ -1,23 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const keyboard = defineObject({
id: 'keyboard',
name: 'Keyboard',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
canPreMeshesMerging: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,167 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject, WORLD_SCALE } from '../engine.js';
import { cm, createPlaneUvMapper } from '../utility.js';
export const laptopPc = defineObject({
id: 'laptopPc',
name: 'Laptop PC',
options: {
schema: {
bodyColor: {
type: 'color',
label: 'Body color',
},
bezelColor: {
type: 'color',
label: 'Bezel color',
},
screenBrightness: {
type: 'range',
label: 'Screen brightness',
min: 0,
max: 1,
step: 0.01,
},
customPicture: {
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
openAngle: {
type: 'range',
label: 'Open',
min: -Math.PI / 2,
max: Math.PI / 2,
step: 0.01,
},
},
default: {
bodyColor: [1, 1, 1],
bezelColor: [0, 0, 0],
screenBrightness: 0.35,
customPicture: null,
fit: 'cover',
openAngle: 0,
},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
createInstance: async ({ room, scene, options, model }) => {
const matrix = model.root.getWorldMatrix(true);
const scale = new BABYLON.Vector3();
matrix.decompose(scale);
const screenMesh = model.findMesh('__X_SCREEN__');
const hutaNode = model.findTransformNode('__X_HUTA__');
const light = new BABYLON.SpotLight('', new BABYLON.Vector3(cm(0), cm(10) / Math.abs(scale.y), 0), new BABYLON.Vector3(0, 0, 1), Math.PI / 1, 2, scene, room?.lightContainer != null);
light.parent = hutaNode;
light.diffuse = new BABYLON.Color3(1.0, 1.0, 1.0);
light.range = cm(100);
if (room?.lightContainer != null) room.lightContainer.addLight(light);
const bodyMaterial = model.findMaterial('__X_BODY__');
const bezelMaterial = model.findMaterial('__X_BEZEL__');
const screenMaterial = model.findMaterial('__X_SCREEN__');
screenMaterial.ambientColor = new BABYLON.Color3(0, 0, 0);
screenMaterial.albedoColor = new BABYLON.Color3(0, 0, 0);
const updateUv = createPlaneUvMapper(screenMesh);
const applyFit = () => {
const tex = screenMaterial.emissiveTexture;
if (tex == null) return;
const srcAspect = tex.getSize().width / tex.getSize().height;
const targetAspect = 31 / 19;
updateUv(srcAspect, targetAspect, options.fit);
model.updated();
};
applyFit();
const applyCustomPicture = () => new Promise<void>((resolve) => {
if (options.customPicture != null) {
const tex = new BABYLON.Texture(options.customPicture, scene, false, false);
tex.wrapU = BABYLON.Texture.MIRROR_ADDRESSMODE;
tex.wrapV = BABYLON.Texture.MIRROR_ADDRESSMODE;
tex.level = 0.5;
screenMaterial.unfreeze();
screenMaterial.emissiveTexture = tex;
tex.onLoadObservable.addOnce(() => {
applyFit();
resolve();
});
} else {
screenMaterial.emissiveTexture = null;
resolve();
}
});
await applyCustomPicture();
const applyScreenBrightness = () => {
const b = options.screenBrightness;
screenMaterial.emissiveColor = new BABYLON.Color3(b, b, b);
light.intensity = (2 * b) * WORLD_SCALE * WORLD_SCALE;
};
applyScreenBrightness();
const applyBodyColor = () => {
const [r, g, b] = options.bodyColor;
bodyMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
const applyBezelColor = () => {
const [r, g, b] = options.bezelColor;
bezelMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyBodyColor();
applyBezelColor();
const applyOpenAngle = () => {
const angle = options.openAngle;
hutaNode.rotationQuaternion = null;
hutaNode.rotation.x = -angle;
if (angle <= -Math.PI / 2) {
light.intensity = 0;
} else {
light.intensity = (2 * options.screenBrightness) * WORLD_SCALE * WORLD_SCALE;
}
model.updated();
};
applyOpenAngle();
return {
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'bodyColor': applyBodyColor(); break;
case 'bezelColor': applyBezelColor(); break;
case 'screenBrightness': applyScreenBrightness(); break;
case 'customPicture': applyCustomPicture(); break;
case 'fit': applyFit(); break;
case 'openAngle': applyOpenAngle(); break;
}
},
interactions: {},
};
},
});

View File

@@ -1,75 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject, WORLD_SCALE } from '../engine.js';
import { cm } from '../utility.js';
export const lavaLamp = defineObject({
id: 'lavaLamp',
name: 'Lava Lamp',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
createInstance: ({ room, scene, root }) => {
return {
onInited: () => {
const light = new BABYLON.PointLight('lavaLampLight', new BABYLON.Vector3(0, cm(11), 0), scene, room?.lightContainer != null);
light.parent = root;
light.diffuse = new BABYLON.Color3(1.0, 0.5, 0.2);
light.intensity = 0.03 * WORLD_SCALE * WORLD_SCALE;
light.range = cm(100);
if (room?.lightContainer != null) room.lightContainer.addLight(light);
const sphere = BABYLON.MeshBuilder.CreateSphere('lavaLampLightSphere', { diameter: cm(4) }, scene);
sphere.parent = root;
sphere.position = new BABYLON.Vector3(0, cm(15), 0);
const mat = new BABYLON.StandardMaterial('lavaLampLightMat', scene);
mat.emissiveColor = new BABYLON.Color3(1.0, 0.5, 0.2);
mat.alpha = 0.5;
//mat.disableLighting = true;
sphere.material = mat;
const anim = new BABYLON.Animation('lavaLampLightAnim', 'position.y', 60, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
anim.setKeys([
{ frame: 0, value: cm(11) },
{ frame: 500, value: cm(38) },
]);
sphere.animations = [anim];
scene.beginAnimation(sphere, 0, 500, true);
const emitter = new BABYLON.TransformNode('emitter', scene);
emitter.parent = root;
emitter.position = new BABYLON.Vector3(0, cm(10), 0);
const ps = new BABYLON.ParticleSystem('', 32, scene);
ps.particleTexture = new BABYLON.Texture('/client-assets/room/objects/lava-lamp/bubble.png');
ps.emitter = emitter;
ps.isLocal = true;
ps.minEmitBox = new BABYLON.Vector3(cm(-1), 0, cm(-1));
ps.maxEmitBox = new BABYLON.Vector3(cm(1), 0, cm(1));
ps.minEmitPower = 2;
ps.maxEmitPower = 3;
ps.minLifeTime = 9;
ps.maxLifeTime = 9;
ps.minSize = cm(0.5);
ps.maxSize = cm(1);
ps.direction1 = new BABYLON.Vector3(0, 1, 0);
ps.direction2 = new BABYLON.Vector3(0, 1, 0);
ps.emitRate = 1;
ps.blendMode = BABYLON.ParticleSystem.BLENDMODE_ADD;
ps.color1 = new BABYLON.Color4(1, 1, 1, 0.3);
ps.color2 = new BABYLON.Color4(1, 1, 1, 0.2);
ps.colorDead = new BABYLON.Color4(1, 1, 1, 0);
ps.preWarmCycles = Math.random() * 1000;
ps.start();
},
interactions: {},
};
},
});

View File

@@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const letterCase = defineObject({
id: 'letterCase',
name: 'Letter Case',
options: {
schema: {},
default: {},
},
placement: 'top',
canPreMeshesMerging: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,23 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const miObjet = defineObject({
id: 'miObjet',
name: 'Mi objet',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,23 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const miPlate = defineObject({
id: 'miPlate',
name: 'Mi Plate',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
canPreMeshesMerging: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,23 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const miPlateDisplayed = defineObject({
id: 'miPlateDisplayed',
name: 'Mi Plate (Displayed)',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
canPreMeshesMerging: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,23 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const milk = defineObject({
id: 'milk',
name: 'Milk',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,23 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const mixer = defineObject({
id: 'mixer',
name: 'Mixer',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const monitor = defineObject({
id: 'monitor',
name: 'Monitor',
options: {
schema: {},
default: {},
},
placement: 'top',
hasTexture: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,43 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const monitorSpeaker = defineObject({
id: 'monitorSpeaker',
name: 'Monitor Speaker',
options: {
schema: {
color: {
type: 'color',
label: 'Color',
},
},
default: {
color: [0, 0, 0],
},
},
placement: 'top',
hasCollisions: false,
createInstance: ({ options, model }) => {
const bodyMesh = model.findMesh('__X_BODY__');
const bodyMaterial = bodyMesh.material as BABYLON.PBRMaterial;
const applyColor = () => {
const [r, g, b] = options.color;
bodyMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyColor();
return {
onOptionsUpdated: ([k, v]) => {
applyColor();
},
interactions: {},
};
},
});

View File

@@ -1,23 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const monstera = defineObject({
id: 'monstera',
name: 'Monstera',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,32 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
import { cm, yuge } from '../utility.js';
export const mug = defineObject({
id: 'mug',
name: 'Mug',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
createInstance: ({ scene, root }) => {
let yugeDispose: (() => void) | null = null;
return {
onInited: () => {
yugeDispose = yuge(scene, root, new BABYLON.Vector3(0, cm(5), 0));
},
interactions: {},
dispose: () => {
yugeDispose?.();
},
};
},
});

View File

@@ -1,44 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const newtonsCradle = defineObject({
id: 'newtonsCradle',
name: 'newtonsCradle',
options: {
schema: {
frameColor: {
type: 'color',
label: 'Frame color',
},
},
default: {
frameColor: [0.15, 0.15, 0.15],
},
},
placement: 'top',
hasCollisions: false,
createInstance: ({ options, model }) => {
const frameMaterial = model.findMaterial('__X_FRAME__');
const applyFrameColor = () => {
const [r, g, b] = options.frameColor;
frameMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyFrameColor();
return {
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'frameColor': applyFrameColor(); break;
}
},
interactions: {},
};
},
});

View File

@@ -1,21 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const openedCardboardBox = defineObject({
id: 'openedCardboardBox',
name: 'Opened Cardboard Box',
options: {
schema: {},
default: {},
},
placement: 'top',
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,24 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const pachira = defineObject({
id: 'pachira',
name: 'Pachira',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: true,
hasTexture: true,
canPreMeshesMerging: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,42 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const pc = defineObject({
id: 'pc',
name: 'PC',
options: {
schema: {
color: {
type: 'color',
label: 'Color',
},
},
default: {
color: [0, 0, 0],
},
},
placement: 'top',
createInstance: ({ options, root }) => {
const bodyMesh = root.getChildMeshes().find(m => m.name.includes('__X_BODY__')) as BABYLON.Mesh;
const bodyMaterial = bodyMesh.material as BABYLON.PBRMaterial;
const applyColor = () => {
const [r, g, b] = options.color;
bodyMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyColor();
return {
onOptionsUpdated: ([k, v]) => {
applyColor();
},
interactions: {},
};
},
});

View File

@@ -1,54 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const petBottle = defineObject({
id: 'petBottle',
name: 'PET Bottle',
options: {
schema: {
withCap: {
type: 'boolean',
label: 'With Cap',
},
empty: {
type: 'boolean',
label: 'Empty',
},
},
default: {
withCap: true,
empty: false,
},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
createInstance: ({ model, options }) => {
const capMesh = model.findMesh('__X_CAP__');
const liquidMesh = model.findMesh('__X_LIQUID__');
const applyWithCap = () => {
capMesh.setEnabled(options.withCap);
};
const applyEmpty = () => {
liquidMesh.setEnabled(!options.empty);
};
applyWithCap();
applyEmpty();
return {
onOptionsUpdated: ([k, v]) => {
applyWithCap();
applyEmpty();
},
interactions: {},
};
},
});

View File

@@ -1,43 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const piano = defineObject({
id: 'piano',
name: 'Piano',
options: {
schema: {
bodyColor: {
type: 'color',
label: 'bodyColor',
},
},
default: {
bodyColor: [0, 0, 0],
},
},
placement: 'floor',
hasCollisions: true,
canPreMeshesMerging: true,
createInstance: ({ options, model }) => {
const bodyMaterial = model.findMaterial('__X_BODY__');
const applyBodyColor = () => {
const [r, g, b] = options.bodyColor;
bodyMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyBodyColor();
return {
onOptionsUpdated: ([k, v]) => {
applyBodyColor();
},
interactions: {},
};
},
});

View File

@@ -1,263 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
import { createPlaneUvMapper } from '../utility.js';
// NOTE: シェイプキーのnormalのエクスポートは無効にしないとmatを大きくしたときに面のレンダリングがグリッチする
export const pictureFrame = defineObject({
id: 'pictureFrame',
name: 'Simple picture frame',
options: {
schema: {
frameColor: {
type: 'color',
label: 'Frame color',
},
width: {
type: 'range',
label: 'Width',
min: 0,
max: 1,
step: 0.01,
},
height: {
type: 'range',
label: 'Height',
min: 0,
max: 1,
step: 0.01,
},
frameThickness: {
type: 'range',
label: 'Frame thickness',
min: 0,
max: 1,
step: 0.01,
},
depth: {
type: 'range',
label: 'Depth',
min: 0,
max: 1,
step: 0.01,
},
matHThickness: {
type: 'range',
label: 'Mat horizontal thickness',
min: 0,
max: 1,
step: 0.01,
},
matVThickness: {
type: 'range',
label: 'Mat vertical thickness',
min: 0,
max: 1,
step: 0.01,
},
customPicture: {
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
frameColor: [0.71, 0.58, 0.39],
width: 0.15,
height: 0.15,
frameThickness: 0.3,
depth: 0,
matHThickness: 0.35,
matVThickness: 0.35,
customPicture: null,
fit: 'cover',
},
},
placement: 'side',
hasCollisions: false,
hasTexture: true,
createInstance: async ({ scene, options, model }) => {
const frameMesh = model.findMesh('__X_FRAME__');
frameMesh.rotationQuaternion = null;
const matMesh = model.findMesh('__X_MAT__');
matMesh.rotationQuaternion = null;
const coverMesh = model.findMesh('__X_COVER__');
coverMesh.rotationQuaternion = null;
const pictureMesh = model.findMesh('__X_PICTURE__');
pictureMesh.rotationQuaternion = null;
const pictureMaterial = model.findMaterial('__X_PICTURE__');
const updateUv = createPlaneUvMapper(pictureMesh);
const applyFit = () => {
const tex = pictureMaterial.albedoTexture;
if (tex == null) return;
const srcWidth = tex.getSize().width;
const srcHeight = tex.getSize().height;
const srcAspect = srcWidth / srcHeight;
const targetWidth = options.width * (1 - options.matHThickness);
const targetHeight = options.height * (1 - options.matVThickness);
const targetAspect = targetWidth / targetHeight;
updateUv(srcAspect, targetAspect, options.fit);
model.updated();
};
applyFit();
const applyFrameThickness = () => {
frameMesh.morphTargetManager!.getTargetByName('Thickness')!.influence = options.frameThickness;
model.updated();
};
applyFrameThickness();
const applyMatThickness = () => {
matMesh.morphTargetManager!.getTargetByName('MatH')!.influence = options.matHThickness * options.width;
matMesh.morphTargetManager!.getTargetByName('MatV')!.influence = options.matVThickness * options.height;
pictureMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width * (1 - options.matHThickness);
pictureMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height * (1 - options.matVThickness);
matMesh.isVisible = options.matHThickness > 0 || options.matVThickness > 0;
model.updated();
applyFit();
};
applyMatThickness();
const applySize = () => {
frameMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
frameMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
matMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
matMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
coverMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
coverMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
model.updated();
applyMatThickness();
};
applySize();
const applyDepth = () => {
frameMesh.morphTargetManager!.getTargetByName('Depth')!.influence = options.depth;
//coverMesh.morphTargetManager!.getTargetByName('Depth')!.influence = options.depth;
coverMesh.morphTargetManager!.getTargetByName('Depth')!.influence = 0;
model.updated();
};
applyDepth();
const applyCustomPicture = () => new Promise<void>((resolve) => {
if (options.customPicture != null) {
const tex = new BABYLON.Texture(options.customPicture, scene, false, false);
tex.wrapU = BABYLON.Texture.MIRROR_ADDRESSMODE;
tex.wrapV = BABYLON.Texture.MIRROR_ADDRESSMODE;
pictureMaterial.unfreeze();
pictureMaterial.albedoColor = new BABYLON.Color3(1, 1, 1);
pictureMaterial.albedoTexture = tex;
tex.onLoadObservable.addOnce(() => {
applyFit();
resolve();
});
} else {
pictureMaterial.albedoColor = new BABYLON.Color3(0.5, 0.5, 0.5);
pictureMaterial.albedoTexture = null;
resolve();
}
});
await applyCustomPicture();
const frameMaterial = model.findMaterial('__X_FRAME__');
const applyFrameColor = () => {
const [r, g, b] = options.frameColor;
frameMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyFrameColor();
return {
onInited: () => {
},
onOptionsUpdated: ([k, v]) => {
if (k === 'frameColor') {
applyFrameColor();
}
if (k === 'width' || k === 'height') {
applySize();
}
if (k === 'frameThickness') {
applyFrameThickness();
}
if (k === 'depth') {
applyDepth();
}
if (k === 'matHThickness' || k === 'matVThickness') {
applyMatThickness();
}
if (k === 'customPicture') {
applyCustomPicture();
}
if (k === 'fit') {
applyFit();
}
},
interactions: {},
};
},
});
/*
const applyDirection = () => {
if (options.direction === 'vertical') {
frameMesh.rotation.z = 0;
matMesh.rotation.z = 0;
coverMesh.rotation.z = 0;
pictureMesh.rotation.z = 0;
uvs[6] = ax;
uvs[7] = ay;
uvs[2] = bx;
uvs[3] = by;
uvs[4] = cx;
uvs[5] = cy;
uvs[0] = dx;
uvs[1] = dy;
} else if (options.direction === 'horizontal') {
frameMesh.rotation.z = -Math.PI / 2;
matMesh.rotation.z = -Math.PI / 2;
coverMesh.rotation.z = -Math.PI / 2;
pictureMesh.rotation.z = -Math.PI / 2;
uvs[6] = cy;
uvs[7] = cx;
uvs[2] = dy;
uvs[3] = dx;
uvs[4] = ay;
uvs[5] = ax;
uvs[0] = by;
uvs[1] = bx;
}
pictureMesh.updateVerticesData(BABYLON.VertexBuffer.UVKind, uvs);
};
*/

View File

@@ -1,23 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const pizza = defineObject({
id: 'pizza',
name: 'pizza',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
canPreMeshesMerging: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,24 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const plant = defineObject({
id: 'plant',
name: 'Plant',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
canPreMeshesMerging: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const plant2 = defineObject({
id: 'plant2',
name: 'Plant 2',
options: {
schema: {},
default: {},
},
placement: 'top',
hasTexture: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,136 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
import { createPlaneUvMapper, getPlaneUvIndexes } from '../utility.js';
const remap = (value: number, fromMin: number, fromMax: number, toMin: number, toMax: number) => {
return toMin + ((value - fromMin) / (fromMax - fromMin)) * (toMax - toMin);
};
export const poster = defineObject({
id: 'poster',
name: 'Poster',
options: {
schema: {
width: {
type: 'range',
label: 'Width',
min: 0,
max: 1,
step: 0.01,
},
height: {
type: 'range',
label: 'Height',
min: 0,
max: 1,
step: 0.01,
},
customPicture: {
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
width: 0.15,
height: 0.15,
customPicture: null,
fit: 'cover',
},
},
placement: 'side',
hasCollisions: false,
hasTexture: true,
createInstance: async ({ scene, options, model }) => {
const pictureMesh = model.findMesh('__X_PICTURE__');
pictureMesh.rotationQuaternion = null;
const pictureMaterial = model.findMaterial('__X_PICTURE__');
const pinMeshes = model.findMeshes('__X_PIN__');
const updateUv = createPlaneUvMapper(pictureMesh);
const applyFit = () => {
const tex = pictureMaterial.albedoTexture;
if (tex == null) return;
const srcWidth = tex.getSize().width;
const srcHeight = tex.getSize().height;
const srcAspect = srcWidth / srcHeight;
const targetWidth = remap(options.width, 0, 1, 2, 100); // 最小値(値を0にした場合)でのサイズは2cmで、最大値(値を1にした場合)でのサイズは100cmなので。比率の計算だから単位はなんでもいいけど、とにかく0が0にならない点を考慮させる必要がある
const targetHeight = remap(options.height, 0, 1, 2, 100); // 最小値(値を0にした場合)でのサイズは2cmで、最大値(値を1にした場合)でのサイズは100cmなので。比率の計算だから単位はなんでもいいけど、とにかく0が0にならない点を考慮させる必要がある
const targetAspect = targetWidth / targetHeight;
updateUv(srcAspect, targetAspect, options.fit);
model.updated();
};
applyFit();
const applySize = () => {
pictureMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
pictureMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
for (const pinMesh of pinMeshes) {
pinMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
pinMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
}
model.updated();
applyFit();
};
applySize();
const applyCustomPicture = () => new Promise<void>((resolve) => {
if (options.customPicture != null) {
const tex = new BABYLON.Texture(options.customPicture, scene, false, false);
tex.wrapU = BABYLON.Texture.MIRROR_ADDRESSMODE;
tex.wrapV = BABYLON.Texture.MIRROR_ADDRESSMODE;
pictureMaterial.unfreeze();
pictureMaterial.albedoColor = new BABYLON.Color3(1, 1, 1);
pictureMaterial.albedoTexture = tex;
tex.onLoadObservable.addOnce(() => {
applyFit();
resolve();
});
} else {
pictureMaterial.albedoColor = new BABYLON.Color3(0.5, 0.5, 0.5);
pictureMaterial.albedoTexture = null;
resolve();
}
});
await applyCustomPicture();
return {
onInited: () => {
},
onOptionsUpdated: ([k, v]) => {
if (k === 'width' || k === 'height') {
applySize();
}
if (k === 'customPicture') {
applyCustomPicture();
}
if (k === 'fit') {
applyFit();
}
},
interactions: {},
};
},
});

View File

@@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const powerStrip = defineObject({
id: 'powerStrip',
name: 'Power Strip',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,36 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const radiometer = defineObject({
id: 'radiometer',
name: 'radiometer',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
createInstance: ({ scene, model }) => {
const vanes = model.findTransformNode('__X_VANES__');
model.bakeExcludeMeshes = [...vanes.getChildMeshes()];
return {
onInited: () => {
vanes.rotation = vanes.rotationQuaternion != null ? vanes.rotationQuaternion.toEulerAngles() : vanes.rotation;
const anim = new BABYLON.Animation('', 'rotation.y', 60, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
anim.setKeys([
{ frame: 0, value: 0 },
{ frame: 240, value: Math.PI * 2 },
]);
vanes.animations = [anim];
scene.beginAnimation(vanes, 0, 240, true);
},
interactions: {},
};
},
});

View File

@@ -1,132 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import seedrandom from 'seedrandom';
import { defineObject, WORLD_SCALE } from '../engine.js';
const remap = (value: number, fromMin: number, fromMax: number, toMin: number, toMax: number) => {
return toMin + ((value - fromMin) / (fromMax - fromMin)) * (toMax - toMin);
};
export const randomBooks = defineObject({
id: 'randomBooks',
name: '雑多な本',
options: {
schema: {
plainCover: {
type: 'boolean',
label: 'Plain cover',
},
count: {
type: 'range',
label: 'Count',
min: 1,
max: 30,
step: 1,
},
seed: {
type: 'range',
label: 'Seed',
min: 0,
max: 1000,
step: 1,
},
},
default: {
plainCover: false,
count: 10,
seed: 0,
},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
createInstance: ({ options, model, scene, id }) => {
const bodyMesh = model.findMesh('__X_BODY__');
const tex = new BABYLON.Texture('/client-assets/room/objects/random-books/texture.png', scene, {
invertY: false,
samplingMode: BABYLON.Texture.NEAREST_SAMPLINGMODE,
});
bodyMesh.material.albedoTexture = tex;
const TEXTURE_DIVISION = 8;
let bookMeshes: BABYLON.Mesh[] = [];
const gen = () => {
for (const m of bookMeshes) {
m.dispose();
}
bookMeshes = [];
const rng = seedrandom(options.seed === 0 ? id : options.seed.toString());
const randomRange = (min: number, max: number) => rng() * (max - min) + min;
let accumulatedPos = 0;
for (let i = 0; i < options.count; i++) {
const mesh = bodyMesh.clone();
mesh.isVisible = true;
mesh.setEnabled(true);
mesh.makeGeometryUnique();
mesh.morphTargetManager = bodyMesh.morphTargetManager.clone();
mesh.markVerticesDataAsUpdatable(BABYLON.VertexBuffer.UVKind, true);
const index = Math.floor(rng() * (TEXTURE_DIVISION * TEXTURE_DIVISION));
const x = index % TEXTURE_DIVISION;
const y = Math.floor(index / TEXTURE_DIVISION);
const uvs = mesh.getVerticesData(BABYLON.VertexBuffer.UVKind)!;
for (let uvi = 0; uvi < uvs.length; uvi += 2) {
const u = uvs[uvi];
const v = uvs[uvi + 1];
uvs[uvi] = (u / TEXTURE_DIVISION) + (x / TEXTURE_DIVISION);
uvs[uvi + 1] = (v / TEXTURE_DIVISION) + (y / TEXTURE_DIVISION);
}
mesh.updateVerticesData(BABYLON.VertexBuffer.UVKind, uvs);
const width = randomRange(0.125, 0.175);
const height = randomRange(0.3, 0.4);
const thickness = randomRange(0, 0.03);
mesh.morphTargetManager!.getTargetByName('Width')!.influence = width;
mesh.morphTargetManager!.getTargetByName('Height')!.influence = height;
mesh.morphTargetManager!.getTargetByName('Thickness')!.influence = thickness;
const thicknessCm = 2 + remap(thickness, 0, 1, 0, 100);
const widthCm = 2 + remap(width, 0, 1, 0, 100);
const gap = 0.25;
mesh.position.x = (accumulatedPos + (thicknessCm / 2)) / WORLD_SCALE;
mesh.position.z = widthCm / 2 / WORLD_SCALE;
mesh.refreshBoundingInfo();
mesh.computeWorldMatrix(true);
accumulatedPos += thicknessCm + gap;
bookMeshes.push(mesh);
}
// centering
for (let i = 0; i < options.count; i++) {
bookMeshes[i].position.x -= accumulatedPos / 2 / WORLD_SCALE;
}
bodyMesh.isVisible = false;
model.updated();
};
gen();
return {
onInited: () => {
},
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'seed': gen(); break;
case 'count': gen(); break;
}
},
interactions: {},
};
},
});

View File

@@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const rolledUpPoster = defineObject({
id: 'rolledUpPoster',
name: 'Rolled-up Poster',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,23 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const roundRug = defineObject({
id: 'roundRug',
name: 'Round Rug',
options: {
schema: {},
default: {},
},
placement: 'floor',
hasCollisions: false,
hasTexture: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,23 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const router = defineObject({
id: 'router',
name: 'Router',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const siphon = defineObject({
id: 'siphon',
name: 'Siphon',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,23 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const snakeplant = defineObject({
id: 'snakeplant',
name: 'Snake Plant',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,58 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const speaker = defineObject({
id: 'speaker',
name: 'Speaker',
options: {
schema: {
outerColor: {
type: 'color',
label: 'Outer Color',
},
innerColor: {
type: 'color',
label: 'Inner Color',
},
},
default: {
outerColor: [0.45, 0.8, 0],
innerColor: [0, 0, 0],
},
},
placement: 'top',
hasCollisions: false,
createInstance: ({ options, model }) => {
const outerMesh = model.findMesh('__X_COVER__');
const outerMaterial = outerMesh.material as BABYLON.PBRMaterial;
const innerMesh = model.findMesh('__X_BODY__');
const innerMaterial = innerMesh.material as BABYLON.PBRMaterial;
const applyOuterColor = () => {
const [r, g, b] = options.outerColor;
outerMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
const applyInnerColor = () => {
const [r, g, b] = options.innerColor;
innerMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyOuterColor();
applyInnerColor();
return {
onOptionsUpdated: ([k, v]) => {
applyOuterColor();
applyInnerColor();
},
interactions: {},
};
},
});

View File

@@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const sprayer = defineObject({
id: 'sprayer',
name: '霧吹き',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const steelRack = defineObject({
id: 'steelRack',
name: 'Steel Rack',
options: {
schema: {},
default: {},
},
placement: 'floor',
hasCollisions: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,24 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const tabletopCalendar = defineObject({
id: 'tabletopCalendar',
name: 'Tabletop Calendar',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
canPreMeshesMerging: true,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,125 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject, WORLD_SCALE } from '../engine.js';
import { cm, get7segMeshesOfCurrentTime } from '../utility.js';
export const tabletopDigitalClock = defineObject({
id: 'tabletopDigitalClock',
name: 'Tabletop Digital Clock',
options: {
schema: {
bodyStyle: {
type: 'enum',
label: 'Body Style',
enum: ['color', 'wood'],
},
bodyColor: {
type: 'color',
label: 'Body Color',
},
lcdColor: {
type: 'color',
label: 'LCD Color',
},
},
default: {
bodyStyle: 'color',
bodyColor: [0.45, 0.8, 0],
lcdColor: [1, 1, 1],
},
},
placement: 'top',
hasCollisions: false,
createInstance: ({ root, room, options, model, scene }) => {
const light = new BABYLON.SpotLight('', new BABYLON.Vector3(0, cm(3), cm(1)), new BABYLON.Vector3(0, 0, 1), Math.PI / 1, 2, scene, room?.lightContainer != null);
light.parent = root;
light.intensity = 0.01 * WORLD_SCALE * WORLD_SCALE;
light.range = cm(30);
if (room?.lightContainer != null) room.lightContainer.addLight(light);
const segmentMeshes = {
'1a': model.findMesh('__TIME_7SEG_1A__'),
'1b': model.findMesh('__TIME_7SEG_1B__'),
'1c': model.findMesh('__TIME_7SEG_1C__'),
'1d': model.findMesh('__TIME_7SEG_1D__'),
'1e': model.findMesh('__TIME_7SEG_1E__'),
'1f': model.findMesh('__TIME_7SEG_1F__'),
'1g': model.findMesh('__TIME_7SEG_1G__'),
'2a': model.findMesh('__TIME_7SEG_2A__'),
'2b': model.findMesh('__TIME_7SEG_2B__'),
'2c': model.findMesh('__TIME_7SEG_2C__'),
'2d': model.findMesh('__TIME_7SEG_2D__'),
'2e': model.findMesh('__TIME_7SEG_2E__'),
'2f': model.findMesh('__TIME_7SEG_2F__'),
'2g': model.findMesh('__TIME_7SEG_2G__'),
'3a': model.findMesh('__TIME_7SEG_3A__'),
'3b': model.findMesh('__TIME_7SEG_3B__'),
'3c': model.findMesh('__TIME_7SEG_3C__'),
'3d': model.findMesh('__TIME_7SEG_3D__'),
'3e': model.findMesh('__TIME_7SEG_3E__'),
'3f': model.findMesh('__TIME_7SEG_3F__'),
'3g': model.findMesh('__TIME_7SEG_3G__'),
'4a': model.findMesh('__TIME_7SEG_4A__'),
'4b': model.findMesh('__TIME_7SEG_4B__'),
'4c': model.findMesh('__TIME_7SEG_4C__'),
'4d': model.findMesh('__TIME_7SEG_4D__'),
'4e': model.findMesh('__TIME_7SEG_4E__'),
'4f': model.findMesh('__TIME_7SEG_4F__'),
'4g': model.findMesh('__TIME_7SEG_4G__'),
};
const colonMeshes = model.findMeshes('__TIME_7SEG_COLON__');
model.bakeExcludeMeshes = Object.values(segmentMeshes).concat(colonMeshes);
const bodyMesh = model.findMesh('__X_BODY__');
const bodyMaterial = bodyMesh.material as BABYLON.PBRMaterial;
const applyBodyColor = () => {
if (options.bodyStyle === 'color') {
const [r, g, b] = options.bodyColor;
bodyMaterial.albedoColor = new BABYLON.Color3(r, g, b);
} else {
}
};
const applyLcdColor = () => {
const mat = segmentMeshes['1a'].material as BABYLON.PBRMaterial;
const [r, g, b] = options.lcdColor;
mat.emissiveColor = new BABYLON.Color3(r, g, b);
light.diffuse = new BABYLON.Color3(r, g, b);
};
return {
onInited: () => {
applyBodyColor();
applyLcdColor();
room.intervalIds.push(setInterval(() => {
const onMeshes = get7segMeshesOfCurrentTime(segmentMeshes);
for (const mesh of Object.values(segmentMeshes)) {
mesh.isVisible = onMeshes.includes(mesh);
}
for (const mesh of colonMeshes) {
mesh.isVisible = Date.now() % 2000 < 1000;
}
}, 1000));
},
onOptionsUpdated: ([k, v]) => {
if (k === 'bodyColor') {
applyBodyColor();
} else if (k === 'lcdColor') {
applyLcdColor();
}
},
interactions: {},
};
},
});

View File

@@ -1,87 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
import { createPlaneUvMapper } from '../utility.js';
export const tabletopFlag = defineObject({
id: 'tabletopFlag',
name: 'Tabletop flag',
options: {
schema: {
customPicture: {
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
customPicture: null,
fit: 'cover',
},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
createInstance: async ({ model, options, scene }) => {
const flagMesh = model.findMesh('__X_FLAG__');
const flagMaterial = model.findMaterial('__X_FLAG__');
const updateUv = createPlaneUvMapper(flagMesh);
const applyFit = () => {
const tex = flagMaterial.albedoTexture;
if (tex == null) return;
const srcWidth = tex.getSize().width;
const srcHeight = tex.getSize().height;
const srcAspect = srcWidth / srcHeight;
updateUv(srcAspect, 24 / 16, options.fit);
model.updated();
};
applyFit();
const applyCustomPicture = () => new Promise<void>((resolve) => {
if (options.customPicture != null) {
const tex = new BABYLON.Texture(options.customPicture, scene, false, false);
tex.wrapU = BABYLON.Texture.MIRROR_ADDRESSMODE;
tex.wrapV = BABYLON.Texture.MIRROR_ADDRESSMODE;
flagMaterial.unfreeze();
flagMaterial.albedoColor = new BABYLON.Color3(1, 1, 1);
flagMaterial.albedoTexture = tex;
tex.onLoadObservable.addOnce(() => {
applyFit();
resolve();
});
} else {
flagMaterial.albedoColor = new BABYLON.Color3(0.5, 0.5, 0.5);
flagMaterial.albedoTexture = null;
resolve();
}
});
await applyCustomPicture();
return {
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'customPicture': applyCustomPicture(); break;
case 'fit': applyFit(); break;
}
},
interactions: {},
};
},
});

View File

@@ -1,139 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
import { createPlaneUvMapper, getPlaneUvIndexes } from '../utility.js';
const remap = (value: number, fromMin: number, fromMax: number, toMin: number, toMax: number) => {
return toMin + ((value - fromMin) / (fromMax - fromMin)) * (toMax - toMin);
};
export const tabletopGlassPictureFrame = defineObject({
id: 'tabletopGlassPictureFrame',
name: 'tabletopGlassPictureFrame',
options: {
schema: {
width: {
type: 'range',
label: 'Width',
min: 0,
max: 1,
step: 0.01,
},
height: {
type: 'range',
label: 'Height',
min: 0,
max: 1,
step: 0.01,
},
customPicture: {
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
width: 0.1,
height: 0.1,
customPicture: null,
fit: 'cover',
},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
createInstance: async ({ scene, options, model }) => {
const pictureMesh = model.findMesh('__X_PICTURE__');
const frameMesh = model.findMesh('__X_FRAME__');
const pinMeshes = model.findMeshes('__X_PIN__');
const pictureMaterial = model.findMaterial('__X_PICTURE__');
const updateUv = createPlaneUvMapper(pictureMesh);
const applyFit = () => {
const tex = pictureMaterial.albedoTexture;
if (tex == null) return;
const srcWidth = tex.getSize().width;
const srcHeight = tex.getSize().height;
const srcAspect = srcWidth / srcHeight;
const targetWidth = remap(options.width, 0, 1, 2, 76); // 最小値(値を0にした場合)でのサイズは2cmで、最大値(値を1にした場合)でのサイズは76cmなので。比率の計算だから単位はなんでもいいけど、とにかく0が0にならない点を考慮させる必要がある
const targetHeight = remap(options.height, 0, 1, 2, 76); // 最小値(値を0にした場合)でのサイズは2cmで、最大値(値を1にした場合)でのサイズは76cmなので。比率の計算だから単位はなんでもいいけど、とにかく0が0にならない点を考慮させる必要がある
const targetAspect = targetWidth / targetHeight;
updateUv(srcAspect, targetAspect, options.fit);
model.updated();
};
applyFit();
const applySize = () => {
pictureMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
pictureMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
frameMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
frameMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
for (const pinMesh of pinMeshes) {
pinMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
pinMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
}
model.updated();
applyFit();
};
applySize();
const applyCustomPicture = () => new Promise<void>((resolve) => {
if (options.customPicture != null) {
const tex = new BABYLON.Texture(options.customPicture, scene, false, false);
tex.wrapU = BABYLON.Texture.MIRROR_ADDRESSMODE;
tex.wrapV = BABYLON.Texture.MIRROR_ADDRESSMODE;
pictureMaterial.unfreeze();
pictureMaterial.albedoColor = new BABYLON.Color3(1, 1, 1);
pictureMaterial.albedoTexture = tex;
tex.onLoadObservable.addOnce(() => {
applyFit();
resolve();
});
} else {
pictureMaterial.albedoColor = new BABYLON.Color3(0.5, 0.5, 0.5);
pictureMaterial.albedoTexture = null;
resolve();
}
});
await applyCustomPicture();
return {
onInited: () => {
},
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'width':
case 'height':
applySize();
break;
case 'customPicture':
applyCustomPicture();
break;
case 'fit':
applyFit();
break;
}
},
interactions: {},
};
},
});

View File

@@ -1,102 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const tabletopIronFrameStand = defineObject({
id: 'tabletopIronFrameStand',
name: 'tabletopIronFrameStand',
options: {
schema: {
frameColor: {
type: 'color',
label: 'Frame color',
},
boardColor: {
type: 'color',
label: 'Board color',
},
width: {
type: 'range',
label: 'Width',
min: 0,
max: 1,
step: 0.01,
},
depth: {
type: 'range',
label: 'Depth',
min: 0,
max: 1,
step: 0.01,
},
height: {
type: 'range',
label: 'Height',
min: 0,
max: 1,
step: 0.01,
},
},
default: {
frameColor: [0.8, 0.8, 0.8],
boardColor: [0.8, 0.4, 0.1],
width: 0.2,
depth: 0.1,
height: 0.05,
},
},
placement: 'top',
hasCollisions: false,
createInstance: ({ options, model }) => {
const frameMaterial = model.findMaterial('__X_FRAME__');
const boardMaterial = model.findMaterial('__X_BOARD__');
const applyFrameColor = () => {
const [r, g, b] = options.frameColor;
frameMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyFrameColor();
const applyBoardColor = () => {
const [r, g, b] = options.boardColor;
boardMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyBoardColor();
const applySize = () => {
for (const mesh of model.root.getChildMeshes()) {
if (mesh.morphTargetManager != null && mesh.morphTargetManager.getTargetByName('W') != null) {
mesh.morphTargetManager.getTargetByName('W').influence = options.width;
}
if (mesh.morphTargetManager != null && mesh.morphTargetManager.getTargetByName('D') != null) {
mesh.morphTargetManager.getTargetByName('D').influence = options.depth;
}
if (mesh.morphTargetManager != null && mesh.morphTargetManager.getTargetByName('H') != null) {
mesh.morphTargetManager.getTargetByName('H').influence = options.height;
}
}
model.updated();
};
applySize();
return {
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'frameColor': applyFrameColor(); break;
case 'boardColor': applyBoardColor(); break;
case 'width': applySize(); break;
case 'depth': applySize(); break;
case 'height': applySize(); break;
}
},
interactions: {},
};
},
});

View File

@@ -1,268 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
import { createPlaneUvMapper } from '../utility.js';
// NOTE: シェイプキーのnormalのエクスポートは無効にしないとmatを大きくしたときに面のレンダリングがグリッチする
export const tabletopPictureFrame = defineObject({
id: 'tabletopPictureFrame',
name: 'Tabletop simple picture frame',
options: {
schema: {
frameColor: {
type: 'color',
label: 'Frame color',
},
width: {
type: 'range',
label: 'Width',
min: 0,
max: 1,
step: 0.01,
},
height: {
type: 'range',
label: 'Height',
min: 0,
max: 1,
step: 0.01,
},
frameThickness: {
type: 'range',
label: 'Frame thickness',
min: 0,
max: 1,
step: 0.01,
},
depth: {
type: 'range',
label: 'Depth',
min: 0,
max: 1,
step: 0.01,
},
matHThickness: {
type: 'range',
label: 'Mat horizontal thickness',
min: 0,
max: 1,
step: 0.01,
},
matVThickness: {
type: 'range',
label: 'Mat vertical thickness',
min: 0,
max: 1,
step: 0.01,
},
customPicture: {
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
frameColor: [0.71, 0.58, 0.39],
width: 0.07,
height: 0.07,
frameThickness: 0.1,
depth: 0,
matHThickness: 0,
matVThickness: 0,
customPicture: null,
fit: 'cover',
},
},
placement: 'top',
hasCollisions: false,
hasTexture: true,
createInstance: async ({ scene, options, model }) => {
const frameMesh = model.findMesh('__X_FRAME__');
frameMesh.rotationQuaternion = null;
const matMesh = model.findMesh('__X_MAT__');
matMesh.rotationQuaternion = null;
const coverMesh = model.findMesh('__X_COVER__');
coverMesh.rotationQuaternion = null;
const pictureMesh = model.findMesh('__X_PICTURE__');
pictureMesh.rotationQuaternion = null;
const pictureMaterial = model.findMaterial('__X_PICTURE__');
const updateUv = createPlaneUvMapper(pictureMesh);
const applyFit = () => {
const tex = pictureMaterial.albedoTexture;
if (tex == null) return;
const srcWidth = tex.getSize().width;
const srcHeight = tex.getSize().height;
const srcAspect = srcWidth / srcHeight;
const targetWidth = options.width * (1 - options.matHThickness);
const targetHeight = options.height * (1 - options.matVThickness);
const targetAspect = targetWidth / targetHeight;
updateUv(srcAspect, targetAspect, options.fit);
model.updated();
};
applyFit();
const applyFrameThickness = () => {
frameMesh.morphTargetManager!.getTargetByName('Thickness')!.influence = options.frameThickness;
coverMesh.morphTargetManager!.getTargetByName('FrameThickness')!.influence = options.frameThickness;
matMesh.morphTargetManager!.getTargetByName('FrameThickness')!.influence = options.frameThickness;
pictureMesh.morphTargetManager!.getTargetByName('FrameThickness')!.influence = options.frameThickness;
model.updated();
};
applyFrameThickness();
const applyMatThickness = () => {
matMesh.morphTargetManager!.getTargetByName('MatH')!.influence = options.matHThickness * options.width;
matMesh.morphTargetManager!.getTargetByName('MatV')!.influence = options.matVThickness * options.height;
pictureMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width * (1 - options.matHThickness);
pictureMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height * (1 - options.matVThickness);
pictureMesh.morphTargetManager!.getTargetByName('MatH')!.influence = options.matHThickness * options.width;
pictureMesh.morphTargetManager!.getTargetByName('MatV')!.influence = options.matVThickness * options.height;
matMesh.isVisible = options.matHThickness > 0 || options.matVThickness > 0;
model.updated();
applyFit();
};
applyMatThickness();
const applySize = () => {
frameMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
frameMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
matMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
matMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
coverMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
coverMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
model.updated();
applyMatThickness();
};
applySize();
const applyDepth = () => {
frameMesh.morphTargetManager!.getTargetByName('Depth')!.influence = options.depth;
//coverMesh.morphTargetManager!.getTargetByName('Depth')!.influence = options.depth;
coverMesh.morphTargetManager!.getTargetByName('Depth')!.influence = 0;
model.updated();
};
applyDepth();
const applyCustomPicture = () => new Promise<void>((resolve) => {
if (options.customPicture != null) {
const tex = new BABYLON.Texture(options.customPicture, scene, false, false);
tex.wrapU = BABYLON.Texture.MIRROR_ADDRESSMODE;
tex.wrapV = BABYLON.Texture.MIRROR_ADDRESSMODE;
pictureMaterial.unfreeze();
pictureMaterial.albedoColor = new BABYLON.Color3(1, 1, 1);
pictureMaterial.albedoTexture = tex;
tex.onLoadObservable.addOnce(() => {
applyFit();
resolve();
});
} else {
pictureMaterial.albedoColor = new BABYLON.Color3(0.5, 0.5, 0.5);
pictureMaterial.albedoTexture = null;
resolve();
}
});
await applyCustomPicture();
const frameMaterial = model.findMaterial('__X_FRAME__');
const applyFrameColor = () => {
const [r, g, b] = options.frameColor;
frameMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyFrameColor();
return {
onInited: () => {
},
onOptionsUpdated: ([k, v]) => {
if (k === 'frameColor') {
applyFrameColor();
}
if (k === 'width' || k === 'height') {
applySize();
}
if (k === 'frameThickness') {
applyFrameThickness();
}
if (k === 'depth') {
applyDepth();
}
if (k === 'matHThickness' || k === 'matVThickness') {
applyMatThickness();
}
if (k === 'customPicture') {
applyCustomPicture();
}
if (k === 'fit') {
applyFit();
}
},
interactions: {},
};
},
});
/*
const applyDirection = () => {
if (options.direction === 'vertical') {
frameMesh.rotation.z = 0;
matMesh.rotation.z = 0;
coverMesh.rotation.z = 0;
pictureMesh.rotation.z = 0;
uvs[6] = ax;
uvs[7] = ay;
uvs[2] = bx;
uvs[3] = by;
uvs[4] = cx;
uvs[5] = cy;
uvs[0] = dx;
uvs[1] = dy;
} else if (options.direction === 'horizontal') {
frameMesh.rotation.z = -Math.PI / 2;
matMesh.rotation.z = -Math.PI / 2;
coverMesh.rotation.z = -Math.PI / 2;
pictureMesh.rotation.z = -Math.PI / 2;
uvs[6] = cy;
uvs[7] = cx;
uvs[2] = dy;
uvs[3] = dx;
uvs[4] = ay;
uvs[5] = ax;
uvs[0] = by;
uvs[1] = bx;
}
pictureMesh.updateVerticesData(BABYLON.VertexBuffer.UVKind, uvs);
};
*/

View File

@@ -1,140 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
import { createPlaneUvMapper, getPlaneUvIndexes } from '../utility.js';
const remap = (value: number, fromMin: number, fromMax: number, toMin: number, toMax: number) => {
return toMin + ((value - fromMin) / (fromMax - fromMin)) * (toMax - toMin);
};
export const tapestry = defineObject({
id: 'tapestry',
name: 'Tapestry',
options: {
schema: {
width: {
type: 'range',
label: 'Width',
min: 0,
max: 1,
step: 0.01,
},
height: {
type: 'range',
label: 'Height',
min: 0,
max: 1,
step: 0.01,
},
customPicture: {
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
width: 0.15,
height: 0.15,
customPicture: null,
fit: 'cover',
},
},
placement: 'side',
hasCollisions: false,
hasTexture: true,
createInstance: async ({ scene, options, model }) => {
const pictureMesh = model.findMesh('__X_PICTURE__');
pictureMesh.rotationQuaternion = null;
const pipeTopMesh = model.findMesh('__X_PIPE_TOP__');
const pipeBottomMesh = model.findMesh('__X_PIPE_BOTTOM__');
const ropeMesh = model.findMesh('__X_ROPE__');
const pictureMaterial = model.findMaterial('__X_PICTURE__');
const updateUv = createPlaneUvMapper(pictureMesh);
const applyFit = () => {
const tex = pictureMaterial.albedoTexture;
if (tex == null) return;
const srcWidth = tex.getSize().width;
const srcHeight = tex.getSize().height;
const srcAspect = srcWidth / srcHeight;
const targetWidth = remap(options.width, 0, 1, 2, 100); // 最小値(値を0にした場合)でのサイズは2cmで、最大値(値を1にした場合)でのサイズは100cmなので。比率の計算だから単位はなんでもいいけど、とにかく0が0にならない点を考慮させる必要がある
const targetHeight = remap(options.height, 0, 1, 2, 100); // 最小値(値を0にした場合)でのサイズは2cmで、最大値(値を1にした場合)でのサイズは100cmなので。比率の計算だから単位はなんでもいいけど、とにかく0が0にならない点を考慮させる必要がある
const targetAspect = targetWidth / targetHeight;
updateUv(srcAspect, targetAspect, options.fit);
model.updated();
};
applyFit();
const applySize = () => {
pictureMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
pictureMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
pipeTopMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
pipeTopMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
pipeBottomMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
pipeBottomMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
ropeMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
ropeMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
model.updated();
applyFit();
};
applySize();
const applyCustomPicture = () => new Promise<void>((resolve) => {
if (options.customPicture != null) {
const tex = new BABYLON.Texture(options.customPicture, scene, false, false);
tex.wrapU = BABYLON.Texture.MIRROR_ADDRESSMODE;
tex.wrapV = BABYLON.Texture.MIRROR_ADDRESSMODE;
pictureMaterial.unfreeze();
pictureMaterial.albedoColor = new BABYLON.Color3(1, 1, 1);
pictureMaterial.albedoTexture = tex;
tex.onLoadObservable.addOnce(() => {
applyFit();
resolve();
});
} else {
pictureMaterial.albedoColor = new BABYLON.Color3(0.5, 0.5, 0.5);
pictureMaterial.albedoTexture = null;
resolve();
}
});
await applyCustomPicture();
return {
onInited: () => {
},
onOptionsUpdated: ([k, v]) => {
if (k === 'width' || k === 'height') {
applySize();
}
if (k === 'customPicture') {
applyCustomPicture();
}
if (k === 'fit') {
applyFit();
}
},
interactions: {},
};
},
});

View File

@@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const tetrapod = defineObject({
id: 'tetrapod',
name: '波消ブロック',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,99 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject, WORLD_SCALE } from '../engine.js';
import { cm, createPlaneUvMapper, initTv } from '../utility.js';
export const tv = defineObject({
id: 'tv',
name: 'TV',
options: {
schema: {
bodyColor: {
type: 'color',
label: 'Body color',
},
screenBrightness: {
type: 'range',
label: 'Screen brightness',
min: 0,
max: 1,
step: 0.01,
},
},
default: {
bodyColor: [0, 0, 0],
screenBrightness: 0.5,
},
},
placement: 'top',
hasCollisions: true,
hasTexture: true,
createInstance: ({ options, room, model, scene }) => {
const matrix = model.root.getWorldMatrix(true);
const scale = new BABYLON.Vector3();
matrix.decompose(scale);
const light = new BABYLON.SpotLight('', new BABYLON.Vector3(cm(0), cm(30) / Math.abs(scale.y), 0), new BABYLON.Vector3(0, 0, 1), Math.PI / 1, 2, scene, room?.lightContainer != null);
light.parent = model.root;
light.diffuse = new BABYLON.Color3(1.0, 1.0, 1.0);
light.range = cm(150);
if (room?.lightContainer != null) room.lightContainer.addLight(light);
const screenMesh = model.findMesh('__TV_SCREEN__');
screenMesh.markVerticesDataAsUpdatable(BABYLON.VertexBuffer.UVKind, true);
model.bakeExcludeMeshes = [screenMesh];
const screenMaterial = model.findMaterial('__X_SCREEN__');
const { dispose: disposeTv } = initTv(room, screenMesh);
//const videoTexture = new BABYLON.VideoTexture('', 'http://syu-win.local:3000/files/97986924-b99e-4fe1-993d-9caf010cca59', room.scene, false, true); ;
//screenMaterial.emissiveTexture = videoTexture;
//videoTexture.video.muted = true;
//videoTexture.video.volume = 0;
//videoTexture.video.loop = true;
const applyScreenBrightness = () => {
const b = options.screenBrightness;
screenMaterial.emissiveColor = new BABYLON.Color3(b, b, b);
light.intensity = (7 * b) * WORLD_SCALE * WORLD_SCALE;
};
applyScreenBrightness();
//const updateUv = createPlaneUvMapper(screenMesh);
//const applyFit = () => {
// const tex = screenMaterial.emissiveTexture;
// if (tex == null) return;
// const srcAspect = 16 / 9;
// const targetAspect = 16 / 9;
// updateUv(srcAspect, targetAspect, 'cover');
// model.updated();
//};
//applyFit();
const bodyMaterial = model.findMaterial('__X_BODY__');
const applyBodyColor = () => {
const [r, g, b] = options.bodyColor;
bodyMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyBodyColor();
return {
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'bodyColor': applyBodyColor(); break;
case 'screenBrightness': applyScreenBrightness(); break;
}
},
interactions: {},
};
},
});

View File

@@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const twistedCubeObjet = defineObject({
id: 'twistedCubeObjet',
name: 'twistedCubeObjet',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const usedTissue = defineObject({
id: 'usedTissue',
name: 'usedTissue',
options: {
schema: {},
default: {},
},
placement: 'top',
hasCollisions: false,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,127 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
import { createPlaneUvMapper, getPlaneUvIndexes } from '../utility.js';
export const wallCanvas = defineObject({
id: 'wallCanvas',
name: 'wallCanvas',
options: {
schema: {
width: {
type: 'range',
label: 'Width',
min: 0,
max: 1,
step: 0.01,
},
height: {
type: 'range',
label: 'Height',
min: 0,
max: 1,
step: 0.01,
},
customPicture: {
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
width: 0.15,
height: 0.15,
customPicture: null,
fit: 'cover',
},
},
placement: 'side',
hasCollisions: false,
hasTexture: true,
createInstance: async ({ scene, options, model }) => {
const canvasMesh = model.findMesh('__X_CANVAS__');
canvasMesh.rotationQuaternion = null;
const canvasMaterial = model.findMaterial('__X_CANVAS__');
const updateUv = createPlaneUvMapper(canvasMesh);
const applyFit = () => {
const tex = canvasMaterial.albedoTexture;
if (tex == null) return;
const srcWidth = tex.getSize().width;
const srcHeight = tex.getSize().height;
const srcAspect = srcWidth / srcHeight;
const targetWidth = options.width;
const targetHeight = options.height;
const targetAspect = targetWidth / targetHeight;
updateUv(srcAspect, targetAspect, options.fit);
model.updated();
};
applyFit();
const applySize = () => {
canvasMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
canvasMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
model.updated();
applyFit();
};
applySize();
const applyCustomPicture = () => new Promise<void>((resolve) => {
if (options.customPicture != null) {
const tex = new BABYLON.Texture(options.customPicture, scene, false, false);
tex.wrapU = BABYLON.Texture.MIRROR_ADDRESSMODE;
tex.wrapV = BABYLON.Texture.MIRROR_ADDRESSMODE;
canvasMaterial.unfreeze();
canvasMaterial.albedoColor = new BABYLON.Color3(1, 1, 1);
canvasMaterial.albedoTexture = tex;
tex.onLoadObservable.addOnce(() => {
applyFit();
resolve();
});
} else {
canvasMaterial.albedoColor = new BABYLON.Color3(0.5, 0.5, 0.5);
canvasMaterial.albedoTexture = null;
resolve();
}
});
await applyCustomPicture();
return {
onInited: () => {
},
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'width':
case 'height':
applySize();
break;
case 'customPicture':
case 'fit':
applyCustomPicture();
break;
}
},
interactions: {},
};
},
});

View File

@@ -1,58 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const wallClock = defineObject({
id: 'wallClock',
name: 'Wall Clock',
options: {
schema: {
frameColor: {
type: 'color',
label: 'Frame color',
},
},
default: {
frameColor: [0.71, 0.58, 0.39],
},
},
placement: 'side',
hasCollisions: false,
createInstance: ({ room, root, options, model }) => {
const hourHand = model.findMesh('HandH');
const minuteHand = model.findMesh('HandM');
const frameMaterial = model.findMaterial('__X_FRAME__');
const applyFrameColor = () => {
const [r, g, b] = options.frameColor;
frameMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyFrameColor();
model.bakeExcludeMeshes = [hourHand, minuteHand];
return {
onInited: () => {
room.intervalIds.push(setInterval(() => {
const now = new Date();
const hours = now.getHours() % 12;
const minutes = now.getMinutes();
const hAngle = -(hours / 12) * Math.PI * 2 - (minutes / 60) * (Math.PI * 2 / 12);
const mAngle = -(minutes / 60) * Math.PI * 2;
hourHand.rotation = new BABYLON.Vector3(0, 0, hAngle);
minuteHand.rotation = new BABYLON.Vector3(0, 0, mAngle);
}, 1000));
},
onOptionsUpdated: ([k, v]) => {
applyFrameColor();
},
interactions: {},
};
},
});

View File

@@ -1,139 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
import { createPlaneUvMapper, getPlaneUvIndexes } from '../utility.js';
const remap = (value: number, fromMin: number, fromMax: number, toMin: number, toMax: number) => {
return toMin + ((value - fromMin) / (fromMax - fromMin)) * (toMax - toMin);
};
export const wallGlassPictureFrame = defineObject({
id: 'wallGlassPictureFrame',
name: 'wallGlassPictureFrame',
options: {
schema: {
width: {
type: 'range',
label: 'Width',
min: 0,
max: 1,
step: 0.01,
},
height: {
type: 'range',
label: 'Height',
min: 0,
max: 1,
step: 0.01,
},
customPicture: {
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
width: 0.1,
height: 0.1,
customPicture: null,
fit: 'cover',
},
},
placement: 'wall',
hasCollisions: false,
hasTexture: true,
createInstance: async ({ scene, options, model }) => {
const pictureMesh = model.findMesh('__X_PICTURE__');
const frameMesh = model.findMesh('__X_FRAME__');
const pinMeshes = model.findMeshes('__X_PIN__');
const pictureMaterial = model.findMaterial('__X_PICTURE__');
const updateUv = createPlaneUvMapper(pictureMesh);
const applyFit = () => {
const tex = pictureMaterial.albedoTexture;
if (tex == null) return;
const srcWidth = tex.getSize().width;
const srcHeight = tex.getSize().height;
const srcAspect = srcWidth / srcHeight;
const targetWidth = remap(options.width, 0, 1, 2, 172); // 最小値(値を0にした場合)でのサイズは2cmで、最大値(値を1にした場合)でのサイズは172cmなので。比率の計算だから単位はなんでもいいけど、とにかく0が0にならない点を考慮させる必要がある
const targetHeight = remap(options.height, 0, 1, 2, 172); // 最小値(値を0にした場合)でのサイズは2cmで、最大値(値を1にした場合)でのサイズは172cmなので。比率の計算だから単位はなんでもいいけど、とにかく0が0にならない点を考慮させる必要がある
const targetAspect = targetWidth / targetHeight;
updateUv(srcAspect, targetAspect, options.fit);
model.updated();
};
applyFit();
const applySize = () => {
pictureMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
pictureMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
frameMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
frameMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
for (const pinMesh of pinMeshes) {
pinMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
pinMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
}
model.updated();
applyFit();
};
applySize();
const applyCustomPicture = () => new Promise<void>((resolve) => {
if (options.customPicture != null) {
const tex = new BABYLON.Texture(options.customPicture, scene, false, false);
tex.wrapU = BABYLON.Texture.MIRROR_ADDRESSMODE;
tex.wrapV = BABYLON.Texture.MIRROR_ADDRESSMODE;
pictureMaterial.unfreeze();
pictureMaterial.albedoColor = new BABYLON.Color3(1, 1, 1);
pictureMaterial.albedoTexture = tex;
tex.onLoadObservable.addOnce(() => {
applyFit();
resolve();
});
} else {
pictureMaterial.albedoColor = new BABYLON.Color3(0.5, 0.5, 0.5);
pictureMaterial.albedoTexture = null;
resolve();
}
});
await applyCustomPicture();
return {
onInited: () => {
},
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'width':
case 'height':
applySize();
break;
case 'customPicture':
applyCustomPicture();
break;
case 'fit':
applyFit();
break;
}
},
interactions: {},
};
},
});

View File

@@ -1,90 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const wallMirror = defineObject({
id: 'wallMirror',
name: 'wallMirror',
options: {
schema: {
width: {
type: 'range',
label: 'Width',
min: 0,
max: 1,
step: 0.01,
},
height: {
type: 'range',
label: 'Height',
min: 0,
max: 1,
step: 0.01,
},
frameThickness: {
type: 'range',
label: 'Frame thickness',
min: 0,
max: 1,
step: 0.01,
},
frameColor: {
type: 'color',
label: 'Frame color',
},
},
default: {
width: 0.2,
height: 0.2,
frameThickness: 0.1,
frameColor: [0.8, 0.28, 0.06],
},
},
placement: 'side',
hasCollisions: false,
createInstance: async ({ options, model }) => {
const frameMaterial = model.findMaterial('__X_FRAME__');
const frameMesh = model.findMesh('__X_FRAME__');
const mirrorMesh = model.findMesh('__X_MIRROR__');
const applySize = () => {
frameMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
frameMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
frameMesh.morphTargetManager!.getTargetByName('FrameThickness')!.influence = options.frameThickness;
mirrorMesh.morphTargetManager!.getTargetByName('Width')!.influence = options.width;
mirrorMesh.morphTargetManager!.getTargetByName('Height')!.influence = options.height;
model.updated();
};
applySize();
const applyFrameColor = () => {
frameMaterial.albedoColor = new BABYLON.Color3(...options.frameColor);
};
applyFrameColor();
return {
onInited: () => {
},
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'width':
case 'height':
case 'frameThickness':
applySize();
break;
case 'frameColor':
applyFrameColor();
break;
}
},
interactions: {},
};
},
});

View File

@@ -1,86 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../engine.js';
export const wallShelf = defineObject({
id: 'wallShelf',
name: 'Wall Shelf',
options: {
schema: {
style: {
type: 'enum',
label: 'Style',
enum: ['A', 'B', 'C', 'D'],
},
boardStyle: {
type: 'enum',
label: 'Board style',
enum: ['color', 'wood'],
},
boardColor: {
type: 'color',
label: 'Board color',
},
},
default: {
style: 'A',
boardStyle: 'wood',
boardColor: [1, 1, 1],
},
},
placement: 'side',
createInstance: ({ model, options }) => {
const applyStyle = () => {
const aMeshes = model.findMeshes('__X_VARIATION_A__');
const bMeshes = model.findMeshes('__X_VARIATION_B__');
const cMeshes = model.findMeshes('__X_VARIATION_C__');
const dMeshes = model.findMeshes('__X_VARIATION_D__');
for (const m of aMeshes) {
m.isVisible = options.style === 'A';
}
for (const m of bMeshes) {
m.isVisible = options.style === 'B';
}
for (const m of cMeshes) {
m.isVisible = options.style === 'C';
}
for (const m of dMeshes) {
m.isVisible = options.style === 'D';
}
model.updated();
};
applyStyle();
const bodyMesh = model.findMesh('__X_BODY__');
const bodyMaterial = bodyMesh.material as BABYLON.PBRMaterial;
const bodyTexture = bodyMaterial.albedoTexture as BABYLON.Texture;
const applyBoardColor = () => {
const [r, g, b] = options.boardColor;
bodyMaterial.albedoColor = new BABYLON.Color3(r, g, b);
if (options.boardStyle === 'color') {
bodyMaterial.albedoTexture = null;
} else {
bodyMaterial.albedoTexture = bodyTexture;
}
};
applyBoardColor();
return {
onOptionsUpdated: ([k, v]) => {
applyStyle();
applyBoardColor();
},
interactions: {},
};
},
});

View File

@@ -1,108 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject, WORLD_SCALE } from '../engine.js';
import { cm } from '../utility.js';
export const woodRingFloorLamp = defineObject({
id: 'woodRingFloorLamp',
name: 'Wood Ring Floor Lamp',
options: {
schema: {
shadeColor: {
type: 'color',
label: 'Shade color',
},
bodyColor: {
type: 'color',
label: 'Body color',
},
lightColor: {
type: 'color',
label: 'Light color',
},
lightBrightness: {
type: 'range',
label: 'Light brightness',
min: 0,
max: 1,
step: 0.01,
},
},
default: {
shadeColor: [0.21, 0.04, 0],
bodyColor: [0.05, 0.05, 0.05],
lightColor: [1, 0.5, 0.2],
lightBrightness: 0.5,
},
},
placement: 'floor',
hasCollisions: true,
createInstance: ({ room, scene, options, model }) => {
const shadeMaterial = model.findMaterial('__X_SHADE__');
const applyShadeColor = () => {
const [r, g, b] = options.shadeColor;
shadeMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyShadeColor();
const bodyMaterial = model.findMaterial('__X_BODY__');
const applyBodyColor = () => {
const [r, g, b] = options.bodyColor;
bodyMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyBodyColor();
const lamps = model.findMeshes('__X_LAMP__');
const lights: BABYLON.SpotLight[] = [];
for (const lamp of lamps) {
const light = new BABYLON.SpotLight('', new BABYLON.Vector3(cm(0), cm(0), 0), new BABYLON.Vector3(0, -1, 0), Math.PI / 1, 2, scene, room?.lightContainer != null);
light.parent = lamp;
if (room?.lightContainer != null) room.lightContainer.addLight(light);
lights.push(light);
}
const applyLightColor = () => {
const [r, g, b] = options.lightColor;
for (const light of lights) {
light.diffuse = new BABYLON.Color3(r, g, b);
}
for (const lamp of lamps) {
const emissive = lamp.material as BABYLON.PBRMaterial;
emissive.emissiveColor = new BABYLON.Color3(r, g, b);
}
};
applyLightColor();
const applyLightBrightness = () => {
for (const light of lights) {
light.intensity = 1 * options.lightBrightness * WORLD_SCALE * WORLD_SCALE;
light.range = cm(200) * options.lightBrightness;
}
for (const lamp of lamps) {
const emissive = lamp.material as BABYLON.PBRMaterial;
emissive.emissiveIntensity = options.lightBrightness * 10;
}
};
applyLightBrightness();
return {
onOptionsUpdated: ([k, v]) => {
applyShadeColor();
applyBodyColor();
applyLightColor();
applyLightBrightness();
},
interactions: {},
};
},
});

View File

@@ -1,122 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject, WORLD_SCALE } from '../engine.js';
import { cm } from '../utility.js';
const remap = (value: number, fromMin: number, fromMax: number, toMin: number, toMax: number) => {
return toMin + ((value - fromMin) / (fromMax - fromMin)) * (toMax - toMin);
};
export const woodRingsPendantLight = defineObject({
id: 'woodRingsPendantLight',
name: 'woodRingsPendantLight',
options: {
schema: {
shadeColor: {
type: 'color',
label: 'Shade color',
},
bodyColor: {
type: 'color',
label: 'Body color',
},
lightColor: {
type: 'color',
label: 'Light color',
},
lightBrightness: {
type: 'range',
label: 'Light brightness',
min: 0,
max: 1,
step: 0.01,
},
length: {
type: 'range',
label: 'Length',
min: 0,
max: 1,
step: 0.01,
},
},
default: {
shadeColor: [0.21, 0.04, 0],
bodyColor: [0.05, 0.05, 0.05],
lightColor: [1, 0.5, 0.2],
lightBrightness: 0.5,
length: 0.2,
},
},
placement: 'ceiling',
hasCollisions: false,
createInstance: ({ room, scene, options, model }) => {
const shadeMaterial = model.findMaterial('__X_SHADE__');
const applyShadeColor = () => {
const [r, g, b] = options.shadeColor;
shadeMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyShadeColor();
const bodyMaterial = model.findMaterial('__X_BODY__');
const applyBodyColor = () => {
const [r, g, b] = options.bodyColor;
bodyMaterial.albedoColor = new BABYLON.Color3(r, g, b);
};
applyBodyColor();
const lamp = model.findMesh('__X_LAMP__');
const light = new BABYLON.PointLight('', new BABYLON.Vector3(0, 0, 0), scene, room?.lightContainer != null);
light.parent = lamp;
if (room?.lightContainer != null) room.lightContainer.addLight(light);
const applyLightColor = () => {
const [r, g, b] = options.lightColor;
light.diffuse = new BABYLON.Color3(r, g, b);
const emissive = lamp.material as BABYLON.PBRMaterial;
emissive.emissiveColor = new BABYLON.Color3(r, g, b);
};
applyLightColor();
const applyLightBrightness = () => {
light.intensity = 1 * options.lightBrightness * WORLD_SCALE * WORLD_SCALE;
light.range = cm(200) * options.lightBrightness;
const emissive = lamp.material as BABYLON.PBRMaterial;
emissive.emissiveIntensity = options.lightBrightness * 10;
};
applyLightBrightness();
const mainNode = model.findTransformNode('__X_MAIN__');
const codeMesh = model.findMesh('__X_CODE__');
const applyLength = () => {
mainNode.position.y = -remap(options.length, 0, 1, 0, 200) / WORLD_SCALE;
codeMesh.morphTargetManager!.getTargetByName('Length')!.influence = options.length;
model.updated();
};
applyLength();
return {
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'shadeColor': applyShadeColor(); break;
case 'bodyColor': applyBodyColor(); break;
case 'lightColor': applyLightColor(); break;
case 'lightBrightness': applyLightBrightness(); break;
case 'length': applyLength(); break;
}
},
interactions: {},
};
},
});

View File

@@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineObject } from '../engine.js';
export const woodSoundAbsorbingPanel = defineObject({
id: 'woodSoundAbsorbingPanel',
name: 'Wood Sound Absorbing Panel',
options: {
schema: {},
default: {},
},
placement: 'side',
hasCollisions: false,
createInstance: () => {
return {
interactions: {},
};
},
});

View File

@@ -1,528 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import type { RoomEngine } from './engine.js';
//export const cm = (value: number) => value / 100;
export const cm = (value: number) => value;
export function yuge(scene: BABYLON.Scene, mesh: BABYLON.Mesh, offset: BABYLON.Vector3) {
const emitter = new BABYLON.TransformNode('emitter', scene);
emitter.parent = mesh;
emitter.position = offset;
const ps = new BABYLON.ParticleSystem('steamParticleSystem', 8, scene);
ps.particleTexture = new BABYLON.Texture('/client-assets/room/steam.png');
ps.emitter = emitter;
ps.minEmitBox = new BABYLON.Vector3(cm(-1), 0, cm(-1));
ps.maxEmitBox = new BABYLON.Vector3(cm(1), 0, cm(1));
ps.minEmitPower = 10;
ps.maxEmitPower = 12;
ps.minLifeTime = 2;
ps.maxLifeTime = 3;
ps.addSizeGradient(0, cm(10), cm(12));
ps.addSizeGradient(1, cm(18), cm(20));
ps.direction1 = new BABYLON.Vector3(-0.3, 1, 0.3);
ps.direction2 = new BABYLON.Vector3(0.3, 1, -0.3);
ps.emitRate = 0.5;
ps.blendMode = BABYLON.ParticleSystem.BLENDMODE_ADD;
ps.color1 = new BABYLON.Color4(1, 1, 1, 0.3);
ps.color2 = new BABYLON.Color4(1, 1, 1, 0.2);
ps.colorDead = new BABYLON.Color4(1, 1, 1, 0);
ps.preWarmCycles = Math.random() * 1000;
ps.start();
// dispose
return () => {
ps.stop();
emitter.dispose();
};
}
export class HorizontalCameraKeyboardMoveInput extends BABYLON.BaseCameraPointersInput {
public camera: BABYLON.FreeCamera;
private engine: BABYLON.AbstractEngine;
private scene: BABYLON.Scene;
preShift = false;
codes = [];
codesUp = ['KeyW'];
codesDown = ['KeyS'];
codesLeft = ['KeyA'];
codesRight = ['KeyD'];
onCanvasBlurObserver = null;
onKeyboardObserver = null;
public canMove = true;
constructor(camera: BABYLON.UniversalCamera) {
super();
this.camera = camera;
this.scene = this.camera.getScene();
this.engine = this.scene.getEngine();
}
attachControl() {
if (this.onCanvasBlurObserver) {
return;
}
this.onCanvasBlurObserver = this.engine.onCanvasBlurObservable.add(() => {
this.codes = [];
});
this.onKeyboardObserver = this.scene.onKeyboardObservable.add(({ event, type }) => {
const { code, shiftKey } = event;
this.preShift = shiftKey;
if (type === BABYLON.KeyboardEventTypes.KEYDOWN) {
if (this.codesUp.indexOf(code) >= 0 ||
this.codesDown.indexOf(code) >= 0 ||
this.codesLeft.indexOf(code) >= 0 ||
this.codesRight.indexOf(code) >= 0) {
const index = this.codes.findIndex(v => v.code === code);
if (index < 0) { // 存在しなかったら追加する
this.codes.push({ code });
}
}
} else {
if (this.codesUp.indexOf(code) >= 0 ||
this.codesDown.indexOf(code) >= 0 ||
this.codesLeft.indexOf(code) >= 0 ||
this.codesRight.indexOf(code) >= 0) {
const index = this.codes.findIndex(v => v.code === code);
if (index >= 0) { // 存在したら削除する
this.codes.splice(index, 1);
}
}
}
});
}
detachControl() {
this.codes = [];
if (this.onKeyboardObserver) this.scene.onKeyboardObservable.remove(this.onKeyboardObserver);
if (this.onCanvasBlurObserver) this.engine.onCanvasBlurObservable.remove(this.onCanvasBlurObserver);
this.onKeyboardObserver = null;
this.onCanvasBlurObserver = null;
}
checkInputs() {
if (!this.onKeyboardObserver) {
return;
}
for (let index = 0; index < this.codes.length; index++) {
const { code } = this.codes[index];
const local = new BABYLON.Vector3();
if (this.codesLeft.indexOf(code) >= 0) {
local.x += -1;
} else if (this.codesUp.indexOf(code) >= 0) {
local.z += this.scene.useRightHandedSystem ? -1 : 1;
} else if (this.codesRight.indexOf(code) >= 0) {
local.x += 1;
} else if (this.codesDown.indexOf(code) >= 0) {
local.z += this.scene.useRightHandedSystem ? 1 : -1;
}
if (local.length() === 0) {
continue;
}
const dir = this.camera.getDirection(local.normalize());
dir.y = 0;
dir.normalize();
const rate = this.preShift ? 3 : 1;
const moveSpeed = 0.1 * this.scene.getAnimationRatio();
const move = dir.scale(moveSpeed * rate);
if (this.canMove) {
this.camera.cameraDirection.addInPlace(move);
}
}
}
getClassName() {
return 'HorizontalCameraKeyboardMoveInput';
}
getSimpleName() {
return 'horizontalkeyboard';
}
}
const nanasegNumberMap = [
['a', 'b', 'c', 'd', 'e', 'f'], // 0
['b', 'c'], // 1
['a', 'b', 'd', 'e', 'g'], // 2
['a', 'b', 'c', 'd', 'g'], // 3
['b', 'c', 'f', 'g'], // 4
['a', 'c', 'd', 'f', 'g'], // 5
['a', 'c', 'd', 'e', 'f', 'g'], // 6
['a', 'b', 'c'], // 7
['a', 'b', 'c', 'd', 'e', 'f', 'g'], // 8
['a', 'b', 'c', 'd', 'f', 'g'], // 9
];
export function get7segMeshesOfCurrentTime(meshes: {
'1a'?: BABYLON.AbstractMesh;
'1b'?: BABYLON.AbstractMesh;
'1c'?: BABYLON.AbstractMesh;
'1d'?: BABYLON.AbstractMesh;
'1e'?: BABYLON.AbstractMesh;
'1f'?: BABYLON.AbstractMesh;
'1g'?: BABYLON.AbstractMesh;
'2a'?: BABYLON.AbstractMesh;
'2b'?: BABYLON.AbstractMesh;
'2c'?: BABYLON.AbstractMesh;
'2d'?: BABYLON.AbstractMesh;
'2e'?: BABYLON.AbstractMesh;
'2f'?: BABYLON.AbstractMesh;
'2g'?: BABYLON.AbstractMesh;
'3a'?: BABYLON.AbstractMesh;
'3b'?: BABYLON.AbstractMesh;
'3c'?: BABYLON.AbstractMesh;
'3d'?: BABYLON.AbstractMesh;
'3e'?: BABYLON.AbstractMesh;
'3f'?: BABYLON.AbstractMesh;
'3g'?: BABYLON.AbstractMesh;
'4a'?: BABYLON.AbstractMesh;
'4b'?: BABYLON.AbstractMesh;
'4c'?: BABYLON.AbstractMesh;
'4d'?: BABYLON.AbstractMesh;
'4e'?: BABYLON.AbstractMesh;
'4f'?: BABYLON.AbstractMesh;
'4g'?: BABYLON.AbstractMesh;
}) {
const now = new Date();
const h = now.getHours();
const m = now.getMinutes();
const chars = [Math.floor(h / 10), h % 10, Math.floor(m / 10), m % 10];
const result: BABYLON.AbstractMesh[] = [];
for (let i = 0; i < chars.length; i++) {
const char = chars[i];
const segs = nanasegNumberMap[char];
for (const seg of segs) {
const mesh = meshes[`${i + 1}${seg}`];
if (mesh) {
result.push(mesh);
}
}
}
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;
}
const TV_PROGRAMS = {
shopping: {
textureColumns: 8,
textureRows: 8,
timeline: [
[0, 500],
[1, 500],
[0, 500],
[1, 500],
[0, 500],
[1, 500],
[2, 500],
[3, 500],
[2, 500],
[3, 500],
[4, 500],
[5, 500],
[4, 500],
[5, 500],
[6, 500],
[7, 500],
[8, 500],
[9, 500],
[8, 500],
[9, 500],
[2, 500],
[3, 500],
[2, 500],
[3, 500],
],
},
} satisfies Record<string, {
textureColumns: number;
textureRows: number;
timeline: [index: number, duration: number][];
}>;
export function initTv(room: RoomEngine, screenMesh: BABYLON.Mesh) {
const tvProgramId = 'shopping';
const tvProgram = TV_PROGRAMS[tvProgramId];
const tvScreenMaterial = screenMesh.material as BABYLON.PBRMaterial;
tvScreenMaterial.albedoColor = new BABYLON.Color3(0, 0, 0);
tvScreenMaterial.ambientColor = new BABYLON.Color3(0, 0, 0);
tvScreenMaterial.roughness = 1;
tvScreenMaterial.emissiveTexture = new BABYLON.Texture(`/client-assets/room/tv/${tvProgramId}/${tvProgramId}.png`, room.scene, false, false);
tvScreenMaterial.emissiveTexture.level = 1.0;
tvScreenMaterial.emissiveColor = new BABYLON.Color3(0.5, 0.5, 0.5);
const uvs = screenMesh.getVerticesData(BABYLON.VertexBuffer.UVKind)!;
const uvIndexes = getPlaneUvIndexes(screenMesh);
const applyTvTexture = (tlIndex: number) => {
const [index, duration] = tvProgram.timeline[tlIndex];
screenMesh.material = tvScreenMaterial;
const aspect = 16 / 9;
const x = index % tvProgram.textureColumns;
const y = Math.floor(index / tvProgram.textureColumns);
const ax = x / tvProgram.textureColumns;
const ay = y / tvProgram.textureRows / aspect;
const bx = (x + 1) / tvProgram.textureColumns;
const by = ay;
const cx = ax;
const cy = (y + 1) / tvProgram.textureRows / aspect;
const dx = bx;
const dy = cy;
uvs[uvIndexes[0]] = ax;
uvs[uvIndexes[0] + 1] = ay;
uvs[uvIndexes[1]] = bx;
uvs[uvIndexes[1] + 1] = by;
uvs[uvIndexes[2]] = cx;
uvs[uvIndexes[2] + 1] = cy;
uvs[uvIndexes[3]] = dx;
uvs[uvIndexes[3] + 1] = dy;
screenMesh.updateVerticesData(BABYLON.VertexBuffer.UVKind, uvs);
const timeoutId = window.setTimeout(() => {
room.timeoutIds = room.timeoutIds.filter(id => id !== timeoutId);
applyTvTexture((tlIndex + 1) % tvProgram.timeline.length);
}, duration);
room.timeoutIds.push(timeoutId);
};
applyTvTexture(0);
return {
dispose() {
},
};
}
/**
* 0 1
* 0 a(x,y) --- b(x,y)
* | |
* 1 c(x,y) --- d(x,y)
*/
export function getPlaneUvIndexes(mesh: BABYLON.Mesh) {
const uvs = mesh.getVerticesData(BABYLON.VertexBuffer.UVKind);
if (uvs == null) {
throw new Error('Mesh does not have UV data');
}
let aIndex = 0;
let bIndex = 0;
let cIndex = 0;
let dIndex = 0;
for (let i = 0; i < 8; i += 2) {
const x = uvs[i];
const y = uvs[i + 1];
// 多少ずれがあってもいいように(例えばblenderではUV展開時にデフォルトでわずかなマージンを追加する)、中心より大きいか/小さいかで判定する
if (x < 0.5 && y < 0.5) {
aIndex = i;
} else if (x > 0.5 && y < 0.5) {
bIndex = i;
} else if (x < 0.5 && y > 0.5) {
cIndex = i;
} else if (x > 0.5 && y > 0.5) {
dIndex = i;
}
}
return [aIndex, bIndex, cIndex, dIndex];
}
export function normalizeUvToSquare(mesh: BABYLON.Mesh) {
const uvs = mesh.getVerticesData(BABYLON.VertexBuffer.UVKind)!;
const uvIndexes = getPlaneUvIndexes(mesh);
uvs[uvIndexes[0]] = 0;
uvs[uvIndexes[0] + 1] = 0;
uvs[uvIndexes[1]] = 1;
uvs[uvIndexes[1] + 1] = 0;
uvs[uvIndexes[2]] = 0;
uvs[uvIndexes[2] + 1] = 1;
uvs[uvIndexes[3]] = 1;
uvs[uvIndexes[3] + 1] = 1;
mesh.updateVerticesData(BABYLON.VertexBuffer.UVKind, uvs);
}
export function createPlaneUvMapper(mesh: BABYLON.Mesh) {
mesh.markVerticesDataAsUpdatable(BABYLON.VertexBuffer.UVKind, true);
const uvs = mesh.getVerticesData(BABYLON.VertexBuffer.UVKind)!;
const originalUvs = uvs.slice();
return (srcAspect: number, targetAspect: number, method: 'cover' | 'contain' | 'stretch') => {
let uScale = 1;
let vScale = 1;
const ratio = targetAspect / srcAspect;
if (method === 'cover') {
uScale = ratio < 1 ? ratio : 1;
vScale = ratio < 1 ? 1 : 1 / ratio;
} else if (method === 'contain') {
uScale = ratio > 1 ? ratio : 1;
vScale = ratio > 1 ? 1 : 1 / ratio;
} else if (method === 'stretch') {
// nop
}
// (0,0)を隅ではなく中心として扱いたいので0.5引いて計算してから最後に0.5足す
for (let i = 0; i < uvs.length; i += 2) {
uvs[i] = ((originalUvs[i] - 0.5) * uScale) + 0.5;
uvs[i + 1] = ((originalUvs[i + 1] - 0.5) * vScale) + 0.5;
}
mesh.updateVerticesData(BABYLON.VertexBuffer.UVKind, uvs);
};
}
export function findMaterial(rootMesh: BABYLON.AbstractMesh, keyword: string, allowMultiMaterial = false): BABYLON.PBRMaterial {
for (const m of rootMesh.getChildMeshes()) {
if (m.material == null) continue;
if (m.material instanceof BABYLON.MultiMaterial) {
if (allowMultiMaterial && m.material.name.includes(keyword)) {
return m.material as BABYLON.MultiMaterial;
}
if ((m.material as BABYLON.MultiMaterial).subMaterials == null) continue;
for (const sm of (m.material as BABYLON.MultiMaterial).subMaterials) {
if (sm == null) continue;
if (sm.name.includes(keyword)) {
return sm as BABYLON.PBRMaterial;
}
}
} else {
if (m.material.name.includes(keyword)) {
return m.material as BABYLON.PBRMaterial;
}
}
}
throw new Error(`Material with keyword "${keyword}" not found`);
}
export function scaleMorph(mesh: BABYLON.Mesh, scale: [number, number, number], offset: [number, number, number] = [0, 0, 0]) {
if (!mesh.morphTargetManager) {
return;
}
const morphTargetManager = mesh.morphTargetManager;
for (let targetIndex = 0; targetIndex < morphTargetManager.numTargets; targetIndex++) {
const target = morphTargetManager.getTarget(targetIndex);
const newPos = target.getPositions();
for (let i = 0; i < newPos.length; i += 3) {
newPos[i] = (newPos[i] + offset[0]) * scale[0];
newPos[i + 1] = (newPos[i + 1] + offset[1]) * scale[1];
newPos[i + 2] = (newPos[i + 2] + offset[2]) * scale[2];
}
target.setPositions(newPos);
}
morphTargetManager.synchronize();
mesh.refreshBoundingInfo();
mesh.computeWorldMatrix(true);
}
export function applyMorphTargetsToMesh(mesh: BABYLON.Mesh) {
if (!mesh.morphTargetManager) {
return;
}
const morphTargetManager = mesh.morphTargetManager;
const positions = mesh.getVerticesData(BABYLON.VertexBuffer.PositionKind);
if (!positions) {
return;
}
// Create a copy of the original positions to work with
const finalPositions = positions.slice();
// Apply each morph target
for (let targetIndex = 0; targetIndex < morphTargetManager.numTargets; targetIndex++) {
const target = morphTargetManager.getTarget(targetIndex);
const influence = target.influence;
if (influence === 0) {
continue;
}
// Get the morph target positions
const targetPositions = target.getPositions();
if (!targetPositions || targetPositions.length !== positions.length) {
console.warn(`Morph target ${targetIndex} has invalid position data`);
continue;
}
// Apply the morph target influence to each vertex
for (let i = 0; i < positions.length; i++) {
finalPositions[i] += (targetPositions[i] - positions[i]) * influence;
}
}
// Update the mesh with the morphed positions
mesh.setVerticesData(BABYLON.VertexBuffer.PositionKind, finalPositions);
//// Update normals if available
//const normals = mesh.getVerticesData(BABYLON.VertexBuffer.NormalKind);
//if (normals) {
// // For simplicity, we'll just recompute the normals
// mesh.createNormals(true);
//}
// Refresh the mesh
mesh.refreshBoundingInfo();
mesh.computeWorldMatrix(true);
}
// ex) hangingTShirt -> hanging-t-shirt
export const camelToKebab = (s: string) => {
return s
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
.toLowerCase();
};

View File

@@ -1,54 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { RoomEngine } from './engine.js';
import type { RoomState } from './engine.js';
let engine: RoomEngine | null = null;
let canvas: HTMLCanvasElement | null = null;
onmessage = async (event) => {
//console.log('Worker received message:', event.data);
switch (event.data?.type) {
case 'init': {
const roomState = event.data.roomState as RoomState;
canvas = event.data.canvas as HTMLCanvasElement;
const babylonEngine = new BABYLON.WebGPUEngine(canvas);
babylonEngine.compatibilityMode = false;
await babylonEngine.initAsync();
engine = new RoomEngine(roomState, { canvas, engine: babylonEngine });
await engine.init();
break;
}
case 'resize': {
canvas.width = event.data.width;
canvas.height = event.data.height;
if (engine != null) engine.resize();
break;
}
case 'dom:keydown': {
if (engine == null) break;
engine.scene.onKeyboardObservable.notifyObservers({ type: BABYLON.KeyboardEventTypes.KEYDOWN, event: event.data.ev });
break;
}
case 'dom:keyup': {
if (engine == null) break;
engine.scene.onKeyboardObservable.notifyObservers({ type: BABYLON.KeyboardEventTypes.KEYUP, event: event.data.ev });
break;
}
case 'dom:pointerdown': {
if (engine == null) break;
event.data.ev.preventDefault = () => {};
event.data.ev.stopPropagation = () => {};
engine.scene.onPointerObservable.notifyObservers({ type: BABYLON.PointerEventTypes.POINTERDOWN, event: event.data.ev });
break;
}
default: {
console.warn('Unrecognized message type:', event.data?.type);
}
}
};