123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740 |
- import { animation } from "@/core/hook/use-animation";
- import {
- Box3,
- BoxGeometry,
- Color,
- DoubleSide,
- Mesh,
- MeshPhongMaterial,
- MeshPhysicalMaterial,
- MeshStandardMaterial,
- Object3D,
- Quaternion,
- Vector3,
- } from "three";
- import { GLTFLoader } from "three/examples/jsm/Addons.js";
- const gltfLoader = new GLTFLoader().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 gltf = await gltfLoader.loadAsync("bay_window/scene.gltf");
- gltf.scene.scale.setX(-1);
- const names = ["01_glass_0", "02_glass_0", "03_glass_0", "04_glass_0"]
- gltf.scene.traverse((node: any) => {
- if (names.includes(node.name)) {
- 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, // 可选:表面清漆层(增强反光)
- });
- }
- })
- let copyModel: Object3D
- let parent: Object3D
- gltf.scene.traverse((node: any) => {
- if (node.name === "01") {
- copyModel = node.clone()
- parent = node.parent
- }
- })
- copyModel!.scale.add({x: 0, y: 0.06, z: -0.1})
- const left = copyModel!.clone()
- left.name = "05"
- left.rotation.y = -Math.PI / 2
- left.position.set(-170, 0, 50)
- parent!.add(left)
- const right = copyModel!
- right.name = "06"
- right.rotation.y = Math.PI / 2
- right.position.set(170, 0, 50)
- parent!.add(right)
-
- const model = await normalized(gltf.scene);
- // model.scale.add(({x: 0.015, y: 0, z: 0}))
- // model.position.add({x: 0, y: 0, z: 0})
- left.scale.add({x: -0.3, y: 0, z: 0})
- left.position.add({x: -7, y: 0, z: -16})
- right.scale.add({x: -0.3, y: 0, z: 0})
- right.position.add({x: 7, y: 0, z: -16})
- return model
- },
- "piaochuang1.svg": async () => {
- 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,
- });
- }
- });
- 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");
- 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: 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, // 可选:表面清漆层(增强反光)
- });
- }
- });
- 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) => {
- 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);
- 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);
- },
- "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 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.position.add({x: 0, y: -0.1, 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"
- );
- gltf.scene.rotateY(Math.PI);
- const model = await normalized(gltf.scene, undefined);
- model.traverse((child: any) => {
- if (child.isMesh) {
- child.material.color = new Color(0xffffff);
- }
- });
- 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 | string;
- height?: number | string | "full";
- top?: number | string;
- }
- > = {
- "SingleBed.svg": {
- height: 70,
- },
- "ThreeSofa.svg": {
- height: 90,
- },
- "SingleSofa.svg": {
- height: 90,
- },
- "CombinationSofa.svg": {
- height: 90,
- },
- "Desk.svg": {
- height: 80,
- },
- "TeaTable.svg": {
- height: 50,
- },
- "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,
- },
- "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 type ModelSwitch = (open: boolean) => ReturnType<typeof animation>;
- export type SwitchResult = ReturnType<ModelSwitch>;
- export const switchResources: Record<
- string,
- (model: Object3D, render: () => void) => ModelSwitch
- > = {
- "men_l.svg": (model, render) => {
- let node: Object3D;
- model.traverse((child) => {
- if (child.name === "Plane001") {
- node = child;
- }
- });
- return (open: boolean) =>
- animation(
- { z: node!.rotation.z },
- { z: open ? -Math.PI / 2 : 0 },
- (data) => {
- node.rotation.z = data.z;
- render();
- }
- );
- },
- "luodichuang.svg": (model, render) => {
- let nodes: Object3D[] = [];
- const initVals = [121.44782257080078, -121.4478];
- const finalVals = [58, -58];
- model.traverse((child) => {
- if (child.name === "01") {
- nodes[0] = child;
- } else if (child.name === "04") {
- nodes[1] = child;
- }
- });
- return (open: boolean) =>
- animation(
- nodes.map((node) => node.position.x),
- open ? finalVals : initVals,
- (data) => {
- nodes.forEach((node, i) => (node.position.x = data[i]));
- render();
- }
- );
- },
- "chuang.svg": (model, render) => {
- let nodes: Object3D[] = [];
- model.traverse((child) => {
- if (child.name === "L") {
- nodes[0] = child;
- } else if (child.name === "R") {
- nodes[1] = child;
- }
- });
- const initVals = nodes.map((item) => ({
- position: item.position.clone(),
- quat: item.quaternion.clone(),
- }));
- const changes = [
- { origin: new Vector3(108, 0, 0), angle: -Math.PI / 3 },
- { origin: new Vector3(-108, 0, 0), angle: Math.PI / 3 },
- ];
- const finalVals = initVals.map((item, i) => {
- const { origin, angle } = changes[i];
- const qua = new Quaternion().setFromAxisAngle(
- { x: 0, y: 1, z: 0 },
- angle
- );
- const finalPosition = item.position
- .clone()
- .sub(origin)
- .applyQuaternion(qua)
- .add(origin);
- const finalQua = item.quat.clone().multiply(qua);
- return {
- position: finalPosition,
- quat: finalQua,
- };
- });
- return (open: boolean) =>
- animation(
- nodes.map((node) => ({
- position: node.position,
- quat: node.quaternion,
- })),
- open ? finalVals : initVals,
- (data) => {
- nodes.forEach((node, i) => {
- node.position.copy(data[i].position);
- node.quaternion.copy(data[i].quat);
- });
- render();
- }
- );
- },
- "yimen.svg": (model, render) => {
- let node: Object3D;
- model.traverse((child) => {
- if (child.name === "16668_84x96_Slider_Door-Black_V1001_0") {
- node = child;
- }
- });
- const initVal = node!.position.x;
- const finalVal = initVal - 100;
- return (open: boolean) =>
- animation(
- { x: node.position.x },
- { x: open ? finalVal : initVal },
- (data) => {
- node.position.setX(data.x);
- render();
- }
- );
- },
- "Wardrobe.svg": (model, render) => {
- let nodes: Object3D[] = [];
- const initVals = [0, 0];
- const finalVals = [Math.PI / 2, Math.PI / 2];
- const names = [
- 'RootNode',
- 'VIFS079_NCS_S_1502-Y50R_semigloss_0',
- 'VIFS077_NCS_S_1502-Y50R_semigloss_0',
- ]
- model.traverse((child) => {
- if (names.includes(child.name)) {
- nodes.push(child)
- }
- });
- return (open: boolean) =>
- animation(
- nodes.map((node) => node.rotation.y),
- open ? finalVals : initVals,
- (data) => {
- nodes.forEach((node, i) => (node.rotateY(data[i])));
- console.log(nodes, data)
- render();
- }
- );
- },
- "piaochuang.svg": (model, render) => {
- let nodes: Object3D[] = [];
- const names = ["01", "02", "03", "04"]
- model.traverse((child) => {
- if (names.includes(child.name)) {
- nodes.push(child)
- }
- });
- const initVals = nodes.map((item) => ({
- position: item.position.clone(),
- quat: item.quaternion.clone(),
- }));
- const changes = [
- { origin: new Vector3(80, 0, 0), angle: Math.PI / 3 },
- { origin: new Vector3(80, 0, 0), angle: -Math.PI / 3 },
- { origin: new Vector3(-80, 0, 0), angle: Math.PI / 3 },
- { origin: new Vector3(-80, 0, 0), angle: -Math.PI / 3 },
- ];
- const finalVals = initVals.map((item, i) => {
- const { origin, angle } = changes[i];
- const qua = new Quaternion().setFromAxisAngle(
- { x: 0, y: 1, z: 0 },
- angle
- );
- const finalPosition = item.position
- .clone()
- .sub(origin)
- .applyQuaternion(qua)
- .add(origin);
- const finalQua = item.quat.clone().multiply(qua);
- return {
- position: finalPosition,
- quat: finalQua,
- // quat: item.quat
- };
- });
- return (open: boolean) =>
- animation(
- nodes.map((node) => ({
- position: node.position,
- quat: node.quaternion,
- })),
- open ? finalVals : initVals,
- (data) => {
- nodes.forEach((node, i) => {
- node.position.copy(data[i].position);
- node.quaternion.copy(data[i].quat);
- });
- render();
- }
- );
- },
- };
- export const getModelSwitch = (type: string) => {
- const ndx = type.lastIndexOf("/");
- if (~ndx) {
- type = type.substring(ndx + 1);
- }
- return switchResources[type];
- };
- 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>> = {};
- 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;
|