1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-05-22 19:54:03 +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

@@ -0,0 +1,43 @@
/*
* 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

@@ -0,0 +1,23 @@
/*
* 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

@@ -0,0 +1,142 @@
/*
* 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

@@ -0,0 +1,58 @@
/*
* 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

@@ -0,0 +1,57 @@
/*
* 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

@@ -0,0 +1,24 @@
/*
* 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

@@ -0,0 +1,33 @@
/*
* 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

@@ -0,0 +1,44 @@
/*
* 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

@@ -0,0 +1,126 @@
/*
* 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

@@ -0,0 +1,77 @@
/*
* 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

@@ -0,0 +1,72 @@
/*
* 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

@@ -0,0 +1,44 @@
/*
* 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

@@ -0,0 +1,44 @@
/*
* 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

@@ -0,0 +1,41 @@
/*
* 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

@@ -0,0 +1,57 @@
/*
* 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

@@ -0,0 +1,24 @@
/*
* 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

@@ -0,0 +1,44 @@
/*
* 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

@@ -0,0 +1,87 @@
/*
* 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

@@ -0,0 +1,34 @@
/*
* 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

@@ -0,0 +1,23 @@
/*
* 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

@@ -0,0 +1,21 @@
/*
* 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

@@ -0,0 +1,90 @@
/*
* 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

@@ -0,0 +1,130 @@
/*
* 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

@@ -0,0 +1,24 @@
/*
* 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

@@ -0,0 +1,104 @@
/*
* 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

@@ -0,0 +1,23 @@
/*
* 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

@@ -0,0 +1,22 @@
/*
* 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

@@ -0,0 +1,23 @@
/*
* 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

@@ -0,0 +1,23 @@
/*
* 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

@@ -0,0 +1,24 @@
/*
* 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

@@ -0,0 +1,24 @@
/*
* 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

@@ -0,0 +1,22 @@
/*
* 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

@@ -0,0 +1,94 @@
/*
* 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

@@ -0,0 +1,104 @@
/*
* 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

@@ -0,0 +1,23 @@
/*
* 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

@@ -0,0 +1,167 @@
/*
* 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

@@ -0,0 +1,75 @@
/*
* 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

@@ -0,0 +1,22 @@
/*
* 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

@@ -0,0 +1,23 @@
/*
* 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

@@ -0,0 +1,23 @@
/*
* 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

@@ -0,0 +1,23 @@
/*
* 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

@@ -0,0 +1,23 @@
/*
* 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

@@ -0,0 +1,23 @@
/*
* 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

@@ -0,0 +1,22 @@
/*
* 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

@@ -0,0 +1,43 @@
/*
* 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

@@ -0,0 +1,23 @@
/*
* 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

@@ -0,0 +1,32 @@
/*
* 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

@@ -0,0 +1,44 @@
/*
* 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

@@ -0,0 +1,21 @@
/*
* 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

@@ -0,0 +1,24 @@
/*
* 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

@@ -0,0 +1,42 @@
/*
* 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

@@ -0,0 +1,54 @@
/*
* 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

@@ -0,0 +1,43 @@
/*
* 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

@@ -0,0 +1,263 @@
/*
* 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

@@ -0,0 +1,23 @@
/*
* 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

@@ -0,0 +1,24 @@
/*
* 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

@@ -0,0 +1,22 @@
/*
* 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

@@ -0,0 +1,136 @@
/*
* 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

@@ -0,0 +1,22 @@
/*
* 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

@@ -0,0 +1,36 @@
/*
* 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

@@ -0,0 +1,132 @@
/*
* 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

@@ -0,0 +1,22 @@
/*
* 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

@@ -0,0 +1,23 @@
/*
* 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

@@ -0,0 +1,23 @@
/*
* 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

@@ -0,0 +1,22 @@
/*
* 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

@@ -0,0 +1,23 @@
/*
* 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

@@ -0,0 +1,58 @@
/*
* 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

@@ -0,0 +1,22 @@
/*
* 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

@@ -0,0 +1,22 @@
/*
* 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

@@ -0,0 +1,24 @@
/*
* 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

@@ -0,0 +1,125 @@
/*
* 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

@@ -0,0 +1,87 @@
/*
* 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

@@ -0,0 +1,139 @@
/*
* 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

@@ -0,0 +1,102 @@
/*
* 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

@@ -0,0 +1,268 @@
/*
* 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

@@ -0,0 +1,140 @@
/*
* 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

@@ -0,0 +1,22 @@
/*
* 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

@@ -0,0 +1,99 @@
/*
* 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

@@ -0,0 +1,22 @@
/*
* 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

@@ -0,0 +1,22 @@
/*
* 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

@@ -0,0 +1,127 @@
/*
* 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

@@ -0,0 +1,58 @@
/*
* 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

@@ -0,0 +1,139 @@
/*
* 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

@@ -0,0 +1,90 @@
/*
* 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

@@ -0,0 +1,86 @@
/*
* 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

@@ -0,0 +1,108 @@
/*
* 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

@@ -0,0 +1,122 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject, 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

@@ -0,0 +1,22 @@
/*
* 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: {},
};
},
});