import { genCache, validNum } from "@/utils/shared"; import { BorderTaggingInfo, CoverLine, getSceneApi, ResourceArgs, Scene, SceneResource, TaggingInfo, WallTaggingInfo, } from "./platform-resource"; import { lineLen, Pos, zeroEq } from "@/utils/math"; import { aiIconMap, getIconItem, styleIconMap, traceIconMap, } from "../constant"; import { Euler, MathUtils, Matrix4, Matrix4Tuple, Object3D, Quaternion, Vector3, } from "three"; import { extractConnectedSegments } from "@/utils/polygon"; const fetchResource = genCache( (scene: Scene) => scene.m, async (scene: Scene) => { const prev = `${scene.mapping ? "/" + scene.mapping : ""}/scene_view_data/${ scene.m }`; let version = Date.now(); const get = (url: string, def?: any) => getSceneApi("oss", `${prev}${url}?_=${version}`) .then((url) => fetch(url)) .then((res) => res.json()) .catch(() => def); const config = await get(`/data/scene.json`, { version: 0, billboards: 0, tags: 0, }); version = config.version; const [ userFloorpan, floorplan, hots, billboards, ais, cadInfo, cad, traces, detects, ] = await Promise.all([ get("/user/floorplan.json", {}), get("/data/floorplan.json", { floors: [] }), get("/user/hot.json", []), get("/user/billboards.json", []), get("/data/floorplan/ai.json", []), get("/data/floorplan/info.json", { floors: [] }), get("/data/floorplan_cad.json", { floors: [] }), get("/user/evidence.json", []), get("/images/ai/detect/detect-ai.json", []), ]); return { userFloorpan, hots, billboards, ais, floorplan, cad, cadInfo, config, traces, detects, }; }, 150000 ); export const getFloors = async (scene: Scene) => { const { floorplan } = await fetchResource(scene); return floorplan.floors; }; export const getCoverLine = async ( scene: Scene, subgroup: any, scale: number ) => { const { cadInfo, cad } = await fetchResource(scene); const floors = cad.floors; let info: CoverLine; const reqs: Promise[] = []; for (let i = 0; i < floors.length; i++) { const floor = floors[i]; if (floor.subgroup !== subgroup) continue; const floorInfo = cadInfo.floors.find( (item: any) => item.subgroup === floor.subgroup ); const 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 * scale, y: -p.y * scale, key: id.toString() }; }); }); let bound = { ...(floor.cadInfo.cadBoundingBox || {}), ...floorInfo?.bound, }; if (!bound || !("x_max" in bound)) { const xs = geos.flatMap((item) => item.map((p) => p.x)); const ys = geos.flatMap((item) => item.map((p) => p.y)); bound = { x_min: Math.min(...xs), x_max: Math.max(...xs), y_min: Math.min(...ys), y_max: Math.max(...ys), }; } else { bound = { x_min: bound.x_min * scale, x_max: bound.x_max * scale, y_min: bound.y_min * scale, y_max: bound.y_max * scale, }; } const item = { name: floor.name, subgroup: Number(floor.subgroup), thumb: "", bound, geos, }; info = item; reqs.push( getSceneApi( "oss", `${scene.mapping ? "/" + scene.mapping : ""}/scene_view_data/${ scene.m }/data/floorplan/floor_${floor.subgroup}.png` ) .then((url) => (item.thumb = url)) .catch(() => {}) ); } await Promise.all(reqs); return info!; }; export const getCompass = async (scene: Scene) => { const { config, userFloorpan } = await fetchResource(scene); return "compass" in userFloorpan ? userFloorpan?.compass : MathUtils.radToDeg(Number(config.orientation || 0)); }; export const getHotTaggingInfos = async (scene: Scene, scale: number) => { const { hots } = await fetchResource(scene); const infos: TaggingInfo[] = []; const reqs: Promise[] = []; for (const hot of hots) { if (!validNum(hot.position.x) || !validNum(hot.position.y)) continue; reqs.push( getSceneApi( "oss", `${scene.mapping ? "/" + scene.mapping : ""}/scene_view_data/${ scene.m }/user/${hot.icon}` ) .then((url) => infos.push({ position: { x: hot.position.x * scale, y: hot.position.y * scale }, url, }) ) .catch(() => {}) ); } await Promise.all(reqs); return infos; }; const getTraceAttri = async (icon: string, trace: any) => { const size = { width: trace.visiSetting.scale, height: trace.visiSetting.scale, }; const attrib: any = { url: icon, size, angle: trace.visiSetting.angle, }; if (trace.iconType === 2 || !trace.tag3d?.object) { return attrib; } const getPath = (children: any, name: string): any[] | undefined => { for (const item of children) { if (item.name === name) { return [item]; } else if (item.children) { const childPath = getPath(item.children, name); if (childPath) { return [item, ...childPath]; } } } }; const path = getPath([trace.tag3d.object], "sprite"); if (!path) return attrib; const mat = new Matrix4(); for (const node of path) { mat.multiply(new Matrix4(...(node.matrix as Matrix4Tuple))); } const position = new Vector3(); const quat = new Quaternion(); const scale = new Vector3(); mat.decompose(position, quat, scale); const euler = new Euler().setFromQuaternion(quat, "XYZ"); return { ...attrib, angle: MathUtils.radToDeg(euler.y), }; }; // 痕迹物证 export const getTraceTaggingInfos = async ( scene: Scene, scale: number, floorIndex: number ) => { const { traces } = await fetchResource(scene); const infos: TaggingInfo[] = []; const reqs: Promise[] = []; for (const trace of traces) { if ( !validNum(trace.position?.x) || !validNum(trace.position?.z) || ("floorIndex" in trace && trace.floorIndex !== floorIndex) || trace.iconType === 0 ) continue; const isSys = trace.icon.indexOf("/") > -1; const icon = isSys ? trace.icon.substring(trace.icon.lastIndexOf("/") + 1) : trace.icon; const styleMap = (traceIconMap as any)[icon]; if (!icon) continue; const getIcon = isSys ? styleMap ? Promise.resolve(`./icons/${styleMap}.svg`) : getSceneApi("./", `./traces/${icon}.svg`) : getSceneApi( "oss", `${scene.mapping ? "/" + scene.mapping : ""}/scene_edit_data/${ scene.m }/user/${icon}` ); const name = (styleMap && getIconItem(styleMap)?.name) || ""; const getAttr = getIcon.then(async (url) => getTraceAttri(url, trace)); reqs.push( getAttr .then((attr) => ({ url: attr.url, name: trace.title || name, position: { x: trace.position.x * scale, y: trace.position.z * scale, z: trace.position.y < 0 ? Math.ceil(trace.position.y * scale * 10) / 10 : Math.floor(trace.position.y * scale * 10) / 10, }, key: "trace", rotate: attr.angle, size: { width: attr.size.width * scale, height: attr.size.height * scale, }, })) .then((info) => infos.push(info)) .catch(() => {}) ); } await Promise.all(reqs); return infos; }; 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 getBillTaggingInfos = async ( scene: Scene, scale: number, subgroup: number ) => { const { billboards } = await fetchResource(scene); const infos: TaggingInfo[] = []; const reqs: Promise[] = []; for (const bill of billboards) { if ( !validNum(bill.pos[0]) || !validNum(bill.pos[2]) || ("subgroup" in bill && bill.subgroup !== subgroup) ) continue; const styleMap = (styleIconMap as any)[bill.icon]; const getIcon = bill.icon.indexOf("style-") === 0 ? styleMap ? Promise.resolve(`./icons/${styleMap}.svg`) : getSceneApi("./", `./styles/${bill.icon}.svg`).catch((e) => { return getSceneApi( "ossRoot", `/sdk/images/billboard/${bill.icon}.png` ); }) : getSceneApi( "oss", `${scene.mapping ? "/" + scene.mapping : ""}/scene_view_data/${ scene.m }/user/${bill.icon}` ); const yRotate = getBillYaw(bill); const name = (styleMap && getIconItem(styleMap)?.name) || ""; reqs.push( getIcon .then((url) => ({ url, name, position: { x: bill.pos[0] * scale, y: bill.pos[2] * scale, z: bill.pos[1] < 0 ? Math.ceil(bill.pos[1] * scale * 10) / 10 : Math.floor(bill.pos[1] * scale * 10) / 10, }, rotate: yRotate, size: { width: bill.width * scale, height: bill.height * scale, }, })) .then((info) => infos.push(info)) .catch(() => {}) ); } await Promise.all(reqs); return infos; }; export const getAITaggingInfos = async ( scene: Scene, subgroup: any, bound: Record ) => { const { ais } = await fetchResource(scene); const infos: TaggingInfo[] = []; const drawBound = { x: bound.x_min, y: bound.y_min, w: bound.x_max - bound.x_min, h: bound.y_max - bound.y_min, }; for (const data of ais) { if (data.imagePath) { const reg = data.imagePath.match(/floor_(\d)\.png/); const curSubgroup = reg ? Number(reg[1]) : undefined; if (curSubgroup !== subgroup) continue; } for (const shape of data.shapes) { const icon = shape.category in aiIconMap ? (aiIconMap as any)[shape.category] : shape.category; const itemIcon = getIconItem(icon); const isWall = itemIcon && "wall" in itemIcon ? itemIcon.wall : false; if (isWall) continue; const name = itemIcon?.name || ""; const isTag = icon === "Tag"; if (!name && !isTag) continue; const pixelCenter = { x: (shape.bbox[0] + shape.bbox[2]) / 2 / data.imageWidth, y: (shape.bbox[1] + shape.bbox[3]) / 2 / data.imageHeight, }; const center = { x: pixelCenter.x * drawBound.w + drawBound.x, y: pixelCenter.y * drawBound.h + drawBound.y, }; const size = { width: ((shape.bbox[2] - shape.bbox[0]) / data.imageWidth) * drawBound.w, height: ((shape.bbox[3] - shape.bbox[1]) / data.imageHeight) * drawBound.h, }; infos.push({ isText: isTag, position: center, rotate: 0, url: isTag ? shape.name : `./icons/${icon ? icon : "circle"}.svg`, name, size, }); } } return infos; }; export const getAIBorderTaggingInfos = async ( scene: Scene, subgroup: any, bound: Record, scale: number ) => { const { detects } = await fetchResource(scene); const infos: BorderTaggingInfo[] = []; const drawBound = { x: bound.x_min, y: bound.y_min, w: bound.x_max - bound.x_min, h: bound.y_max - bound.y_min, }; // console.log(drawBound); for (const shape of detects) { // if (data.imagePath) { // const reg = data.imagePath.match(/floor_(\d)\.png/); // const curSubgroup = reg ? Number(reg[1]) : undefined; // if (curSubgroup !== subgroup) continue; // } const min = { x: shape.bbox[0][0], y: shape.bbox[0][2] }; const max = { x: shape.bbox[0][0], y: shape.bbox[0][2] }; for (const box of shape.bbox) { min.x = Math.min(box[0], min.x); min.y = Math.min(box[2], min.y); max.x = Math.max(box[0], max.x); max.y = Math.max(box[2], max.y); } min.x *= scale; min.y *= scale; max.x *= scale; max.y *= scale; const center = { x: (min.x + max.x) / 2, y: (min.y + max.y) / 2, }; const size = { width: max.x - min.x, height: max.y - min.y, }; infos.push({ text: shape.name, points: [ { x: center.x - size.width / 2, y: center.y - size.height / 2 }, { x: center.x + size.width / 2, y: center.y - size.height / 2 }, { x: center.x + size.width / 2, y: center.y + size.height / 2 }, { x: center.x - size.width / 2, y: center.y + size.height / 2 }, ], center, }); } return infos; }; export const getWallAITaggingInfos = async ( scene: Scene, subgroup: any, scale: number, cover: CoverLine ) => { const { floorplan } = await fetchResource(scene); const infos: WallTaggingInfo[] = []; const getCoverLine = (wall: Pos[]) => { type Info = { p: Pos & { key: string }; len: number }; const infos: { start: Info; end: Info; inv: boolean }[] = []; for (let i = 0; i < cover.geos.length; i++) { const geo = cover.geos[i]; for (let j = 0; j < geo.length - 1; j++) { const len00 = lineLen(geo[j], wall[0]); const len10 = lineLen(geo[j + 1], wall[0]); const len01 = lineLen(geo[j], wall[1]); const len11 = lineLen(geo[j + 1], wall[1]); let start: Info, end: Info, inv: boolean; if (len00 + len11 < len10 + len01) { inv = false; start = { p: geo[j], len: len00 }; end = { p: geo[j + 1], len: len11 }; } else { inv = true; start = { p: geo[j + 1], len: len10 }; end = { p: geo[j], len: len01 }; } if (zeroEq(start.len) && zeroEq(end.len)) { return { points: [start.p, end.p], inv }; } infos.push({ start, end, inv }); } } const sortInfos = [...infos].sort( (a, b) => a.start.len + a.end.len - (b.start.len + b.end.len) ); const target = sortInfos[0]; if (target.start.len + target.end.len < 10) { return { points: [target.start.p, target.end.p], inv: target.inv }; } }; for (const floor of floorplan.floors) { if (floor.subgroup !== subgroup) continue; for (const key in floor.symbols) { const item = floor.symbols[key]; const icon = item.geoType in aiIconMap ? (aiIconMap as any)[item.geoType] : item.geoType; const itemIcon = getIconItem(icon); let name = itemIcon?.name; const isWall = itemIcon && "wall" in itemIcon ? itemIcon.wall : false; if (!isWall) continue; const cadWall = [ floor.points[floor.walls[item.parent].start], floor.points[floor.walls[item.parent].end], ].map((p) => ({ x: p.x * scale, y: -p.y * scale, })); const wall = getCoverLine(cadWall); const line = [item.startPoint, item.endPoint].map((p) => ({ x: p.x * scale, y: -p.y * scale, })); if (!wall) { continue; } const points = wall.inv ? wall.points.reverse() : wall.points; infos.push({ name: name, url: `./icons/${icon ? icon : "circle"}.svg`, startLen: lineLen(points[0], line[0]), endLen: lineLen(points[0], line[1]), openSide: item.openSide, height: itemIcon?.parse?.height, type: itemIcon?.parse?.type || "full", pointIds: points.map((item) => item.key), }); } } return infos; }; export const getResource = async ({ scene, floorName, syncs, scale, }: ResourceArgs) => { const data = await fetchResource(scene); const floorIndex = data.floorplan.floors.findIndex( (item: any) => item.name === floorName ); const floor = data.floorplan.floors[floorIndex]; const key = floor.subgroup; const taggings: TaggingInfo[] = []; const wallTaggings: WallTaggingInfo[] = []; const borderTaggings: BorderTaggingInfo[] = []; let coverLine: CoverLine; const compass = await getCompass(scene); console.log(compass); const reqs: Promise[] = [ getCompass(scene), getCoverLine(scene, key, scale) .then((lines) => (coverLine = lines)) .then((cover) => Promise.all([ getWallAITaggingInfos(scene, key, scale, cover).then((ts) => wallTaggings.push(...ts) ), getAITaggingInfos(scene, key, cover.bound).then((ts) => taggings.push(...ts) ), getAIBorderTaggingInfos(scene, key, cover.bound, scale).then((ts) => borderTaggings.push(...ts) ), ]) ), ]; if (syncs.includes("hot")) { reqs.push( getHotTaggingInfos(scene, scale).then((ts) => taggings.push(...ts)) ); } if (syncs.includes("signage")) { reqs.push( getBillTaggingInfos(scene, scale, key).then((ts) => taggings.push(...ts)) ); } if (syncs.includes("traces")) { reqs.push( getTraceTaggingInfos(scene, scale, floorIndex).then((ts) => taggings.push(...ts) ) ); } await Promise.all(reqs); return { taggings: taggings, borderTaggings, wallTaggings, cover: coverLine!, compass: compass, } as SceneResource; };