|
@@ -0,0 +1,330 @@
|
|
|
+import {
|
|
|
+ Box3,
|
|
|
+ BoxGeometry,
|
|
|
+ Color,
|
|
|
+ DoubleSide,
|
|
|
+ Mesh,
|
|
|
+ MeshPhongMaterial,
|
|
|
+ MeshPhysicalMaterial,
|
|
|
+ Object3D,
|
|
|
+ Vector3,
|
|
|
+} from "three";
|
|
|
+import { GLTFLoader, MTLLoader, OBJLoader } 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();
|
|
|
+ parent.add(model);
|
|
|
+
|
|
|
+ const bbox = new Box3().setFromObject(parent);
|
|
|
+ const size = bbox.getSize(new Vector3());
|
|
|
+ if (pub) {
|
|
|
+ parent.scale.set(1 / size.x, 1 / size.y, 1 / size.z);
|
|
|
+ } else {
|
|
|
+ const min = Math.max(size.x, size.y, size.z);
|
|
|
+ parent.scale.set(1 / min, 1 / min, 1 / min);
|
|
|
+ }
|
|
|
+ model.traverse((child: any) => {
|
|
|
+ if (child.isMesh) {
|
|
|
+ child.receiveShadow = true;
|
|
|
+ child.castShadow = true;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const center = new Box3().setFromObject(parent).getCenter(new Vector3());
|
|
|
+ parent.position.sub({ x: center.x, y: center.y, z: center.z });
|
|
|
+
|
|
|
+ return parent;
|
|
|
+};
|
|
|
+
|
|
|
+const resources: Record<string, () => Promise<Object3D>> = {
|
|
|
+ "men_l.svg": async () => {
|
|
|
+ const gltf = await gltfLoader.loadAsync("door_with_frame/scene.gltf");
|
|
|
+ gltf.scene.rotateY(Math.PI);
|
|
|
+ gltf.scene.scale.setX(-1);
|
|
|
+ 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);
|
|
|
+
|
|
|
+ return await normalized(model);
|
|
|
+ },
|
|
|
+ "chuang.svg": async () => {
|
|
|
+ const gltf = await gltfLoader.loadAsync("window (3)/scene.gltf");
|
|
|
+
|
|
|
+ return await normalized(gltf.scene);
|
|
|
+ },
|
|
|
+ "yimen.svg": async () => {
|
|
|
+ const gltf = await gltfLoader.loadAsync("sliding_door/scene.gltf");
|
|
|
+ return await normalized(gltf.scene);
|
|
|
+ },
|
|
|
+ "shuangkaimen.svg": async () => {
|
|
|
+ const gltf = await gltfLoader.loadAsync(
|
|
|
+ "white_double_windowed_door/scene.gltf"
|
|
|
+ );
|
|
|
+ return await normalized(gltf.scene);
|
|
|
+ },
|
|
|
+ "luodichuang.svg": async () => {
|
|
|
+ const gltf = await gltfLoader.loadAsync("window2/scene.gltf");
|
|
|
+ gltf.scene.traverse((node: any) => {
|
|
|
+ if (node.name?.includes("glass_0")) {
|
|
|
+ node.material = new MeshPhysicalMaterial({
|
|
|
+ color: 0xeeeeee, // 浅灰色(可根据需求调整,如0xcccccc)
|
|
|
+ metalness: 0.1, // 轻微金属感(增强反射)
|
|
|
+ roughness: 0.05, // 表面光滑度(0-1,越小越光滑)
|
|
|
+ transmission: 0.9, // 透光率(模拟玻璃透光,需环境光遮蔽和光源支持)
|
|
|
+ opacity: 0.8, // 透明度(与transmission配合使用)
|
|
|
+ transparent: true, // 启用透明
|
|
|
+ side: DoubleSide, // 双面渲染(玻璃通常需要)
|
|
|
+ ior: 1.5, // 折射率(玻璃约为1.5)
|
|
|
+ envMapIntensity: 1, // 环境贴图反射强度(需设置scene.environment)
|
|
|
+ clearcoat: 0.1, // 可选:表面清漆层(增强反光)
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return await normalized(gltf.scene);
|
|
|
+ },
|
|
|
+ "DoubleBed.svg": async () => {
|
|
|
+ const gltf = await gltfLoader.loadAsync("bed/scene.gltf");
|
|
|
+
|
|
|
+ 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)
|
|
|
+ }
|
|
|
+ });
|
|
|
+ models.forEach(m => m.parent?.remove(m))
|
|
|
+
|
|
|
+ const model = await normalized(gltf.scene);
|
|
|
+ model.position.setY(model.position.y - 0.131);
|
|
|
+ return model;
|
|
|
+ },
|
|
|
+ "SingleBed.svg": async () => {
|
|
|
+ const gltf = await gltfLoader.loadAsync("woodbed/scene.gltf");
|
|
|
+ const model = await normalized(gltf.scene);
|
|
|
+ model.rotateY(Math.PI / 2);
|
|
|
+ return model;
|
|
|
+ },
|
|
|
+ "sf": async () => {
|
|
|
+ const gltf = await gltfLoader.loadAsync(
|
|
|
+ "sofa_set_-_4_type_of_sofa_lowpoly./scene.gltf"
|
|
|
+ );
|
|
|
+ return gltf.scene
|
|
|
+ },
|
|
|
+ "ThreeSofa.svg": async () => {
|
|
|
+ const gltf = await gltfLoader.loadAsync(
|
|
|
+ "sofa_-_game_ready_model/scene.gltf"
|
|
|
+ );
|
|
|
+ const model = await normalized(gltf.scene, undefined);
|
|
|
+
|
|
|
+ model.traverse((child: any) => {
|
|
|
+ if (child.isMesh) {
|
|
|
+ child.material.color = new Color(0x444444);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return model;
|
|
|
+ },
|
|
|
+ "SingleSofa.svg": async () => {
|
|
|
+ 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)
|
|
|
+ }
|
|
|
+ });
|
|
|
+ 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']
|
|
|
+ scene.traverse((child: any) => {
|
|
|
+ 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)
|
|
|
+ return await normalized(model);
|
|
|
+ },
|
|
|
+ "DiningTable.svg": async () => {
|
|
|
+ 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 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 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 model = await normalized(gltf.scene, undefined);
|
|
|
+ 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 model = await normalized(gltf.scene, undefined);
|
|
|
+ return model;
|
|
|
+ },
|
|
|
+ "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 model = await normalized(gltf.scene, undefined);
|
|
|
+ return model;
|
|
|
+ },
|
|
|
+ "Closestool.svg": async () => {
|
|
|
+ const gltf = await gltfLoader.loadAsync(
|
|
|
+ "toilet/scene.gltf"
|
|
|
+ );
|
|
|
+ const model = await normalized(gltf.scene, undefined);
|
|
|
+ model.traverse((child: any) => {
|
|
|
+ if (child.isMesh) {
|
|
|
+ child.material.color = new Color(0xffffff);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return model;
|
|
|
+ },
|
|
|
+ "Wardrobe.svg": async () => {
|
|
|
+ const gltf = await gltfLoader.loadAsync(
|
|
|
+ "wardrobe_14722-22/scene.gltf"
|
|
|
+ );
|
|
|
+ const model = await normalized(gltf.scene, undefined);
|
|
|
+ model.traverse((child: any) => {
|
|
|
+ if (child.isMesh) {
|
|
|
+ child.material.color = new Color(0xcbc3b3);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return model;
|
|
|
+
|
|
|
+ },
|
|
|
+ "BedsideCupboard.svg": async () => {
|
|
|
+ const gltf = await gltfLoader.loadAsync(
|
|
|
+ "low_poly_bedside_table/scene.gltf"
|
|
|
+ );
|
|
|
+ const model = await normalized(gltf.scene, undefined);
|
|
|
+ model.traverse((child: any) => {
|
|
|
+ if (child.isMesh) {
|
|
|
+ child.material.color = new Color(0xffffff);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return model;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+export const levelResources: Record<string, {bottom?: number, height?: number | 'full' }> = {
|
|
|
+ 'SingleBed.svg': {
|
|
|
+ height: 70,
|
|
|
+ },
|
|
|
+ 'ThreeSofa.svg': {
|
|
|
+ height: 90
|
|
|
+ },
|
|
|
+ 'SingleSofa.svg': {
|
|
|
+ height: 90
|
|
|
+ },
|
|
|
+ 'Desk.svg': {
|
|
|
+ height: 80
|
|
|
+ },
|
|
|
+ 'DiningTable.svg': {
|
|
|
+ height: 100
|
|
|
+ },
|
|
|
+ 'Chair.svg': {
|
|
|
+ height: 80
|
|
|
+ },
|
|
|
+ 'TV.svg': {
|
|
|
+ height: 120
|
|
|
+ },
|
|
|
+ 'Washstand.svg': {
|
|
|
+ height: 100
|
|
|
+ },
|
|
|
+ 'Closestool.svg': {
|
|
|
+ height: 45
|
|
|
+ },
|
|
|
+ 'Wardrobe.svg': {
|
|
|
+ height: 'full'
|
|
|
+ },
|
|
|
+ 'BedsideCupboard.svg': {
|
|
|
+ height: 50
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export const getModel = (() => {
|
|
|
+ const typeModels: Record<string, Promise<Object3D | undefined>> = {};
|
|
|
+
|
|
|
+ return (type: string) => {
|
|
|
+ const ndx = type.lastIndexOf("/");
|
|
|
+ if (~ndx) {
|
|
|
+ type = type.substring(ndx + 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (type in typeModels) {
|
|
|
+ return typeModels[type];
|
|
|
+ }
|
|
|
+ if (type in resources) {
|
|
|
+ typeModels[type] = resources[type]();
|
|
|
+ typeModels[type].catch(() => {
|
|
|
+ delete typeModels[type];
|
|
|
+ });
|
|
|
+ return typeModels[type];
|
|
|
+ }
|
|
|
+ };
|
|
|
+})();
|
|
|
+
|
|
|
+export const fullMesh = new Mesh(
|
|
|
+ new BoxGeometry(1, 1, 1),
|
|
|
+ new MeshPhongMaterial({ color: 0xffffff })
|
|
|
+);
|
|
|
+fullMesh.receiveShadow = fullMesh.castShadow = true;
|