ellipse.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Ellipse = void 0;
  4. const point_1 = require("./point");
  5. const rectangle_1 = require("./rectangle");
  6. const geometry_1 = require("./geometry");
  7. class Ellipse extends geometry_1.Geometry {
  8. get center() {
  9. return new point_1.Point(this.x, this.y);
  10. }
  11. constructor(x, y, a, b) {
  12. super();
  13. this.x = x == null ? 0 : x;
  14. this.y = y == null ? 0 : y;
  15. this.a = a == null ? 0 : a;
  16. this.b = b == null ? 0 : b;
  17. }
  18. /**
  19. * Returns a rectangle that is the bounding box of the ellipse.
  20. */
  21. bbox() {
  22. return rectangle_1.Rectangle.fromEllipse(this);
  23. }
  24. /**
  25. * Returns a point that is the center of the ellipse.
  26. */
  27. getCenter() {
  28. return this.center;
  29. }
  30. inflate(dx, dy) {
  31. const w = dx;
  32. const h = dy != null ? dy : dx;
  33. this.a += 2 * w;
  34. this.b += 2 * h;
  35. return this;
  36. }
  37. normalizedDistance(x, y) {
  38. const ref = point_1.Point.create(x, y);
  39. const dx = ref.x - this.x;
  40. const dy = ref.y - this.y;
  41. const a = this.a;
  42. const b = this.b;
  43. return (dx * dx) / (a * a) + (dy * dy) / (b * b);
  44. }
  45. containsPoint(x, y) {
  46. return this.normalizedDistance(x, y) <= 1;
  47. }
  48. /**
  49. * Returns an array of the intersection points of the ellipse and the line.
  50. * Returns `null` if no intersection exists.
  51. */
  52. intersectsWithLine(line) {
  53. const intersections = [];
  54. const rx = this.a;
  55. const ry = this.b;
  56. const a1 = line.start;
  57. const a2 = line.end;
  58. const dir = line.vector();
  59. const diff = a1.diff(new point_1.Point(this.x, this.y));
  60. const mDir = new point_1.Point(dir.x / (rx * rx), dir.y / (ry * ry));
  61. const mDiff = new point_1.Point(diff.x / (rx * rx), diff.y / (ry * ry));
  62. const a = dir.dot(mDir);
  63. const b = dir.dot(mDiff);
  64. const c = diff.dot(mDiff) - 1.0;
  65. const d = b * b - a * c;
  66. if (d < 0) {
  67. return null;
  68. }
  69. if (d > 0) {
  70. const root = Math.sqrt(d);
  71. const ta = (-b - root) / a;
  72. const tb = (-b + root) / a;
  73. if ((ta < 0 || ta > 1) && (tb < 0 || tb > 1)) {
  74. // outside
  75. return null;
  76. }
  77. if (ta >= 0 && ta <= 1) {
  78. intersections.push(a1.lerp(a2, ta));
  79. }
  80. if (tb >= 0 && tb <= 1) {
  81. intersections.push(a1.lerp(a2, tb));
  82. }
  83. }
  84. else {
  85. const t = -b / a;
  86. if (t >= 0 && t <= 1) {
  87. intersections.push(a1.lerp(a2, t));
  88. }
  89. else {
  90. // outside
  91. return null;
  92. }
  93. }
  94. return intersections;
  95. }
  96. /**
  97. * Returns the point on the boundary of the ellipse that is the
  98. * intersection of the ellipse with a line starting in the center
  99. * of the ellipse ending in the point `p`.
  100. *
  101. * If angle is specified, the intersection will take into account
  102. * the rotation of the ellipse by angle degrees around its center.
  103. */
  104. intersectsWithLineFromCenterToPoint(p, angle = 0) {
  105. const ref = point_1.Point.clone(p);
  106. if (angle) {
  107. ref.rotate(angle, this.getCenter());
  108. }
  109. const dx = ref.x - this.x;
  110. const dy = ref.y - this.y;
  111. let result;
  112. if (dx === 0) {
  113. result = this.bbox().getNearestPointToPoint(ref);
  114. if (angle) {
  115. return result.rotate(-angle, this.getCenter());
  116. }
  117. return result;
  118. }
  119. const m = dy / dx;
  120. const mSquared = m * m;
  121. const aSquared = this.a * this.a;
  122. const bSquared = this.b * this.b;
  123. let x = Math.sqrt(1 / (1 / aSquared + mSquared / bSquared));
  124. x = dx < 0 ? -x : x;
  125. const y = m * x;
  126. result = new point_1.Point(this.x + x, this.y + y);
  127. if (angle) {
  128. return result.rotate(-angle, this.getCenter());
  129. }
  130. return result;
  131. }
  132. /**
  133. * Returns the angle between the x-axis and the tangent from a point. It is
  134. * valid for points lying on the ellipse boundary only.
  135. */
  136. tangentTheta(p) {
  137. const ref = point_1.Point.clone(p);
  138. const x0 = ref.x;
  139. const y0 = ref.y;
  140. const a = this.a;
  141. const b = this.b;
  142. const center = this.bbox().center;
  143. const cx = center.x;
  144. const cy = center.y;
  145. const refPointDelta = 30;
  146. const q1 = x0 > center.x + a / 2;
  147. const q3 = x0 < center.x - a / 2;
  148. let x;
  149. let y;
  150. if (q1 || q3) {
  151. y = x0 > center.x ? y0 - refPointDelta : y0 + refPointDelta;
  152. x =
  153. (a * a) / (x0 - cx) -
  154. (a * a * (y0 - cy) * (y - cy)) / (b * b * (x0 - cx)) +
  155. cx;
  156. }
  157. else {
  158. x = y0 > center.y ? x0 + refPointDelta : x0 - refPointDelta;
  159. y =
  160. (b * b) / (y0 - cy) -
  161. (b * b * (x0 - cx) * (x - cx)) / (a * a * (y0 - cy)) +
  162. cy;
  163. }
  164. return new point_1.Point(x, y).theta(ref);
  165. }
  166. scale(sx, sy) {
  167. this.a *= sx;
  168. this.b *= sy;
  169. return this;
  170. }
  171. rotate(angle, origin) {
  172. const rect = rectangle_1.Rectangle.fromEllipse(this);
  173. rect.rotate(angle, origin);
  174. const ellipse = Ellipse.fromRect(rect);
  175. this.a = ellipse.a;
  176. this.b = ellipse.b;
  177. this.x = ellipse.x;
  178. this.y = ellipse.y;
  179. return this;
  180. }
  181. translate(dx, dy) {
  182. const p = point_1.Point.create(dx, dy);
  183. this.x += p.x;
  184. this.y += p.y;
  185. return this;
  186. }
  187. equals(ellipse) {
  188. return (ellipse != null &&
  189. ellipse.x === this.x &&
  190. ellipse.y === this.y &&
  191. ellipse.a === this.a &&
  192. ellipse.b === this.b);
  193. }
  194. clone() {
  195. return new Ellipse(this.x, this.y, this.a, this.b);
  196. }
  197. toJSON() {
  198. return { x: this.x, y: this.y, a: this.a, b: this.b };
  199. }
  200. serialize() {
  201. return `${this.x} ${this.y} ${this.a} ${this.b}`;
  202. }
  203. }
  204. exports.Ellipse = Ellipse;
  205. (function (Ellipse) {
  206. function isEllipse(instance) {
  207. return instance != null && instance instanceof Ellipse;
  208. }
  209. Ellipse.isEllipse = isEllipse;
  210. })(Ellipse = exports.Ellipse || (exports.Ellipse = {}));
  211. (function (Ellipse) {
  212. function create(x, y, a, b) {
  213. if (x == null || typeof x === 'number') {
  214. return new Ellipse(x, y, a, b);
  215. }
  216. return parse(x);
  217. }
  218. Ellipse.create = create;
  219. function parse(e) {
  220. if (Ellipse.isEllipse(e)) {
  221. return e.clone();
  222. }
  223. if (Array.isArray(e)) {
  224. return new Ellipse(e[0], e[1], e[2], e[3]);
  225. }
  226. return new Ellipse(e.x, e.y, e.a, e.b);
  227. }
  228. Ellipse.parse = parse;
  229. function fromRect(rect) {
  230. const center = rect.center;
  231. return new Ellipse(center.x, center.y, rect.width / 2, rect.height / 2);
  232. }
  233. Ellipse.fromRect = fromRect;
  234. })(Ellipse = exports.Ellipse || (exports.Ellipse = {}));
  235. //# sourceMappingURL=ellipse.js.map