import { Pos, Size } from "@/utils/math"; import { extractConnectedSegments } from "@/utils/polygon"; import { validNum } from "@/utils/shared"; import { aiIconMap, iconGroups } from "../constant"; import { Euler, MathUtils, Object3D, Quaternion, Vector3 } from "three"; export enum SCENE_TYPE { fuse = "fuse", mesh = "mesh", cloud = "cloud", } export type Scene = { type: SCENE_TYPE; m: string; title: string; id: string; }; export type SceneFloor = { name: string; subgroup?: number; geos: (Pos & { z: number })[][]; thumb?: string; box?: { bound: { x_min: number; x_max: number; y_min: number; y_max: number; z_min: number; z_max: number; }; rotate: number; scale: number; }; compass?: number; }; export type SceneFloors = SceneFloor[]; export type Taging = { url: string; position: Pos & { z: number }; size?: Size; fixed?: boolean rotate?: number; name?: string; pixel?: boolean; isText?: boolean; subgroup?: string; }; export const SceneTypeNames = { [SCENE_TYPE.fuse]: "融合场景", [SCENE_TYPE.mesh]: "Mesh场景", [SCENE_TYPE.cloud]: "点云场景", }; export const getSceneApi = async (type: string | undefined, url: string) => { if (url[0] === "/") { url = url.substring(1, url.length); } let origin = type ? window.platform.resourceURLS[type] ? window.platform.resourceURLS[type] : type : ""; if (origin[origin.length - 1] !== "/") { origin = origin + "/"; } const uri = origin + url; // try { // uri = new URL(window.platform.resourceURLS[type]).toString(); // } catch { // uri = window.platform.resourceURLS[type] + url; // } const res = await fetch(uri, { method: "HEAD" }); if (res.status !== 200) { throw `${uri}链接错误`; } return uri; }; export type Tagings = Taging[]; const getBillYaw = (bill: any) => { function isLieDown(quaternion: Quaternion) { let direction = new Vector3(0, 0, -1).applyQuaternion(quaternion); return Math.abs(direction.y) > 0.9; } let billboard = new Object3D(); let plane = new Object3D(); billboard.add(plane); billboard.quaternion.copy({x: bill.qua[0], y: bill.qua[1], z: bill.qua[2], w: bill.qua[3]}).normalize(); //qua数据里的 plane.quaternion.setFromAxisAngle( new Vector3(0, 0, 1), MathUtils.degToRad(-bill.faceAngle) ); const up = new Vector3(0, 1, 0); //plane的上方指示着方向 const right = new Vector3(1, 0, 0); //令躺倒时的旋转轴 let qua = plane.getWorldQuaternion(new Quaternion()); const ld = isLieDown(billboard.quaternion) if (!ld) { //使朝其后方躺倒后再求angle let rotAxis = right.clone().applyQuaternion(qua); //旋转轴 (rotAxis.y = 0), rotAxis.normalize(); let rot = new Quaternion().setFromAxisAngle(rotAxis, Math.PI / 2); qua.premultiply(rot); } let dir = up.clone().applyQuaternion(qua); //在墙面朝x时正反得到的一样,很奇怪,所以得到的会反向 let yaw = Math.atan2(-dir.z, dir.x) - Math.PI / 2; return -yaw; }; export const compassGets = { [SCENE_TYPE.fuse]: () => void 0, [SCENE_TYPE.cloud]: () => void 0, [SCENE_TYPE.mesh]: async (scene: Scene) => { const prev = `/scene_view_data/${scene.m}`; const config = await getSceneApi("oss", `${prev}/data/scene.json`) .then((url) => fetch(url)) .then((res) => res.json()) .catch(() => ({ version: 0, billboards: 0, tags: 0, orientation: 0 })); const floorpanCompass = await getSceneApi( "oss", `${prev}/user/floorplan.json?_=${config.version}` ) .then((url) => fetch(url)) .then((res) => res.json()) .then((data) => data.compass) .catch(() => null); return typeof floorpanCompass === "number" ? floorpanCompass : Number(config.orientation || 0); }, }; export const taggingGets = { [SCENE_TYPE.fuse]: async (scene: Scene, options: string[]) => { if (!options.includes("hot")) return []; const reqOpts = { headers: { share: "1" } }; const icons = await getSceneApi( scene.type, `/fusion/edit/hotIcon/list?caseId=${scene.m}` ) .then((url) => fetch(url, reqOpts)) .then((res) => res.json()) .then((res) => res.data) .catch(() => []); const tagTypes: any[] = await getSceneApi( scene.type, `/fusion/caseTag/allList?caseId=${scene.m}` ) .then((url) => fetch(url, reqOpts)) .then((res) => res.json()) .then((res) => res.data) .catch(() => []); const tags: Tagings = []; const reqs = tagTypes.map((type) => getSceneApi( scene.type, `/fusion/caseTagPoint/allList?tagId=${type.tagId}` ) .then((url) => fetch(url, reqOpts)) .then((res) => res.json()) .then((res) => res.data) .then((items) => { items.forEach((item: any) => { tags.push({ url: icons.find((icon: any) => icon.iconId === type.hotIconId) ?.iconUrl, position: JSON.parse(item.tagPoint), }); }); }) .catch(() => []) ); await Promise.all(reqs); return tags; }, [SCENE_TYPE.cloud]: async (scene: Scene, options: string[]) => { if (!options.includes("hot")) return []; const tags: Tagings = await getSceneApi( scene.type, `/laser/poi/${scene.m}/list` ) .then((url) => fetch(url)) .then((res) => res.json()) .then((res) => res.data.list) .then((pois) => pois.map((poi: any) => ({ url: poi.hotStyleAtom.icon, position: poi.dataset_location, })) ) .catch(() => []); return tags; }, [SCENE_TYPE.mesh]: async (scene: Scene, options: string[]) => { const tags: Tagings = []; const prev = `/scene_view_data/${scene.m}`; const config = await getSceneApi("oss", `${prev}/data/scene.json`) .then((url) => fetch(url)) .then((res) => res.json()) .catch(() => ({ version: 0, billboards: 0, tags: 0, orientation: 0 })); if (options.includes("hot") && config.tags) { const medias = await getSceneApi( "oss", `${prev}/user/hot.json?_=${config.version}` ) .then((url) => fetch(url)) .then((res) => res.json()) .catch(() => []); const reqs = medias.map((media: any) => { if (!validNum(media.position.x) || !validNum(media.position.y)) return; return getSceneApi("oss", `${prev}/user/${media.icon}`) .then((url) => { tags.push({ url, position: media.position }); }) .catch(() => {}); }); await Promise.all(reqs); } if (options.includes("signage") && config.billboards) { const signages = await getSceneApi( "oss", `${prev}/user/billboards.json?_=${config.version}` ) .then((url) => fetch(url)) .then((res) => res.json()) .catch(() => []); const reqs = signages.map((signage: any) => { if (!validNum(signage.pos[0]) || !validNum(signage.pos[2])) return; const getIcon = signage.icon.indexOf("style-") === 0 ? getSceneApi("./", `./styles/${signage.icon}.svg`).catch((e) => { console.error(e); return getSceneApi( "ossRoot", `/sdk/images/billboard/${signage.icon}.png` ); }) : getSceneApi("oss", `${prev}/user/${signage.icon}`); const yRotate = getBillYaw(signage) console.log(signage) return getIcon .then((url) => { tags.push({ url, position: { x: signage.pos[0], y: signage.pos[2], z: signage.pos[1] < 0 ? Math.ceil(signage.pos[1] * 10) / 10 : Math.floor(signage.pos[1] * 10) / 10, }, rotate: yRotate, size: { // width: signage.width * (signage.scaleRatio / 100), // height: signage.height * (signage.scaleRatio / 100), width: signage.width , height: signage.height, }, }); }) .catch(() => {}); }); await Promise.all(reqs); } await getSceneApi( "oss", `${prev}/data/floorplan/ai-entire.json?_=${config.version}` ) .then((url) => fetch(url)) .then((res) => res.json()) .then((datas) => { for (const data of datas) { const reg = data.imagePath.match(/floor_(\d)\.png/); const subgroup = reg ? Number(reg[1]) : undefined; for (const shape of data.shapes) { const pos = { x: (shape.bbox[0] + shape.bbox[2]) / 2 / data.imageWidth, y: (shape.bbox[1] + shape.bbox[3]) / 2 / data.imageHeight, z: undefined, }; const size = { width: (shape.bbox[2] - shape.bbox[0]) / data.imageWidth, height: (shape.bbox[3] - shape.bbox[1]) / data.imageHeight, }; const icon = shape.category in aiIconMap ? (aiIconMap as any)[shape.category] : shape.category; let name = ""; for (const group of iconGroups) { for (const itemGroup of group.children) { for (const item of itemGroup.children) { if (item.icon === icon) { name = item.name; } } } } const isTag = icon === "Tag"; if (name || isTag) { tags.push({ isText: isTag, position: pos, url: isTag ? shape.name : `./icons/${icon ? icon : "circle"}.svg`, name, pixel: true, size, subgroup, } as any); } else { console.error("找不到ai家具", icon, name, pos); } } } }) .catch((e) => { console.error(e); }); console.log("tags", tags); return tags; }, }; export const getFloors = { [SCENE_TYPE.mesh]: async (scene: Scene) => { return getSceneApi( "oss", `/scene_view_data/${scene.m}/data/floorplan_cad.json?_=${Date.now()}` ) .then((url) => fetch(url)) .then((res) => res.json()) .catch(() => ({ floors: [] })); }, }; export const lineGets = { [SCENE_TYPE.fuse]: async (scene: Scene) => { const tags = await taggingGets[SCENE_TYPE.fuse](scene, ["hot"]); return { name: "1楼", geos: [tags.map((item) => item.position)] }; }, [SCENE_TYPE.mesh]: async (scene: Scene, floorName?: string) => { const prev = `/scene_view_data/${scene.m}/data/`; const [{ floors }, bounds] = await Promise.all([ getFloors[SCENE_TYPE.mesh](scene), getSceneApi("oss", `${prev}floorplan/info.json`) .then((url) => fetch(url)) .then((res) => res.json()) .then((data) => data.floors) .catch(() => []), ]); const data: any = []; const reqs = floors .filter((item: any) => !floorName || item.name === floorName) .map((floor: any) => { const bound = { ...(floor.cadInfo.cadBoundingBox || {}), ...(bounds.find((i: any) => i.subgroup === floor.subgroup)?.bound || {}), }; const item: any = { name: floor.name, subgroup: floor.subgroup, thumb: "", box: { bound: { ...bound, y_min: -bound.y_max, y_max: -bound.y_min, z_max: bound.z_max ? Number(bound.z_max) : bound.z_max, z_min: bound.z_min ? Number(bound.z_min) : bound.z_min, }, rotate: floor.cadInfo.res, scale: floor.cadInfo.currentScale, }, geos: extractConnectedSegments(floor.segment).map((geo) => { return geo.map((id) => { const p = floor["vertex-xy"].find((item: any) => item.id === id); return { x: p.x, y: -p.y } as Pos; }); }), }; data.push(item); return getSceneApi( "oss", `${prev}floorplan/floor_${floor.subgroup}.png` ) .then((url) => (item.thumb = url)) .catch(() => (item.thumb = "")); }); await Promise.all(reqs); return data; }, [SCENE_TYPE.cloud]: async (scene: Scene) => { const tags = await taggingGets[SCENE_TYPE.cloud](scene, ["hot"]); return { name: "1楼", geos: [tags.map((item) => item.position)] }; }, };