Source: model/Segment.js

  1. /**
  2. * @file Class Segment.
  3. * @version March 22, 2017
  4. *
  5. * @author Olivier Pirson --- http://www.opimedia.be/
  6. * @license GPLv3 --- Copyright (C) 2017 Olivier Pirson
  7. */
  8. /**
  9. * A segment,
  10. * represented by its two points.
  11. */
  12. class Segment {
  13. /**
  14. * Constructs a segment from point a to point b.
  15. *
  16. * @param a: Point
  17. * @param b: Point != a
  18. */
  19. constructor(a, b) {
  20. assert(a instanceof Point, a);
  21. assert(b instanceof Point, b);
  22. assert(!a.isEquals(b));
  23. this._a = a;
  24. this._b = b;
  25. }
  26. /**
  27. * Returns the first point.
  28. *
  29. * @returns {Point}
  30. */
  31. get a() { return this._a; }
  32. /**
  33. * Returns the second point.
  34. *
  35. * @returns {Point}
  36. */
  37. get b() { return this._b; }
  38. /**
  39. * Compare two segments before with min points
  40. * and maybe after with max points.
  41. *
  42. * @param {Segment} other
  43. *
  44. * @returns {number} -1, 0 or 1
  45. */
  46. compare(other) {
  47. assert(other instanceof Segment, other);
  48. const compA = this.a.compare(this.b);
  49. const compOtherA = other.a.compare(other.b);
  50. let comp = (compA <= 0
  51. ? (compOtherA <= 0
  52. ? this.a.compare(other.a)
  53. : this.a.compare(other.b))
  54. : (compOtherA <= 0
  55. ? this.b.compare(other.a)
  56. : this.b.compare(other.b)));
  57. return (comp !== 0
  58. ? comp
  59. : (compA <= 0
  60. ? (compOtherA <= 0
  61. ? this.b.compare(other.b)
  62. : this.b.compare(other.a))
  63. : (compOtherA <= 0
  64. ? this.a.compare(other.b)
  65. : this.a.compare(other.a))));
  66. }
  67. /**
  68. * Returns true iff have at leat one common endpoint.
  69. */
  70. isCommonEndPoint(other) {
  71. assert(other instanceof Segment, other);
  72. return (this.a.isEquals(other.a) || this.a.isEquals(other.b)
  73. || this.b.isEquals(other.b) || this.b.isEquals(other.a));
  74. }
  75. /**
  76. * Returns true iff the segment is equals to the segment other,
  77. * namely iff they are same points (regardless of the order).
  78. *
  79. * @param {Segment} other
  80. *
  81. * @returns {boolean}
  82. */
  83. isEquals(other) {
  84. assert(other instanceof Segment, other);
  85. return (((this.a === other.a) && (this.b === other.b))
  86. || ((this.a === other.b) && (this.b === other.a)));
  87. }
  88. /**
  89. * Returns true iff point is an extremity point of the segment.
  90. *
  91. * @param {Point} point
  92. *
  93. * @returns {boolean}
  94. */
  95. isExtremityPoint(point) {
  96. assert(point instanceof Point, point);
  97. return point.isEquals(this.a) || point.isEquals(this.b);
  98. }
  99. /**
  100. * Returns true iff is a horizontal segment
  101. *
  102. * @returns {boolean}
  103. */
  104. isHorizontal() {
  105. return (this.a.y === this.b.y);
  106. }
  107. /**
  108. * Returns true iff p is in the segment (inclusive its extremities).
  109. *
  110. * @param {Point} p
  111. *
  112. * @returns {boolean}
  113. */
  114. isIn(p) {
  115. assert(p instanceof Point, p);
  116. if (!this.isInSameLine(p)) { // not on the same line
  117. return false;
  118. }
  119. else if (this.isVertical()) { // on the same vertical line
  120. return ((this.a.x <= p.x) && (p.x <= this.b.x)) || ((this.b.x <= p.x) && (p.x <= this.a.x));
  121. }
  122. else { // on the same line, not vertical
  123. return ((this.a.y <= p.y) && (p.y <= this.b.y)) || ((this.b.y <= p.y) && (p.y <= this.a.y));
  124. }
  125. }
  126. /**
  127. * Returns true iff p is in the same line that the segment.
  128. *
  129. * @param {Point} p
  130. *
  131. * @returns {boolean}
  132. */
  133. isInSameLine(p) {
  134. assert(p instanceof Point, p);
  135. return (new Triplet(this.a, this.b, p)).isCollinear();
  136. }
  137. /**
  138. * Returns true iff two segments intersect.
  139. *
  140. * @param {Segment} other
  141. *
  142. * @returns {boolean}
  143. */
  144. isIntersect(other) {
  145. assert(other instanceof Segment, other);
  146. return (this.isProperIntersect(other)
  147. || this.isIn(other.a) || this.isIn(other.b)
  148. || other.isIn(this.a) || other.isIn(this.b));
  149. }
  150. /**
  151. * Returns true iff p is in the segment (exclusive its extremities).
  152. *
  153. * @param {Point} p
  154. *
  155. * @returns {boolean}
  156. */
  157. isProperIn(p) {
  158. assert(p instanceof Point, p);
  159. if (!this.isInSameLine(p)) { // not on the same line
  160. return false;
  161. }
  162. else if (this.isVertical()) { // on the same line, not vertical
  163. return ((this.a.x < p.x) && (p.x < this.b.x)) || ((this.b.x < p.x) && (p.x < this.a.x));
  164. }
  165. else { // on the same vertical line
  166. return ((this.a.y < p.y) && (p.y < this.b.y)) || ((this.b.y < p.y) && (p.y < this.a.y));
  167. }
  168. }
  169. /**
  170. * Returns true iff two segments intersect without their endpoints.
  171. *
  172. * @param {Segment} other
  173. *
  174. * @returns {boolean}
  175. */
  176. isProperIntersect(other) {
  177. assert(other instanceof Segment, other);
  178. const abc_abd = ((new Triplet(this.a, this.b, other.a)).orientation()
  179. *(new Triplet(this.a, this.b, other.b)).orientation());
  180. const cda_cdb = ((new Triplet(other.a, other.b, this.a)).orientation()
  181. *(new Triplet(other.a, other.b, this.b)).orientation());
  182. return (abc_abd * cda_cdb !== 0) && (abc_abd < 0) && (cda_cdb < 0);
  183. }
  184. /**
  185. * Returns true iff the horizontal line of vertical coordonate y
  186. * (strictically) intersect the segment.
  187. */
  188. isProperIntersectWithHorizontalLine(y) {
  189. assert(typeof y === "number", y);
  190. return (((this.a.y < y) && (y < this.b.y))
  191. || ((this.b.y < y) && (y < this.a.y)));
  192. }
  193. /**
  194. * Returns true iff is a vertical segment
  195. *
  196. * @returns {boolean}
  197. */
  198. isVertical() {
  199. return (this.a.x === this.b.x);
  200. }
  201. /**
  202. * If segment.a <= segment.b
  203. * then return this,
  204. * else return a new segment with points swapped.
  205. *
  206. * @returns {Segment}
  207. */
  208. ordonned() {
  209. return (this.a.compare(this.b) <= 0
  210. ? this
  211. : new Segment(this.b, this.a));
  212. }
  213. /**
  214. * Returns the slope of the segment.
  215. * If the segment is vertical, then return null.
  216. *
  217. * @returns {number|null}
  218. */
  219. slope() {
  220. const diff_x = this.a.x - this.b.x;
  221. return (this.isVertical()
  222. ? null
  223. : (this.a.y - this.b.y)/diff_x);
  224. }
  225. /**
  226. * Returns a string representation of the segment.
  227. *
  228. * Each number is represented with at most precision figures after the decimal point.
  229. *
  230. * @returns {String}
  231. */
  232. toString(precision=2) {
  233. assert(Number.isInteger(precision), precision);
  234. assert(precision >= 0, precision);
  235. return (this.a.toString(precision)
  236. + ":" + this.b.toString(precision));
  237. }
  238. /**
  239. * Returns the y coordinate for the given x coordinate.
  240. * If the segment is vertical, then return null.
  241. *
  242. * @param {number} x
  243. *
  244. * @returns {number|null}
  245. */
  246. yOnLine(x) {
  247. assert(typeof x === "number", x);
  248. return (this.isVertical()
  249. ? null
  250. : this.slope()*(x - this.a.x) + this.a.y);
  251. }
  252. }