resource.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740
  1. import { animation } from "@/core/hook/use-animation";
  2. import {
  3. Box3,
  4. BoxGeometry,
  5. Color,
  6. DoubleSide,
  7. Mesh,
  8. MeshPhongMaterial,
  9. MeshPhysicalMaterial,
  10. MeshStandardMaterial,
  11. Object3D,
  12. Quaternion,
  13. Vector3,
  14. } from "three";
  15. import { GLTFLoader } from "three/examples/jsm/Addons.js";
  16. const gltfLoader = new GLTFLoader().setPath("/static/models/");
  17. const normalized = async (model: Object3D, pub = true) => {
  18. const parent = new Object3D();
  19. parent.add(model);
  20. const bbox = new Box3().setFromObject(parent);
  21. const size = bbox.getSize(new Vector3());
  22. if (pub) {
  23. parent.scale.set(1 / size.x, 1 / size.y, 1 / size.z);
  24. } else {
  25. const min = Math.max(size.x, size.y, size.z);
  26. parent.scale.set(1 / min, 1 / min, 1 / min);
  27. }
  28. model.traverse((child: any) => {
  29. if (child.isMesh) {
  30. child.receiveShadow = true;
  31. child.castShadow = true;
  32. }
  33. });
  34. const center = new Box3().setFromObject(parent).getCenter(new Vector3());
  35. parent.position.sub({ x: center.x, y: center.y, z: center.z });
  36. return parent;
  37. };
  38. const resources: Record<string, () => Promise<Object3D>> = {
  39. "men_l.svg": async () => {
  40. const gltf = await gltfLoader.loadAsync("door_with_frame/scene.gltf");
  41. gltf.scene.rotateY(Math.PI);
  42. gltf.scene.scale.setX(-1);
  43. return await normalized(gltf.scene);
  44. },
  45. "piaochuang.svg": async () => {
  46. const gltf = await gltfLoader.loadAsync("bay_window/scene.gltf");
  47. gltf.scene.scale.setX(-1);
  48. const names = ["01_glass_0", "02_glass_0", "03_glass_0", "04_glass_0"]
  49. gltf.scene.traverse((node: any) => {
  50. if (names.includes(node.name)) {
  51. node.material = new MeshPhysicalMaterial({
  52. color: 0xffffff, // 浅灰色(可根据需求调整,如0xcccccc)
  53. metalness: 0.1, // 轻微金属感(增强反射)
  54. roughness: 0.01, // 表面光滑度(0-1,越小越光滑)
  55. transmission: 1, // 透光率(模拟玻璃透光,需环境光遮蔽和光源支持)
  56. opacity: 1, // 透明度(与transmission配合使用)
  57. transparent: true, // 启用透明
  58. side: DoubleSide, // 双面渲染(玻璃通常需要)
  59. ior: 0, // 折射率(玻璃约为1.5)
  60. clearcoat: 0.5, // 可选:表面清漆层(增强反光)
  61. });
  62. }
  63. })
  64. let copyModel: Object3D
  65. let parent: Object3D
  66. gltf.scene.traverse((node: any) => {
  67. if (node.name === "01") {
  68. copyModel = node.clone()
  69. parent = node.parent
  70. }
  71. })
  72. copyModel!.scale.add({x: 0, y: 0.06, z: -0.1})
  73. const left = copyModel!.clone()
  74. left.name = "05"
  75. left.rotation.y = -Math.PI / 2
  76. left.position.set(-170, 0, 50)
  77. parent!.add(left)
  78. const right = copyModel!
  79. right.name = "06"
  80. right.rotation.y = Math.PI / 2
  81. right.position.set(170, 0, 50)
  82. parent!.add(right)
  83. const model = await normalized(gltf.scene);
  84. // model.scale.add(({x: 0.015, y: 0, z: 0}))
  85. // model.position.add({x: 0, y: 0, z: 0})
  86. left.scale.add({x: -0.3, y: 0, z: 0})
  87. left.position.add({x: -7, y: 0, z: -16})
  88. right.scale.add({x: -0.3, y: 0, z: 0})
  89. right.position.add({x: 7, y: 0, z: -16})
  90. return model
  91. },
  92. "piaochuang1.svg": async () => {
  93. const gltf = await gltfLoader.loadAsync("window_1/scene.gltf");
  94. gltf.scene.rotateY(Math.PI);
  95. gltf.scene.traverse((node: any) => {
  96. if (!node.isMesh) return;
  97. if (node.name.includes("Object")) {
  98. node.material = new MeshPhysicalMaterial({
  99. color: 0xffffff, // 浅灰色(可根据需求调整,如0xcccccc)
  100. metalness: 0.1, // 轻微金属感(增强反射)
  101. roughness: 0.01, // 表面光滑度(0-1,越小越光滑)
  102. transmission: 1, // 透光率(模拟玻璃透光,需环境光遮蔽和光源支持)
  103. opacity: 1, // 透明度(与transmission配合使用)
  104. transparent: true, // 启用透明
  105. side: DoubleSide, // 双面渲染(玻璃通常需要)
  106. ior: 0, // 折射率(玻璃约为1.5)
  107. clearcoat: 0.5, // 可选:表面清漆层(增强反光)
  108. });
  109. } else if (node.name.includes("_111111_white_plastic")) {
  110. node.material = new MeshStandardMaterial({
  111. color: 0xffffff, // 浅灰色
  112. metalness: 0.9, // 高金属度
  113. roughness: 0.3, // 中等粗糙度
  114. side: DoubleSide,
  115. });
  116. } else if (
  117. node.name.includes("_111111_seam_0") ||
  118. node.name.includes("_111111__15_0") ||
  119. node.name.includes("_111111_Aluminium_profile_0")
  120. ) {
  121. node.material = new MeshStandardMaterial({
  122. color: 0xffffff,
  123. metalness: 0.8,
  124. roughness: 0.4,
  125. aoMapIntensity: 1.0,
  126. side: DoubleSide,
  127. });
  128. } else {
  129. node.material = new MeshPhongMaterial({
  130. side: DoubleSide,
  131. color: 0xffffff,
  132. });
  133. }
  134. });
  135. const model = await normalized(gltf.scene);
  136. model.scale.add({ x: 0.00015, y: 0.0001, z: 0 });
  137. model.position.add({ x: -0.01, y: -0.005, z: 0.02 });
  138. return model;
  139. },
  140. "chuang.svg": async () => {
  141. const gltf = await gltfLoader.loadAsync("window (3)/scene.gltf");
  142. return await normalized(gltf.scene);
  143. },
  144. "yimen.svg": async () => {
  145. const gltf = await gltfLoader.loadAsync("sliding_door/scene.gltf");
  146. return await normalized(gltf.scene);
  147. },
  148. "shuangkaimen.svg": async () => {
  149. const gltf = await gltfLoader.loadAsync(
  150. "white_double_windowed_door/scene.gltf"
  151. );
  152. return await normalized(gltf.scene);
  153. },
  154. "luodichuang.svg": async () => {
  155. const gltf = await gltfLoader.loadAsync("window2/scene.gltf");
  156. gltf.scene.traverse((node: any) => {
  157. if (node.name?.includes("glass_0")) {
  158. node.material = new MeshPhysicalMaterial({
  159. color: 0xffffff, // 浅灰色(可根据需求调整,如0xcccccc)
  160. metalness: 0.1, // 轻微金属感(增强反射)
  161. roughness: 0.01, // 表面光滑度(0-1,越小越光滑)
  162. transmission: 1, // 透光率(模拟玻璃透光,需环境光遮蔽和光源支持)
  163. opacity: 1, // 透明度(与transmission配合使用)
  164. transparent: true, // 启用透明
  165. side: DoubleSide, // 双面渲染(玻璃通常需要)
  166. ior: 0, // 折射率(玻璃约为1.5)
  167. clearcoat: 0.5, // 可选:表面清漆层(增强反光)
  168. });
  169. }
  170. });
  171. return await normalized(gltf.scene);
  172. },
  173. "DoubleBed.svg": async () => {
  174. const gltf = await gltfLoader.loadAsync("bed/scene.gltf");
  175. const models: Object3D[] = [];
  176. const delModelName = ["Pillow_2002", "Plane002"];
  177. gltf.scene.traverse((child: any) => {
  178. if (delModelName.some((n) => n === child.name)) {
  179. models.push(child);
  180. }
  181. });
  182. models.forEach((m) => m.parent?.remove(m));
  183. const model = await normalized(gltf.scene);
  184. model.position.setY(model.position.y);
  185. return model;
  186. },
  187. "SingleBed.svg": async () => {
  188. const gltf = await gltfLoader.loadAsync("woodbed/scene.gltf");
  189. const model = await normalized(gltf.scene);
  190. model.rotateY(Math.PI / 2);
  191. return model;
  192. },
  193. sf: async () => {
  194. const gltf = await gltfLoader.loadAsync(
  195. "sofa_set_-_4_type_of_sofa_lowpoly./scene.gltf"
  196. );
  197. return gltf.scene;
  198. },
  199. "ThreeSofa.svg": async () => {
  200. const gltf = await gltfLoader.loadAsync(
  201. "sofa_-_game_ready_model/scene.gltf"
  202. );
  203. const model = await normalized(gltf.scene, undefined);
  204. model.traverse((child: any) => {
  205. if (child.isMesh) {
  206. child.material.color = new Color(0x444444);
  207. }
  208. });
  209. return model;
  210. },
  211. "SingleSofa.svg": async () => {
  212. const scene = (await getModel("sf"))!;
  213. const models: Object3D[] = [];
  214. const pickModelName = ["Cube026"];
  215. scene.traverse((child: any) => {
  216. if (pickModelName.some((n) => n === child.name)) {
  217. models.push(child);
  218. }
  219. });
  220. const model = new Object3D().add(...models.map((item) => item.clone()));
  221. model.rotateY(Math.PI / 2);
  222. return await normalized(model);
  223. },
  224. "Desk.svg": async () => {
  225. const scene = (await getModel("sf"))!;
  226. const models: Object3D[] = [];
  227. const pickModelName = ["Cube004"];
  228. scene.traverse((child: any) => {
  229. if (pickModelName.some((n) => n === child.name)) {
  230. models.push(child);
  231. }
  232. });
  233. const model = new Object3D().add(...models.map((item) => item.clone()));
  234. model.rotateY(Math.PI / 2);
  235. return await normalized(model);
  236. },
  237. "TeaTable.svg": async () => {
  238. return (await getModel("Desk.svg"))!.clone();
  239. },
  240. "DiningTable.svg": async () => {
  241. const desk = new Object3D().add((await getModel("Desk.svg"))!.clone());
  242. const chair = (await getModel("Chair.svg"))!;
  243. const model = new Object3D();
  244. const lt = chair.clone();
  245. lt.position.set(-0.14, -0.5, 0.25);
  246. lt.scale.set(0.5, 1.2, 0.8);
  247. lt.rotateY(Math.PI);
  248. model.add(lt);
  249. const rt = chair.clone();
  250. rt.position.set(0.14, -0.5, 0.25);
  251. rt.scale.set(0.5, 1.2, 0.8);
  252. rt.rotateY(Math.PI);
  253. model.add(rt);
  254. const lb = chair.clone();
  255. lb.position.set(-0.14, -0.5, -0.25);
  256. lb.scale.set(0.5, 1.2, 0.8);
  257. model.add(lb);
  258. const rb = chair.clone();
  259. rb.position.set(0.14, -0.5, -0.25);
  260. rb.scale.set(0.5, 1.2, 0.8);
  261. model.add(rb);
  262. desk.scale.set(1.2, 1, 0.55);
  263. model.add(desk);
  264. const nModel = await normalized(model);
  265. nModel.position.sub({ x: 0, y: 0.075, z: 0 });
  266. return nModel;
  267. },
  268. "Chair.svg": async () => {
  269. const gltf = await gltfLoader.loadAsync("psx_chair/scene.gltf");
  270. const model = await normalized(gltf.scene, undefined);
  271. model.position.add({x: 0, y: -0.1, z: 0})
  272. return model;
  273. },
  274. "TV.svg": async () => {
  275. const gltf = await gltfLoader.loadAsync("tv_and_tv_stand/scene.gltf");
  276. const model = await normalized(gltf.scene, undefined);
  277. return model;
  278. },
  279. "Plant.svg": async () => {
  280. const gltf = await gltfLoader.loadAsync("pothos_plant/scene.gltf");
  281. const model = await normalized(gltf.scene, undefined);
  282. return model;
  283. },
  284. "Washstand.svg": async () => {
  285. const gltf = await gltfLoader.loadAsync("washbasin/scene.gltf");
  286. gltf.scene.rotateY(Math.PI);
  287. const model = await normalized(gltf.scene, undefined);
  288. return model;
  289. },
  290. "Closestool.svg": async () => {
  291. const gltf = await gltfLoader.loadAsync("toilet/scene.gltf");
  292. const model = await normalized(gltf.scene, undefined);
  293. model.traverse((child: any) => {
  294. if (child.isMesh) {
  295. child.material.color = new Color(0xffffff);
  296. }
  297. });
  298. return model;
  299. },
  300. "Wardrobe.svg": async () => {
  301. const gltf = await gltfLoader.loadAsync("wardrobe_14722-22/scene.gltf");
  302. const model = await normalized(gltf.scene, undefined);
  303. model.traverse((child: any) => {
  304. if (child.isMesh) {
  305. child.material.color = new Color(0xcbc3b3);
  306. }
  307. });
  308. return model;
  309. },
  310. "BedsideCupboard.svg": async () => {
  311. const gltf = await gltfLoader.loadAsync(
  312. "low_poly_bedside_table/scene.gltf"
  313. );
  314. gltf.scene.rotateY(Math.PI);
  315. const model = await normalized(gltf.scene, undefined);
  316. model.traverse((child: any) => {
  317. if (child.isMesh) {
  318. child.material.color = new Color(0xffffff);
  319. }
  320. });
  321. return model;
  322. },
  323. "CombinationSofa.svg": async () => {
  324. const tsofa = (await getModel("ThreeSofa.svg"))!.clone();
  325. const ssofa = (await getModel("SingleSofa.svg"))!.clone();
  326. const tea = (await getModel("TeaTable.svg"))!.clone();
  327. const model = new Object3D();
  328. // tsofa.rotateY(-Math.PI / 2)
  329. tsofa.scale.multiply({ x: 0.8, y: 1, z: 0.4 });
  330. tsofa.position.add({ x: -0, y: 0, z: -0.6 });
  331. model.add(tsofa);
  332. ssofa.rotateY(-Math.PI / 2);
  333. ssofa.scale.multiply({ x: 0.4, y: 1, z: 0.4 });
  334. ssofa.position.add({ x: -0.15, y: 0, z: -2.2 });
  335. model.add(ssofa);
  336. tea.scale.multiply({ x: 0.8, y: 0.5, z: 0.4 });
  337. tea.position.add({ x: -0, y: -0.13, z: 0 });
  338. model.add(tea);
  339. return normalized(model);
  340. },
  341. kitchen: async () => {
  342. const gltf = await gltfLoader.loadAsync(
  343. "basic_kitchen_cabinets_and_counter/scene.gltf"
  344. );
  345. gltf.scene.rotateY(-Math.PI);
  346. return gltf.scene;
  347. },
  348. "Cupboard.svg": async () => {
  349. const gltf = await gltfLoader.loadAsync("kitchen_cabinets (1)/scene.gltf");
  350. gltf.scene.rotateY(Math.PI / 2);
  351. const model = await normalized(gltf.scene);
  352. model.traverse((child: any) => {
  353. if (
  354. child.isMesh &&
  355. ["pCube1_cor_0", "pCube8_cor_0"].includes(child.name)
  356. ) {
  357. child.material.color = new Color(0xffffff);
  358. }
  359. });
  360. return model;
  361. },
  362. "GasStove.svg": async () => {
  363. const gltf = await gltfLoader.loadAsync("burner_gas_stove/scene.gltf");
  364. const model = await normalized(gltf.scene);
  365. model.traverse((child: any) => {
  366. if (child.isMesh) {
  367. child.material.emissive = new Color(0x222222);
  368. }
  369. });
  370. return model;
  371. },
  372. };
  373. export const levelResources: Record<
  374. string,
  375. {
  376. bottom?: number | string;
  377. height?: number | string | "full";
  378. top?: number | string;
  379. }
  380. > = {
  381. "SingleBed.svg": {
  382. height: 70,
  383. },
  384. "ThreeSofa.svg": {
  385. height: 90,
  386. },
  387. "SingleSofa.svg": {
  388. height: 90,
  389. },
  390. "CombinationSofa.svg": {
  391. height: 90,
  392. },
  393. "Desk.svg": {
  394. height: 80,
  395. },
  396. "TeaTable.svg": {
  397. height: 50,
  398. },
  399. "DiningTable.svg": {
  400. height: 100,
  401. },
  402. "Chair.svg": {
  403. height: 80,
  404. },
  405. "TV.svg": {
  406. height: 120,
  407. },
  408. "Washstand.svg": {
  409. height: 100,
  410. },
  411. "Closestool.svg": {
  412. height: 45,
  413. },
  414. "Wardrobe.svg": {
  415. height: "full",
  416. },
  417. "BedsideCupboard.svg": {
  418. height: 50,
  419. },
  420. "piaochuang.svg": {
  421. top: 4,
  422. bottom: 40,
  423. },
  424. "men_l.svg": {
  425. height: "full",
  426. },
  427. "yimen.svg": {
  428. height: "full",
  429. },
  430. "shuangkaimen.svg": {
  431. height: "full",
  432. },
  433. "luodichuang.svg": {
  434. height: "full",
  435. },
  436. "Cupboard.svg": {
  437. height: "full",
  438. },
  439. "GasStove.svg": {
  440. height: 10,
  441. bottom: "0.335",
  442. },
  443. };
  444. export type ModelSwitch = (open: boolean) => ReturnType<typeof animation>;
  445. export type SwitchResult = ReturnType<ModelSwitch>;
  446. export const switchResources: Record<
  447. string,
  448. (model: Object3D, render: () => void) => ModelSwitch
  449. > = {
  450. "men_l.svg": (model, render) => {
  451. let node: Object3D;
  452. model.traverse((child) => {
  453. if (child.name === "Plane001") {
  454. node = child;
  455. }
  456. });
  457. return (open: boolean) =>
  458. animation(
  459. { z: node!.rotation.z },
  460. { z: open ? -Math.PI / 2 : 0 },
  461. (data) => {
  462. node.rotation.z = data.z;
  463. render();
  464. }
  465. );
  466. },
  467. "luodichuang.svg": (model, render) => {
  468. let nodes: Object3D[] = [];
  469. const initVals = [121.44782257080078, -121.4478];
  470. const finalVals = [58, -58];
  471. model.traverse((child) => {
  472. if (child.name === "01") {
  473. nodes[0] = child;
  474. } else if (child.name === "04") {
  475. nodes[1] = child;
  476. }
  477. });
  478. return (open: boolean) =>
  479. animation(
  480. nodes.map((node) => node.position.x),
  481. open ? finalVals : initVals,
  482. (data) => {
  483. nodes.forEach((node, i) => (node.position.x = data[i]));
  484. render();
  485. }
  486. );
  487. },
  488. "chuang.svg": (model, render) => {
  489. let nodes: Object3D[] = [];
  490. model.traverse((child) => {
  491. if (child.name === "L") {
  492. nodes[0] = child;
  493. } else if (child.name === "R") {
  494. nodes[1] = child;
  495. }
  496. });
  497. const initVals = nodes.map((item) => ({
  498. position: item.position.clone(),
  499. quat: item.quaternion.clone(),
  500. }));
  501. const changes = [
  502. { origin: new Vector3(108, 0, 0), angle: -Math.PI / 3 },
  503. { origin: new Vector3(-108, 0, 0), angle: Math.PI / 3 },
  504. ];
  505. const finalVals = initVals.map((item, i) => {
  506. const { origin, angle } = changes[i];
  507. const qua = new Quaternion().setFromAxisAngle(
  508. { x: 0, y: 1, z: 0 },
  509. angle
  510. );
  511. const finalPosition = item.position
  512. .clone()
  513. .sub(origin)
  514. .applyQuaternion(qua)
  515. .add(origin);
  516. const finalQua = item.quat.clone().multiply(qua);
  517. return {
  518. position: finalPosition,
  519. quat: finalQua,
  520. };
  521. });
  522. return (open: boolean) =>
  523. animation(
  524. nodes.map((node) => ({
  525. position: node.position,
  526. quat: node.quaternion,
  527. })),
  528. open ? finalVals : initVals,
  529. (data) => {
  530. nodes.forEach((node, i) => {
  531. node.position.copy(data[i].position);
  532. node.quaternion.copy(data[i].quat);
  533. });
  534. render();
  535. }
  536. );
  537. },
  538. "yimen.svg": (model, render) => {
  539. let node: Object3D;
  540. model.traverse((child) => {
  541. if (child.name === "16668_84x96_Slider_Door-Black_V1001_0") {
  542. node = child;
  543. }
  544. });
  545. const initVal = node!.position.x;
  546. const finalVal = initVal - 100;
  547. return (open: boolean) =>
  548. animation(
  549. { x: node.position.x },
  550. { x: open ? finalVal : initVal },
  551. (data) => {
  552. node.position.setX(data.x);
  553. render();
  554. }
  555. );
  556. },
  557. "Wardrobe.svg": (model, render) => {
  558. let nodes: Object3D[] = [];
  559. const initVals = [0, 0];
  560. const finalVals = [Math.PI / 2, Math.PI / 2];
  561. const names = [
  562. 'RootNode',
  563. 'VIFS079_NCS_S_1502-Y50R_semigloss_0',
  564. 'VIFS077_NCS_S_1502-Y50R_semigloss_0',
  565. ]
  566. model.traverse((child) => {
  567. if (names.includes(child.name)) {
  568. nodes.push(child)
  569. }
  570. });
  571. return (open: boolean) =>
  572. animation(
  573. nodes.map((node) => node.rotation.y),
  574. open ? finalVals : initVals,
  575. (data) => {
  576. nodes.forEach((node, i) => (node.rotateY(data[i])));
  577. console.log(nodes, data)
  578. render();
  579. }
  580. );
  581. },
  582. "piaochuang.svg": (model, render) => {
  583. let nodes: Object3D[] = [];
  584. const names = ["01", "02", "03", "04"]
  585. model.traverse((child) => {
  586. if (names.includes(child.name)) {
  587. nodes.push(child)
  588. }
  589. });
  590. const initVals = nodes.map((item) => ({
  591. position: item.position.clone(),
  592. quat: item.quaternion.clone(),
  593. }));
  594. const changes = [
  595. { origin: new Vector3(80, 0, 0), angle: Math.PI / 3 },
  596. { origin: new Vector3(80, 0, 0), angle: -Math.PI / 3 },
  597. { origin: new Vector3(-80, 0, 0), angle: Math.PI / 3 },
  598. { origin: new Vector3(-80, 0, 0), angle: -Math.PI / 3 },
  599. ];
  600. const finalVals = initVals.map((item, i) => {
  601. const { origin, angle } = changes[i];
  602. const qua = new Quaternion().setFromAxisAngle(
  603. { x: 0, y: 1, z: 0 },
  604. angle
  605. );
  606. const finalPosition = item.position
  607. .clone()
  608. .sub(origin)
  609. .applyQuaternion(qua)
  610. .add(origin);
  611. const finalQua = item.quat.clone().multiply(qua);
  612. return {
  613. position: finalPosition,
  614. quat: finalQua,
  615. // quat: item.quat
  616. };
  617. });
  618. return (open: boolean) =>
  619. animation(
  620. nodes.map((node) => ({
  621. position: node.position,
  622. quat: node.quaternion,
  623. })),
  624. open ? finalVals : initVals,
  625. (data) => {
  626. nodes.forEach((node, i) => {
  627. node.position.copy(data[i].position);
  628. node.quaternion.copy(data[i].quat);
  629. });
  630. render();
  631. }
  632. );
  633. },
  634. };
  635. export const getModelSwitch = (type: string) => {
  636. const ndx = type.lastIndexOf("/");
  637. if (~ndx) {
  638. type = type.substring(ndx + 1);
  639. }
  640. return switchResources[type];
  641. };
  642. export const getLevel = (type: string, fullHeight: number) => {
  643. const ndx = type.lastIndexOf("/");
  644. if (~ndx) {
  645. type = type.substring(ndx + 1);
  646. }
  647. const transform = (data: any): Record<string, number> => {
  648. const tdata: Record<string, number> = {};
  649. for (const key of Object.keys(data)) {
  650. if (data[key] === "full") {
  651. tdata[key] = fullHeight;
  652. } else if (typeof data[key] === "string") {
  653. tdata[key] = parseFloat(data[key]) * fullHeight;
  654. } else {
  655. tdata[key] = data[key];
  656. }
  657. }
  658. return tdata;
  659. };
  660. if (!levelResources[type]) {
  661. return {};
  662. }
  663. const data = transform(levelResources[type]);
  664. if (!data.height && "top" in data && "bottom" in data) {
  665. data.height = fullHeight - data.top - data.bottom;
  666. }
  667. return data;
  668. };
  669. export const getModel = (() => {
  670. const typeModels: Record<string, Promise<Object3D | undefined>> = {};
  671. return (type: string) => {
  672. const ndx = type.lastIndexOf("/");
  673. if (~ndx) {
  674. type = type.substring(ndx + 1);
  675. }
  676. if (type in typeModels) {
  677. return typeModels[type];
  678. }
  679. if (type in resources) {
  680. typeModels[type] = resources[type]();
  681. typeModels[type].catch(() => {
  682. delete typeModels[type];
  683. });
  684. return typeModels[type];
  685. }
  686. };
  687. })();
  688. export const fullMesh = new Mesh(
  689. new BoxGeometry(1, 1, 1),
  690. new MeshPhongMaterial({ color: 0xffffff })
  691. );
  692. fullMesh.receiveShadow = fullMesh.castShadow = true;