|
@@ -2,18 +2,18 @@ import {
|
|
|
Box3,
|
|
|
BoxGeometry,
|
|
|
Color,
|
|
|
+ DirectionalLight,
|
|
|
DoubleSide,
|
|
|
Mesh,
|
|
|
MeshPhongMaterial,
|
|
|
MeshPhysicalMaterial,
|
|
|
+ MeshStandardMaterial,
|
|
|
Object3D,
|
|
|
Vector3,
|
|
|
} from "three";
|
|
|
-import { GLTFLoader, MTLLoader, OBJLoader } from "three/examples/jsm/Addons.js";
|
|
|
+import { GLTFLoader } from "three/examples/jsm/Addons.js";
|
|
|
|
|
|
const gltfLoader = new GLTFLoader().setPath("/static/models/");
|
|
|
-const objLoader = new OBJLoader().setPath("/static/models/");
|
|
|
-const mtlLoader = new MTLLoader().setPath("/static/models/");
|
|
|
|
|
|
const normalized = async (model: Object3D, pub = true) => {
|
|
|
const parent = new Object3D();
|
|
@@ -48,13 +48,53 @@ const resources: Record<string, () => Promise<Object3D>> = {
|
|
|
return await normalized(gltf.scene);
|
|
|
},
|
|
|
"piaochuang.svg": async () => {
|
|
|
- const materials = await mtlLoader.loadAsync("bay_window/3d-model.mtl");
|
|
|
-
|
|
|
- objLoader.setMaterials(materials);
|
|
|
- const model = await objLoader.loadAsync("bay_window/3d-model.obj");
|
|
|
- model.rotateY(Math.PI);
|
|
|
+ const gltf = await gltfLoader.loadAsync("window_1/scene.gltf");
|
|
|
+ gltf.scene.rotateY(Math.PI);
|
|
|
+ gltf.scene.traverse((node: any) => {
|
|
|
+ if (!node.isMesh) return;
|
|
|
+ if (node.name.includes("Object")) {
|
|
|
+ node.material = new MeshPhysicalMaterial({
|
|
|
+ color: 0xffffff, // 浅灰色(可根据需求调整,如0xcccccc)
|
|
|
+ metalness: 0.1, // 轻微金属感(增强反射)
|
|
|
+ roughness: 0.01, // 表面光滑度(0-1,越小越光滑)
|
|
|
+ transmission: 1, // 透光率(模拟玻璃透光,需环境光遮蔽和光源支持)
|
|
|
+ opacity: 1, // 透明度(与transmission配合使用)
|
|
|
+ transparent: true, // 启用透明
|
|
|
+ side: DoubleSide, // 双面渲染(玻璃通常需要)
|
|
|
+ ior: 0, // 折射率(玻璃约为1.5)
|
|
|
+ clearcoat: 0.5, // 可选:表面清漆层(增强反光)
|
|
|
+ });
|
|
|
+ } else if (node.name.includes("_111111_white_plastic")) {
|
|
|
+ node.material = new MeshStandardMaterial({
|
|
|
+ color: 0xffffff, // 浅灰色
|
|
|
+ metalness: 0.9, // 高金属度
|
|
|
+ roughness: 0.3, // 中等粗糙度
|
|
|
+ side: DoubleSide,
|
|
|
+ });
|
|
|
+ } else if (
|
|
|
+ node.name.includes("_111111_seam_0") ||
|
|
|
+ node.name.includes("_111111__15_0") ||
|
|
|
+ node.name.includes("_111111_Aluminium_profile_0")
|
|
|
+ ) {
|
|
|
+ node.material = new MeshStandardMaterial({
|
|
|
+ color: 0xffffff,
|
|
|
+ metalness: 0.8,
|
|
|
+ roughness: 0.4,
|
|
|
+ aoMapIntensity: 1.0,
|
|
|
+ side: DoubleSide,
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ node.material = new MeshPhongMaterial({
|
|
|
+ side: DoubleSide,
|
|
|
+ color: 0xffffff,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
- return await normalized(model);
|
|
|
+ const model = await normalized(gltf.scene);
|
|
|
+ model.scale.add({ x: 0.00015, y: 0.0001, z: 0 });
|
|
|
+ model.position.add({ x: -0.01, y: -0.005, z: 0.02 });
|
|
|
+ return model;
|
|
|
},
|
|
|
"chuang.svg": async () => {
|
|
|
const gltf = await gltfLoader.loadAsync("window (3)/scene.gltf");
|
|
@@ -76,16 +116,15 @@ const resources: Record<string, () => Promise<Object3D>> = {
|
|
|
gltf.scene.traverse((node: any) => {
|
|
|
if (node.name?.includes("glass_0")) {
|
|
|
node.material = new MeshPhysicalMaterial({
|
|
|
- color: 0xeeeeee, // 浅灰色(可根据需求调整,如0xcccccc)
|
|
|
+ color: 0xffffff, // 浅灰色(可根据需求调整,如0xcccccc)
|
|
|
metalness: 0.1, // 轻微金属感(增强反射)
|
|
|
- roughness: 0.05, // 表面光滑度(0-1,越小越光滑)
|
|
|
- transmission: 0.9, // 透光率(模拟玻璃透光,需环境光遮蔽和光源支持)
|
|
|
- opacity: 0.8, // 透明度(与transmission配合使用)
|
|
|
+ roughness: 0.01, // 表面光滑度(0-1,越小越光滑)
|
|
|
+ transmission: 1, // 透光率(模拟玻璃透光,需环境光遮蔽和光源支持)
|
|
|
+ opacity: 1, // 透明度(与transmission配合使用)
|
|
|
transparent: true, // 启用透明
|
|
|
side: DoubleSide, // 双面渲染(玻璃通常需要)
|
|
|
- ior: 1.5, // 折射率(玻璃约为1.5)
|
|
|
- envMapIntensity: 1, // 环境贴图反射强度(需设置scene.environment)
|
|
|
- clearcoat: 0.1, // 可选:表面清漆层(增强反光)
|
|
|
+ ior: 0, // 折射率(玻璃约为1.5)
|
|
|
+ clearcoat: 0.5, // 可选:表面清漆层(增强反光)
|
|
|
});
|
|
|
}
|
|
|
});
|
|
@@ -94,15 +133,14 @@ const resources: Record<string, () => Promise<Object3D>> = {
|
|
|
"DoubleBed.svg": async () => {
|
|
|
const gltf = await gltfLoader.loadAsync("bed/scene.gltf");
|
|
|
|
|
|
- const models: Object3D[] = []
|
|
|
- const delModelName = ['Pillow_2002', 'Plane002']
|
|
|
+ const models: Object3D[] = [];
|
|
|
+ const delModelName = ["Pillow_2002", "Plane002"];
|
|
|
gltf.scene.traverse((child: any) => {
|
|
|
- console.log(child.name)
|
|
|
- if (delModelName.some(n => n === child.name)) {
|
|
|
- models.push(child)
|
|
|
+ if (delModelName.some((n) => n === child.name)) {
|
|
|
+ models.push(child);
|
|
|
}
|
|
|
});
|
|
|
- models.forEach(m => m.parent?.remove(m))
|
|
|
+ models.forEach((m) => m.parent?.remove(m));
|
|
|
|
|
|
const model = await normalized(gltf.scene);
|
|
|
model.position.setY(model.position.y - 0.131);
|
|
@@ -114,11 +152,11 @@ const resources: Record<string, () => Promise<Object3D>> = {
|
|
|
model.rotateY(Math.PI / 2);
|
|
|
return model;
|
|
|
},
|
|
|
- "sf": async () => {
|
|
|
+ sf: async () => {
|
|
|
const gltf = await gltfLoader.loadAsync(
|
|
|
"sofa_set_-_4_type_of_sofa_lowpoly./scene.gltf"
|
|
|
);
|
|
|
- return gltf.scene
|
|
|
+ return gltf.scene;
|
|
|
},
|
|
|
"ThreeSofa.svg": async () => {
|
|
|
const gltf = await gltfLoader.loadAsync(
|
|
@@ -135,101 +173,92 @@ const resources: Record<string, () => Promise<Object3D>> = {
|
|
|
return model;
|
|
|
},
|
|
|
"SingleSofa.svg": async () => {
|
|
|
- const scene = (await getModel('sf'))!
|
|
|
- const models: Object3D[] = []
|
|
|
- const pickModelName = ['Cube026']
|
|
|
+ const scene = (await getModel("sf"))!;
|
|
|
+ const models: Object3D[] = [];
|
|
|
+ const pickModelName = ["Cube026"];
|
|
|
scene.traverse((child: any) => {
|
|
|
- if (pickModelName.some(n => n === child.name)) {
|
|
|
- models.push(child)
|
|
|
+ if (pickModelName.some((n) => n === child.name)) {
|
|
|
+ models.push(child);
|
|
|
}
|
|
|
});
|
|
|
- const model = new Object3D().add(...models.map(item => item.clone()))
|
|
|
- model.rotateY(Math.PI / 2)
|
|
|
+ const model = new Object3D().add(...models.map((item) => item.clone()));
|
|
|
+ model.rotateY(Math.PI / 2);
|
|
|
return await normalized(model);
|
|
|
},
|
|
|
"Desk.svg": async () => {
|
|
|
- const scene = (await getModel('sf'))!
|
|
|
- const models: Object3D[] = []
|
|
|
- const pickModelName = ['Cube004']
|
|
|
+ const scene = (await getModel("sf"))!;
|
|
|
+ const models: Object3D[] = [];
|
|
|
+ const pickModelName = ["Cube004"];
|
|
|
scene.traverse((child: any) => {
|
|
|
- if (pickModelName.some(n => n === child.name)) {
|
|
|
- models.push(child)
|
|
|
+ if (pickModelName.some((n) => n === child.name)) {
|
|
|
+ models.push(child);
|
|
|
}
|
|
|
});
|
|
|
- const model = new Object3D().add(...models.map(item => item.clone()))
|
|
|
- model.rotateY(Math.PI / 2)
|
|
|
+ const model = new Object3D().add(...models.map((item) => item.clone()));
|
|
|
+ model.rotateY(Math.PI / 2);
|
|
|
return await normalized(model);
|
|
|
},
|
|
|
+ "TeaTable.svg": async () => {
|
|
|
+ return (await getModel("Desk.svg"))!.clone();
|
|
|
+ },
|
|
|
"DiningTable.svg": async () => {
|
|
|
- const desk = new Object3D().add((await getModel('Desk.svg'))!.clone())
|
|
|
- const chair = (await getModel('Chair.svg'))!
|
|
|
- const model = new Object3D()
|
|
|
+ const desk = new Object3D().add((await getModel("Desk.svg"))!.clone());
|
|
|
+ const chair = (await getModel("Chair.svg"))!;
|
|
|
+ const model = new Object3D();
|
|
|
|
|
|
- const lt = chair.clone()
|
|
|
- lt.position.set(-0.14, -0.5, 0.25)
|
|
|
- lt.scale.set(0.5, 1.2, 0.8)
|
|
|
- lt.rotateY(Math.PI)
|
|
|
- model.add(lt)
|
|
|
+ const lt = chair.clone();
|
|
|
+ lt.position.set(-0.14, -0.5, 0.25);
|
|
|
+ lt.scale.set(0.5, 1.2, 0.8);
|
|
|
+ lt.rotateY(Math.PI);
|
|
|
+ model.add(lt);
|
|
|
|
|
|
+ const rt = chair.clone();
|
|
|
+ rt.position.set(0.14, -0.5, 0.25);
|
|
|
+ rt.scale.set(0.5, 1.2, 0.8);
|
|
|
+ rt.rotateY(Math.PI);
|
|
|
+ model.add(rt);
|
|
|
|
|
|
- const rt = chair.clone()
|
|
|
- rt.position.set(0.14, -0.5, 0.25)
|
|
|
- rt.scale.set(0.5, 1.2, 0.8)
|
|
|
- rt.rotateY(Math.PI)
|
|
|
- model.add(rt)
|
|
|
+ const lb = chair.clone();
|
|
|
+ lb.position.set(-0.14, -0.5, -0.25);
|
|
|
+ lb.scale.set(0.5, 1.2, 0.8);
|
|
|
+ model.add(lb);
|
|
|
|
|
|
- const lb = chair.clone()
|
|
|
- lb.position.set(-0.14, -0.5, -0.25)
|
|
|
- lb.scale.set(0.5, 1.2, 0.8)
|
|
|
- model.add(lb)
|
|
|
+ const rb = chair.clone();
|
|
|
+ rb.position.set(0.14, -0.5, -0.25);
|
|
|
+ rb.scale.set(0.5, 1.2, 0.8);
|
|
|
+ model.add(rb);
|
|
|
|
|
|
+ desk.scale.set(1.2, 1, 0.55);
|
|
|
+ model.add(desk);
|
|
|
|
|
|
- const rb = chair.clone()
|
|
|
- rb.position.set(0.14, -0.5, -0.25)
|
|
|
- rb.scale.set(0.5, 1.2, 0.8)
|
|
|
- model.add(rb)
|
|
|
-
|
|
|
- desk.scale.set(1.2, 1, 0.55)
|
|
|
- model.add(desk)
|
|
|
-
|
|
|
- const nModel = await normalized(model)
|
|
|
- nModel.position.sub({x: 0, y: 0.075, z: 0})
|
|
|
- return nModel
|
|
|
- },
|
|
|
- "Chair.svg": async () => {
|
|
|
- const gltf = await gltfLoader.loadAsync(
|
|
|
- "psx_chair/scene.gltf"
|
|
|
- );
|
|
|
+ const nModel = await normalized(model);
|
|
|
+ nModel.position.sub({ x: 0, y: 0.075, z: 0 });
|
|
|
+ return nModel;
|
|
|
+ },
|
|
|
+ "Chair.svg": async () => {
|
|
|
+ const gltf = await gltfLoader.loadAsync("psx_chair/scene.gltf");
|
|
|
const model = await normalized(gltf.scene, undefined);
|
|
|
- model.scale.add({x: 0, y: 0.3, z: 0})
|
|
|
+ model.scale.add({ x: 0, y: 0.3, z: 0 });
|
|
|
return model;
|
|
|
},
|
|
|
"TV.svg": async () => {
|
|
|
- const gltf = await gltfLoader.loadAsync(
|
|
|
- "tv_and_tv_stand/scene.gltf"
|
|
|
- );
|
|
|
+ const gltf = await gltfLoader.loadAsync("tv_and_tv_stand/scene.gltf");
|
|
|
const model = await normalized(gltf.scene, undefined);
|
|
|
return model;
|
|
|
},
|
|
|
- "Plant.svg": async() => {
|
|
|
- const gltf = await gltfLoader.loadAsync(
|
|
|
- "pothos_plant/scene.gltf"
|
|
|
- );
|
|
|
+ "Plant.svg": async () => {
|
|
|
+ const gltf = await gltfLoader.loadAsync("pothos_plant/scene.gltf");
|
|
|
const model = await normalized(gltf.scene, undefined);
|
|
|
return model;
|
|
|
},
|
|
|
"Washstand.svg": async () => {
|
|
|
- const gltf = await gltfLoader.loadAsync(
|
|
|
- "washbasin/scene.gltf"
|
|
|
- );
|
|
|
- gltf.scene.rotateY(Math.PI)
|
|
|
+ const gltf = await gltfLoader.loadAsync("washbasin/scene.gltf");
|
|
|
+ gltf.scene.rotateY(Math.PI);
|
|
|
const model = await normalized(gltf.scene, undefined);
|
|
|
return model;
|
|
|
},
|
|
|
"Closestool.svg": async () => {
|
|
|
- const gltf = await gltfLoader.loadAsync(
|
|
|
- "toilet/scene.gltf"
|
|
|
- );
|
|
|
+ const gltf = await gltfLoader.loadAsync("toilet/scene.gltf");
|
|
|
const model = await normalized(gltf.scene, undefined);
|
|
|
model.traverse((child: any) => {
|
|
|
if (child.isMesh) {
|
|
@@ -239,9 +268,7 @@ const resources: Record<string, () => Promise<Object3D>> = {
|
|
|
return model;
|
|
|
},
|
|
|
"Wardrobe.svg": async () => {
|
|
|
- const gltf = await gltfLoader.loadAsync(
|
|
|
- "wardrobe_14722-22/scene.gltf"
|
|
|
- );
|
|
|
+ const gltf = await gltfLoader.loadAsync("wardrobe_14722-22/scene.gltf");
|
|
|
const model = await normalized(gltf.scene, undefined);
|
|
|
model.traverse((child: any) => {
|
|
|
if (child.isMesh) {
|
|
@@ -249,7 +276,6 @@ const resources: Record<string, () => Promise<Object3D>> = {
|
|
|
}
|
|
|
});
|
|
|
return model;
|
|
|
-
|
|
|
},
|
|
|
"BedsideCupboard.svg": async () => {
|
|
|
const gltf = await gltfLoader.loadAsync(
|
|
@@ -262,44 +288,164 @@ const resources: Record<string, () => Promise<Object3D>> = {
|
|
|
}
|
|
|
});
|
|
|
return model;
|
|
|
- }
|
|
|
+ },
|
|
|
+ "CombinationSofa.svg": async () => {
|
|
|
+ const tsofa = (await getModel("ThreeSofa.svg"))!.clone();
|
|
|
+ const ssofa = (await getModel("SingleSofa.svg"))!.clone();
|
|
|
+ const tea = (await getModel("TeaTable.svg"))!.clone();
|
|
|
+ const model = new Object3D();
|
|
|
+
|
|
|
+ // tsofa.rotateY(-Math.PI / 2)
|
|
|
+ tsofa.scale.multiply({ x: 0.8, y: 1, z: 0.4 });
|
|
|
+ tsofa.position.add({ x: -0, y: 0, z: -0.6 });
|
|
|
+ model.add(tsofa);
|
|
|
+
|
|
|
+ ssofa.rotateY(-Math.PI / 2);
|
|
|
+ ssofa.scale.multiply({ x: 0.4, y: 1, z: 0.4 });
|
|
|
+ ssofa.position.add({ x: -0.15, y: 0, z: -2.2 });
|
|
|
+ model.add(ssofa);
|
|
|
+
|
|
|
+ tea.scale.multiply({ x: 0.8, y: 0.5, z: 0.4 });
|
|
|
+ tea.position.add({ x: -0, y: -0.13, z: 0 });
|
|
|
+ model.add(tea);
|
|
|
+ return normalized(model);
|
|
|
+ },
|
|
|
+ kitchen: async () => {
|
|
|
+ const gltf = await gltfLoader.loadAsync(
|
|
|
+ "basic_kitchen_cabinets_and_counter/scene.gltf"
|
|
|
+ );
|
|
|
+ gltf.scene.rotateY(-Math.PI);
|
|
|
+ return gltf.scene;
|
|
|
+ },
|
|
|
+ "Cupboard.svg": async () => {
|
|
|
+ const gltf = await gltfLoader.loadAsync("kitchen_cabinets (1)/scene.gltf");
|
|
|
+
|
|
|
+ gltf.scene.rotateY(Math.PI / 2);
|
|
|
+ const model = await normalized(gltf.scene);
|
|
|
+ model.traverse((child: any) => {
|
|
|
+ if (
|
|
|
+ child.isMesh &&
|
|
|
+ ["pCube1_cor_0", "pCube8_cor_0"].includes(child.name)
|
|
|
+ ) {
|
|
|
+ child.material.color = new Color(0xffffff);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return model;
|
|
|
+ },
|
|
|
+ "GasStove.svg": async () => {
|
|
|
+ const gltf = await gltfLoader.loadAsync("burner_gas_stove/scene.gltf");
|
|
|
+ const model = await normalized(gltf.scene);
|
|
|
+ model.traverse((child: any) => {
|
|
|
+ if (child.isMesh) {
|
|
|
+ child.material.emissive = new Color(0x222222)
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return model;
|
|
|
+ },
|
|
|
};
|
|
|
|
|
|
-export const levelResources: Record<string, {bottom?: number, height?: number | 'full' }> = {
|
|
|
- 'SingleBed.svg': {
|
|
|
+export const levelResources: Record<
|
|
|
+ string,
|
|
|
+ {
|
|
|
+ bottom?: number | string;
|
|
|
+ height?: number | string | "full";
|
|
|
+ top?: number | string;
|
|
|
+ }
|
|
|
+> = {
|
|
|
+ "SingleBed.svg": {
|
|
|
height: 70,
|
|
|
},
|
|
|
- 'ThreeSofa.svg': {
|
|
|
- height: 90
|
|
|
+ "ThreeSofa.svg": {
|
|
|
+ height: 90,
|
|
|
},
|
|
|
- 'SingleSofa.svg': {
|
|
|
- height: 90
|
|
|
+ "SingleSofa.svg": {
|
|
|
+ height: 90,
|
|
|
},
|
|
|
- 'Desk.svg': {
|
|
|
- height: 80
|
|
|
+ "CombinationSofa.svg": {
|
|
|
+ height: 90,
|
|
|
},
|
|
|
- 'DiningTable.svg': {
|
|
|
- height: 100
|
|
|
+ "Desk.svg": {
|
|
|
+ height: 80,
|
|
|
},
|
|
|
- 'Chair.svg': {
|
|
|
- height: 80
|
|
|
+ "TeaTable.svg": {
|
|
|
+ height: 50,
|
|
|
},
|
|
|
- 'TV.svg': {
|
|
|
- height: 120
|
|
|
+ "DiningTable.svg": {
|
|
|
+ height: 100,
|
|
|
},
|
|
|
- 'Washstand.svg': {
|
|
|
- height: 100
|
|
|
+ "Chair.svg": {
|
|
|
+ height: 80,
|
|
|
},
|
|
|
- 'Closestool.svg': {
|
|
|
- height: 45
|
|
|
+ "TV.svg": {
|
|
|
+ height: 120,
|
|
|
},
|
|
|
- 'Wardrobe.svg': {
|
|
|
- height: 'full'
|
|
|
+ "Washstand.svg": {
|
|
|
+ height: 100,
|
|
|
},
|
|
|
- 'BedsideCupboard.svg': {
|
|
|
- height: 50
|
|
|
+ "Closestool.svg": {
|
|
|
+ height: 45,
|
|
|
+ },
|
|
|
+ "Wardrobe.svg": {
|
|
|
+ height: "full",
|
|
|
+ },
|
|
|
+ "BedsideCupboard.svg": {
|
|
|
+ height: 50,
|
|
|
+ },
|
|
|
+ "piaochuang.svg": {
|
|
|
+ top: 4,
|
|
|
+ bottom: 40,
|
|
|
+ },
|
|
|
+ "men_l.svg": {
|
|
|
+ height: "full",
|
|
|
+ },
|
|
|
+ "yimen.svg": {
|
|
|
+ height: "full",
|
|
|
+ },
|
|
|
+ "shuangkaimen.svg": {
|
|
|
+ height: "full",
|
|
|
+ },
|
|
|
+ "luodichuang.svg": {
|
|
|
+ height: "full",
|
|
|
+ },
|
|
|
+ "Cupboard.svg": {
|
|
|
+ height: "full",
|
|
|
+ },
|
|
|
+ "GasStove.svg": {
|
|
|
+ height: 10,
|
|
|
+ bottom: "0.335",
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
+export const getLevel = (type: string, fullHeight: number) => {
|
|
|
+ const ndx = type.lastIndexOf("/");
|
|
|
+ if (~ndx) {
|
|
|
+ type = type.substring(ndx + 1);
|
|
|
+ }
|
|
|
+ const transform = (data: any): Record<string, number> => {
|
|
|
+ const tdata: Record<string, number> = {};
|
|
|
+ for (const key of Object.keys(data)) {
|
|
|
+ if (data[key] === "full") {
|
|
|
+ tdata[key] = fullHeight;
|
|
|
+ } else if (typeof data[key] === "string") {
|
|
|
+ tdata[key] = parseFloat(data[key]) * fullHeight;
|
|
|
+ } else {
|
|
|
+ tdata[key] = data[key];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return tdata;
|
|
|
+ };
|
|
|
+ if (!levelResources[type]) {
|
|
|
+ return {};
|
|
|
+ }
|
|
|
+
|
|
|
+ const data = transform(levelResources[type]);
|
|
|
+ if (!data.height && "top" in data && "bottom" in data) {
|
|
|
+ data.height = fullHeight - data.top - data.bottom;
|
|
|
}
|
|
|
-}
|
|
|
+
|
|
|
+ return data;
|
|
|
+};
|
|
|
|
|
|
export const getModel = (() => {
|
|
|
const typeModels: Record<string, Promise<Object3D | undefined>> = {};
|