1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-07-25 10:54:56 +02:00
This commit is contained in:
syuilo
2026-06-08 21:35:29 +09:00
parent 1f604db203
commit 66cd678cd3
10 changed files with 359 additions and 288 deletions

View File

@@ -194,10 +194,10 @@ export class CustomMadoriEnvManager extends EnvManager<CustomMadoriEnvOptions> {
const unitDef = options.units[posToIndex(x, z)];
if (unitDef == null) return;
const unitNDef = options.units[posToIndex(x, z + 1)];
const unitSDef = options.units[posToIndex(x, z - 1)];
const unitWDef = options.units[posToIndex(x + 1, z)];
const unitEDef = options.units[posToIndex(x - 1, z)];
const unitZPositiveDef = options.units[posToIndex(x, z + 1)];
const unitZNegativeDef = options.units[posToIndex(x, z - 1)];
const unitXPositiveDef = options.units[posToIndex(x + 1, z)];
const unitXNegativeDef = options.units[posToIndex(x - 1, z)];
const shiftedX = x - (options.dimension[0] / 2) + 0.5;
@@ -215,24 +215,24 @@ export class CustomMadoriEnvManager extends EnvManager<CustomMadoriEnvOptions> {
ceilingMesh.material = unitDef.ceiling?.material != null && this.ceilingMaterials[unitDef.ceiling.material] != null ? this.ceilingMaterials[unitDef.ceiling.material] : defaultCeilingMaterial;
const defaultWallMaterial = this.wallMaterials[options.wallMaterials[0].id];
const createWall = (dir: 'n' | 's' | 'w' | 'e') => {
const createWall = (dir: 'zPositive' | 'zNegative' | 'xPositive' | 'xNegative') => {
const wallDef = unitDef.walls?.[dir] ?? {};
const wallRootNode = this.wallRootNode.clone(`unit_${x}_${z}_wall_${dir}`, unitRoot)!;
wallRootNode.scaling = new BABYLON.Vector3(-WORLD_SCALE, WORLD_SCALE, WORLD_SCALE);
switch (dir) {
case 'n':
case 'zPositive':
wallRootNode.rotation = new BABYLON.Vector3(0, Math.PI, 0);
wallRootNode.position = new BABYLON.Vector3(0, 0, cm(50));
break;
case 's':
case 'zNegative':
wallRootNode.position = new BABYLON.Vector3(0, 0, cm(-50));
break;
case 'w':
case 'xPositive':
wallRootNode.rotation = new BABYLON.Vector3(0, -Math.PI / 2, 0);
wallRootNode.position = new BABYLON.Vector3(cm(50), 0, 0);
break;
case 'e':
case 'xNegative':
wallRootNode.rotation = new BABYLON.Vector3(0, Math.PI / 2, 0);
wallRootNode.position = new BABYLON.Vector3(cm(-50), 0, 0);
break;
@@ -264,10 +264,10 @@ export class CustomMadoriEnvManager extends EnvManager<CustomMadoriEnvOptions> {
}
};
if (unitNDef == null) createWall('n');
if (unitSDef == null) createWall('s');
if (unitWDef == null) createWall('w');
if (unitEDef == null) createWall('e');
if (unitZPositiveDef == null) createWall('zPositive');
if (unitZNegativeDef == null) createWall('zNegative');
if (unitXPositiveDef == null) createWall('xPositive');
if (unitXNegativeDef == null) createWall('xNegative');
for (const mesh of unitRoot.getChildMeshes()) {
this.meshes.push(mesh);

View File

@@ -5,7 +5,7 @@
import * as BABYLON from '@babylonjs/core/pure.js';
import { cm, WORLD_SCALE } from 'misskey-world/src/utility.js';
import { findMaterial, GRAPHICS_QUALITY } from '../../utility.js';
import { findMaterial, GRAPHICS_QUALITY, treeClone } from '../../utility.js';
import { SYSTEM_HEYA_MESH_NAMES } from '../utility.js';
import { EnvManager } from '../env.js';
import type { RoomEngine } from '../engine.js';
@@ -15,14 +15,15 @@ import type { SimpleEnvOptions } from 'misskey-world/src/room/env.js';
export class SimpleEnvManager extends EnvManager<SimpleEnvOptions> {
private loaderResult: BABYLON.ISceneLoaderAsyncResult | null = null;
private meshes: BABYLON.Mesh[] = [];
private wallRoots: Record<'n' | 's' | 'w' | 'e', BABYLON.TransformNode> = null as any;
private wallMaterials: Record<'n' | 's' | 'w' | 'e', BABYLON.PBRMaterial> | null = null;
private wallBeamMaterials: Record<'n' | 's' | 'w' | 'e', BABYLON.PBRMaterial> | null = null;
private pillarRoots: Record<'nw' | 'ne' | 'sw' | 'se', BABYLON.TransformNode> | null = null;
private pillarMaterials: Record<'nw' | 'ne' | 'sw' | 'se', BABYLON.PBRMaterial> | null = null;
private ceilingMaterial: BABYLON.PBRMaterial | null = null;
private floorMaterial: BABYLON.PBRMaterial | null = null;
private rootNode: BABYLON.TransformNode;
private wallRoots: Record<'zPositive' | 'zNegative' | 'xPositive' | 'xNegative', BABYLON.TransformNode>;
private wallScalingContainers: Record<'zPositive' | 'zNegative' | 'xPositive' | 'xNegative', BABYLON.TransformNode>;
private wallMaterials: Record<'zPositive' | 'zNegative' | 'xPositive' | 'xNegative', BABYLON.PBRMaterial>;
private wallBeamMaterials: Record<'zPositive' | 'zNegative' | 'xPositive' | 'xNegative', BABYLON.PBRMaterial>;
private pillarRoots: Record<'zp_xp' | 'zp_xn' | 'zn_xp' | 'zn_xn', BABYLON.TransformNode>;
private pillarMaterials: Record<'zp_xp' | 'zp_xn' | 'zn_xp' | 'zn_xn', BABYLON.PBRMaterial>;
private ceilingMaterial: BABYLON.PBRMaterial;
private floorMaterial: BABYLON.PBRMaterial;
private skybox: BABYLON.Mesh | null = null;
private skyboxMat: BABYLON.StandardMaterial | null = null;
private roomLight: BABYLON.SpotLight | null = null;
@@ -32,9 +33,89 @@ export class SimpleEnvManager extends EnvManager<SimpleEnvOptions> {
constructor(engine: RoomEngine) {
super(engine);
}
public async load(options: SimpleEnvOptions) {
this.rootNode = new BABYLON.TransformNode('simpleEnvRoot', this.engine.scene);
this.wallRoots = {
zPositive: new BABYLON.TransformNode('wallRootZPositive', this.engine.scene),
zNegative: new BABYLON.TransformNode('wallRootZNegative', this.engine.scene),
xPositive: new BABYLON.TransformNode('wallRootXPositive', this.engine.scene),
xNegative: new BABYLON.TransformNode('wallRootXNegative', this.engine.scene),
};
this.wallRoots.zPositive.parent = this.rootNode;
this.wallRoots.zPositive.position.z = cm(150);
this.wallRoots.zPositive.rotation.y = Math.PI;
this.wallRoots.zNegative.parent = this.rootNode;
this.wallRoots.zNegative.position.z = -cm(150);
this.wallRoots.xPositive.parent = this.rootNode;
this.wallRoots.xPositive.position.x = cm(150);
this.wallRoots.xPositive.rotation.y = -Math.PI / 2;
this.wallRoots.xNegative.parent = this.rootNode;
this.wallRoots.xNegative.position.x = -cm(150);
this.wallRoots.xNegative.rotation.y = Math.PI / 2;
this.wallScalingContainers = {
zPositive: new BABYLON.TransformNode('wallScalingContainerZPositive', this.engine.scene),
zNegative: new BABYLON.TransformNode('wallScalingContainerZNegative', this.engine.scene),
xPositive: new BABYLON.TransformNode('wallScalingContainerXPositive', this.engine.scene),
xNegative: new BABYLON.TransformNode('wallScalingContainerXNegative', this.engine.scene),
};
for (const [k, v] of Object.entries(this.wallScalingContainers)) {
v.parent = this.wallRoots[k as keyof typeof this.wallRoots];
v.scaling = new BABYLON.Vector3(-WORLD_SCALE, WORLD_SCALE, WORLD_SCALE);
}
this.pillarRoots = {
zp_xp: new BABYLON.TransformNode('pillarRootZpXp', this.engine.scene),
zp_xn: new BABYLON.TransformNode('pillarRootZpXn', this.engine.scene),
zn_xp: new BABYLON.TransformNode('pillarRootZnXp', this.engine.scene),
zn_xn: new BABYLON.TransformNode('pillarRootZnXn', this.engine.scene),
};
for (const v of Object.values(this.pillarRoots)) {
v.parent = this.rootNode;
}
const wallMaterial = new BABYLON.PBRMaterial('wallMaterial', this.engine.scene);
wallMaterial.albedoColor = new BABYLON.Color3(0.8, 0.8, 0.8);
wallMaterial.roughness = 1;
wallMaterial.metallic = 0;
this.wallMaterials = {
zPositive: wallMaterial.clone('wallZPositiveMaterial'),
zNegative: wallMaterial.clone('wallZNegativeMaterial'),
xPositive: wallMaterial.clone('wallXPositiveMaterial'),
xNegative: wallMaterial.clone('wallXNegativeMaterial'),
};
const beamMaterial = wallMaterial.clone('beamMaterial');
this.wallBeamMaterials = {
zPositive: beamMaterial.clone('beamZPositiveMaterial'),
zNegative: beamMaterial.clone('beamZNegativeMaterial'),
xPositive: beamMaterial.clone('beamXPositiveMaterial'),
xNegative: beamMaterial.clone('beamXNegativeMaterial'),
};
const pillarMaterial = wallMaterial.clone('pillarMaterial');
this.pillarMaterials = {
zp_xp: pillarMaterial.clone('pillarMaterialZpXp'),
zp_xn: pillarMaterial.clone('pillarMaterialZpXn'),
zn_xp: pillarMaterial.clone('pillarMaterialZnXp'),
zn_xn: pillarMaterial.clone('pillarMaterialZnXn'),
};
this.ceilingMaterial = new BABYLON.PBRMaterial('ceilingMaterial', this.engine.scene);
this.ceilingMaterial.albedoColor = new BABYLON.Color3(0.8, 0.8, 0.8);
this.ceilingMaterial.roughness = 1;
this.ceilingMaterial.metallic = 0;
this.floorMaterial = new BABYLON.PBRMaterial('floorMaterial', this.engine.scene);
this.floorMaterial.albedoColor = new BABYLON.Color3(0.8, 0.8, 0.8);
this.floorMaterial.roughness = 1;
this.floorMaterial.metallic = 0;
const baseboardMaterial = new BABYLON.PBRMaterial('baseboardMaterial', this.engine.scene);
baseboardMaterial.albedoColor = new BABYLON.Color3(0.8, 0.8, 0.8);
baseboardMaterial.roughness = 1;
baseboardMaterial.metallic = 0;
this.skybox = BABYLON.MeshBuilder.CreateBox('skybox', { size: cm(1000) }, this.engine.scene);
this.skyboxMat = new BABYLON.StandardMaterial('skyboxMat', this.engine.scene);
this.skyboxMat.backFaceCulling = false;
@@ -79,111 +160,219 @@ export class SimpleEnvManager extends EnvManager<SimpleEnvOptions> {
this.shadowGenerators.push(shadowGeneratorForSunLight);
}
this.loaderResult = await BABYLON.ImportMeshAsync('/client-assets/room/envs/default/300.glb', this.engine.scene);
this.envMapIndoor = BABYLON.CubeTexture.CreateFromPrefilteredData('/client-assets/room/indoor.env', this.engine.scene);
this.envMapIndoor.boundingBoxSize = new BABYLON.Vector3(cm(500), cm(500), cm(500));
}
this.meshes = this.loaderResult.meshes.filter(m => m instanceof BABYLON.Mesh);
this.meshes[0].scaling = this.meshes[0].scaling.scale(WORLD_SCALE);
this.meshes[0].rotationQuaternion = null;
this.meshes[0].rotation = new BABYLON.Vector3(0, 0, 0);
public async load(options: SimpleEnvOptions) {
this.loaderResult = await BABYLON.LoadAssetContainerAsync('/client-assets/room/envs/simple/300.glb', this.engine.scene);
// instanced mesh を通常の mesh に変換 (そうしないとマテリアルが共有される)
for (const mesh of this.loaderResult.meshes) {
if (mesh instanceof BABYLON.InstancedMesh) {
const realizedMesh = mesh.sourceMesh.clone(mesh.name, null, true);
const collisionScalingContainer = new BABYLON.TransformNode('collisionScalingContainer', this.engine.scene);
collisionScalingContainer.scaling = new BABYLON.Vector3(-WORLD_SCALE, WORLD_SCALE, WORLD_SCALE);
collisionScalingContainer.parent = this.rootNode;
const collision = this.loaderResult.meshes.find(m => m.name.includes('__COLLISION__'))!;
collision.parent = collisionScalingContainer;
realizedMesh.position = mesh.position.clone();
if (mesh.rotationQuaternion) {
realizedMesh.rotationQuaternion = mesh.rotationQuaternion.clone();
} else {
realizedMesh.rotation = mesh.rotation.clone();
}
realizedMesh.scaling = mesh.scaling.clone();
realizedMesh.parent = mesh.parent;
const lightBlockerScalingContainer = new BABYLON.TransformNode('lightBlockerScalingContainer', this.engine.scene);
lightBlockerScalingContainer.scaling = new BABYLON.Vector3(-WORLD_SCALE, WORLD_SCALE, WORLD_SCALE);
lightBlockerScalingContainer.parent = this.rootNode;
const lightBlocker = this.loaderResult.meshes.find(m => m.name.includes('__LIGHT_BLOCKER__'))!;
lightBlocker.parent = lightBlockerScalingContainer;
lightBlocker.rotationQuaternion = null;
lightBlocker.rotation.y = Math.PI;
mesh.dispose();
this.engine.scene.removeMesh(mesh);
this.meshes.push(realizedMesh);
const originalFloorRoot = this.loaderResult.transformNodes.find(t => t.name.includes('__FLOOR__'))!;
originalFloorRoot.scaling = new BABYLON.Vector3(-WORLD_SCALE, WORLD_SCALE, WORLD_SCALE);
originalFloorRoot.parent = this.rootNode;
for (const child of originalFloorRoot.getChildMeshes()) {
if (child.material.name.includes('__FLOOR__')) {
child.material = this.floorMaterial;
}
}
this.wallRoots = {
n: this.loaderResult.transformNodes.find(t => t.name.includes('__WALL_N__'))!,
s: this.loaderResult.transformNodes.find(t => t.name.includes('__WALL_S__'))!,
w: this.loaderResult.transformNodes.find(t => t.name.includes('__WALL_W__'))!,
e: this.loaderResult.transformNodes.find(t => t.name.includes('__WALL_E__'))!,
};
this.pillarRoots = {
nw: this.loaderResult.transformNodes.find(t => t.name.includes('__PILLAR_NW__'))!,
ne: this.loaderResult.transformNodes.find(t => t.name.includes('__PILLAR_NE__'))!,
sw: this.loaderResult.transformNodes.find(t => t.name.includes('__PILLAR_SW__'))!,
se: this.loaderResult.transformNodes.find(t => t.name.includes('__PILLAR_SE__'))!,
};
const wallMaterial = findMaterial(this.meshes[0], '__WALL__');
//wallMaterial.metadata.disableEnvMap = true;
this.wallMaterials = {
n: wallMaterial.clone('wallNMaterial'),
s: wallMaterial.clone('wallSMaterial'),
w: wallMaterial.clone('wallWMaterial'),
e: wallMaterial.clone('wallEMaterial'),
};
const beamMaterial = findMaterial(this.meshes[0], '__BEAM__');
//beamMaterial.metadata.disableEnvMap = true;
this.wallBeamMaterials = {
n: beamMaterial.clone('wallNBeamMaterial'),
s: beamMaterial.clone('wallSBeamMaterial'),
w: beamMaterial.clone('wallWBeamMaterial'),
e: beamMaterial.clone('wallEBeamMaterial'),
};
const pillarMaterial = findMaterial(this.meshes[0], '__PILLAR__');
//pillarMaterial.metadata.disableEnvMap = true;
this.pillarMaterials = {
nw: pillarMaterial.clone('pillarNWMaterial'),
ne: pillarMaterial.clone('pillarNEMaterial'),
sw: pillarMaterial.clone('pillarSWMaterial'),
se: pillarMaterial.clone('pillarSEMaterial'),
};
for (const [k, v] of Object.entries(this.wallRoots)) {
for (const m of v.getChildMeshes().filter(m => m.material === wallMaterial)) {
m.material = this.wallMaterials[k];
}
for (const m of v.getChildMeshes().filter(m => m.material === beamMaterial)) {
m.material = this.wallBeamMaterials[k];
}
}
for (const [k, v] of Object.entries(this.pillarRoots)) {
for (const m of v.getChildMeshes().filter(m => m.material === pillarMaterial)) {
m.material = this.pillarMaterials[k];
}
}
this.ceilingMaterial = findMaterial(this.meshes[0], '__CEILING__');
//this.ceilingMaterial.metadata.disableEnvMap = true;
this.floorMaterial = findMaterial(this.meshes[0], '__FLOOR__');
//this.floorMaterial.metadata.disableEnvMap = true;
const baseboardMaterial = findMaterial(this.meshes[0], '__BASEBOARD__');
//baseboardMaterial.metadata.disableEnvMap = true;
for (const mesh of this.meshes) {
if (SYSTEM_HEYA_MESH_NAMES.some(name => mesh.name.includes(name))) continue;
mesh.receiveShadows = true;
if (mesh.material !== this.floorMaterial) { // 床は他の何にも影を落とさないことが確定している
this.addShadowCaster(mesh);
const originalCeilingRoot = this.loaderResult.transformNodes.find(t => t.name.includes('__CEILING__'))!;
originalCeilingRoot.scaling = new BABYLON.Vector3(-WORLD_SCALE, WORLD_SCALE, WORLD_SCALE);
originalCeilingRoot.parent = this.rootNode;
for (const child of originalCeilingRoot.getChildMeshes()) {
if (child.material.name.includes('__CEILING__')) {
child.material = this.ceilingMaterial;
}
}
await this.applyOptions(options);
}
public applyOptions(options: SimpleEnvOptions) {
// clean up
for (const type of ['zPositive', 'zNegative', 'xPositive', 'xNegative'] as const) {
const wallRoot = this.wallScalingContainers[type];
for (const mesh of wallRoot.getChildMeshes()) {
mesh.dispose();
this.engine.scene.removeMesh(mesh);
this.removeShadowCaster(mesh);
}
}
// TODO: 返り値をpromiseにしてちゃんとテクスチャが読み終わってからresolveする
for (const type of ['zPositive', 'zNegative', 'xPositive', 'xNegative'] as const) {
const wallRoot = this.wallScalingContainers[type];
const wallOptions = options.walls[type];
const originalRoot =
type === 'zPositive' ?
options.window === 'kosidakamado' ? this.loaderResult!.transformNodes.find(t => t.name.includes('__WALL_KOSIDAKAMADO__'))! :
options.window === 'demado' ? this.loaderResult!.transformNodes.find(t => t.name.includes('__WALL_DEMADO__'))! :
this.loaderResult!.transformNodes.find(t => t.name.includes('__WALL__'))!
: type === 'zNegative'
? this.loaderResult!.transformNodes.find(t => t.name.includes('__WALL_DOOR__'))!
: this.loaderResult!.transformNodes.find(t => t.name.includes('__WALL__'))!;
for (const child of treeClone(originalRoot).getChildren()) {
child.parent = wallRoot;
}
for (const child of wallRoot.getChildMeshes()) {
if (child.material.name.includes('__WALL__')) {
child.material = this.wallMaterials[type];
} else if (child.material.name.includes('__BEAM__')) {
child.material = this.wallBeamMaterials[type];
}
}
for (const mesh of wallRoot.getChildMeshes()) {
if (mesh.name.includes('__BEAM__')) {
mesh.setEnabled(wallOptions.withBeam);
} else if (mesh.name.includes('__BASEBOARD__')) {
mesh.setEnabled(wallOptions.withBaseboard);
}
}
{
const targetMaterial = this.wallMaterials[type];
targetMaterial.unfreeze();
targetMaterial.albedoColor = new BABYLON.Color3(...wallOptions.color);
const texPath = wallOptions.material === 'wood' ? '/client-assets/room/textures/wall-wood2.png'
: wallOptions.material === 'concrete' ? '/client-assets/room/textures/concrete1.png'
: null;
if (texPath != null) {
const tex = new BABYLON.Texture(texPath, this.engine.scene, false, false);
targetMaterial.albedoTexture = tex;
} else {
targetMaterial.albedoTexture = null;
}
targetMaterial.freeze();
}
{
const targetMaterial = this.wallBeamMaterials[type];
targetMaterial.unfreeze();
targetMaterial.albedoColor = new BABYLON.Color3(...wallOptions.beamColor);
const texPath = wallOptions.beamMaterial === 'wood' ? '/client-assets/room/textures/wall-wood2.png'
: wallOptions.beamMaterial === 'concrete' ? '/client-assets/room/textures/concrete1.png'
: null;
if (texPath != null) {
const tex = new BABYLON.Texture(texPath, this.engine.scene, false, false);
targetMaterial.albedoTexture = tex;
} else {
targetMaterial.albedoTexture = null;
}
targetMaterial.freeze();
}
}
for (const type of ['zp_xp', 'zp_xn', 'zn_xp', 'zn_xn'] as const) {
const pillarRoot = this.pillarRoots[type];
const pillarOptions = options.pillars[type];
let isEnabled = pillarOptions.show;
if (!isEnabled) {
// 梁同士が直交することは許さない(z-fightingが発生する)ので柱を強制追加
if (type === 'zp_xp') {
isEnabled = options.walls.zPositive.withBeam && options.walls.xPositive.withBeam;
} else if (type === 'zp_xn') {
isEnabled = options.walls.zPositive.withBeam && options.walls.xNegative.withBeam;
} else if (type === 'zn_xp') {
isEnabled = options.walls.zNegative.withBeam && options.walls.xPositive.withBeam;
} else if (type === 'zn_xn') {
isEnabled = options.walls.zNegative.withBeam && options.walls.xNegative.withBeam;
}
}
pillarRoot.setEnabled(isEnabled);
const targetMaterial = this.pillarMaterials[type];
targetMaterial.unfreeze();
targetMaterial.albedoColor = new BABYLON.Color3(...pillarOptions.color);
const texPath = pillarOptions.material === 'wood' ? '/client-assets/room/textures/wall-wood2.png'
: pillarOptions.material === 'concrete' ? '/client-assets/room/textures/concrete1.png'
: null;
if (texPath != null) {
const tex = new BABYLON.Texture(texPath, this.engine.scene, false, false);
targetMaterial.albedoTexture = tex;
} else {
targetMaterial.albedoTexture = null;
}
targetMaterial.freeze();
}
{
this.ceilingMaterial.unfreeze();
this.ceilingMaterial.albedoColor = new BABYLON.Color3(...options.ceiling.color);
const texPath = options.ceiling.material === 'wood' ? '/client-assets/room/textures/ceiling-wood.png'
: options.ceiling.material === 'concrete' ? '/client-assets/room/textures/concrete3.png'
: null;
if (texPath != null) {
const tex = new BABYLON.Texture(texPath, this.engine.scene, false, false);
this.ceilingMaterial.albedoTexture = tex;
} else {
this.ceilingMaterial.albedoTexture = null;
}
this.ceilingMaterial.freeze();
}
{
this.floorMaterial.unfreeze();
this.floorMaterial.albedoColor = new BABYLON.Color3(...options.flooring.color);
const texPath = options.flooring.material === 'wood' ? '/client-assets/room/textures/flooring-wood.png'
: options.flooring.material === 'concrete' ? '/client-assets/room/textures/concrete3.png'
: null;
if (texPath != null) {
const tex = new BABYLON.Texture(texPath, this.engine.scene, false, false);
this.floorMaterial.albedoTexture = tex;
} else {
this.floorMaterial.albedoTexture = null;
}
this.floorMaterial.freeze();
}
for (const mesh of this.rootNode.getChildMeshes()) {
if (SYSTEM_HEYA_MESH_NAMES.some(name => mesh.name.includes(name))) continue;
mesh.receiveShadows = true;
//if (mesh.material !== this.floorMaterial) { // 床は他の何にも影を落とさないことが確定している
this.addShadowCaster(mesh);
//}
}
this.registerMeshes(this.rootNode.getChildMeshes());
}
public setTime(time: number) {
if (this.skyboxMat == null) return;
@@ -228,141 +417,8 @@ export class SimpleEnvManager extends EnvManager<SimpleEnvOptions> {
}
}
public applyOptions(options: SimpleEnvOptions) {
// TODO: 返り値をpromiseにしてちゃんとテクスチャが読み終わってからresolveする
for (const type of ['n', 's', 'w', 'e'] as const) {
const wallRoot = this.wallRoots[type];
const wallOptions = options.walls[type];
for (const mesh of wallRoot.getChildMeshes()) {
if (mesh.name.includes('__BEAM__')) {
mesh.setEnabled(wallOptions.withBeam);
} else if (mesh.name.includes('__BASEBOARD__')) {
mesh.setEnabled(wallOptions.withBaseboard);
}
}
{
const targetMaterial = this.wallMaterials[type];
targetMaterial.unfreeze();
targetMaterial.albedoColor = new BABYLON.Color3(...wallOptions.color);
const texPath = wallOptions.material === 'wood' ? '/client-assets/room/textures/wall-wood2.png'
: wallOptions.material === 'concrete' ? '/client-assets/room/textures/concrete1.png'
: null;
if (texPath != null) {
const tex = new BABYLON.Texture(texPath, this.meshes[0].getScene(), false, false);
targetMaterial.albedoTexture = tex;
} else {
targetMaterial.albedoTexture = null;
}
targetMaterial.freeze();
}
{
const targetMaterial = this.wallBeamMaterials[type];
targetMaterial.unfreeze();
targetMaterial.albedoColor = new BABYLON.Color3(...wallOptions.beamColor);
const texPath = wallOptions.beamMaterial === 'wood' ? '/client-assets/room/textures/wall-wood2.png'
: wallOptions.beamMaterial === 'concrete' ? '/client-assets/room/textures/concrete1.png'
: null;
if (texPath != null) {
const tex = new BABYLON.Texture(texPath, this.meshes[0].getScene(), false, false);
targetMaterial.albedoTexture = tex;
} else {
targetMaterial.albedoTexture = null;
}
targetMaterial.freeze();
}
}
for (const type of ['nw', 'ne', 'sw', 'se'] as const) {
const pillarRoot = this.pillarRoots[type];
const pillarOptions = options.pillars[type];
let isEnabled = pillarOptions.show;
if (!isEnabled) {
// 梁同士が直交することは許さない(z-fightingが発生する)ので柱を強制追加
if (type === 'nw') {
isEnabled = options.walls.n.withBeam && options.walls.w.withBeam;
} else if (type === 'ne') {
isEnabled = options.walls.n.withBeam && options.walls.e.withBeam;
} else if (type === 'sw') {
isEnabled = options.walls.s.withBeam && options.walls.w.withBeam;
} else if (type === 'se') {
isEnabled = options.walls.s.withBeam && options.walls.e.withBeam;
}
}
pillarRoot.setEnabled(isEnabled);
const targetMaterial = this.pillarMaterials[type];
targetMaterial.unfreeze();
targetMaterial.albedoColor = new BABYLON.Color3(...pillarOptions.color);
const texPath = pillarOptions.material === 'wood' ? '/client-assets/room/textures/wall-wood2.png'
: pillarOptions.material === 'concrete' ? '/client-assets/room/textures/concrete1.png'
: null;
if (texPath != null) {
const tex = new BABYLON.Texture(texPath, this.meshes[0].getScene(), false, false);
targetMaterial.albedoTexture = tex;
} else {
targetMaterial.albedoTexture = null;
}
targetMaterial.freeze();
}
{
this.ceilingMaterial.unfreeze();
this.ceilingMaterial.albedoColor = new BABYLON.Color3(...options.ceiling.color);
const texPath = options.ceiling.material === 'wood' ? '/client-assets/room/textures/ceiling-wood.png'
: options.ceiling.material === 'concrete' ? '/client-assets/room/textures/concrete3.png'
: null;
if (texPath != null) {
const tex = new BABYLON.Texture(texPath, this.meshes[0].getScene(), false, false);
this.ceilingMaterial.albedoTexture = tex;
} else {
this.ceilingMaterial.albedoTexture = null;
}
this.ceilingMaterial.freeze();
}
{
this.floorMaterial.unfreeze();
this.floorMaterial.albedoColor = new BABYLON.Color3(...options.flooring.color);
const texPath = options.flooring.material === 'wood' ? '/client-assets/room/textures/flooring-wood.png'
: options.flooring.material === 'concrete' ? '/client-assets/room/textures/concrete3.png'
: null;
if (texPath != null) {
const tex = new BABYLON.Texture(texPath, this.meshes[0].getScene(), false, false);
this.floorMaterial.albedoTexture = tex;
} else {
this.floorMaterial.albedoTexture = null;
}
this.floorMaterial.freeze();
}
this.registerMeshes(this.meshes);
}
public dispose() {
for (const m of this.meshes) {
for (const m of this.rootNode.getChildMeshes()) {
m.dispose(false, true);
}
for (const m of Object.values(this.wallMaterials ?? {})) {

View File

@@ -769,3 +769,18 @@ export class ModelExplorer {
return node;
}
}
export function treeClone(source: BABYLON.TransformNode) {
const clonedRoot = source.clone(source.name, null, true)!;
for (const child of source.getChildren()) {
if (child instanceof BABYLON.TransformNode) {
const clonedChild = treeClone(child);
clonedChild.parent = clonedRoot;
} else {
child.clone(child.name, clonedRoot);
}
}
return clonedRoot;
}

Binary file not shown.

View File

@@ -11,23 +11,23 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps_s">
<MkFolder>
<template #label>Wall N</template>
<XWallOption :options="options.walls.n" @update="v => { update({ walls: { ...options.walls, n: v } }); }"></XWallOption>
<template #label>Wall S</template>
<XWallOption :options="options.walls.zPositive" @update="v => { update({ walls: { ...options.walls, zPositive: v } }); }"></XWallOption>
</MkFolder>
<MkFolder>
<template #label>Wall S</template>
<XWallOption :options="options.walls.s" @update="v => { update({ walls: { ...options.walls, s: v } }); }"></XWallOption>
<template #label>Wall N</template>
<XWallOption :options="options.walls.zNegative" @update="v => { update({ walls: { ...options.walls, zNegative: v } }); }"></XWallOption>
</MkFolder>
<MkFolder>
<template #label>Wall W</template>
<XWallOption :options="options.walls.w" @update="v => { update({ walls: { ...options.walls, w: v } }); }"></XWallOption>
<XWallOption :options="options.walls.xPositive" @update="v => { update({ walls: { ...options.walls, xPositive: v } }); }"></XWallOption>
</MkFolder>
<MkFolder>
<template #label>Wall E</template>
<XWallOption :options="options.walls.e" @update="v => { update({ walls: { ...options.walls, e: v } }); }"></XWallOption>
<XWallOption :options="options.walls.xNegative" @update="v => { update({ walls: { ...options.walls, xNegative: v } }); }"></XWallOption>
</MkFolder>
</div>
</MkFolder>
@@ -36,24 +36,24 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>Pillars</template>
<div class="_gaps_s">
<MkFolder>
<template #label>Pillar NW</template>
<XPillarOption :options="options.pillars.nw" @update="v => { update({ pillars: { ...options.pillars, nw: v } }); }"></XPillarOption>
</MkFolder>
<MkFolder>
<template #label>Pillar NE</template>
<XPillarOption :options="options.pillars.ne" @update="v => { update({ pillars: { ...options.pillars, ne: v } }); }"></XPillarOption>
</MkFolder>
<MkFolder>
<template #label>Pillar SW</template>
<XPillarOption :options="options.pillars.sw" @update="v => { update({ pillars: { ...options.pillars, sw: v } }); }"></XPillarOption>
<XPillarOption :options="options.pillars.zp_xp" @update="v => { update({ pillars: { ...options.pillars, zp_xp: v } }); }"></XPillarOption>
</MkFolder>
<MkFolder>
<template #label>Pillar SE</template>
<XPillarOption :options="options.pillars.se" @update="v => { update({ pillars: { ...options.pillars, se: v } }); }"></XPillarOption>
<XPillarOption :options="options.pillars.zp_xn" @update="v => { update({ pillars: { ...options.pillars, zp_xn: v } }); }"></XPillarOption>
</MkFolder>
<MkFolder>
<template #label>Pillar NW</template>
<XPillarOption :options="options.pillars.zn_xp" @update="v => { update({ pillars: { ...options.pillars, zn_xp: v } }); }"></XPillarOption>
</MkFolder>
<MkFolder>
<template #label>Pillar NE</template>
<XPillarOption :options="options.pillars.zn_xn" @update="v => { update({ pillars: { ...options.pillars, zn_xn: v } }); }"></XPillarOption>
</MkFolder>
</div>
</MkFolder>

View File

@@ -39,14 +39,14 @@ import MkSwitch from '@/components/MkSwitch.vue';
import MkRange from '@/components/MkRange.vue';
const props = defineProps<{
options: SimpleEnvOptions['pillars']['nw' | 'ne' | 'sw' | 'se'];
options: SimpleEnvOptions['pillars']['zp_xp' | 'zp_xn' | 'zn_xp' | 'zn_xn'];
}>();
const emit = defineEmits<{
(ev: 'update', v: SimpleEnvOptions['pillars']['nw' | 'ne' | 'sw' | 'se']): void;
(ev: 'update', v: SimpleEnvOptions['pillars']['zp_xp' | 'zp_xn' | 'zn_xp' | 'zn_xn']): void;
}>();
function update(v: Partial<SimpleEnvOptions['pillars']['nw' | 'ne' | 'sw' | 'se']>) {
function update(v: Partial<SimpleEnvOptions['pillars']['zp_xp' | 'zp_xn' | 'zn_xp' | 'zn_xn']>) {
emit('update', { ...props.options, ...v });
}
</script>

View File

@@ -57,14 +57,14 @@ import MkSwitch from '@/components/MkSwitch.vue';
import MkRange from '@/components/MkRange.vue';
const props = defineProps<{
options: SimpleEnvOptions['walls']['n' | 's' | 'w' | 'e'];
options: SimpleEnvOptions['walls']['zPositive' | 'zNegative' | 'xPositive' | 'xNegative'];
}>();
const emit = defineEmits<{
(ev: 'update', v: SimpleEnvOptions['walls']['n' | 's' | 'w' | 'e']): void;
(ev: 'update', v: SimpleEnvOptions['walls']['zPositive' | 'zNegative' | 'xPositive' | 'xNegative']): void;
}>();
function update(v: Partial<SimpleEnvOptions['walls']['n' | 's' | 'w' | 'e']>) {
function update(v: Partial<SimpleEnvOptions['walls']['zPositive' | 'zNegative' | 'xPositive' | 'xNegative']>) {
emit('update', { ...props.options, ...v });
}
</script>

View File

@@ -10,7 +10,7 @@ export type JapaneseEnvOptions = {
export type SimpleEnvOptions = {
dimension: [number, number];
window: 'none' | 'kosidakamado' | 'demado' | 'hakidasimado';
walls: Record<'n' | 's' | 'w' | 'e', {
walls: Record<'zPositive' | 'zNegative' | 'xPositive' | 'xNegative', {
material: null | 'wood' | 'concrete';
color: [number, number, number];
withBeam: boolean;
@@ -18,7 +18,7 @@ export type SimpleEnvOptions = {
beamColor: [number, number, number];
withBaseboard: boolean;
}>;
pillars: Record<'nw' | 'ne' | 'sw' | 'se', {
pillars: Record<'zp_xp' | 'zp_xn' | 'zn_xp' | 'zn_xn', {
material: null | 'wood' | 'concrete';
color: [number, number, number];
show: boolean;
@@ -62,8 +62,8 @@ export type CustomMadoriEnvOptions = {
}[];
units: ({
type: 'floor';
walls?: Record<'n' | 's' | 'w' | 'e', CustomMadoriEnvWall | undefined>;
pillars?: Record<'nw' | 'ne' | 'sw' | 'se', {
walls?: Record<'zPositive' | 'zNegative' | 'xPositive' | 'xNegative', CustomMadoriEnvWall | undefined>;
pillars?: Record<'zp_xp' | 'zp_xn' | 'zn_xp' | 'zn_xn', {
material?: string;
show?: boolean;
}>;
@@ -81,7 +81,7 @@ export function getDefaultSimpleEnvOptions(): SimpleEnvOptions {
dimension: [300, 300],
window: 'demado',
walls: {
n: {
zPositive: {
material: null,
color: [0.9, 0.9, 0.9],
withBeam: false,
@@ -89,7 +89,7 @@ export function getDefaultSimpleEnvOptions(): SimpleEnvOptions {
beamColor: [0.8, 0.8, 0.8],
withBaseboard: true,
},
e: {
zNegative: {
material: null,
color: [0.9, 0.9, 0.9],
withBeam: false,
@@ -97,7 +97,7 @@ export function getDefaultSimpleEnvOptions(): SimpleEnvOptions {
beamColor: [0.8, 0.8, 0.8],
withBaseboard: true,
},
s: {
xPositive: {
material: null,
color: [0.9, 0.9, 0.9],
withBeam: false,
@@ -105,7 +105,7 @@ export function getDefaultSimpleEnvOptions(): SimpleEnvOptions {
beamColor: [0.8, 0.8, 0.8],
withBaseboard: true,
},
w: {
xNegative: {
material: null,
color: [0.9, 0.9, 0.9],
withBeam: false,
@@ -115,22 +115,22 @@ export function getDefaultSimpleEnvOptions(): SimpleEnvOptions {
},
},
pillars: {
nw: {
zp_xp: {
material: null,
color: [0.9, 0.9, 0.9],
show: false,
},
ne: {
zp_xn: {
material: null,
color: [0.9, 0.9, 0.9],
show: false,
},
sw: {
zn_xp: {
material: null,
color: [0.9, 0.9, 0.9],
show: false,
},
se: {
zn_xn: {
material: null,
color: [0.9, 0.9, 0.9],
show: false,
@@ -161,16 +161,16 @@ export function getDefaultCustomMadoriEnvOptions(): CustomMadoriEnvOptions {
const units = Array.from({ length: 15 * 15 }, () => ({
type: 'floor',
walls: {
n: {
zPositive: {
material: '0',
},
e: {
zNegative: {
material: '0',
},
s: {
xPositive: {
material: '0',
},
w: {
xNegative: {
material: '0',
},
},