platform-resource.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. import { Pos, Size } from "@/utils/math";
  2. import { extractConnectedSegments } from "@/utils/polygon";
  3. import { validNum } from "@/utils/shared";
  4. import { aiIconMap, iconGroups } from "../constant";
  5. import { Euler, MathUtils, Object3D, Quaternion, Vector3 } from "three";
  6. export enum SCENE_TYPE {
  7. fuse = "fuse",
  8. mesh = "mesh",
  9. cloud = "cloud",
  10. }
  11. export type Scene = {
  12. type: SCENE_TYPE;
  13. m: string;
  14. title: string;
  15. id: string;
  16. };
  17. export type SceneFloor = {
  18. name: string;
  19. subgroup?: number;
  20. geos: (Pos & { z: number })[][];
  21. thumb?: string;
  22. box?: {
  23. bound: {
  24. x_min: number;
  25. x_max: number;
  26. y_min: number;
  27. y_max: number;
  28. z_min: number;
  29. z_max: number;
  30. };
  31. rotate: number;
  32. scale: number;
  33. };
  34. compass?: number;
  35. };
  36. export type SceneFloors = SceneFloor[];
  37. export type Taging = {
  38. url: string;
  39. position: Pos & { z: number };
  40. size?: Size;
  41. fixed?: boolean
  42. rotate?: number;
  43. name?: string;
  44. pixel?: boolean;
  45. isText?: boolean;
  46. subgroup?: string;
  47. };
  48. export const SceneTypeNames = {
  49. [SCENE_TYPE.fuse]: "融合场景",
  50. [SCENE_TYPE.mesh]: "Mesh场景",
  51. [SCENE_TYPE.cloud]: "点云场景",
  52. };
  53. export const getSceneApi = async (type: string | undefined, url: string) => {
  54. if (url[0] === "/") {
  55. url = url.substring(1, url.length);
  56. }
  57. let origin = type
  58. ? window.platform.resourceURLS[type]
  59. ? window.platform.resourceURLS[type]
  60. : type
  61. : "";
  62. if (origin[origin.length - 1] !== "/") {
  63. origin = origin + "/";
  64. }
  65. const uri = origin + url;
  66. // try {
  67. // uri = new URL(window.platform.resourceURLS[type]).toString();
  68. // } catch {
  69. // uri = window.platform.resourceURLS[type] + url;
  70. // }
  71. const res = await fetch(uri, { method: "HEAD" });
  72. if (res.status !== 200) {
  73. throw `${uri}链接错误`;
  74. }
  75. return uri;
  76. };
  77. export type Tagings = Taging[];
  78. const getBillYaw = (bill: any) => {
  79. function isLieDown(quaternion: Quaternion) {
  80. let direction = new Vector3(0, 0, -1).applyQuaternion(quaternion);
  81. return Math.abs(direction.y) > 0.9;
  82. }
  83. let billboard = new Object3D();
  84. let plane = new Object3D();
  85. billboard.add(plane);
  86. billboard.quaternion.copy({x: bill.qua[0], y: bill.qua[1], z: bill.qua[2], w: bill.qua[3]}).normalize(); //qua数据里的
  87. plane.quaternion.setFromAxisAngle(
  88. new Vector3(0, 0, 1),
  89. MathUtils.degToRad(-bill.faceAngle)
  90. );
  91. const up = new Vector3(0, 1, 0); //plane的上方指示着方向
  92. const right = new Vector3(1, 0, 0); //令躺倒时的旋转轴
  93. let qua = plane.getWorldQuaternion(new Quaternion());
  94. const ld = isLieDown(billboard.quaternion)
  95. if (!ld) {
  96. //使朝其后方躺倒后再求angle
  97. let rotAxis = right.clone().applyQuaternion(qua); //旋转轴
  98. (rotAxis.y = 0), rotAxis.normalize();
  99. let rot = new Quaternion().setFromAxisAngle(rotAxis, Math.PI / 2);
  100. qua.premultiply(rot);
  101. }
  102. let dir = up.clone().applyQuaternion(qua); //在墙面朝x时正反得到的一样,很奇怪,所以得到的会反向
  103. let yaw = Math.atan2(-dir.z, dir.x) - Math.PI / 2;
  104. return -yaw;
  105. };
  106. export const compassGets = {
  107. [SCENE_TYPE.fuse]: () => void 0,
  108. [SCENE_TYPE.cloud]: () => void 0,
  109. [SCENE_TYPE.mesh]: async (scene: Scene) => {
  110. const prev = `/scene_view_data/${scene.m}`;
  111. const config = await getSceneApi("oss", `${prev}/data/scene.json`)
  112. .then((url) => fetch(url))
  113. .then((res) => res.json())
  114. .catch(() => ({ version: 0, billboards: 0, tags: 0, orientation: 0 }));
  115. const floorpanCompass = await getSceneApi(
  116. "oss",
  117. `${prev}/user/floorplan.json?_=${config.version}`
  118. )
  119. .then((url) => fetch(url))
  120. .then((res) => res.json())
  121. .then((data) => data.compass)
  122. .catch(() => null);
  123. return typeof floorpanCompass === "number"
  124. ? floorpanCompass
  125. : Number(config.orientation || 0);
  126. },
  127. };
  128. export const taggingGets = {
  129. [SCENE_TYPE.fuse]: async (scene: Scene, options: string[]) => {
  130. if (!options.includes("hot")) return [];
  131. const reqOpts = { headers: { share: "1" } };
  132. const icons = await getSceneApi(
  133. scene.type,
  134. `/fusion/edit/hotIcon/list?caseId=${scene.m}`
  135. )
  136. .then((url) => fetch(url, reqOpts))
  137. .then((res) => res.json())
  138. .then((res) => res.data)
  139. .catch(() => []);
  140. const tagTypes: any[] = await getSceneApi(
  141. scene.type,
  142. `/fusion/caseTag/allList?caseId=${scene.m}`
  143. )
  144. .then((url) => fetch(url, reqOpts))
  145. .then((res) => res.json())
  146. .then((res) => res.data)
  147. .catch(() => []);
  148. const tags: Tagings = [];
  149. const reqs = tagTypes.map((type) =>
  150. getSceneApi(
  151. scene.type,
  152. `/fusion/caseTagPoint/allList?tagId=${type.tagId}`
  153. )
  154. .then((url) => fetch(url, reqOpts))
  155. .then((res) => res.json())
  156. .then((res) => res.data)
  157. .then((items) => {
  158. items.forEach((item: any) => {
  159. tags.push({
  160. url: icons.find((icon: any) => icon.iconId === type.hotIconId)
  161. ?.iconUrl,
  162. position: JSON.parse(item.tagPoint),
  163. });
  164. });
  165. })
  166. .catch(() => [])
  167. );
  168. await Promise.all(reqs);
  169. return tags;
  170. },
  171. [SCENE_TYPE.cloud]: async (scene: Scene, options: string[]) => {
  172. if (!options.includes("hot")) return [];
  173. const tags: Tagings = await getSceneApi(
  174. scene.type,
  175. `/laser/poi/${scene.m}/list`
  176. )
  177. .then((url) => fetch(url))
  178. .then((res) => res.json())
  179. .then((res) => res.data.list)
  180. .then((pois) =>
  181. pois.map((poi: any) => ({
  182. url: poi.hotStyleAtom.icon,
  183. position: poi.dataset_location,
  184. }))
  185. )
  186. .catch(() => []);
  187. return tags;
  188. },
  189. [SCENE_TYPE.mesh]: async (scene: Scene, options: string[]) => {
  190. const tags: Tagings = [];
  191. const prev = `/scene_view_data/${scene.m}`;
  192. const config = await getSceneApi("oss", `${prev}/data/scene.json`)
  193. .then((url) => fetch(url))
  194. .then((res) => res.json())
  195. .catch(() => ({ version: 0, billboards: 0, tags: 0, orientation: 0 }));
  196. if (options.includes("hot") && config.tags) {
  197. const medias = await getSceneApi(
  198. "oss",
  199. `${prev}/user/hot.json?_=${config.version}`
  200. )
  201. .then((url) => fetch(url))
  202. .then((res) => res.json())
  203. .catch(() => []);
  204. const reqs = medias.map((media: any) => {
  205. if (!validNum(media.position.x) || !validNum(media.position.y)) return;
  206. return getSceneApi("oss", `${prev}/user/${media.icon}`)
  207. .then((url) => {
  208. tags.push({ url, position: media.position });
  209. })
  210. .catch(() => {});
  211. });
  212. await Promise.all(reqs);
  213. }
  214. if (options.includes("signage") && config.billboards) {
  215. const signages = await getSceneApi(
  216. "oss",
  217. `${prev}/user/billboards.json?_=${config.version}`
  218. )
  219. .then((url) => fetch(url))
  220. .then((res) => res.json())
  221. .catch(() => []);
  222. const reqs = signages.map((signage: any) => {
  223. if (!validNum(signage.pos[0]) || !validNum(signage.pos[2])) return;
  224. const getIcon =
  225. signage.icon.indexOf("style-") === 0
  226. ? getSceneApi("./", `./styles/${signage.icon}.svg`).catch((e) => {
  227. console.error(e);
  228. return getSceneApi(
  229. "ossRoot",
  230. `/sdk/images/billboard/${signage.icon}.png`
  231. );
  232. })
  233. : getSceneApi("oss", `${prev}/user/${signage.icon}`);
  234. const yRotate = getBillYaw(signage)
  235. console.log(signage)
  236. return getIcon
  237. .then((url) => {
  238. tags.push({
  239. url,
  240. position: {
  241. x: signage.pos[0],
  242. y: signage.pos[2],
  243. z:
  244. signage.pos[1] < 0
  245. ? Math.ceil(signage.pos[1] * 10) / 10
  246. : Math.floor(signage.pos[1] * 10) / 10,
  247. },
  248. rotate: yRotate,
  249. size: {
  250. // width: signage.width * (signage.scaleRatio / 100),
  251. // height: signage.height * (signage.scaleRatio / 100),
  252. width: signage.width ,
  253. height: signage.height,
  254. },
  255. });
  256. })
  257. .catch(() => {});
  258. });
  259. await Promise.all(reqs);
  260. }
  261. await getSceneApi(
  262. "oss",
  263. `${prev}/data/floorplan/ai-entire.json?_=${config.version}`
  264. )
  265. .then((url) => fetch(url))
  266. .then((res) => res.json())
  267. .then((datas) => {
  268. for (const data of datas) {
  269. const reg = data.imagePath.match(/floor_(\d)\.png/);
  270. const subgroup = reg ? Number(reg[1]) : undefined;
  271. for (const shape of data.shapes) {
  272. const pos = {
  273. x: (shape.bbox[0] + shape.bbox[2]) / 2 / data.imageWidth,
  274. y: (shape.bbox[1] + shape.bbox[3]) / 2 / data.imageHeight,
  275. z: undefined,
  276. };
  277. const size = {
  278. width: (shape.bbox[2] - shape.bbox[0]) / data.imageWidth,
  279. height: (shape.bbox[3] - shape.bbox[1]) / data.imageHeight,
  280. };
  281. const icon =
  282. shape.category in aiIconMap
  283. ? (aiIconMap as any)[shape.category]
  284. : shape.category;
  285. let name = "";
  286. let isWall = false
  287. for (const group of iconGroups) {
  288. for (const itemGroup of group.children) {
  289. for (const item of itemGroup.children) {
  290. if (item.icon === icon) {
  291. name = item.name;
  292. isWall = 'wall' in item && item.wall
  293. }
  294. }
  295. }
  296. }
  297. const isTag = icon === "Tag";
  298. if (name || isTag) {
  299. const item = {
  300. isText: isTag,
  301. position: pos,
  302. url: isTag
  303. ? shape.name
  304. : `./icons/${icon ? icon : "circle"}.svg`,
  305. name,
  306. pixel: true,
  307. size,
  308. subgroup,
  309. }
  310. if (isWall) {
  311. const wh = Math.max(item.size.width, item.size.height)
  312. item.size.width = wh
  313. item.size.height = wh
  314. }
  315. tags.push(item as any);
  316. } else {
  317. console.error("找不到ai家具", icon, name, pos);
  318. }
  319. }
  320. }
  321. })
  322. .catch((e) => {
  323. console.error(e);
  324. });
  325. console.log("tags", tags);
  326. return tags;
  327. },
  328. };
  329. export const getFloors = {
  330. [SCENE_TYPE.mesh]: async (scene: Scene) => {
  331. return getSceneApi(
  332. "oss",
  333. `/scene_view_data/${scene.m}/data/floorplan_cad.json?_=${Date.now()}`
  334. )
  335. .then((url) => fetch(url))
  336. .then((res) => res.json())
  337. .catch(() => ({ floors: [] }));
  338. },
  339. };
  340. export const lineGets = {
  341. [SCENE_TYPE.fuse]: async (scene: Scene) => {
  342. const tags = await taggingGets[SCENE_TYPE.fuse](scene, ["hot"]);
  343. return { name: "1楼", geos: [tags.map((item) => item.position)] };
  344. },
  345. [SCENE_TYPE.mesh]: async (scene: Scene, floorName?: string) => {
  346. const prev = `/scene_view_data/${scene.m}/data/`;
  347. const [{ floors }, bounds] = await Promise.all([
  348. getFloors[SCENE_TYPE.mesh](scene),
  349. getSceneApi("oss", `${prev}floorplan/info.json`)
  350. .then((url) => fetch(url))
  351. .then((res) => res.json())
  352. .then((data) => data.floors)
  353. .catch(() => []),
  354. ]);
  355. const data: any = [];
  356. const reqs = floors
  357. .filter((item: any) => !floorName || item.name === floorName)
  358. .map((floor: any) => {
  359. const bound = {
  360. ...(floor.cadInfo.cadBoundingBox || {}),
  361. ...(bounds.find((i: any) => i.subgroup === floor.subgroup)?.bound ||
  362. {}),
  363. };
  364. const item: any = {
  365. name: floor.name,
  366. subgroup: floor.subgroup,
  367. thumb: "",
  368. box: {
  369. bound: {
  370. ...bound,
  371. y_min: -bound.y_max,
  372. y_max: -bound.y_min,
  373. z_max: bound.z_max ? Number(bound.z_max) : bound.z_max,
  374. z_min: bound.z_min ? Number(bound.z_min) : bound.z_min,
  375. },
  376. rotate: floor.cadInfo.res,
  377. scale: floor.cadInfo.currentScale,
  378. },
  379. geos: extractConnectedSegments(floor.segment).map((geo) => {
  380. return geo.map((id) => {
  381. const p = floor["vertex-xy"].find((item: any) => item.id === id);
  382. return { x: p.x, y: -p.y } as Pos;
  383. });
  384. }),
  385. };
  386. data.push(item);
  387. return getSceneApi(
  388. "oss",
  389. `${prev}floorplan/floor_${floor.subgroup}.png`
  390. )
  391. .then((url) => (item.thumb = url))
  392. .catch(() => (item.thumb = ""));
  393. });
  394. await Promise.all(reqs);
  395. return data;
  396. },
  397. [SCENE_TYPE.cloud]: async (scene: Scene) => {
  398. const tags = await taggingGets[SCENE_TYPE.cloud](scene, ["hot"]);
  399. return { name: "1楼", geos: [tags.map((item) => item.position)] };
  400. },
  401. };