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:
43
packages/frontend/src/world/room/objects/a4Case.ts
Normal file
43
packages/frontend/src/world/room/objects/a4Case.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
23
packages/frontend/src/world/room/objects/aircon.ts
Normal file
23
packages/frontend/src/world/room/objects/aircon.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
142
packages/frontend/src/world/room/objects/allInOnePc.ts
Normal file
142
packages/frontend/src/world/room/objects/allInOnePc.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
58
packages/frontend/src/world/room/objects/aquarium.ts
Normal file
58
packages/frontend/src/world/room/objects/aquarium.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
24
packages/frontend/src/world/room/objects/banknote.ts
Normal file
24
packages/frontend/src/world/room/objects/banknote.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
33
packages/frontend/src/world/room/objects/beamLamp.ts
Normal file
33
packages/frontend/src/world/room/objects/beamLamp.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
44
packages/frontend/src/world/room/objects/bed.ts
Normal file
44
packages/frontend/src/world/room/objects/bed.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
126
packages/frontend/src/world/room/objects/blind.ts
Normal file
126
packages/frontend/src/world/room/objects/blind.ts
Normal 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',
|
||||
};
|
||||
},
|
||||
});
|
||||
77
packages/frontend/src/world/room/objects/book.ts
Normal file
77
packages/frontend/src/world/room/objects/book.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
72
packages/frontend/src/world/room/objects/books.ts
Normal file
72
packages/frontend/src/world/room/objects/books.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
44
packages/frontend/src/world/room/objects/cactusS.ts
Normal file
44
packages/frontend/src/world/room/objects/cactusS.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
44
packages/frontend/src/world/room/objects/cardboardBox.ts
Normal file
44
packages/frontend/src/world/room/objects/cardboardBox.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
41
packages/frontend/src/world/room/objects/ceilingFanLight.ts
Normal file
41
packages/frontend/src/world/room/objects/ceilingFanLight.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
57
packages/frontend/src/world/room/objects/chair.ts
Normal file
57
packages/frontend/src/world/room/objects/chair.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
24
packages/frontend/src/world/room/objects/coffeeCup.ts
Normal file
24
packages/frontend/src/world/room/objects/coffeeCup.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
44
packages/frontend/src/world/room/objects/colorBox.ts
Normal file
44
packages/frontend/src/world/room/objects/colorBox.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
87
packages/frontend/src/world/room/objects/cuboid.ts
Normal file
87
packages/frontend/src/world/room/objects/cuboid.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
34
packages/frontend/src/world/room/objects/cupNoodle.ts
Normal file
34
packages/frontend/src/world/room/objects/cupNoodle.ts
Normal 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?.();
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
23
packages/frontend/src/world/room/objects/custardPudding.ts
Normal file
23
packages/frontend/src/world/room/objects/custardPudding.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
21
packages/frontend/src/world/room/objects/debugHipoly.ts
Normal file
21
packages/frontend/src/world/room/objects/debugHipoly.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
90
packages/frontend/src/world/room/objects/desk.ts
Normal file
90
packages/frontend/src/world/room/objects/desk.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
130
packages/frontend/src/world/room/objects/desktopPc.ts
Normal file
130
packages/frontend/src/world/room/objects/desktopPc.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
24
packages/frontend/src/world/room/objects/djMixer.ts
Normal file
24
packages/frontend/src/world/room/objects/djMixer.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
104
packages/frontend/src/world/room/objects/djPlayer.ts
Normal file
104
packages/frontend/src/world/room/objects/djPlayer.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
23
packages/frontend/src/world/room/objects/ductTape.ts
Normal file
23
packages/frontend/src/world/room/objects/ductTape.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
22
packages/frontend/src/world/room/objects/emptyBento.ts
Normal file
22
packages/frontend/src/world/room/objects/emptyBento.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
23
packages/frontend/src/world/room/objects/energyDrink.ts
Normal file
23
packages/frontend/src/world/room/objects/energyDrink.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
23
packages/frontend/src/world/room/objects/envelope.ts
Normal file
23
packages/frontend/src/world/room/objects/envelope.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
24
packages/frontend/src/world/room/objects/facialTissue.ts
Normal file
24
packages/frontend/src/world/room/objects/facialTissue.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
24
packages/frontend/src/world/room/objects/hangingTShirt.ts
Normal file
24
packages/frontend/src/world/room/objects/hangingTShirt.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
22
packages/frontend/src/world/room/objects/icosahedron.ts
Normal file
22
packages/frontend/src/world/room/objects/icosahedron.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
94
packages/frontend/src/world/room/objects/ironFrameShelf.ts
Normal file
94
packages/frontend/src/world/room/objects/ironFrameShelf.ts
Normal 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',
|
||||
});
|
||||
104
packages/frontend/src/world/room/objects/ironFrameTable.ts
Normal file
104
packages/frontend/src/world/room/objects/ironFrameTable.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
23
packages/frontend/src/world/room/objects/keyboard.ts
Normal file
23
packages/frontend/src/world/room/objects/keyboard.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
167
packages/frontend/src/world/room/objects/laptopPc.ts
Normal file
167
packages/frontend/src/world/room/objects/laptopPc.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
75
packages/frontend/src/world/room/objects/lavaLamp.ts
Normal file
75
packages/frontend/src/world/room/objects/lavaLamp.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
|
||||
});
|
||||
22
packages/frontend/src/world/room/objects/letterCase.ts
Normal file
22
packages/frontend/src/world/room/objects/letterCase.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
23
packages/frontend/src/world/room/objects/mi-objet.ts
Normal file
23
packages/frontend/src/world/room/objects/mi-objet.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
23
packages/frontend/src/world/room/objects/miPlate.ts
Normal file
23
packages/frontend/src/world/room/objects/miPlate.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
23
packages/frontend/src/world/room/objects/miPlateDisplayed.ts
Normal file
23
packages/frontend/src/world/room/objects/miPlateDisplayed.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
23
packages/frontend/src/world/room/objects/milk.ts
Normal file
23
packages/frontend/src/world/room/objects/milk.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
23
packages/frontend/src/world/room/objects/mixer.ts
Normal file
23
packages/frontend/src/world/room/objects/mixer.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
22
packages/frontend/src/world/room/objects/monitor.ts
Normal file
22
packages/frontend/src/world/room/objects/monitor.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
43
packages/frontend/src/world/room/objects/monitorSpeaker.ts
Normal file
43
packages/frontend/src/world/room/objects/monitorSpeaker.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
23
packages/frontend/src/world/room/objects/monstera.ts
Normal file
23
packages/frontend/src/world/room/objects/monstera.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
32
packages/frontend/src/world/room/objects/mug.ts
Normal file
32
packages/frontend/src/world/room/objects/mug.ts
Normal 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?.();
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
44
packages/frontend/src/world/room/objects/newtonsCradle.ts
Normal file
44
packages/frontend/src/world/room/objects/newtonsCradle.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
24
packages/frontend/src/world/room/objects/pachira.ts
Normal file
24
packages/frontend/src/world/room/objects/pachira.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
42
packages/frontend/src/world/room/objects/pc.ts
Normal file
42
packages/frontend/src/world/room/objects/pc.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
54
packages/frontend/src/world/room/objects/petBottle.ts
Normal file
54
packages/frontend/src/world/room/objects/petBottle.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
43
packages/frontend/src/world/room/objects/piano.ts
Normal file
43
packages/frontend/src/world/room/objects/piano.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
263
packages/frontend/src/world/room/objects/pictureFrame.ts
Normal file
263
packages/frontend/src/world/room/objects/pictureFrame.ts
Normal 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);
|
||||
};
|
||||
|
||||
*/
|
||||
23
packages/frontend/src/world/room/objects/pizza.ts
Normal file
23
packages/frontend/src/world/room/objects/pizza.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
24
packages/frontend/src/world/room/objects/plant.ts
Normal file
24
packages/frontend/src/world/room/objects/plant.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
22
packages/frontend/src/world/room/objects/plant2.ts
Normal file
22
packages/frontend/src/world/room/objects/plant2.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
136
packages/frontend/src/world/room/objects/poster.ts
Normal file
136
packages/frontend/src/world/room/objects/poster.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
22
packages/frontend/src/world/room/objects/powerStrip.ts
Normal file
22
packages/frontend/src/world/room/objects/powerStrip.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
36
packages/frontend/src/world/room/objects/radiometer.ts
Normal file
36
packages/frontend/src/world/room/objects/radiometer.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
132
packages/frontend/src/world/room/objects/randomBooks.ts
Normal file
132
packages/frontend/src/world/room/objects/randomBooks.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
22
packages/frontend/src/world/room/objects/rolledUpPoster.ts
Normal file
22
packages/frontend/src/world/room/objects/rolledUpPoster.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
23
packages/frontend/src/world/room/objects/roundRug.ts
Normal file
23
packages/frontend/src/world/room/objects/roundRug.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
23
packages/frontend/src/world/room/objects/router.ts
Normal file
23
packages/frontend/src/world/room/objects/router.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
22
packages/frontend/src/world/room/objects/siphon.ts
Normal file
22
packages/frontend/src/world/room/objects/siphon.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
23
packages/frontend/src/world/room/objects/snakeplant.ts
Normal file
23
packages/frontend/src/world/room/objects/snakeplant.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
58
packages/frontend/src/world/room/objects/speaker.ts
Normal file
58
packages/frontend/src/world/room/objects/speaker.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
22
packages/frontend/src/world/room/objects/sprayer.ts
Normal file
22
packages/frontend/src/world/room/objects/sprayer.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
22
packages/frontend/src/world/room/objects/steelRack.ts
Normal file
22
packages/frontend/src/world/room/objects/steelRack.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
24
packages/frontend/src/world/room/objects/tabletopCalendar.ts
Normal file
24
packages/frontend/src/world/room/objects/tabletopCalendar.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
125
packages/frontend/src/world/room/objects/tabletopDigitalClock.ts
Normal file
125
packages/frontend/src/world/room/objects/tabletopDigitalClock.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
87
packages/frontend/src/world/room/objects/tabletopFlag.ts
Normal file
87
packages/frontend/src/world/room/objects/tabletopFlag.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
268
packages/frontend/src/world/room/objects/tabletopPictureFrame.ts
Normal file
268
packages/frontend/src/world/room/objects/tabletopPictureFrame.ts
Normal 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);
|
||||
};
|
||||
|
||||
*/
|
||||
140
packages/frontend/src/world/room/objects/tapestry.ts
Normal file
140
packages/frontend/src/world/room/objects/tapestry.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
22
packages/frontend/src/world/room/objects/tetrapod.ts
Normal file
22
packages/frontend/src/world/room/objects/tetrapod.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
99
packages/frontend/src/world/room/objects/tv.ts
Normal file
99
packages/frontend/src/world/room/objects/tv.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
22
packages/frontend/src/world/room/objects/twistedCubeObjet.ts
Normal file
22
packages/frontend/src/world/room/objects/twistedCubeObjet.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
22
packages/frontend/src/world/room/objects/usedTissue.ts
Normal file
22
packages/frontend/src/world/room/objects/usedTissue.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
127
packages/frontend/src/world/room/objects/wallCanvas.ts
Normal file
127
packages/frontend/src/world/room/objects/wallCanvas.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
58
packages/frontend/src/world/room/objects/wallClock.ts
Normal file
58
packages/frontend/src/world/room/objects/wallClock.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
90
packages/frontend/src/world/room/objects/wallMirror.ts
Normal file
90
packages/frontend/src/world/room/objects/wallMirror.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
86
packages/frontend/src/world/room/objects/wallShelf.ts
Normal file
86
packages/frontend/src/world/room/objects/wallShelf.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
108
packages/frontend/src/world/room/objects/woodRingFloorLamp.ts
Normal file
108
packages/frontend/src/world/room/objects/woodRingFloorLamp.ts
Normal 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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -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: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user