DivideFloorScene.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. import { SMouseEvent } from "@persagy-web/base/lib";
  2. import { SMathUtil } from "@persagy-web/big/lib/utils/SMathUtil";
  3. import { SPoint, SRect } from "@persagy-web/draw/lib";
  4. import { FloorScene } from "./FloorScene";
  5. import { HighlightItem } from "./HighlightItem";
  6. import { ShadeItem } from "./ShadeItem";
  7. // @ts-ignore
  8. import { intersect, polygon, segments, combine, selectIntersect, selectUnion, selectDifference, selectDifferenceRev, difference } from "polybooljs";
  9. import { DrawZoneItem } from "./DrawZoneItem";
  10. import { SItemStatus } from "@persagy-web/big/lib";
  11. export class DivideFloorScene extends FloorScene {
  12. /** 划分item */
  13. cutItem: ShadeItem | null = null;
  14. /** 绘制的分区item */
  15. drawItem: DrawZoneItem | null = null;
  16. /** 当前绘制命令 cut-交集区域绘制 zoneDraw-绘制业务空间 */
  17. // 2021.3.4需求不算交集,直接绘制业务空间
  18. drawCmd: string = '';
  19. /** 是否开启吸附 */
  20. private _isAbsorbing: boolean = false;
  21. get isAbsorbing(): boolean {
  22. return this._isAbsorbing;
  23. } // Get isAbsorbing
  24. set isAbsorbing(v: boolean) {
  25. this._isAbsorbing = v;
  26. } // Set isAbsorbing
  27. /** 高亮item */
  28. highLight: HighlightItem | null = null;
  29. /**
  30. * 清除划分区域
  31. */
  32. clearCut(): void {
  33. if (this.cutItem && this.drawCmd == 'cut') {
  34. this.grabItem = null;
  35. this.removeItem(this.cutItem);
  36. this.cutItem = null;
  37. this.view && this.view.update();
  38. }
  39. if (this.drawItem && this.drawCmd == 'zoneDraw') {
  40. this.grabItem = null;
  41. this.removeItem(this.drawItem);
  42. this.drawItem = null;
  43. this.view && this.view.update();
  44. }
  45. this.drawCmd = '';
  46. } // Function clearCut()
  47. /**
  48. * 鼠标按下事件
  49. *
  50. * @param event 保存事件参数
  51. * @return boolean
  52. */
  53. onMouseDown(event: SMouseEvent): boolean {
  54. if (event.buttons == 1) {
  55. // 判断是否开启吸附,并且有吸附的点
  56. if (
  57. this.isAbsorbing &&
  58. this.highLight &&
  59. this.highLight.visible
  60. ) {
  61. event.x = this.highLight.point.x;
  62. event.y = this.highLight.point.y;
  63. }
  64. if (this.drawCmd == 'cut') {
  65. if (!this.cutItem) {
  66. let point = new SPoint(event.x, event.y);
  67. let item = new ShadeItem(null, point);
  68. this.addItem(item);
  69. this.cutItem = item;
  70. this.grabItem = item;
  71. return true;
  72. }
  73. } else if (this.drawCmd == 'zoneDraw') {
  74. if (!this.drawItem) {
  75. let point = new SPoint(event.x, event.y);
  76. let item = new DrawZoneItem(null, point);
  77. item.status = SItemStatus.Create;
  78. this.addItem(item);
  79. this.drawItem = item;
  80. this.grabItem = item;
  81. return true;
  82. }
  83. }
  84. }
  85. return super.onMouseDown(event)
  86. }
  87. /**
  88. * 吸附空间
  89. *
  90. * @param event 鼠标事件对象
  91. */
  92. onMouseMove(event: SMouseEvent): boolean {
  93. super.onMouseMove(event);
  94. if (this.isAbsorbing) {
  95. if (!this.highLight) {
  96. this.highLight = new HighlightItem(null);
  97. this.addItem(this.highLight);
  98. }
  99. this.highLight.visible = false;
  100. // if (!this.absorbShade(event)) {
  101. this.absorbSpace(event);
  102. // }
  103. }
  104. return false;
  105. } // Function onMouseMove()
  106. /**
  107. * 吸附空间
  108. *
  109. * @param event 鼠标事件对象
  110. * @return boolean 是否找到吸附的对象
  111. */
  112. absorbSpace(event: SMouseEvent): boolean {
  113. if (!this.highLight) {
  114. return false;
  115. }
  116. let absorbLen = 1000;
  117. if (this.view) {
  118. absorbLen = 10 / this.view.scale;
  119. }
  120. let P = this.absorbSpacePoint(event, absorbLen);
  121. if (P.Point) {
  122. this.highLight.distance = P.MinDis;
  123. this.highLight.point = new SPoint(P.Point.X, -P.Point.Y);
  124. this.highLight.visible = true;
  125. return true;
  126. } else {
  127. let L = this.absorbSpaceLine(event, absorbLen);
  128. if (L.Line && L.Point) {
  129. this.highLight.distance = L.MinDis;
  130. this.highLight.point = L.Point;
  131. this.highLight.line = L.Line;
  132. this.highLight.visible = true;
  133. return true;
  134. }
  135. return false;
  136. }
  137. } // Function absorbSpace()
  138. /**
  139. * 吸附空间点
  140. *
  141. * @param event 鼠标事件对象
  142. * @param absorbLen 吸附距离
  143. * @return MinDis 吸附的点
  144. */
  145. absorbSpacePoint(event: SMouseEvent, absorbLen: number): any {
  146. let minPointDis = Number.MAX_SAFE_INTEGER;
  147. let Point;
  148. this.spaceList.map((space): void => {
  149. if (
  150. DivideFloorScene.isPointInAbsorbArea(
  151. new SPoint(event.x, event.y),
  152. space.minX,
  153. space.maxX,
  154. space.minY,
  155. space.maxY
  156. )
  157. ) {
  158. space.data.OutLine.forEach((item): void => {
  159. let minDis = SMathUtil.getMinDisPoint(
  160. new SPoint(event.x, event.y),
  161. item
  162. );
  163. if (
  164. minDis &&
  165. minDis.MinDis < absorbLen &&
  166. minDis.MinDis < minPointDis
  167. ) {
  168. minPointDis = minDis.MinDis;
  169. Point = minDis.Point;
  170. }
  171. });
  172. }
  173. });
  174. return {
  175. MinDis: minPointDis,
  176. Point: Point
  177. };
  178. } // Function absorbSpacePoint()
  179. /**
  180. * 点是否在吸附区域内
  181. *
  182. * @param p 要判断的点
  183. * @param minX 空间区域
  184. * @param minY 空间区域
  185. * @param maxX 空间区域
  186. * @param maxY 空间区域
  187. */
  188. static isPointInAbsorbArea(
  189. p: SPoint,
  190. minX: number,
  191. maxX: number,
  192. minY: number,
  193. maxY: number
  194. ): boolean {
  195. let rect = new SRect(
  196. minX - 1000,
  197. minY - 1000,
  198. maxX - minX + 2000,
  199. maxY - minY + 2000
  200. );
  201. return rect.contains(p.x, p.y);
  202. } // Function isPointInAbsorbArea()
  203. /**
  204. * 吸附空间线
  205. *
  206. * @param event 鼠标事件对象
  207. * @param absorbLen 吸附距离
  208. * @return PointToLine 吸附的线
  209. */
  210. absorbSpaceLine(event: SMouseEvent, absorbLen: number): any {
  211. let minPointDis = Number.MAX_SAFE_INTEGER;
  212. let Point, Line;
  213. this.spaceList.forEach((space): void => {
  214. if (
  215. DivideFloorScene.isPointInAbsorbArea(
  216. new SPoint(event.x, event.y),
  217. space.minX,
  218. space.maxX,
  219. space.minY,
  220. space.maxY
  221. )
  222. ) {
  223. space.data.OutLine.forEach((item): void => {
  224. let minDisLine = SMathUtil.getMinDisLine(
  225. new SPoint(event.x, event.y),
  226. item
  227. );
  228. if (
  229. minDisLine &&
  230. minDisLine.MinDis < absorbLen &&
  231. minDisLine.MinDis < minPointDis
  232. ) {
  233. minPointDis = minDisLine.MinDis;
  234. Point = minDisLine.Point;
  235. Line = minDisLine.Line;
  236. }
  237. });
  238. }
  239. });
  240. return {
  241. MinDis: minPointDis,
  242. Point: Point,
  243. Line: Line
  244. };
  245. } // Function absorbSpaceLine()
  246. /**
  247. * 计算选中的空间与业务空间的差集
  248. */
  249. getSpaceZoneIntersect(): SPoint[][] | undefined {
  250. // 未选中空间- 不计算
  251. if (!this.selectContainer.itemList.length) {
  252. return
  253. }
  254. // 没有业务空间不计算
  255. if (!this.zoneList.length) {
  256. return
  257. }
  258. // 生成选中的空间计算时用的格式
  259. const space = {
  260. regions: [],
  261. inverted: false
  262. }
  263. const sourceIdToDiff = {};
  264. try {
  265. let poly1, poly2;
  266. const start = +new Date();
  267. let rect1: SRect, list = [];
  268. // 计算外接矩阵与选中空间相交的业务空间的所有轮廓
  269. this.selectContainer.itemList.forEach(item => {
  270. rect1 = item.boundingRect();
  271. this.zoneList.forEach(t => {
  272. if (t.visible) {
  273. let rect2 = t.boundingRect();
  274. // 外接矩阵是否相交,缩小计算范围
  275. if (SMathUtil.rectIntersection(rect1, rect2)) {
  276. t.pointList.forEach((zoneLine): void => {
  277. let polygons = {
  278. regions: [],
  279. inverted: false
  280. };
  281. zoneLine.forEach((po): void => {
  282. let point = SMathUtil.transferToArray(po);
  283. polygons.regions.push(point);
  284. });
  285. list.push(polygons);
  286. })
  287. }
  288. }
  289. })
  290. })
  291. //
  292. if (list.length) {
  293. let seg1 = segments(list[0]),
  294. seg2,
  295. comb;
  296. for (let i = 1; i < list.length; i++) {
  297. seg2 = segments(list[i]);
  298. comb = combine(seg1, seg2);
  299. seg1 = selectUnion(comb);
  300. }
  301. poly1 = seg1;
  302. this.selectContainer.itemList.forEach(t => {
  303. let key = t.data.SourceId;
  304. poly2 = {
  305. regions: [],
  306. inverted: false
  307. }
  308. poly2.regions = t.pointList[0].map((item): number[][] => {
  309. return SMathUtil.transferToArray(item);
  310. });
  311. const comb = combine(segments(poly2), poly1)
  312. const diffObj = selectDifference(comb);
  313. const diffPoly = polygon(diffObj)
  314. // 理论上只要选择了空间 length就不会为0
  315. if (diffPoly.regions.length) {
  316. let outlineList = SMathUtil.getIntersectInArray(
  317. diffPoly.regions
  318. );
  319. console.log(outlineList);
  320. // @ts-ignore
  321. sourceIdToDiff[key] = outlineList.map(t => {
  322. let arr = [t.Outer];
  323. t.Inner.forEach((inner): void => {
  324. arr.push(inner);
  325. });
  326. return arr;
  327. });
  328. }
  329. });
  330. const end = +new Date()
  331. console.log(sourceIdToDiff);
  332. console.log(end - start, 'comb-diff');
  333. return sourceIdToDiff
  334. }
  335. } catch (err) {
  336. console.log(err);
  337. }
  338. }
  339. /**
  340. * 计算选中的空间与绘制的区域交集
  341. */
  342. getSpaceCutIntersect(seg?: any): SPoint[][] | undefined {
  343. // 未选中空间- 不计算
  344. if (!this.selectContainer.itemList.length) {
  345. return
  346. }
  347. // 没有绘制不计算
  348. if (!this.cutItem) {
  349. return
  350. }
  351. const sourceIdToIntersect = {};
  352. try {
  353. const start = +new Date();
  354. let cutPoly;
  355. if (seg) {
  356. cutPoly = seg
  357. } else {
  358. const poly = {
  359. regions: [SMathUtil.transferToArray(this.cutItem.pointList)],
  360. inverted: false
  361. }
  362. cutPoly = segments(poly)
  363. }
  364. this.selectContainer.itemList.forEach(item => {
  365. const arr = item.pointList[0].map(t => {
  366. return SMathUtil.transferToArray(t);
  367. })
  368. const poly2 = {
  369. regions: arr,
  370. inverted: false
  371. }
  372. const seg = segments(poly2)
  373. const comb = combine(cutPoly, seg)
  374. if (item.data.SourceId) {
  375. const spoly = polygon(selectIntersect(comb));
  376. // 绘制切割区域可能与空间没有交集
  377. if (spoly.regions.length) {
  378. let outlineList = SMathUtil.getIntersectInArray(
  379. spoly.regions
  380. );
  381. console.log(outlineList);
  382. // @ts-ignore
  383. sourceIdToIntersect[item.data.SourceId] = outlineList.map(t => {
  384. let arr = [t.Outer];
  385. t.Inner.forEach((inner): void => {
  386. arr.push(inner);
  387. });
  388. return arr;
  389. });
  390. // 得到的结果
  391. // sourceIdToIntersect[item.data.SourceId] = polygon(selectIntersect(comb))
  392. } else {
  393. // 没有交集
  394. }
  395. }
  396. })
  397. const end = +new Date()
  398. console.log(end - start, 'comb-intersect', sourceIdToIntersect);
  399. return sourceIdToIntersect
  400. } catch (err) {
  401. console.log(err);
  402. }
  403. return
  404. }
  405. /**
  406. * 如果场景中既有绘制的区域也有创建好的业务空间,则在区域中将已创建的业务空间减去
  407. */
  408. getCurInZone() {
  409. if (!this.cutItem) {
  410. return
  411. }
  412. // 绘制区域外接矩阵
  413. const rect2 = this.cutItem.boundingRect()
  414. // 绘制区域的多边形 - 也是每次减去业务空间后剩下的多边形
  415. let poly = segments({
  416. regions: [SMathUtil.transferToArray(this.cutItem.pointList)],
  417. inverted: false
  418. })
  419. this.zoneList.forEach(item => {
  420. if (item.visible) {
  421. const rect1 = item.boundingRect();
  422. if (SMathUtil.rectIntersection(rect1, rect2)) {
  423. item.pointList.forEach((zoneLine): void => {
  424. let polygons = {
  425. regions: [],
  426. inverted: false
  427. }
  428. zoneLine.forEach((po): void => {
  429. let point = SMathUtil.transferToArray(po);
  430. polygons.regions.push(point);
  431. });
  432. const segZone = segments(polygons);
  433. const comb = combine(poly, segZone);
  434. poly = selectDifference(comb)
  435. })
  436. }
  437. }
  438. })
  439. console.log(poly);
  440. // poly为segments类型
  441. return poly;
  442. }
  443. }