BoundingBox.ts 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. import {ArgumentOutOfRangeException} from "../Exceptions";
  2. import {PointF2D} from "../../Common/DataObjects/PointF2D";
  3. import {SizeF2D} from "../../Common/DataObjects/SizeF2D";
  4. import {RectangleF2D} from "../../Common/DataObjects/RectangleF2D";
  5. /**
  6. * A bounding box delimits an area on the 2D plane.
  7. * @param dataObject Graphical object where the bounding box will be attached
  8. * @param parent Parent bounding box of an object in a higher hierarchy position
  9. * @param connectChildToParent Create a child to parent relationship too. Will be true by default
  10. */
  11. export class BoundingBox {
  12. protected isSymbol: boolean = false;
  13. protected relativePositionHasBeenSet: boolean = false;
  14. protected xBordersHaveBeenSet: boolean = false;
  15. protected yBordersHaveBeenSet: boolean = false;
  16. protected absolutePosition: PointF2D = new PointF2D();
  17. protected relativePosition: PointF2D = new PointF2D();
  18. protected size: SizeF2D = new SizeF2D();
  19. protected marginSize: SizeF2D = new SizeF2D();
  20. protected upperLeftCorner: PointF2D = new PointF2D();
  21. protected upperLeftMarginCorner: PointF2D = new PointF2D();
  22. protected borderLeft: number = 0;
  23. protected borderRight: number = 0;
  24. protected borderTop: number = 0;
  25. protected borderBottom: number = 0;
  26. protected borderMarginLeft: number = 0;
  27. protected borderMarginRight: number = 0;
  28. protected borderMarginTop: number = 0;
  29. protected borderMarginBottom: number = 0;
  30. protected boundingRectangle: RectangleF2D;
  31. protected boundingMarginRectangle: RectangleF2D;
  32. protected childElements: BoundingBox[] = [];
  33. protected parent: BoundingBox;
  34. protected dataObject: Object;
  35. /**
  36. * Create a bounding box
  37. * @param dataObject Graphical object where the bounding box will be attached
  38. * @param parent Parent bounding box of an object in a higher hierarchy position
  39. * @param connectChildToParent Create a child to parent relationship too. Will be true by default
  40. */
  41. constructor(dataObject: Object = undefined, parent: BoundingBox = undefined) {
  42. this.parent = parent;
  43. this.dataObject = dataObject;
  44. this.xBordersHaveBeenSet = false;
  45. this.yBordersHaveBeenSet = false;
  46. if (parent !== undefined) {
  47. this.Parent = parent;
  48. }
  49. }
  50. public get RelativePositionHasBeenSet(): boolean {
  51. return this.relativePositionHasBeenSet;
  52. }
  53. public get XBordersHaveBeenSet(): boolean {
  54. return this.xBordersHaveBeenSet;
  55. }
  56. public set XBordersHaveBeenSet(value: boolean) {
  57. this.xBordersHaveBeenSet = value;
  58. }
  59. public get YBordersHaveBeenSet(): boolean {
  60. return this.yBordersHaveBeenSet;
  61. }
  62. public set YBordersHaveBeenSet(value: boolean) {
  63. this.yBordersHaveBeenSet = value;
  64. }
  65. public get AbsolutePosition(): PointF2D {
  66. return this.absolutePosition;
  67. }
  68. public set AbsolutePosition(value: PointF2D) {
  69. this.absolutePosition = value;
  70. }
  71. public get RelativePosition(): PointF2D {
  72. return this.relativePosition;
  73. }
  74. public set RelativePosition(value: PointF2D) {
  75. this.relativePosition = value;
  76. this.relativePositionHasBeenSet = true;
  77. }
  78. public get Size(): SizeF2D {
  79. return this.size;
  80. }
  81. public set Size(value: SizeF2D) {
  82. this.size = value;
  83. }
  84. public get MarginSize(): SizeF2D {
  85. return this.marginSize;
  86. }
  87. public get UpperLeftCorner(): PointF2D {
  88. return this.upperLeftCorner;
  89. }
  90. public get UpperLeftMarginCorner(): PointF2D {
  91. return this.upperLeftMarginCorner;
  92. }
  93. public get BorderLeft(): number {
  94. return this.borderLeft;
  95. }
  96. public set BorderLeft(value: number) {
  97. this.borderLeft = value;
  98. this.calculateRectangle();
  99. }
  100. public get BorderRight(): number {
  101. return this.borderRight;
  102. }
  103. public set BorderRight(value: number) {
  104. this.borderRight = value;
  105. this.calculateRectangle();
  106. }
  107. public get BorderTop(): number {
  108. return this.borderTop;
  109. }
  110. public set BorderTop(value: number) {
  111. this.borderTop = value;
  112. this.calculateRectangle();
  113. }
  114. public get BorderBottom(): number {
  115. return this.borderBottom;
  116. }
  117. public set BorderBottom(value: number) {
  118. this.borderBottom = value;
  119. this.calculateRectangle();
  120. }
  121. public get BorderMarginLeft(): number {
  122. return this.borderMarginLeft;
  123. }
  124. public set BorderMarginLeft(value: number) {
  125. this.borderMarginLeft = value;
  126. this.calculateMarginRectangle();
  127. }
  128. public get BorderMarginRight(): number {
  129. return this.borderMarginRight;
  130. }
  131. public set BorderMarginRight(value: number) {
  132. this.borderMarginRight = value;
  133. this.calculateMarginRectangle();
  134. }
  135. public get BorderMarginTop(): number {
  136. return this.borderMarginTop;
  137. }
  138. public set BorderMarginTop(value: number) {
  139. this.borderMarginTop = value;
  140. this.calculateMarginRectangle();
  141. }
  142. public get BorderMarginBottom(): number {
  143. return this.borderMarginBottom;
  144. }
  145. public set BorderMarginBottom(value: number) {
  146. this.borderMarginBottom = value;
  147. this.calculateMarginRectangle();
  148. }
  149. public get BoundingRectangle(): RectangleF2D {
  150. return this.boundingRectangle;
  151. }
  152. public get BoundingMarginRectangle(): RectangleF2D {
  153. return this.boundingMarginRectangle;
  154. }
  155. public get ChildElements(): BoundingBox[] {
  156. return this.childElements;
  157. }
  158. public set ChildElements(value: BoundingBox[]) {
  159. this.childElements = value;
  160. }
  161. public get Parent(): BoundingBox {
  162. return this.parent;
  163. }
  164. public set Parent(value: BoundingBox) {
  165. this.parent = value;
  166. if (this.parent.ChildElements.indexOf(this) > -1) {
  167. console.error("BoundingBox of " + (this.dataObject.constructor as any).name +
  168. " already in children list of " + (this.parent.dataObject.constructor as any).name + "'s BoundingBox");
  169. } else {
  170. this.parent.ChildElements.push(this);
  171. }
  172. }
  173. public get DataObject(): Object {
  174. return this.dataObject;
  175. }
  176. public setAbsolutePositionFromParent(): void {
  177. if (this.parent !== undefined) {
  178. this.absolutePosition.x = this.parent.AbsolutePosition.x + this.relativePosition.x;
  179. this.absolutePosition.y = this.parent.AbsolutePosition.y + this.relativePosition.y;
  180. } else {
  181. this.absolutePosition = this.relativePosition;
  182. }
  183. }
  184. /**
  185. * Calculate the the absolute position by adding up all relative positions of all parents (including the own rel. pos.)
  186. */
  187. public calculateAbsolutePosition(): void {
  188. this.absolutePosition.x = this.relativePosition.x;
  189. this.absolutePosition.y = this.relativePosition.y;
  190. let parent: BoundingBox = this.parent;
  191. while (parent !== undefined) {
  192. this.absolutePosition.x += parent.relativePosition.x;
  193. this.absolutePosition.y += parent.relativePosition.y;
  194. parent = parent.parent;
  195. }
  196. }
  197. /**
  198. * This method calculates the Absolute Positions recursively
  199. */
  200. public calculateAbsolutePositionsRecursiveWithoutTopelement(): void {
  201. this.absolutePosition.x = 0.0;
  202. this.absolutePosition.y = 0.0;
  203. for (let idx: number = 0, len: number = this.ChildElements.length; idx < len; ++idx) {
  204. const child: BoundingBox = this.ChildElements[idx];
  205. child.calculateAbsolutePositionsRecursive(this.absolutePosition.x, this.absolutePosition.y);
  206. }
  207. }
  208. /**
  209. * This method calculates the Absolute Positions recursively
  210. * from the root element down to the leaf elements
  211. * @param x
  212. * @param y
  213. */
  214. public calculateAbsolutePositionsRecursive(x: number, y: number): void {
  215. this.absolutePosition.x = this.relativePosition.x + x;
  216. this.absolutePosition.y = this.relativePosition.y + y;
  217. for (let idx: number = 0, len: number = this.ChildElements.length; idx < len; ++idx) {
  218. const child: BoundingBox = this.ChildElements[idx];
  219. child.calculateAbsolutePositionsRecursive(this.absolutePosition.x, this.absolutePosition.y);
  220. }
  221. }
  222. /**
  223. * calculates the absolute positions of all children of this boundingBox
  224. */
  225. public calculateAbsolutePositionsOfChildren(): void {
  226. for (let idx: number = 0, len: number = this.ChildElements.length; idx < len; ++idx) {
  227. const child: BoundingBox = this.ChildElements[idx];
  228. child.calculateAbsolutePositionsRecursive(this.absolutePosition.x, this.absolutePosition.y);
  229. }
  230. }
  231. /**
  232. * This method calculates the BoundingBoxes
  233. */
  234. public calculateBoundingBox(): void {
  235. if (this.childElements.length === 0) {
  236. return;
  237. }
  238. for (let idx: number = 0, len: number = this.ChildElements.length; idx < len; ++idx) {
  239. const childElement: BoundingBox = this.ChildElements[idx];
  240. childElement.calculateBoundingBox();
  241. }
  242. // initialize with max/min values
  243. let minLeft: number = Number.MAX_VALUE;
  244. let maxRight: number = Number.MIN_VALUE;
  245. let minTop: number = Number.MAX_VALUE;
  246. let maxBottom: number = Number.MIN_VALUE;
  247. let minMarginLeft: number = Number.MAX_VALUE;
  248. let maxMarginRight: number = Number.MIN_VALUE;
  249. let minMarginTop: number = Number.MAX_VALUE;
  250. let maxMarginBottom: number = Number.MIN_VALUE;
  251. // apart from symbol elements, where we initialize with the symbol's borders
  252. if (this.isSymbol) {
  253. minLeft = this.borderLeft;
  254. maxRight = this.borderRight;
  255. minTop = this.borderTop;
  256. maxBottom = this.borderBottom;
  257. minMarginLeft = this.borderMarginLeft;
  258. maxMarginRight = this.borderMarginRight;
  259. minMarginTop = this.borderMarginTop;
  260. maxMarginBottom = this.borderMarginBottom;
  261. }
  262. // ChildElements will have their borders calculated, so calculate current borders
  263. for (let idx: number = 0, len: number = this.ChildElements.length; idx < len; ++idx) {
  264. const childElement: BoundingBox = this.ChildElements[idx];
  265. minLeft = Math.min(minLeft, childElement.relativePosition.x + childElement.borderLeft);
  266. maxRight = Math.max(maxRight, childElement.relativePosition.x + childElement.borderRight);
  267. minTop = Math.min(minTop, childElement.relativePosition.y + childElement.borderTop);
  268. maxBottom = Math.max(maxBottom, childElement.relativePosition.y + childElement.borderBottom);
  269. minMarginLeft = Math.min(minMarginLeft, childElement.relativePosition.x + childElement.borderMarginLeft);
  270. maxMarginRight = Math.max(maxMarginRight, childElement.relativePosition.x + childElement.borderMarginRight);
  271. minMarginTop = Math.min(minMarginTop, childElement.relativePosition.y + childElement.borderMarginTop);
  272. maxMarginBottom = Math.max(maxMarginBottom, childElement.relativePosition.y + childElement.borderMarginBottom);
  273. }
  274. // ChildElements will have their borders calculated, so calculate current borders
  275. this.borderLeft = minLeft;
  276. this.borderRight = maxRight;
  277. this.borderTop = minTop;
  278. this.borderBottom = maxBottom;
  279. this.borderMarginLeft = minMarginLeft;
  280. this.borderMarginRight = maxMarginRight;
  281. this.borderMarginTop = minMarginTop;
  282. this.borderMarginBottom = maxMarginBottom;
  283. this.calculateRectangle();
  284. this.calculateMarginRectangle();
  285. this.xBordersHaveBeenSet = true;
  286. this.yBordersHaveBeenSet = true;
  287. }
  288. public calculateTopBottomBorders(): void {
  289. if (this.childElements.length === 0) {
  290. return;
  291. }
  292. for (let idx: number = 0, len: number = this.ChildElements.length; idx < len; ++idx) {
  293. const childElement: BoundingBox = this.ChildElements[idx];
  294. childElement.calculateTopBottomBorders();
  295. }
  296. let minTop: number = Number.MAX_VALUE;
  297. let maxBottom: number = Number.MIN_VALUE;
  298. let minMarginTop: number = Number.MAX_VALUE;
  299. let maxMarginBottom: number = Number.MIN_VALUE;
  300. if (this.yBordersHaveBeenSet) {
  301. minTop = this.borderTop;
  302. maxBottom = this.borderBottom;
  303. minMarginTop = this.borderMarginTop;
  304. maxMarginBottom = this.borderMarginBottom;
  305. }
  306. for (let idx: number = 0, len: number = this.ChildElements.length; idx < len; ++idx) {
  307. const childElement: BoundingBox = this.ChildElements[idx];
  308. minTop = Math.min(minTop, childElement.relativePosition.y + childElement.borderTop);
  309. maxBottom = Math.max(maxBottom, childElement.relativePosition.y + childElement.borderBottom);
  310. minMarginTop = Math.min(minMarginTop, childElement.relativePosition.y + childElement.borderMarginTop);
  311. maxMarginBottom = Math.max(maxMarginBottom, childElement.relativePosition.y + childElement.borderMarginBottom);
  312. }
  313. this.borderTop = minTop;
  314. this.borderBottom = maxBottom;
  315. this.borderMarginTop = minMarginTop;
  316. this.borderMarginBottom = maxMarginBottom;
  317. this.calculateRectangle();
  318. this.calculateMarginRectangle();
  319. }
  320. /**
  321. * This method computes the first non-overlapping position in the placementPsi Element for the current (this) positionAndShapeInfo
  322. * @param placementPsi
  323. * @param direction
  324. * @param position
  325. */
  326. public computeNonOverlappingPositionWithMargin(placementPsi: BoundingBox, direction: ColDirEnum, position: PointF2D): void {
  327. this.RelativePosition = new PointF2D(position.x, position.y);
  328. this.setAbsolutePositionFromParent();
  329. let currentPosition: number = 0.0;
  330. let hasBeenMoved: boolean = false;
  331. do {
  332. switch (direction) {
  333. case ColDirEnum.Left:
  334. case ColDirEnum.Right:
  335. currentPosition = this.relativePosition.x;
  336. placementPsi.calculateMarginPositionAlongDirection(this, direction);
  337. hasBeenMoved = Math.abs(currentPosition - this.relativePosition.x) > 0.001;
  338. break;
  339. case ColDirEnum.Up:
  340. case ColDirEnum.Down:
  341. currentPosition = this.relativePosition.y;
  342. placementPsi.calculateMarginPositionAlongDirection(this, direction);
  343. hasBeenMoved = Math.abs(currentPosition - this.relativePosition.y) > 0.001;
  344. break;
  345. default:
  346. throw new ArgumentOutOfRangeException("direction");
  347. }
  348. }
  349. while (hasBeenMoved);
  350. }
  351. /**
  352. * This method detects a collision (without margins)
  353. * @param psi
  354. * @returns {boolean}
  355. */
  356. public collisionDetection(psi: BoundingBox): boolean {
  357. const overlapWidth: number = Math.min(this.AbsolutePosition.x + this.borderRight, psi.absolutePosition.x + psi.borderRight)
  358. - Math.max(this.AbsolutePosition.x + this.borderLeft, psi.absolutePosition.x + psi.borderLeft);
  359. const overlapHeight: number = Math.min(this.AbsolutePosition.y + this.borderBottom, psi.absolutePosition.y + psi.borderBottom)
  360. - Math.max(this.AbsolutePosition.y + this.borderTop, psi.absolutePosition.y + psi.borderTop);
  361. if (overlapWidth > 0 && overlapHeight > 0) {
  362. return true;
  363. }
  364. return false;
  365. }
  366. /**
  367. * This method checks if the given Psi's Margins lie inside the current Psi's Margins.
  368. * @param psi
  369. * @returns {boolean}
  370. */
  371. public liesInsideBorders(psi: BoundingBox): boolean {
  372. const leftBorderInside: boolean = (this.AbsolutePosition.x + this.borderLeft) <= (psi.absolutePosition.x + psi.borderLeft)
  373. && (psi.absolutePosition.x + psi.borderLeft) <= (this.AbsolutePosition.x + this.borderRight);
  374. const rightBorderInside: boolean = (this.AbsolutePosition.x + this.borderLeft) <= (psi.absolutePosition.x + psi.borderRight)
  375. && (psi.absolutePosition.x + psi.borderRight) <= (this.AbsolutePosition.x + this.borderRight);
  376. if (leftBorderInside && rightBorderInside) {
  377. const topBorderInside: boolean = (this.AbsolutePosition.y + this.borderTop) <= (psi.absolutePosition.y + psi.borderTop)
  378. && (psi.absolutePosition.y + psi.borderTop) <= (this.AbsolutePosition.y + this.borderBottom);
  379. const bottomBorderInside: boolean = (this.AbsolutePosition.y + this.borderTop) <= (psi.absolutePosition.y + psi.borderBottom)
  380. && (psi.absolutePosition.y + psi.borderBottom) <= (this.AbsolutePosition.y + this.borderBottom);
  381. if (topBorderInside && bottomBorderInside) {
  382. return true;
  383. }
  384. }
  385. return false;
  386. }
  387. public pointLiesInsideBorders(position: PointF2D): boolean {
  388. const xInside: boolean = (this.AbsolutePosition.x + this.borderLeft) <= position.x && position.x <= (this.AbsolutePosition.x + this.borderRight);
  389. if (xInside) {
  390. const yInside: boolean = (this.AbsolutePosition.y + this.borderTop) <= position.y && position.y <= (this.AbsolutePosition.y + this.borderBottom);
  391. if (yInside) {
  392. return true;
  393. }
  394. }
  395. return false;
  396. }
  397. /**
  398. * This method detects a collision (margin-wide)
  399. * @param psi
  400. * @returns {boolean}
  401. */
  402. public marginCollisionDetection(psi: BoundingBox): boolean {
  403. const overlapWidth: number = Math.min(this.AbsolutePosition.x + this.borderMarginRight, psi.absolutePosition.x + psi.borderMarginRight)
  404. - Math.max(this.AbsolutePosition.x + this.borderMarginLeft, psi.absolutePosition.x + psi.borderMarginLeft);
  405. const overlapHeight: number = Math.min(this.AbsolutePosition.y + this.borderMarginBottom, psi.absolutePosition.y + psi.borderMarginBottom)
  406. - Math.max(this.AbsolutePosition.y + this.borderMarginTop, psi.absolutePosition.y + psi.borderMarginTop);
  407. if (overlapWidth > 0 && overlapHeight > 0) {
  408. return true;
  409. }
  410. return false;
  411. }
  412. /**
  413. * This method checks if the given Psi's Margins lie inside the current Psi's Margins
  414. * @param psi
  415. * @returns {boolean}
  416. */
  417. public liesInsideMargins(psi: BoundingBox): boolean {
  418. const leftMarginInside: boolean = (this.AbsolutePosition.x + this.borderMarginLeft) <= (psi.absolutePosition.x + psi.borderMarginLeft)
  419. && (psi.absolutePosition.x + psi.borderMarginLeft) <= (this.AbsolutePosition.x + this.borderMarginRight);
  420. const rightMarginInside: boolean = (this.AbsolutePosition.x + this.borderMarginLeft) <= (psi.absolutePosition.x + psi.borderMarginRight)
  421. && (psi.absolutePosition.x + psi.borderMarginRight) <= (this.AbsolutePosition.x + this.borderMarginRight);
  422. if (leftMarginInside && rightMarginInside) {
  423. const topMarginInside: boolean = (this.AbsolutePosition.y + this.borderMarginTop) <= (psi.absolutePosition.y + psi.borderMarginTop)
  424. && (psi.absolutePosition.y + psi.borderMarginTop) <= (this.AbsolutePosition.y + this.borderMarginBottom);
  425. const bottomMarginInside: boolean = (this.AbsolutePosition.y + this.borderMarginTop) <= (psi.absolutePosition.y + psi.borderMarginBottom)
  426. && (psi.absolutePosition.y + psi.borderMarginBottom) <= (this.AbsolutePosition.y + this.borderMarginBottom);
  427. if (topMarginInside && bottomMarginInside) {
  428. return true;
  429. }
  430. }
  431. return false;
  432. }
  433. public pointLiesInsideMargins(position: PointF2D): boolean {
  434. const xInside: boolean = (this.AbsolutePosition.x + this.borderMarginLeft) <= position.x
  435. && position.x <= (this.AbsolutePosition.x + this.borderMarginRight);
  436. if (xInside) {
  437. const yInside: boolean = (this.AbsolutePosition.y + this.borderMarginTop) <= position.y
  438. && position.y <= (this.AbsolutePosition.y + this.borderMarginBottom);
  439. if (yInside) {
  440. return true;
  441. }
  442. }
  443. return false;
  444. }
  445. /**
  446. * This method computes the first non-overlapping position in the placementPsi Element for the current (this) positionAndShapeInfo
  447. * @param placementPsi
  448. * @param direction
  449. * @param position
  450. */
  451. public computeNonOverlappingPosition(placementPsi: BoundingBox, direction: ColDirEnum, position: PointF2D): void {
  452. this.RelativePosition = new PointF2D(position.x, position.y);
  453. this.setAbsolutePositionFromParent();
  454. let currentPosition: number = 0.0;
  455. let hasBeenMoved: boolean = false;
  456. do {
  457. switch (direction) {
  458. case ColDirEnum.Left:
  459. case ColDirEnum.Right:
  460. currentPosition = this.relativePosition.x;
  461. placementPsi.calculatePositionAlongDirection(this, direction);
  462. hasBeenMoved = Math.abs(currentPosition - this.relativePosition.x) > 0.0001;
  463. break;
  464. case ColDirEnum.Up:
  465. case ColDirEnum.Down:
  466. currentPosition = this.relativePosition.y;
  467. placementPsi.calculatePositionAlongDirection(this, direction);
  468. hasBeenMoved = Math.abs(currentPosition - this.relativePosition.y) > 0.0001;
  469. break;
  470. default:
  471. throw new ArgumentOutOfRangeException("direction");
  472. }
  473. } while (hasBeenMoved); // as long as the element is moved
  474. }
  475. public getClickedObjectOfType<T>(clickPosition: PointF2D): T {
  476. const obj: Object = this.dataObject;
  477. if (this.pointLiesInsideBorders(clickPosition) && (<T>obj !== undefined)) {
  478. return (obj as T);
  479. }
  480. for (let idx: number = 0, len: number = this.childElements.length; idx < len; ++idx) {
  481. const psi: BoundingBox = this.childElements[idx];
  482. const innerObject: Object = psi.getClickedObjectOfType<T>(clickPosition);
  483. if (innerObject !== undefined) {
  484. return (innerObject as T);
  485. }
  486. }
  487. return undefined;
  488. }
  489. public getObjectsInRegion<T>(region: BoundingBox, liesInside: boolean = true): T[] {
  490. if (<T>this.dataObject !== undefined) {
  491. if (liesInside) {
  492. if (region.liesInsideBorders(this)) {
  493. return [this.dataObject as T];
  494. }
  495. } else {
  496. if (region.collisionDetection(this)) {
  497. return [this.dataObject as T];
  498. }
  499. }
  500. // FIXME Andrea: add here "return []"?
  501. }
  502. const result: T[] = [];
  503. for (const child of this.childElements) {
  504. result.concat(child.getObjectsInRegion<T>(region, liesInside));
  505. }
  506. return result;
  507. //return this.childElements.SelectMany(psi => psi.getObjectsInRegion<T>(region, liesInside));
  508. }
  509. protected calculateRectangle(): void {
  510. this.upperLeftCorner = new PointF2D(this.borderLeft, this.borderTop);
  511. this.size = new SizeF2D(this.borderRight - this.borderLeft, this.borderBottom - this.borderTop);
  512. this.boundingRectangle = RectangleF2D.createFromLocationAndSize(this.upperLeftCorner, this.size);
  513. }
  514. protected calculateMarginRectangle(): void {
  515. this.upperLeftMarginCorner = new PointF2D(this.borderMarginLeft, this.borderMarginTop);
  516. this.marginSize = new SizeF2D(this.borderMarginRight - this.borderMarginLeft, this.borderMarginBottom - this.borderMarginTop);
  517. this.boundingMarginRectangle = RectangleF2D.createFromLocationAndSize(this.upperLeftMarginCorner, this.marginSize);
  518. }
  519. /**
  520. * This method calculates the margin border along the given direction so that no collision takes place along this direction
  521. * @param toBePlaced
  522. * @param direction
  523. */
  524. private calculateMarginPositionAlongDirection(toBePlaced: BoundingBox, direction: ColDirEnum): void {
  525. // now this will be the "known" Element, about to get bigger with the toBePlaced
  526. // eg toBePlaced will always be in the PositionAndShape hierarchy a Child of this
  527. // example: this = StaffEntry, toBePlaced = Accidental
  528. // logical return
  529. if (this === toBePlaced) {
  530. return;
  531. }
  532. // check for collision only at symbols and return border
  533. if (this.isSymbol && this.marginCollisionDetection(toBePlaced)) {
  534. let shiftDistance: number = 0;
  535. switch (direction) {
  536. case ColDirEnum.Left:
  537. shiftDistance = (this.absolutePosition.x + this.borderMarginLeft) - (toBePlaced.absolutePosition.x + toBePlaced.borderMarginRight);
  538. toBePlaced.relativePosition.x += shiftDistance;
  539. toBePlaced.absolutePosition.x += shiftDistance;
  540. return;
  541. case ColDirEnum.Right:
  542. shiftDistance = (this.absolutePosition.x + this.borderMarginRight) - (toBePlaced.absolutePosition.x + toBePlaced.borderMarginLeft);
  543. toBePlaced.relativePosition.x += shiftDistance;
  544. toBePlaced.absolutePosition.x += shiftDistance;
  545. return;
  546. case ColDirEnum.Up:
  547. shiftDistance = (this.absolutePosition.y + this.borderMarginTop) - (toBePlaced.absolutePosition.y + toBePlaced.borderMarginBottom);
  548. toBePlaced.relativePosition.y += shiftDistance;
  549. toBePlaced.absolutePosition.y += shiftDistance;
  550. return;
  551. case ColDirEnum.Down:
  552. shiftDistance = (this.absolutePosition.y + this.borderMarginBottom) - (toBePlaced.absolutePosition.y + toBePlaced.borderMarginTop);
  553. toBePlaced.relativePosition.y += shiftDistance;
  554. toBePlaced.absolutePosition.y += shiftDistance;
  555. return;
  556. default:
  557. throw new ArgumentOutOfRangeException("direction");
  558. }
  559. }
  560. // perform check for all children iteratively and return border from children symbols
  561. for (let idx: number = 0, len: number = this.ChildElements.length; idx < len; ++idx) {
  562. const childElement: BoundingBox = this.ChildElements[idx];
  563. childElement.calculateMarginPositionAlongDirection(toBePlaced, direction);
  564. }
  565. }
  566. /**
  567. * This method calculates the border along the given direction so that no collision takes place along this direction
  568. * @param toBePlaced
  569. * @param direction
  570. */
  571. private calculatePositionAlongDirection(toBePlaced: BoundingBox, direction: ColDirEnum): void {
  572. // now this will be the "known" Element, about to get bigger with the toBePlaced
  573. // eg toBePlaced will always be in the PositionAndShape hierarchy a Child of this
  574. // example: this = StaffEntry, toBePlaced = Accidental
  575. // logical return
  576. if (this === toBePlaced) {
  577. return;
  578. }
  579. // check for collision only at symbols and return border
  580. if (this.isSymbol && this.collisionDetection(toBePlaced)) {
  581. let shiftDistance: number;
  582. switch (direction) {
  583. case ColDirEnum.Left:
  584. shiftDistance = (this.absolutePosition.x + this.borderLeft) - (toBePlaced.absolutePosition.x + toBePlaced.borderRight);
  585. toBePlaced.relativePosition.x += shiftDistance;
  586. toBePlaced.absolutePosition.x += shiftDistance;
  587. return;
  588. case ColDirEnum.Right:
  589. shiftDistance = (this.absolutePosition.x + this.borderRight) - (toBePlaced.absolutePosition.x + toBePlaced.borderLeft);
  590. toBePlaced.relativePosition.x += shiftDistance;
  591. toBePlaced.absolutePosition.x += shiftDistance;
  592. return;
  593. case ColDirEnum.Up:
  594. shiftDistance = (this.absolutePosition.y + this.borderTop) - (toBePlaced.absolutePosition.y + toBePlaced.borderBottom);
  595. toBePlaced.relativePosition.y += shiftDistance;
  596. toBePlaced.absolutePosition.y += shiftDistance;
  597. return;
  598. case ColDirEnum.Down:
  599. shiftDistance = (this.absolutePosition.y + this.borderBottom) - (toBePlaced.absolutePosition.y + toBePlaced.borderTop);
  600. toBePlaced.relativePosition.y += shiftDistance;
  601. toBePlaced.absolutePosition.y += shiftDistance;
  602. return;
  603. default:
  604. throw new ArgumentOutOfRangeException("direction");
  605. }
  606. }
  607. // perform check for all children iteratively and return border from children symbols
  608. for (let idx: number = 0, len: number = this.ChildElements.length; idx < len; ++idx) {
  609. const childElement: BoundingBox = this.ChildElements[idx];
  610. childElement.calculatePositionAlongDirection(toBePlaced, direction);
  611. }
  612. }
  613. }
  614. export enum ColDirEnum {
  615. Left = 0,
  616. Right = 1,
  617. Up = 2,
  618. Down = 3
  619. }