resource.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. import {
  2. Box3,
  3. BoxGeometry,
  4. Color,
  5. DirectionalLight,
  6. DoubleSide,
  7. Mesh,
  8. MeshPhongMaterial,
  9. MeshPhysicalMaterial,
  10. MeshStandardMaterial,
  11. Object3D,
  12. Vector3,
  13. } from "three";
  14. import { GLTFLoader } from "three/examples/jsm/Addons.js";
  15. const gltfLoader = new GLTFLoader().setPath("/static/models/");
  16. const normalized = async (model: Object3D, pub = true) => {
  17. const parent = new Object3D();
  18. parent.add(model);
  19. const bbox = new Box3().setFromObject(parent);
  20. const size = bbox.getSize(new Vector3());
  21. if (pub) {
  22. parent.scale.set(1 / size.x, 1 / size.y, 1 / size.z);
  23. } else {
  24. const min = Math.max(size.x, size.y, size.z);
  25. parent.scale.set(1 / min, 1 / min, 1 / min);
  26. }
  27. model.traverse((child: any) => {
  28. if (child.isMesh) {
  29. child.receiveShadow = true;
  30. child.castShadow = true;
  31. }
  32. });
  33. const center = new Box3().setFromObject(parent).getCenter(new Vector3());
  34. parent.position.sub({ x: center.x, y: center.y, z: center.z });
  35. return parent;
  36. };
  37. const resources: Record<string, () => Promise<Object3D>> = {
  38. "men_l.svg": async () => {
  39. const gltf = await gltfLoader.loadAsync("door_with_frame/scene.gltf");
  40. gltf.scene.rotateY(Math.PI);
  41. gltf.scene.scale.setX(-1);
  42. return await normalized(gltf.scene);
  43. },
  44. "piaochuang.svg": async () => {
  45. const gltf = await gltfLoader.loadAsync("window_1/scene.gltf");
  46. gltf.scene.rotateY(Math.PI);
  47. gltf.scene.traverse((node: any) => {
  48. if (!node.isMesh) return;
  49. if (node.name.includes("Object")) {
  50. node.material = new MeshPhysicalMaterial({
  51. color: 0xffffff, // 浅灰色(可根据需求调整,如0xcccccc)
  52. metalness: 0.1, // 轻微金属感(增强反射)
  53. roughness: 0.01, // 表面光滑度(0-1,越小越光滑)
  54. transmission: 1, // 透光率(模拟玻璃透光,需环境光遮蔽和光源支持)
  55. opacity: 1, // 透明度(与transmission配合使用)
  56. transparent: true, // 启用透明
  57. side: DoubleSide, // 双面渲染(玻璃通常需要)
  58. ior: 0, // 折射率(玻璃约为1.5)
  59. clearcoat: 0.5, // 可选:表面清漆层(增强反光)
  60. });
  61. } else if (node.name.includes("_111111_white_plastic")) {
  62. node.material = new MeshStandardMaterial({
  63. color: 0xffffff, // 浅灰色
  64. metalness: 0.9, // 高金属度
  65. roughness: 0.3, // 中等粗糙度
  66. side: DoubleSide,
  67. });
  68. } else if (
  69. node.name.includes("_111111_seam_0") ||
  70. node.name.includes("_111111__15_0") ||
  71. node.name.includes("_111111_Aluminium_profile_0")
  72. ) {
  73. node.material = new MeshStandardMaterial({
  74. color: 0xffffff,
  75. metalness: 0.8,
  76. roughness: 0.4,
  77. aoMapIntensity: 1.0,
  78. side: DoubleSide,
  79. });
  80. } else {
  81. node.material = new MeshPhongMaterial({
  82. side: DoubleSide,
  83. color: 0xffffff,
  84. });
  85. }
  86. });
  87. const model = await normalized(gltf.scene);
  88. model.scale.add({ x: 0.00015, y: 0.0001, z: 0 });
  89. model.position.add({ x: -0.01, y: -0.005, z: 0.02 });
  90. return model;
  91. },
  92. "chuang.svg": async () => {
  93. const gltf = await gltfLoader.loadAsync("window (3)/scene.gltf");
  94. return await normalized(gltf.scene);
  95. },
  96. "yimen.svg": async () => {
  97. const gltf = await gltfLoader.loadAsync("sliding_door/scene.gltf");
  98. return await normalized(gltf.scene);
  99. },
  100. "shuangkaimen.svg": async () => {
  101. const gltf = await gltfLoader.loadAsync(
  102. "white_double_windowed_door/scene.gltf"
  103. );
  104. return await normalized(gltf.scene);
  105. },
  106. "luodichuang.svg": async () => {
  107. const gltf = await gltfLoader.loadAsync("window2/scene.gltf");
  108. gltf.scene.traverse((node: any) => {
  109. if (node.name?.includes("glass_0")) {
  110. node.material = new MeshPhysicalMaterial({
  111. color: 0xffffff, // 浅灰色(可根据需求调整,如0xcccccc)
  112. metalness: 0.1, // 轻微金属感(增强反射)
  113. roughness: 0.01, // 表面光滑度(0-1,越小越光滑)
  114. transmission: 1, // 透光率(模拟玻璃透光,需环境光遮蔽和光源支持)
  115. opacity: 1, // 透明度(与transmission配合使用)
  116. transparent: true, // 启用透明
  117. side: DoubleSide, // 双面渲染(玻璃通常需要)
  118. ior: 0, // 折射率(玻璃约为1.5)
  119. clearcoat: 0.5, // 可选:表面清漆层(增强反光)
  120. });
  121. }
  122. });
  123. return await normalized(gltf.scene);
  124. },
  125. "DoubleBed.svg": async () => {
  126. const gltf = await gltfLoader.loadAsync("bed/scene.gltf");
  127. const models: Object3D[] = [];
  128. const delModelName = ["Pillow_2002", "Plane002"];
  129. gltf.scene.traverse((child: any) => {
  130. if (delModelName.some((n) => n === child.name)) {
  131. models.push(child);
  132. }
  133. });
  134. models.forEach((m) => m.parent?.remove(m));
  135. const model = await normalized(gltf.scene);
  136. model.position.setY(model.position.y - 0.131);
  137. return model;
  138. },
  139. "SingleBed.svg": async () => {
  140. const gltf = await gltfLoader.loadAsync("woodbed/scene.gltf");
  141. const model = await normalized(gltf.scene);
  142. model.rotateY(Math.PI / 2);
  143. return model;
  144. },
  145. sf: async () => {
  146. const gltf = await gltfLoader.loadAsync(
  147. "sofa_set_-_4_type_of_sofa_lowpoly./scene.gltf"
  148. );
  149. return gltf.scene;
  150. },
  151. "ThreeSofa.svg": async () => {
  152. const gltf = await gltfLoader.loadAsync(
  153. "sofa_-_game_ready_model/scene.gltf"
  154. );
  155. const model = await normalized(gltf.scene, undefined);
  156. model.traverse((child: any) => {
  157. if (child.isMesh) {
  158. child.material.color = new Color(0x444444);
  159. }
  160. });
  161. return model;
  162. },
  163. "SingleSofa.svg": async () => {
  164. const scene = (await getModel("sf"))!;
  165. const models: Object3D[] = [];
  166. const pickModelName = ["Cube026"];
  167. scene.traverse((child: any) => {
  168. if (pickModelName.some((n) => n === child.name)) {
  169. models.push(child);
  170. }
  171. });
  172. const model = new Object3D().add(...models.map((item) => item.clone()));
  173. model.rotateY(Math.PI / 2);
  174. return await normalized(model);
  175. },
  176. "Desk.svg": async () => {
  177. const scene = (await getModel("sf"))!;
  178. const models: Object3D[] = [];
  179. const pickModelName = ["Cube004"];
  180. scene.traverse((child: any) => {
  181. if (pickModelName.some((n) => n === child.name)) {
  182. models.push(child);
  183. }
  184. });
  185. const model = new Object3D().add(...models.map((item) => item.clone()));
  186. model.rotateY(Math.PI / 2);
  187. return await normalized(model);
  188. },
  189. "TeaTable.svg": async () => {
  190. return (await getModel("Desk.svg"))!.clone();
  191. },
  192. "DiningTable.svg": async () => {
  193. const desk = new Object3D().add((await getModel("Desk.svg"))!.clone());
  194. const chair = (await getModel("Chair.svg"))!;
  195. const model = new Object3D();
  196. const lt = chair.clone();
  197. lt.position.set(-0.14, -0.5, 0.25);
  198. lt.scale.set(0.5, 1.2, 0.8);
  199. lt.rotateY(Math.PI);
  200. model.add(lt);
  201. const rt = chair.clone();
  202. rt.position.set(0.14, -0.5, 0.25);
  203. rt.scale.set(0.5, 1.2, 0.8);
  204. rt.rotateY(Math.PI);
  205. model.add(rt);
  206. const lb = chair.clone();
  207. lb.position.set(-0.14, -0.5, -0.25);
  208. lb.scale.set(0.5, 1.2, 0.8);
  209. model.add(lb);
  210. const rb = chair.clone();
  211. rb.position.set(0.14, -0.5, -0.25);
  212. rb.scale.set(0.5, 1.2, 0.8);
  213. model.add(rb);
  214. desk.scale.set(1.2, 1, 0.55);
  215. model.add(desk);
  216. const nModel = await normalized(model);
  217. nModel.position.sub({ x: 0, y: 0.075, z: 0 });
  218. return nModel;
  219. },
  220. "Chair.svg": async () => {
  221. const gltf = await gltfLoader.loadAsync("psx_chair/scene.gltf");
  222. const model = await normalized(gltf.scene, undefined);
  223. model.scale.add({ x: 0, y: 0.3, z: 0 });
  224. return model;
  225. },
  226. "TV.svg": async () => {
  227. const gltf = await gltfLoader.loadAsync("tv_and_tv_stand/scene.gltf");
  228. const model = await normalized(gltf.scene, undefined);
  229. return model;
  230. },
  231. "Plant.svg": async () => {
  232. const gltf = await gltfLoader.loadAsync("pothos_plant/scene.gltf");
  233. const model = await normalized(gltf.scene, undefined);
  234. return model;
  235. },
  236. "Washstand.svg": async () => {
  237. const gltf = await gltfLoader.loadAsync("washbasin/scene.gltf");
  238. gltf.scene.rotateY(Math.PI);
  239. const model = await normalized(gltf.scene, undefined);
  240. return model;
  241. },
  242. "Closestool.svg": async () => {
  243. const gltf = await gltfLoader.loadAsync("toilet/scene.gltf");
  244. const model = await normalized(gltf.scene, undefined);
  245. model.traverse((child: any) => {
  246. if (child.isMesh) {
  247. child.material.color = new Color(0xffffff);
  248. }
  249. });
  250. return model;
  251. },
  252. "Wardrobe.svg": async () => {
  253. const gltf = await gltfLoader.loadAsync("wardrobe_14722-22/scene.gltf");
  254. const model = await normalized(gltf.scene, undefined);
  255. model.traverse((child: any) => {
  256. if (child.isMesh) {
  257. child.material.color = new Color(0xcbc3b3);
  258. }
  259. });
  260. return model;
  261. },
  262. "BedsideCupboard.svg": async () => {
  263. const gltf = await gltfLoader.loadAsync(
  264. "low_poly_bedside_table/scene.gltf"
  265. );
  266. const model = await normalized(gltf.scene, undefined);
  267. model.traverse((child: any) => {
  268. if (child.isMesh) {
  269. child.material.color = new Color(0xffffff);
  270. }
  271. });
  272. return model;
  273. },
  274. "CombinationSofa.svg": async () => {
  275. const tsofa = (await getModel("ThreeSofa.svg"))!.clone();
  276. const ssofa = (await getModel("SingleSofa.svg"))!.clone();
  277. const tea = (await getModel("TeaTable.svg"))!.clone();
  278. const model = new Object3D();
  279. // tsofa.rotateY(-Math.PI / 2)
  280. tsofa.scale.multiply({ x: 0.8, y: 1, z: 0.4 });
  281. tsofa.position.add({ x: -0, y: 0, z: -0.6 });
  282. model.add(tsofa);
  283. ssofa.rotateY(-Math.PI / 2);
  284. ssofa.scale.multiply({ x: 0.4, y: 1, z: 0.4 });
  285. ssofa.position.add({ x: -0.15, y: 0, z: -2.2 });
  286. model.add(ssofa);
  287. tea.scale.multiply({ x: 0.8, y: 0.5, z: 0.4 });
  288. tea.position.add({ x: -0, y: -0.13, z: 0 });
  289. model.add(tea);
  290. return normalized(model);
  291. },
  292. kitchen: async () => {
  293. const gltf = await gltfLoader.loadAsync(
  294. "basic_kitchen_cabinets_and_counter/scene.gltf"
  295. );
  296. gltf.scene.rotateY(-Math.PI);
  297. return gltf.scene;
  298. },
  299. "Cupboard.svg": async () => {
  300. const gltf = await gltfLoader.loadAsync("kitchen_cabinets (1)/scene.gltf");
  301. gltf.scene.rotateY(Math.PI / 2);
  302. const model = await normalized(gltf.scene);
  303. model.traverse((child: any) => {
  304. if (
  305. child.isMesh &&
  306. ["pCube1_cor_0", "pCube8_cor_0"].includes(child.name)
  307. ) {
  308. child.material.color = new Color(0xffffff);
  309. }
  310. });
  311. return model;
  312. },
  313. "GasStove.svg": async () => {
  314. const gltf = await gltfLoader.loadAsync("burner_gas_stove/scene.gltf");
  315. const model = await normalized(gltf.scene);
  316. model.traverse((child: any) => {
  317. if (child.isMesh) {
  318. child.material.emissive = new Color(0x222222)
  319. }
  320. });
  321. return model;
  322. },
  323. };
  324. export const levelResources: Record<
  325. string,
  326. {
  327. bottom?: number | string;
  328. height?: number | string | "full";
  329. top?: number | string;
  330. }
  331. > = {
  332. "SingleBed.svg": {
  333. height: 70,
  334. },
  335. "ThreeSofa.svg": {
  336. height: 90,
  337. },
  338. "SingleSofa.svg": {
  339. height: 90,
  340. },
  341. "CombinationSofa.svg": {
  342. height: 90,
  343. },
  344. "Desk.svg": {
  345. height: 80,
  346. },
  347. "TeaTable.svg": {
  348. height: 50,
  349. },
  350. "DiningTable.svg": {
  351. height: 100,
  352. },
  353. "Chair.svg": {
  354. height: 80,
  355. },
  356. "TV.svg": {
  357. height: 120,
  358. },
  359. "Washstand.svg": {
  360. height: 100,
  361. },
  362. "Closestool.svg": {
  363. height: 45,
  364. },
  365. "Wardrobe.svg": {
  366. height: "full",
  367. },
  368. "BedsideCupboard.svg": {
  369. height: 50,
  370. },
  371. "piaochuang.svg": {
  372. top: 4,
  373. bottom: 40,
  374. },
  375. "men_l.svg": {
  376. height: "full",
  377. },
  378. "yimen.svg": {
  379. height: "full",
  380. },
  381. "shuangkaimen.svg": {
  382. height: "full",
  383. },
  384. "luodichuang.svg": {
  385. height: "full",
  386. },
  387. "Cupboard.svg": {
  388. height: "full",
  389. },
  390. "GasStove.svg": {
  391. height: 10,
  392. bottom: "0.335",
  393. },
  394. };
  395. export const getLevel = (type: string, fullHeight: number) => {
  396. const ndx = type.lastIndexOf("/");
  397. if (~ndx) {
  398. type = type.substring(ndx + 1);
  399. }
  400. const transform = (data: any): Record<string, number> => {
  401. const tdata: Record<string, number> = {};
  402. for (const key of Object.keys(data)) {
  403. if (data[key] === "full") {
  404. tdata[key] = fullHeight;
  405. } else if (typeof data[key] === "string") {
  406. tdata[key] = parseFloat(data[key]) * fullHeight;
  407. } else {
  408. tdata[key] = data[key];
  409. }
  410. }
  411. return tdata;
  412. };
  413. if (!levelResources[type]) {
  414. return {};
  415. }
  416. const data = transform(levelResources[type]);
  417. if (!data.height && "top" in data && "bottom" in data) {
  418. data.height = fullHeight - data.top - data.bottom;
  419. }
  420. return data;
  421. };
  422. export const getModel = (() => {
  423. const typeModels: Record<string, Promise<Object3D | undefined>> = {};
  424. return (type: string) => {
  425. const ndx = type.lastIndexOf("/");
  426. if (~ndx) {
  427. type = type.substring(ndx + 1);
  428. }
  429. if (type in typeModels) {
  430. return typeModels[type];
  431. }
  432. if (type in resources) {
  433. typeModels[type] = resources[type]();
  434. typeModels[type].catch(() => {
  435. delete typeModels[type];
  436. });
  437. return typeModels[type];
  438. }
  439. };
  440. })();
  441. export const fullMesh = new Mesh(
  442. new BoxGeometry(1, 1, 1),
  443. new MeshPhongMaterial({ color: 0xffffff })
  444. );
  445. fullMesh.receiveShadow = fullMesh.castShadow = true;