mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-30 21:33:56 +02:00
wip
This commit is contained in:
@@ -14,59 +14,19 @@ import { registerBuiltInLoaders } from '@babylonjs/loaders/dynamic';
|
||||
import { BoundingBoxRenderer } from '@babylonjs/core/Rendering/boundingBoxRenderer';
|
||||
import { GridMaterial } from '@babylonjs/materials';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { TIME_MAP, scaleMorph, HorizontalCameraKeyboardMoveInput, camelToKebab, cm, WORLD_SCALE, getMeshesBoundingBox } from '../utility.js';
|
||||
import { getObjectDef } from './object-defs.js';
|
||||
import { HorizontalCameraKeyboardMoveInput, applyMorphTargetsToMesh, camelToKebab, cm, findMaterial, scaleMorph } from './utility.js';
|
||||
import { findMaterial, ModelManager, SYSTEM_MESH_NAMES } from './utility.js';
|
||||
import type { ObjectDef, RoomObjectInstance, RoomStateObject } from './object.js';
|
||||
import { genId } from '@/utility/id.js';
|
||||
import { deepClone } from '@/utility/clone.js';
|
||||
|
||||
const BAKE_TRANSFORM = false; // 実験的
|
||||
const SNAPSHOT_RENDERING = true; // 実験的
|
||||
const IGNORE_OBJECTS: string[] = []; // for debug
|
||||
const SYSTEM_MESH_NAMES = ['__TOP__', '__SIDE__', '__PICK__', '__COLLISION__'];
|
||||
const USE_GLOW = true; // ドローコールが増えて重い
|
||||
const IN_WEB_WORKER = typeof window === 'undefined';
|
||||
|
||||
const TIME_MAP = {
|
||||
0: 2,
|
||||
1: 2,
|
||||
2: 2,
|
||||
3: 2,
|
||||
4: 2,
|
||||
5: 1,
|
||||
6: 1,
|
||||
7: 0,
|
||||
8: 0,
|
||||
9: 0,
|
||||
10: 0,
|
||||
11: 0,
|
||||
12: 0,
|
||||
13: 0,
|
||||
14: 0,
|
||||
15: 0,
|
||||
16: 1,
|
||||
17: 1,
|
||||
18: 2,
|
||||
19: 2,
|
||||
20: 2,
|
||||
21: 2,
|
||||
22: 2,
|
||||
23: 2,
|
||||
} as const;
|
||||
|
||||
// babylonのドメイン知識は持たない
|
||||
export type RoomStateObject<Options = any> = {
|
||||
id: string;
|
||||
type: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
options: Options;
|
||||
|
||||
/**
|
||||
* 別のオブジェクトのID
|
||||
*/
|
||||
sticky?: string | null;
|
||||
};
|
||||
|
||||
type SimpleHeyaWallBase = {
|
||||
material: null | 'wood' | 'concrete';
|
||||
color: [number, number, number];
|
||||
@@ -99,221 +59,6 @@ export type RoomState = {
|
||||
installedObjects: RoomStateObject<any>[];
|
||||
};
|
||||
|
||||
type RoomObjectInstance<Options> = {
|
||||
onInited?: () => void;
|
||||
onOptionsUpdated?: <K extends keyof Options, V extends Options[K]>(kv: [K, V]) => void;
|
||||
interactions: Record<string, {
|
||||
label: string;
|
||||
fn: () => void;
|
||||
}>;
|
||||
primaryInteraction?: string | null;
|
||||
resetTemporaryState?: () => void;
|
||||
dispose?: () => void;
|
||||
};
|
||||
|
||||
export const WORLD_SCALE = 100;
|
||||
|
||||
type NumberOptionSchema = {
|
||||
type: 'number';
|
||||
label: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
};
|
||||
|
||||
type BooleanOptionSchema = {
|
||||
type: 'boolean';
|
||||
label: string;
|
||||
};
|
||||
|
||||
type ColorOptionSchema = {
|
||||
type: 'color';
|
||||
label: string;
|
||||
};
|
||||
|
||||
type EnumOptionSchema = {
|
||||
type: 'enum';
|
||||
label: string;
|
||||
enum: string[];
|
||||
};
|
||||
|
||||
type RangeOptionSchema = {
|
||||
type: 'range';
|
||||
label: string;
|
||||
min: number;
|
||||
max: number;
|
||||
step?: number;
|
||||
};
|
||||
|
||||
type ImageOptionSchema = {
|
||||
type: 'image';
|
||||
label: string;
|
||||
};
|
||||
|
||||
type OptionsSchema = Record<string, NumberOptionSchema | BooleanOptionSchema | ColorOptionSchema | EnumOptionSchema | RangeOptionSchema | ImageOptionSchema>;
|
||||
|
||||
type GetOptionsSchemaValues<T extends OptionsSchema> = {
|
||||
[K in keyof T]:
|
||||
T[K] extends NumberOptionSchema ? number :
|
||||
T[K] extends BooleanOptionSchema ? boolean :
|
||||
T[K] extends ColorOptionSchema ? [number, number, number] :
|
||||
T[K] extends EnumOptionSchema ? T[K]['enum'][number] :
|
||||
T[K] extends RangeOptionSchema ? number :
|
||||
T[K] extends ImageOptionSchema ? string | null :
|
||||
never;
|
||||
};
|
||||
|
||||
class ModelManager {
|
||||
public root: BABYLON.Mesh;
|
||||
public bakedCallback: (() => void) | null = null;
|
||||
public bakeExcludeMeshes: BABYLON.Mesh[] = [];
|
||||
private originalMeshes: BABYLON.Mesh[] = [];
|
||||
private bakedMeshes: BABYLON.Mesh[] = [];
|
||||
private hasTexture: boolean;
|
||||
|
||||
constructor(root: BABYLON.Mesh, originalMeshes: BABYLON.Mesh[], hasTexture: boolean, bakedCallback: (() => void) | null = null) {
|
||||
this.root = root;
|
||||
this.originalMeshes = originalMeshes;
|
||||
this.hasTexture = hasTexture;
|
||||
this.bakedCallback = bakedCallback;
|
||||
}
|
||||
|
||||
public findMesh(keyword: string) {
|
||||
const mesh = this.root.getChildMeshes().find(m => m.name.includes(keyword));
|
||||
if (mesh == null) {
|
||||
throw new Error(`Mesh with keyword "${keyword}" not found for object ${this.root.metadata?.objectType}`);
|
||||
}
|
||||
return mesh as BABYLON.Mesh;
|
||||
}
|
||||
|
||||
public findMeshes(keyword: string) {
|
||||
const meshes = this.root.getChildMeshes().filter(m => m.name.includes(keyword));
|
||||
return meshes as BABYLON.Mesh[];
|
||||
}
|
||||
|
||||
public findMaterial(keyword: string) {
|
||||
return findMaterial(this.root, keyword);
|
||||
}
|
||||
|
||||
public findTransformNode(keyword: string) {
|
||||
const node = this.root.getChildTransformNodes().find(n => n.name.includes(keyword));
|
||||
if (node == null) {
|
||||
throw new Error(`TransformNode with keyword "${keyword}" not found for object ${this.root.metadata?.objectType}`);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
public updated() {
|
||||
}
|
||||
|
||||
public bakeMesh() {
|
||||
for (const m of this.bakedMeshes) {
|
||||
m.dispose();
|
||||
}
|
||||
this.bakedMeshes = [];
|
||||
|
||||
const excludeMeshes = [...this.bakeExcludeMeshes, ...this.root.getChildMeshes().filter(m => SYSTEM_MESH_NAMES.some(s => m.name.includes(s)))];
|
||||
|
||||
const childMeshes = this.root.getChildMeshes().filter(m => !excludeMeshes.some(x => x === m) && m.isVisible && !m.isDisposed());
|
||||
|
||||
if (childMeshes.length <= 1) {
|
||||
this.bakedCallback?.([...childMeshes, ...excludeMeshes]);
|
||||
return;
|
||||
}
|
||||
|
||||
const _toMerge = [] as BABYLON.Mesh[];
|
||||
for (const mesh of childMeshes) {
|
||||
let fixedMesh = mesh;
|
||||
fixedMesh.setEnabled(false);
|
||||
|
||||
if (mesh instanceof BABYLON.InstancedMesh) {
|
||||
const sourceMesh = mesh.sourceMesh;
|
||||
const realizedMesh = sourceMesh.clone(mesh.name + '_realized', null, true);
|
||||
realizedMesh.getScene().removeMesh(realizedMesh);
|
||||
|
||||
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;
|
||||
realizedMesh.setEnabled(false);
|
||||
|
||||
fixedMesh = realizedMesh;
|
||||
}
|
||||
|
||||
_toMerge.push(fixedMesh);
|
||||
}
|
||||
|
||||
const toMerge = [] as BABYLON.Mesh[];
|
||||
for (const mesh of _toMerge) {
|
||||
const newMesh = mesh.name.endsWith('_realized') ? mesh : mesh.clone(mesh.name + '_bakeMerged', null, true);
|
||||
newMesh.makeGeometryUnique();
|
||||
applyMorphTargetsToMesh(newMesh);
|
||||
if (newMesh.parent === this.root) {
|
||||
newMesh.parent = null;
|
||||
} else {
|
||||
newMesh.setParent(this.root);
|
||||
//newMesh.bakeCurrentTransformIntoVertices();
|
||||
newMesh.parent = null;
|
||||
}
|
||||
//newMesh.bakeCurrentTransformIntoVertices();
|
||||
|
||||
if (this.hasTexture) {
|
||||
if (newMesh.getVerticesData(BABYLON.VertexBuffer.UVKind) == null) {
|
||||
const vertexCount = newMesh.getTotalVertices();
|
||||
const uvs = new Array(vertexCount * 2).fill(0);
|
||||
newMesh.setVerticesData(BABYLON.VertexBuffer.UVKind, uvs, false, 2);
|
||||
}
|
||||
if (newMesh.getVerticesData(BABYLON.VertexBuffer.UV2Kind) == null) {
|
||||
const vertexCount = newMesh.getTotalVertices();
|
||||
const uvs = new Array(vertexCount * 2).fill(0);
|
||||
newMesh.setVerticesData(BABYLON.VertexBuffer.UV2Kind, uvs, false, 2);
|
||||
}
|
||||
}
|
||||
|
||||
toMerge.push(newMesh);
|
||||
}
|
||||
|
||||
if (toMerge.length === 0) {
|
||||
this.bakedCallback?.([...childMeshes, ...excludeMeshes]);
|
||||
return;
|
||||
}
|
||||
|
||||
const merged = BABYLON.Mesh.MergeMeshes(toMerge, true, false, undefined, false, true);
|
||||
merged.parent = this.root;
|
||||
merged.material.freeze();
|
||||
if (merged.material instanceof BABYLON.MultiMaterial) {
|
||||
for (const subMat of merged.material.subMaterials) {
|
||||
(subMat as BABYLON.PBRMaterial).freeze();
|
||||
}
|
||||
}
|
||||
merged.freezeWorldMatrix();
|
||||
merged.metadata = { ...this.root.metadata };
|
||||
if (!this.hasTexture) merged.convertToUnIndexedMesh();
|
||||
this.bakedMeshes = [merged];
|
||||
|
||||
this.bakedCallback?.([...this.bakedMeshes, ...excludeMeshes]);
|
||||
}
|
||||
|
||||
public unbakeMesh() {
|
||||
for (const m of this.bakedMeshes) {
|
||||
m.dispose();
|
||||
}
|
||||
this.bakedMeshes = [];
|
||||
|
||||
const childMeshes = this.root.getChildMeshes();
|
||||
|
||||
for (const mesh of childMeshes) {
|
||||
mesh.setEnabled(true);
|
||||
}
|
||||
|
||||
this.bakedCallback?.(this.root.getChildMeshes());
|
||||
}
|
||||
}
|
||||
|
||||
function mergeMeshes(meshes: BABYLON.Mesh[], root: BABYLON.Mesh, hasTexture: boolean) {
|
||||
const excludeMeshes = root.getChildMeshes().filter(m => SYSTEM_MESH_NAMES.some(s => m.name.includes(s)));
|
||||
|
||||
@@ -352,58 +97,6 @@ function mergeMeshes(meshes: BABYLON.Mesh[], root: BABYLON.Mesh, hasTexture: boo
|
||||
return merged;
|
||||
}
|
||||
|
||||
export type ObjectDef<OpSc extends OptionsSchema = OptionsSchema> = {
|
||||
id: string;
|
||||
name: string;
|
||||
path?: string;
|
||||
options: {
|
||||
schema: OpSc;
|
||||
default: GetOptionsSchemaValues<OpSc>;
|
||||
};
|
||||
placement: 'top' | 'side' | 'bottom' | 'wall' | 'ceiling' | 'floor';
|
||||
hasCollisions?: boolean;
|
||||
hasTexture?: boolean;
|
||||
canPreMeshesMerging?: boolean;
|
||||
//groupingMeshes: string[]; // multi-materialなメッシュは複数のメッシュに分割されるが、それだと不便な場合に追加の親メッシュでグルーピングするための指定
|
||||
isChair?: boolean;
|
||||
treatLoaderResult?: (loaderResult: BABYLON.AssetContainer) => void;
|
||||
createInstance: (args: {
|
||||
room?: RoomEngine | null;
|
||||
scene: BABYLON.Scene;
|
||||
root: BABYLON.Mesh;
|
||||
options: Readonly<GetOptionsSchemaValues<OpSc>>;
|
||||
model: ModelManager;
|
||||
id: string;
|
||||
stickyMarkerMeshUpdated?: (mesh: BABYLON.Mesh) => void;
|
||||
}) => RoomObjectInstance<GetOptionsSchemaValues<OpSc>> | Promise<RoomObjectInstance<GetOptionsSchemaValues<OpSc>>>; // TODO: createInstanceをasyncにするのではなく、別にreadyみたいなものを返させる
|
||||
};
|
||||
|
||||
export function defineObject<const OpSc extends OptionsSchema>(def: ObjectDef<OpSc>): ObjectDef<OpSc> {
|
||||
return def;
|
||||
}
|
||||
|
||||
export function defineObjectClass<const OpSc extends OptionsSchema>(baseDef: Partial<ObjectDef<OpSc>>): {
|
||||
extend: (childDef: Partial<ObjectDef<OpSc>>) => ObjectDef<OpSc>;
|
||||
} {
|
||||
return {
|
||||
extend: (childDef) => ({ ...baseDef, ...childDef }) as ObjectDef<OpSc>,
|
||||
};
|
||||
}
|
||||
|
||||
// この実装方法だとマイナスの座標をうまく処理できず結果がおかしくなるので応急処置で全体を+10000cmオフセットしてから計算している
|
||||
function getMeshesBoundingBox(meshes: BABYLON.Mesh[]): BABYLON.BoundingBox {
|
||||
let min = new BABYLON.Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
|
||||
let max = new BABYLON.Vector3(Number.MIN_VALUE, Number.MIN_VALUE, Number.MIN_VALUE);
|
||||
|
||||
for (const mesh of meshes) {
|
||||
const boundingInfo = mesh.getBoundingInfo();
|
||||
min = BABYLON.Vector3.Minimize(min, boundingInfo.boundingBox.minimumWorld.add(new BABYLON.Vector3(10000, 10000, 10000)));
|
||||
max = BABYLON.Vector3.Maximize(max, boundingInfo.boundingBox.maximumWorld.add(new BABYLON.Vector3(10000, 10000, 10000)));
|
||||
}
|
||||
|
||||
return new BABYLON.BoundingBox(min.subtract(new BABYLON.Vector3(10000, 10000, 10000)), max.subtract(new BABYLON.Vector3(10000, 10000, 10000)));
|
||||
}
|
||||
|
||||
function enableObjectCollision(meshes: BABYLON.Mesh[]) {
|
||||
for (const mesh of meshes) {
|
||||
if (mesh.name.includes('__COLLISION__')) {
|
||||
@@ -444,8 +137,6 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
|
||||
private shadowGeneratorForRoomLight: BABYLON.ShadowGenerator;
|
||||
private shadowGeneratorForSunLight: BABYLON.ShadowGenerator;
|
||||
public camera: BABYLON.UniversalCamera;
|
||||
private fixedCamera: BABYLON.UniversalCamera;
|
||||
private birdeyeCamera: BABYLON.ArcRotateCamera;
|
||||
public intervalIds: number[] = [];
|
||||
public timeoutIds: number[] = [];
|
||||
public objectEntities: Map<string, {
|
||||
@@ -655,45 +346,6 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
//const postProcess = new BABYLON.ImageProcessingPostProcess('processing', 1.0, this.camera);
|
||||
//postProcess.exposure = 1.1;
|
||||
//postProcess.contrast = 0.9;
|
||||
|
||||
//const curve = new BABYLON.ColorCurves();
|
||||
//curve.highlightsHue = 40;
|
||||
//curve.highlightsDensity = 50;
|
||||
//curve.highlightsSaturation = 40;
|
||||
//curve.shadowsHue = 200;
|
||||
//curve.shadowsDensity = 100;
|
||||
//curve.shadowsSaturation = 40;
|
||||
//postProcess.colorCurvesEnabled = true;
|
||||
//postProcess.colorCurves = curve;
|
||||
|
||||
//const postProcess2 = new BABYLON.ImageProcessingPostProcess('processing2', 1.0, this.birdeyeCamera);
|
||||
//postProcess2.exposure = 2;
|
||||
//postProcess2.contrast = 0.9;
|
||||
|
||||
//const ssao = new BABYLON.SSAORenderingPipeline('ssao', this.scene, {
|
||||
// ssaoRatio: 4,
|
||||
// combineRatio: 1,
|
||||
//});
|
||||
//ssao.radius = 0.0001;
|
||||
//ssao.totalStrength = 0.8;
|
||||
//this.scene.postProcessRenderPipelineManager.attachCamerasToRenderPipeline('ssao', this.camera);
|
||||
|
||||
//const lensEffect = new BABYLON.LensRenderingPipeline('lens', {
|
||||
// edge_blur: 1.0,
|
||||
// distortion: 0.5,
|
||||
// dof_focus_distance: cm(90),
|
||||
// dof_aperture: 6.0,
|
||||
// dof_pentagon: true,
|
||||
// dof_gain: 2.0,
|
||||
// dof_threshold: 1.0,
|
||||
// dof_darken: 0,
|
||||
//}, this.scene, 1, [this.camera]);
|
||||
}
|
||||
|
||||
this.putParticleSystem = new BABYLON.ParticleSystem('', 64, this.scene);
|
||||
this.putParticleSystem.particleTexture = new BABYLON.Texture('/client-assets/room/steam.png');
|
||||
this.putParticleSystem.createCylinderEmitter(cm(5), cm(1), cm(5));
|
||||
@@ -1952,14 +1604,6 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
|
||||
}
|
||||
}
|
||||
|
||||
public showBoundingBox() {
|
||||
for (const mesh of this.objectMeshs.values()) {
|
||||
for (const m of mesh.getChildMeshes()) {
|
||||
m.showBoundingBox = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private playSfxUrl(url: string, options: { volume: number; playbackRate: number }) {
|
||||
this.emit('playSfxUrl', { url, options });
|
||||
}
|
||||
@@ -1981,239 +1625,3 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
|
||||
this.disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createRoomObjectPreviewEngine(canvas: HTMLCanvasElement) {
|
||||
//const babylonEngine = new BABYLON.WebGPUEngine(canvas);
|
||||
//babylonEngine.compatibilityMode = false;
|
||||
//await babylonEngine.initAsync();
|
||||
const babylonEngine = new BABYLON.Engine(canvas, false, { alpha: false, antialias: false });
|
||||
return new RoomObjectPreviewEngine({ canvas, engine: babylonEngine });
|
||||
}
|
||||
|
||||
export class RoomObjectPreviewEngine {
|
||||
private canvas: HTMLCanvasElement;
|
||||
private engine: BABYLON.WebGPUEngine;
|
||||
private scene: BABYLON.Scene;
|
||||
private shadowGenerator1: BABYLON.ShadowGenerator;
|
||||
private camera: BABYLON.ArcRotateCamera;
|
||||
private objectMesh: BABYLON.Mesh | null = null;
|
||||
private objectInstance: RoomObjectInstance<any> | null = null;
|
||||
private envMapIndoor: BABYLON.CubeTexture;
|
||||
private roomLight: BABYLON.SpotLight;
|
||||
private zGridPreviewPlane: BABYLON.Mesh;
|
||||
private fps = 60;
|
||||
|
||||
constructor(options: {
|
||||
canvas: HTMLCanvasElement;
|
||||
engine: BABYLON.WebGPUEngine;
|
||||
}) {
|
||||
this.canvas = options.canvas;
|
||||
|
||||
registerBuiltInLoaders();
|
||||
|
||||
this.engine = options.engine;
|
||||
this.scene = new BABYLON.Scene(this.engine);
|
||||
|
||||
this.scene.ambientColor = new BABYLON.Color3(1.0, 0.9, 0.8);
|
||||
|
||||
this.envMapIndoor = BABYLON.CubeTexture.CreateFromPrefilteredData('/client-assets/room/indoor.env', this.scene);
|
||||
this.envMapIndoor.boundingBoxSize = new BABYLON.Vector3(cm(500), cm(500), cm(500));
|
||||
|
||||
this.camera = new BABYLON.ArcRotateCamera('camera', -Math.PI / 2, Math.PI / 2.5, cm(300), new BABYLON.Vector3(0, cm(90), 0), this.scene);
|
||||
this.camera.attachControl(this.canvas);
|
||||
this.camera.minZ = cm(1);
|
||||
this.camera.maxZ = cm(100000);
|
||||
this.camera.fov = 0.5;
|
||||
this.camera.lowerBetaLimit = 0;
|
||||
this.camera.upperBetaLimit = (Math.PI / 2) + 0.1;
|
||||
this.camera.lowerRadiusLimit = cm(50);
|
||||
this.camera.upperRadiusLimit = cm(1000);
|
||||
this.camera.useAutoRotationBehavior = true;
|
||||
this.camera.autoRotationBehavior!.idleRotationSpeed = 0.3;
|
||||
//this.camera.mode = BABYLON.Camera.ORTHOGRAPHIC_CAMERA;
|
||||
this.scene.activeCamera = this.camera;
|
||||
|
||||
const ambientLight = new BABYLON.HemisphericLight('ambientLight', new BABYLON.Vector3(0, 1, -0.5), this.scene);
|
||||
ambientLight.diffuse = new BABYLON.Color3(1.0, 1.0, 1.0);
|
||||
ambientLight.intensity = 0.5;
|
||||
//ambientLight.intensity = 0;
|
||||
|
||||
this.roomLight = new BABYLON.SpotLight('roomLight', new BABYLON.Vector3(0, cm(249), 0), new BABYLON.Vector3(0, -1, 0), 16, 8, this.scene);
|
||||
this.roomLight.diffuse = new BABYLON.Color3(1.0, 0.9, 0.8);
|
||||
this.roomLight.shadowMinZ = cm(10);
|
||||
this.roomLight.shadowMaxZ = cm(300);
|
||||
|
||||
this.shadowGenerator1 = new BABYLON.ShadowGenerator(4096, this.roomLight);
|
||||
this.shadowGenerator1.forceBackFacesOnly = true;
|
||||
this.shadowGenerator1.bias = 0.0001;
|
||||
this.shadowGenerator1.usePercentageCloserFiltering = true;
|
||||
this.shadowGenerator1.filteringQuality = BABYLON.ShadowGenerator.QUALITY_HIGH;
|
||||
//this.shadowGenerator1.useContactHardeningShadow = true;
|
||||
|
||||
const gridMaterial = new GridMaterial('grid', this.scene);
|
||||
gridMaterial.lineColor = new BABYLON.Color3(0.5, 0.5, 0.5);
|
||||
gridMaterial.mainColor = new BABYLON.Color3(0, 0, 0);
|
||||
gridMaterial.minorUnitVisibility = 1;
|
||||
gridMaterial.opacity = 0.5;
|
||||
gridMaterial.gridRatio = cm(10);
|
||||
|
||||
//this.zGridPreviewPlane = BABYLON.MeshBuilder.CreatePlane('zGridPreviewPlane', { width: cm(1000), height: cm(1000) }, this.scene);
|
||||
//this.zGridPreviewPlane.material = gridMaterial;
|
||||
//this.zGridPreviewPlane.rotation = new BABYLON.Vector3(Math.PI / 2, 0, 0);
|
||||
|
||||
//this.scene.fogMode = BABYLON.Scene.FOGMODE_LINEAR;
|
||||
//this.scene.fogStart = cm(100);
|
||||
//this.scene.fogEnd = cm(110);
|
||||
//this.scene.fogColor = new BABYLON.Color3(0.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
public async init() {
|
||||
const frameInterval = 1000 / this.fps;
|
||||
let lastTime = performance.now();
|
||||
|
||||
this.engine.runRenderLoop(() => {
|
||||
const currentTime = performance.now();
|
||||
const delta = currentTime - lastTime;
|
||||
|
||||
if (delta >= frameInterval) {
|
||||
this.scene.render();
|
||||
lastTime = currentTime - (delta % frameInterval);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async load(type: string) {
|
||||
if (this.objectInstance != null) {
|
||||
this.objectInstance.dispose?.();
|
||||
this.objectInstance = null;
|
||||
this.objectMesh!.dispose();
|
||||
}
|
||||
|
||||
// reset camera rotation
|
||||
this.camera.setPosition(new BABYLON.Vector3(0, cm(90), cm(300)));
|
||||
|
||||
const def = getObjectDef(type);
|
||||
|
||||
const options = deepClone(def.options.default);
|
||||
|
||||
const id = genId();
|
||||
|
||||
await this.loadObject({
|
||||
type,
|
||||
options,
|
||||
id,
|
||||
});
|
||||
|
||||
// なぜかちょっと待たないとbounding boxのサイズが正しくない
|
||||
window.setTimeout(() => {
|
||||
const boundingInfo = getMeshesBoundingBox(this.objectMesh!.getChildMeshes().filter(m => m.isEnabled() && m.isVisible));
|
||||
this.camera.setTarget(new BABYLON.Vector3(0, boundingInfo.center.y, 0));
|
||||
|
||||
// zoom to fit
|
||||
const size = boundingInfo.extendSize;
|
||||
const distance = Math.max(size.x, size.y, size.z) * 2;
|
||||
this.camera.radius = distance * 3;
|
||||
//this.camera.orthoLeft = -distance;
|
||||
//this.camera.orthoRight = distance;
|
||||
//this.camera.orthoTop = distance;
|
||||
//this.camera.orthoBottom = -distance;
|
||||
}, 10);
|
||||
}
|
||||
|
||||
// TODO: RoomEngineのものとほぼ同じだからいい感じに共通化
|
||||
private async loadObject(args: {
|
||||
type: string;
|
||||
options: any;
|
||||
id: string;
|
||||
}) {
|
||||
const def = getObjectDef(args.type);
|
||||
|
||||
const root = new BABYLON.Mesh(`object_${args.type}`, this.scene);
|
||||
|
||||
const filePath = def.path != null ? `/client-assets/room/objects/${def.path}.glb` : `/client-assets/room/objects/${camelToKebab(args.type)}/${camelToKebab(args.type)}.glb`;
|
||||
const loaderResult = await BABYLON.LoadAssetContainerAsync(filePath, this.scene);
|
||||
|
||||
// 不要なUVを掃除
|
||||
if (!def.hasTexture) {
|
||||
for (const m of loaderResult.meshes) {
|
||||
if (m.geometry != null) {
|
||||
m.geometry.removeVerticesData(BABYLON.VertexBuffer.UVKind);
|
||||
m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV2Kind);
|
||||
m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV3Kind);
|
||||
m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV4Kind);
|
||||
m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV5Kind);
|
||||
m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV6Kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// babylonによって自動で追加される右手系変換用ノード
|
||||
const subRoot = loaderResult.meshes[0];
|
||||
subRoot.scaling = subRoot.scaling.scale(WORLD_SCALE);// cmをmに
|
||||
|
||||
def.treatLoaderResult?.(loaderResult);
|
||||
|
||||
root.addChild(subRoot);
|
||||
|
||||
const model = new ModelManager(subRoot, loaderResult.meshes.filter(m => !m.isDisposed() && m !== subRoot), def.hasTexture, (meshes) => {
|
||||
for (const m of meshes) {
|
||||
const mesh = m;
|
||||
|
||||
// シェイプキー(morph)を考慮してbounding boxを更新するために必要
|
||||
mesh.refreshBoundingInfo({ applyMorph: true });
|
||||
|
||||
if (SYSTEM_MESH_NAMES.some(n => mesh.name.includes(n))) {
|
||||
mesh.receiveShadows = false;
|
||||
mesh.isVisible = false;
|
||||
} else {
|
||||
if (def.receiveShadows !== false) mesh.receiveShadows = true;
|
||||
if (def.castShadows !== false) {
|
||||
this.shadowGenerator1.addShadowCaster(mesh);
|
||||
}
|
||||
|
||||
if (mesh.material) {
|
||||
if (mesh.material instanceof BABYLON.MultiMaterial) {
|
||||
for (const subMat of mesh.material.subMaterials) {
|
||||
(subMat as BABYLON.PBRMaterial).reflectionTexture = this.envMapIndoor;
|
||||
(subMat as BABYLON.PBRMaterial).useGLTFLightFalloff = true; // Clustered Lightingではphysical falloffを持つマテリアルはアーチファクトが発生する https://doc.babylonjs.com/features/featuresDeepDive/lights/clusteredLighting/#materials-with-a-physical-falloff-may-cause-artefacts
|
||||
}
|
||||
} else {
|
||||
(mesh.material as BABYLON.PBRMaterial).reflectionTexture = this.envMapIndoor;
|
||||
(mesh.material as BABYLON.PBRMaterial).useGLTFLightFalloff = true; // Clustered Lightingではphysical falloffを持つマテリアルはアーチファクトが発生する https://doc.babylonjs.com/features/featuresDeepDive/lights/clusteredLighting/#materials-with-a-physical-falloff-may-cause-artefacts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.scene.meshes.includes(mesh)) this.scene.addMesh(mesh);
|
||||
}
|
||||
});
|
||||
|
||||
const objectInstance = await def.createInstance({
|
||||
room: null,
|
||||
scene: this.scene,
|
||||
root,
|
||||
options: args.options,
|
||||
model,
|
||||
id: args.id,
|
||||
});
|
||||
|
||||
objectInstance.onInited?.();
|
||||
|
||||
model.bakeMesh();
|
||||
|
||||
this.objectInstance = objectInstance;
|
||||
this.objectMesh = root;
|
||||
}
|
||||
|
||||
public updateObjectOption(key: string, value: any) {
|
||||
this.objectInstance?.onOptionsUpdated?.([key, value]);
|
||||
}
|
||||
|
||||
public resize() {
|
||||
this.engine.resize();
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.engine.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user