geometric-library 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -0
  3. package/dist/cjs/abstracts/Angle.js +60 -0
  4. package/dist/cjs/abstracts/Figure.js +147 -0
  5. package/dist/cjs/abstracts/Flag.js +27 -0
  6. package/dist/cjs/abstracts/Magnitude.js +25 -0
  7. package/dist/cjs/abstracts/Point.js +50 -0
  8. package/dist/cjs/abstracts/Vector.js +74 -0
  9. package/dist/cjs/figures/ArcCurve.js +132 -0
  10. package/dist/cjs/figures/Circle.js +43 -0
  11. package/dist/cjs/figures/CubicBezierCurve.js +149 -0
  12. package/dist/cjs/figures/Ellipse.js +111 -0
  13. package/dist/cjs/figures/Line.js +264 -0
  14. package/dist/cjs/figures/Polygon.js +17 -0
  15. package/dist/cjs/figures/QuadraticBezierCurve.js +104 -0
  16. package/dist/cjs/index.js +48 -0
  17. package/dist/cjs/types/index.js +2 -0
  18. package/dist/cjs/utilities/Calculator.js +153 -0
  19. package/dist/cjs/utilities/index.js +10 -0
  20. package/dist/esm/abstracts/Angle.js +56 -0
  21. package/dist/esm/abstracts/Figure.js +143 -0
  22. package/dist/esm/abstracts/Flag.js +23 -0
  23. package/dist/esm/abstracts/Magnitude.js +21 -0
  24. package/dist/esm/abstracts/Point.js +46 -0
  25. package/dist/esm/abstracts/Vector.js +70 -0
  26. package/dist/esm/figures/ArcCurve.js +128 -0
  27. package/dist/esm/figures/Circle.js +39 -0
  28. package/dist/esm/figures/CubicBezierCurve.js +145 -0
  29. package/dist/esm/figures/Ellipse.js +107 -0
  30. package/dist/esm/figures/Line.js +260 -0
  31. package/dist/esm/figures/Polygon.js +13 -0
  32. package/dist/esm/figures/QuadraticBezierCurve.js +100 -0
  33. package/dist/esm/index.js +17 -0
  34. package/dist/esm/types/index.js +1 -0
  35. package/dist/esm/utilities/Calculator.js +149 -0
  36. package/dist/esm/utilities/index.js +7 -0
  37. package/dist/types/abstracts/Angle.d.ts +18 -0
  38. package/dist/types/abstracts/Figure.d.ts +22 -0
  39. package/dist/types/abstracts/Flag.d.ts +10 -0
  40. package/dist/types/abstracts/Magnitude.d.ts +9 -0
  41. package/dist/types/abstracts/Point.d.ts +14 -0
  42. package/dist/types/abstracts/Vector.d.ts +20 -0
  43. package/dist/types/figures/ArcCurve.d.ts +23 -0
  44. package/dist/types/figures/Circle.d.ts +12 -0
  45. package/dist/types/figures/CubicBezierCurve.d.ts +23 -0
  46. package/dist/types/figures/Ellipse.d.ts +26 -0
  47. package/dist/types/figures/Line.d.ts +44 -0
  48. package/dist/types/figures/Polygon.d.ts +7 -0
  49. package/dist/types/figures/QuadraticBezierCurve.d.ts +21 -0
  50. package/dist/types/index.d.ts +16 -0
  51. package/dist/types/types/index.d.ts +144 -0
  52. package/dist/types/utilities/Calculator.d.ts +49 -0
  53. package/dist/types/utilities/index.d.ts +6 -0
  54. package/package.json +60 -0
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Calculator = void 0;
4
+ const decimal_js_1 = require("decimal.js");
5
+ class Calculator {
6
+ _instance;
7
+ constructor(arg) {
8
+ this._instance = new decimal_js_1.Decimal(arg);
9
+ }
10
+ // STATIC METHODS
11
+ static abs(arg) {
12
+ return Calculator.computeUnaryOperation('abs', arg);
13
+ }
14
+ static acos(arg) {
15
+ return Calculator.computeUnaryOperation('acos', arg);
16
+ }
17
+ static add(first, second) {
18
+ return Calculator.computeBinaryOperation('add', [first, second]);
19
+ }
20
+ static atan(arg) {
21
+ return Calculator.computeUnaryOperation('atan', arg);
22
+ }
23
+ static atan2(first, second) {
24
+ return Calculator.computeBinaryOperation('atan2', [first, second]);
25
+ }
26
+ static cos(arg) {
27
+ return Calculator.computeUnaryOperation('cos', arg);
28
+ }
29
+ static div(first, second) {
30
+ return Calculator.computeBinaryOperation('div', [first, second]);
31
+ }
32
+ static max(args) {
33
+ return Calculator.computeIndefiniteOperation('max', args);
34
+ }
35
+ static min(args) {
36
+ return Calculator.computeIndefiniteOperation('min', args);
37
+ }
38
+ static mod(first, second) {
39
+ return Calculator.computeBinaryOperation('mod', [first, second]);
40
+ }
41
+ static mul(first, second) {
42
+ return Calculator.computeBinaryOperation('mul', [first, second]);
43
+ }
44
+ static neg(arg) {
45
+ return Calculator.computeUnaryOperation('neg', arg);
46
+ }
47
+ static pow(first, second) {
48
+ return Calculator.computeBinaryOperation('pow', [first, second]);
49
+ }
50
+ static sin(arg) {
51
+ return Calculator.computeUnaryOperation('sin', arg);
52
+ }
53
+ static sqrt(arg) {
54
+ return Calculator.computeUnaryOperation('sqrt', arg);
55
+ }
56
+ static sub(first, second) {
57
+ return Calculator.computeBinaryOperation('sub', [first, second]);
58
+ }
59
+ static tan(arg) {
60
+ return Calculator.computeUnaryOperation('tan', arg);
61
+ }
62
+ // PRIVATE METHODS
63
+ static computeBinaryOperation(operation, args) {
64
+ const [a, b] = Calculator.toDecimalArgs(args);
65
+ const result = decimal_js_1.Decimal[operation](a, b);
66
+ const roundedResult = this.roundDecimalWithPrecision(result);
67
+ return new Calculator(roundedResult);
68
+ }
69
+ static computeIndefiniteOperation(operation, args) {
70
+ const nargs = Calculator.toDecimalArgs(args);
71
+ const result = decimal_js_1.Decimal[operation](...nargs);
72
+ const roundedResult = this.roundDecimalWithPrecision(result);
73
+ return new Calculator(roundedResult);
74
+ }
75
+ static computeUnaryOperation(operation, arg) {
76
+ const [a] = Calculator.toDecimalArgs([arg]);
77
+ const result = new decimal_js_1.Decimal(a)[operation]();
78
+ const roundedResult = this.roundDecimalWithPrecision(result);
79
+ return new Calculator(roundedResult);
80
+ }
81
+ /**
82
+ * @todo The precision calculation could be improved to test at what decimal point is the distance less
83
+ * than epsilon, and then to round up to that point instead of choosing an arbitrary precision.
84
+ */
85
+ static roundDecimalWithPrecision(value) {
86
+ const epsilon = new decimal_js_1.Decimal('1e-8');
87
+ const distanceToLowerInt = value.sub(value.floor());
88
+ const distanceToUpperInt = value.ceil().sub(value);
89
+ if (distanceToLowerInt.lte(epsilon)) {
90
+ return value.floor().toNumber();
91
+ }
92
+ if (distanceToUpperInt.lte(epsilon)) {
93
+ return value.ceil().toNumber();
94
+ }
95
+ return value.toNumber();
96
+ }
97
+ static toDecimalArgs(args) {
98
+ return args.map((arg) => +arg);
99
+ }
100
+ // INSTANCE METHODS
101
+ abs() {
102
+ return Calculator.computeUnaryOperation('abs', this);
103
+ }
104
+ acos() {
105
+ return Calculator.computeUnaryOperation('acos', this);
106
+ }
107
+ add(second) {
108
+ return Calculator.computeBinaryOperation('add', [this, second]);
109
+ }
110
+ atan() {
111
+ return Calculator.computeUnaryOperation('atan', this);
112
+ }
113
+ atan2(second) {
114
+ return Calculator.computeBinaryOperation('atan2', [this, second]);
115
+ }
116
+ cos() {
117
+ return Calculator.computeUnaryOperation('cos', this);
118
+ }
119
+ div(second) {
120
+ return Calculator.computeBinaryOperation('div', [this, second]);
121
+ }
122
+ isFinite() {
123
+ return this._instance.isFinite();
124
+ }
125
+ mod(second) {
126
+ return Calculator.computeBinaryOperation('mod', [this, second]);
127
+ }
128
+ mul(second) {
129
+ return Calculator.computeBinaryOperation('mul', [this, second]);
130
+ }
131
+ neg() {
132
+ return Calculator.computeUnaryOperation('neg', this);
133
+ }
134
+ pow(second) {
135
+ return Calculator.computeBinaryOperation('pow', [this, second]);
136
+ }
137
+ sin() {
138
+ return Calculator.computeUnaryOperation('sin', this);
139
+ }
140
+ sqrt() {
141
+ return Calculator.computeUnaryOperation('sqrt', this);
142
+ }
143
+ sub(second) {
144
+ return Calculator.computeBinaryOperation('sub', [this, second]);
145
+ }
146
+ tan() {
147
+ return Calculator.computeUnaryOperation('tan', this);
148
+ }
149
+ valueOf() {
150
+ return this._instance.toNumber();
151
+ }
152
+ }
153
+ exports.Calculator = Calculator;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PI2 = exports.yAxis = exports.xAxis = exports.coordinateOrigin = void 0;
4
+ const Calculator_1 = require("./Calculator");
5
+ const Point_1 = require("../abstracts/Point");
6
+ const Line_1 = require("../figures/Line");
7
+ exports.coordinateOrigin = new Point_1.Point([0, 0]);
8
+ exports.xAxis = new Line_1.Line([exports.coordinateOrigin, new Point_1.Point([1, 0])]);
9
+ exports.yAxis = new Line_1.Line([exports.coordinateOrigin, new Point_1.Point([0, 1])]);
10
+ exports.PI2 = +Calculator_1.Calculator.mul(Math.PI, 2); // 360 degrees
@@ -0,0 +1,56 @@
1
+ import { Calculator } from '../utilities/Calculator';
2
+ const PI2 = +Calculator.mul(Math.PI, 2);
3
+ export class Angle {
4
+ _radians;
5
+ constructor(value, unit) {
6
+ this._radians = unit === 'radians' ? value : Angle.toRadians(value);
7
+ }
8
+ get cos() {
9
+ return +Calculator.cos(this.radians);
10
+ }
11
+ get cot() {
12
+ return +Calculator.div(1, this.tan);
13
+ }
14
+ get degrees() {
15
+ return Angle.toDegrees(this.radians);
16
+ }
17
+ get radians() {
18
+ return this._radians;
19
+ }
20
+ get sin() {
21
+ return +Calculator.sin(this.radians);
22
+ }
23
+ get tan() {
24
+ return +Calculator.tan(this.radians);
25
+ }
26
+ static toDegrees(radians) {
27
+ return +Calculator.mul(radians, 180).div(Math.PI);
28
+ }
29
+ static toRadians(degrees) {
30
+ return +Calculator.mul(degrees, Math.PI).div(180);
31
+ }
32
+ clone() {
33
+ return new Angle(this.radians, 'radians');
34
+ }
35
+ normalize() {
36
+ const { radians } = this;
37
+ if (radians < 0) {
38
+ this._radians = +Calculator.add(radians, PI2);
39
+ }
40
+ else if (radians > 0) {
41
+ this._radians = +Calculator.mod(radians, PI2);
42
+ }
43
+ return this;
44
+ }
45
+ replace(value, unit) {
46
+ this._radians = unit === 'radians' ? value : Angle.toRadians(value);
47
+ return this;
48
+ }
49
+ scale(factor) {
50
+ this._radians = +Calculator.mul(this.radians, factor);
51
+ return this;
52
+ }
53
+ valueOf() {
54
+ return this.radians;
55
+ }
56
+ }
@@ -0,0 +1,143 @@
1
+ import { Calculator } from '../utilities/Calculator';
2
+ import { Magnitude } from './Magnitude';
3
+ import { Point } from './Point';
4
+ import { Vector } from './Vector';
5
+ export class Figure {
6
+ angles = [];
7
+ isRelative = false;
8
+ magnitudes = [];
9
+ points = [];
10
+ vectors = [];
11
+ _values;
12
+ constructor(values) {
13
+ this.values = this._values = values;
14
+ }
15
+ get boundingBox() {
16
+ const { points } = this;
17
+ return Figure.computeBoundingBox(points);
18
+ }
19
+ get values() {
20
+ return this._values;
21
+ }
22
+ /**
23
+ * @todo Figure out a better way to type narrow than iterating again.
24
+ */
25
+ set values(values) {
26
+ this._values = values;
27
+ const [points, vectors, magnitudes, angles] = values.reduce((result, value) => {
28
+ if (value instanceof Point) {
29
+ result[0].push(value);
30
+ }
31
+ if (value instanceof Vector) {
32
+ result[1].push(value);
33
+ }
34
+ if (value instanceof Magnitude) {
35
+ result[2].push(value);
36
+ }
37
+ if (typeof value === 'object' && 'radians' in value) {
38
+ result[3].push(value);
39
+ }
40
+ return result;
41
+ }, [[], [], [], []]);
42
+ this.magnitudes = magnitudes;
43
+ this.points = points;
44
+ this.vectors = vectors;
45
+ this.angles = angles;
46
+ this.isRelative = !!vectors.length;
47
+ }
48
+ static computeBoundingBox(points) {
49
+ if (!points) {
50
+ throw new Error(this.getNoPointsErrorMessage('boundingBox', true));
51
+ }
52
+ const [xValues, yValues] = points.reduce((values, point) => {
53
+ values[0].push(point.x);
54
+ values[1].push(point.y);
55
+ return values;
56
+ }, [[], []]);
57
+ const xMax = +Calculator.max(xValues);
58
+ const xMin = +Calculator.min(xValues);
59
+ const yMax = +Calculator.max(yValues);
60
+ const yMin = +Calculator.min(yValues);
61
+ return { xMax, xMin, yMax, yMin };
62
+ }
63
+ static getNoPointsErrorMessage(name, property = false) {
64
+ return `Attempting to ${property ? 'get' : 'call'} ${name} of Figure with no points assigned. Assign 'this.points' from the child class and try again.`;
65
+ }
66
+ reflect(about) {
67
+ const { points, vectors } = this;
68
+ if (!points) {
69
+ throw new Error(Figure.getNoPointsErrorMessage('reflect'));
70
+ }
71
+ if ('getPerpendicularProjection' in about) {
72
+ points.forEach((point) => {
73
+ const perpendicularRoot = about.getPerpendicularProjection(point);
74
+ point.reflect(perpendicularRoot);
75
+ });
76
+ vectors.forEach((vector) => {
77
+ vector.reflect({
78
+ x: !about.isHorizontal,
79
+ y: !about.isVertical
80
+ });
81
+ });
82
+ return this;
83
+ }
84
+ points.forEach((point) => {
85
+ point.reflect(about);
86
+ });
87
+ vectors.forEach((vector) => {
88
+ vector.reflect({
89
+ x: true,
90
+ y: true
91
+ });
92
+ });
93
+ return this;
94
+ }
95
+ rotate(phi, about) {
96
+ const { points, vectors, angles } = this;
97
+ if (!points) {
98
+ throw new Error(Figure.getNoPointsErrorMessage('rotate'));
99
+ }
100
+ points.forEach((point) => {
101
+ point.rotate(phi, about);
102
+ });
103
+ vectors.forEach((vector) => {
104
+ const referencePoint = points[0].clone().rotate(phi, about);
105
+ const positionalPoint = points[0].clone().translate(vector).rotate(phi, about);
106
+ const rotatedVector = new Vector([referencePoint, positionalPoint]);
107
+ vector.replace(rotatedVector);
108
+ });
109
+ angles.forEach((angle) => {
110
+ angle.replace(+Calculator.add(+angle, +phi), 'radians').normalize();
111
+ });
112
+ return this;
113
+ }
114
+ scale(factor, about = new Point([0, 0])) {
115
+ const { points, vectors, magnitudes } = this;
116
+ if (!points) {
117
+ throw new Error(Figure.getNoPointsErrorMessage('scale'));
118
+ }
119
+ points.forEach((point) => {
120
+ const positionalVector = new Vector([about, point]);
121
+ const scaledPositionalVector = positionalVector.clone().scale(factor);
122
+ const scaledPoint = about.clone().translate(scaledPositionalVector);
123
+ point.replace(scaledPoint);
124
+ });
125
+ vectors.forEach((vector) => {
126
+ vector.scale(factor);
127
+ });
128
+ magnitudes.forEach((magnitude) => {
129
+ magnitude.scale(factor);
130
+ });
131
+ return this;
132
+ }
133
+ translate(vector) {
134
+ const { points } = this;
135
+ if (!points) {
136
+ throw new Error(Figure.getNoPointsErrorMessage('translate'));
137
+ }
138
+ points.forEach((point) => {
139
+ point.translate(vector);
140
+ });
141
+ return this;
142
+ }
143
+ }
@@ -0,0 +1,23 @@
1
+ export class Flag {
2
+ _value;
3
+ constructor(value) {
4
+ this._value = value;
5
+ }
6
+ get value() {
7
+ return this._value;
8
+ }
9
+ clone() {
10
+ return new Flag(this._value);
11
+ }
12
+ invert() {
13
+ this._value = !this._value;
14
+ return this;
15
+ }
16
+ replace(value) {
17
+ this._value = value;
18
+ return this;
19
+ }
20
+ valueOf() {
21
+ return +this._value;
22
+ }
23
+ }
@@ -0,0 +1,21 @@
1
+ import { Calculator } from '../utilities/Calculator';
2
+ export class Magnitude {
3
+ _value;
4
+ constructor(value) {
5
+ this._value = value;
6
+ }
7
+ clone() {
8
+ return new Magnitude(+this);
9
+ }
10
+ replace(value) {
11
+ this._value = value;
12
+ return this;
13
+ }
14
+ scale(factor) {
15
+ this._value = +Calculator.mul(+this, factor);
16
+ return this;
17
+ }
18
+ valueOf() {
19
+ return this._value;
20
+ }
21
+ }
@@ -0,0 +1,46 @@
1
+ import { Calculator } from '../utilities/Calculator';
2
+ export class Point {
3
+ _x;
4
+ _y;
5
+ constructor([x, y]) {
6
+ this._x = x;
7
+ this._y = y;
8
+ }
9
+ get values() {
10
+ return [this.x, this.y];
11
+ }
12
+ get x() {
13
+ return +this._x;
14
+ }
15
+ get y() {
16
+ return +this._y;
17
+ }
18
+ clone() {
19
+ const { x, y } = this;
20
+ return new Point([x, y]);
21
+ }
22
+ reflect(about) {
23
+ const { x, y } = this;
24
+ this._x = +Calculator.sub(about.x, x).mul(2).add(x);
25
+ this._y = +Calculator.sub(about.y, y).mul(2).add(y);
26
+ return this;
27
+ }
28
+ replace(point) {
29
+ this._x = point.x;
30
+ this._y = point.y;
31
+ return this;
32
+ }
33
+ rotate(phi, about = new Point([0, 0])) {
34
+ const dx = Calculator.sub(this.x, about.x);
35
+ const dy = Calculator.sub(this.y, about.y);
36
+ this._x = +dx.mul(phi.cos).sub(dy.mul(phi.sin)).add(about.x);
37
+ this._y = +dy.mul(phi.cos).add(dx.mul(phi.sin)).add(about.y);
38
+ return this;
39
+ }
40
+ translate(vector) {
41
+ const { x, y } = this;
42
+ this._x = +Calculator.add(x, vector.dx);
43
+ this._y = +Calculator.add(y, vector.dy);
44
+ return this;
45
+ }
46
+ }
@@ -0,0 +1,70 @@
1
+ import { Calculator } from '../utilities/Calculator';
2
+ import { Angle } from './Angle';
3
+ export class Vector {
4
+ _dx;
5
+ _dy;
6
+ constructor(values) {
7
+ const fromPoints = values.some((value) => typeof value !== 'number');
8
+ if (fromPoints) {
9
+ const [P1, P2] = values;
10
+ this._dx = +Calculator.sub(P2.x, P1.x);
11
+ this._dy = +Calculator.sub(P2.y, P1.y);
12
+ return;
13
+ }
14
+ const [dx, dy] = values;
15
+ this._dx = dx;
16
+ this._dy = dy;
17
+ }
18
+ get dx() {
19
+ return this._dx;
20
+ }
21
+ get dy() {
22
+ return this._dy;
23
+ }
24
+ get magnitude() {
25
+ const { dx, dy } = this;
26
+ return +Calculator.pow(dx, 2).add(Calculator.pow(dy, 2)).sqrt();
27
+ }
28
+ get values() {
29
+ return [this.dx, this.dy];
30
+ }
31
+ angleTo(vector) {
32
+ const dotProd = this.dotProduct(vector);
33
+ const magV1 = this.magnitude;
34
+ const magV2 = vector.magnitude;
35
+ const magProd = Calculator.mul(magV1, magV2);
36
+ const cosAngle = Calculator.div(dotProd, magProd);
37
+ const angle = cosAngle.acos();
38
+ return new Angle(+angle, 'radians');
39
+ }
40
+ clone() {
41
+ const { dx, dy } = this;
42
+ return new Vector([dx, dy]);
43
+ }
44
+ dotProduct(vector) {
45
+ const { dx, dy } = this;
46
+ return +Calculator.mul(dx, vector.dx).add(Calculator.mul(dy, vector.dy));
47
+ }
48
+ reflect({ x, y }) {
49
+ this._dx = x ? +Calculator.neg(this.dx) : this.dx;
50
+ this._dy = y ? +Calculator.neg(this.dy) : this.dy;
51
+ return this;
52
+ }
53
+ replace(vector) {
54
+ this._dx = vector.dx;
55
+ this._dy = vector.dy;
56
+ return this;
57
+ }
58
+ rotate(phi) {
59
+ const { dx, dy } = this;
60
+ this._dx = +Calculator.mul(phi.cos, dx).sub(Calculator.mul(phi.sin, dy));
61
+ this._dy = +Calculator.mul(phi.sin, dx).add(Calculator.mul(phi.cos, dy));
62
+ return this;
63
+ }
64
+ scale(factor) {
65
+ const { dx, dy } = this;
66
+ this._dx = +Calculator.mul(dx, factor);
67
+ this._dy = +Calculator.mul(dy, factor);
68
+ return this;
69
+ }
70
+ }
@@ -0,0 +1,128 @@
1
+ import { Calculator } from '../utilities/Calculator';
2
+ import { Point } from '../abstracts/Point';
3
+ import { Figure } from '../abstracts/Figure';
4
+ import { Ellipse } from './Ellipse';
5
+ export class ArcCurve extends Figure {
6
+ ellipse;
7
+ largeArcFlag;
8
+ phi;
9
+ rx;
10
+ ry;
11
+ sweepFlag;
12
+ constructor(values) {
13
+ super(values);
14
+ const [, rx, ry, phi, largeArcFlag, sweepFlag] = values;
15
+ this.rx = rx;
16
+ this.ry = ry;
17
+ this.phi = phi;
18
+ this.largeArcFlag = largeArcFlag;
19
+ this.sweepFlag = sweepFlag;
20
+ this.adjustRadii();
21
+ const center = this.computeCenter();
22
+ this.ellipse = new Ellipse([center, rx, ry, phi]);
23
+ }
24
+ get boundingBox() {
25
+ const { P0, P1 } = this;
26
+ const points = [P0, P1, ...this.criticalPoints];
27
+ return Figure.computeBoundingBox(points);
28
+ }
29
+ get center() {
30
+ return this.ellipse.center;
31
+ }
32
+ get criticalPoints() {
33
+ const { ellipse } = this;
34
+ const [minTheta, maxTheta] = this.computeThetaRange();
35
+ return ellipse.criticalPoints.filter((point) => {
36
+ const { P0, P1 } = this;
37
+ if ((point.x === P0.x && point.y === P0.y) || (point.x === P1.x && point.y === P1.y)) {
38
+ return false;
39
+ }
40
+ const theta = ellipse.computeThetaForPoint(point);
41
+ return +theta >= +minTheta && +theta <= +maxTheta;
42
+ });
43
+ }
44
+ get P0() {
45
+ return this.points[0];
46
+ }
47
+ get P1() {
48
+ if (this.isRelative) {
49
+ return this.P0.clone().translate(this.vectors[0]);
50
+ }
51
+ return this.points[1];
52
+ }
53
+ clone() {
54
+ const values = this.values.map((value) => (typeof value === 'object' && 'clone' in value ? value.clone() : value));
55
+ return new ArcCurve(values);
56
+ }
57
+ reflect(about) {
58
+ super.reflect(about);
59
+ this.sweepFlag.invert();
60
+ return this;
61
+ }
62
+ adjustRadii() {
63
+ const { rx, ry } = this;
64
+ const P0_prime = this.computeP0Prime();
65
+ const { x: x1_prime, y: y1_prime } = P0_prime;
66
+ const rx_sq = Calculator.pow(+rx, 2);
67
+ const ry_sq = Calculator.pow(+ry, 2);
68
+ const x1_prime_sq = Calculator.pow(x1_prime, 2);
69
+ const y1_prime_sq = Calculator.pow(y1_prime, 2);
70
+ const radii_check = x1_prime_sq.div(rx_sq).add(y1_prime_sq.div(ry_sq));
71
+ if (+radii_check > 1) {
72
+ rx.replace(+Calculator.mul(+rx, radii_check.sqrt()));
73
+ ry.replace(+Calculator.mul(+ry, radii_check.sqrt()));
74
+ }
75
+ }
76
+ computeCenter() {
77
+ const { P0, P1, phi } = this;
78
+ const center_prime = this.computeCenterPrime();
79
+ const { x: x1, y: y1 } = P0;
80
+ const { x: x2, y: y2 } = P1;
81
+ const { x: cx_prime, y: cy_prime } = center_prime;
82
+ const dx = Calculator.add(x1, x2).div(2);
83
+ const dy = Calculator.add(y1, y2).div(2);
84
+ const cx = Calculator.mul(phi.cos, cx_prime).sub(Calculator.mul(phi.sin, cy_prime)).add(dx);
85
+ const cy = Calculator.mul(phi.sin, cx_prime).add(Calculator.mul(phi.cos, cy_prime)).add(dy);
86
+ return new Point([+cx, +cy]);
87
+ }
88
+ computeCenterPrime() {
89
+ const { largeArcFlag, sweepFlag, rx, ry } = this;
90
+ const P0_prime = this.computeP0Prime();
91
+ const { x: x1_prime, y: y1_prime } = P0_prime;
92
+ const rx_sq = Calculator.pow(+rx, 2);
93
+ const ry_sq = Calculator.pow(+ry, 2);
94
+ const x1_prime_sq = Calculator.pow(x1_prime, 2);
95
+ const y1_prime_sq = Calculator.pow(y1_prime, 2);
96
+ const sign = new Calculator(+largeArcFlag === +sweepFlag ? -1 : 1);
97
+ let sq = rx_sq
98
+ .mul(ry_sq)
99
+ .sub(rx_sq.mul(y1_prime_sq))
100
+ .sub(ry_sq.mul(x1_prime_sq))
101
+ .div(rx_sq.mul(y1_prime_sq).add(ry_sq.mul(x1_prime_sq)));
102
+ sq = +sq < 0 ? new Calculator(0) : sq;
103
+ const coef = sign.mul(sq.sqrt());
104
+ const cx_prime = coef.mul(Calculator.mul(+rx, y1_prime).div(+ry));
105
+ const cy_prime = coef.mul(Calculator.mul(+ry, x1_prime).div(+rx).neg());
106
+ return new Point([+cx_prime, +cy_prime]);
107
+ }
108
+ computeP0Prime() {
109
+ const { P0, P1, phi } = this;
110
+ const { x: x1, y: y1 } = P0;
111
+ const { x: x2, y: y2 } = P1;
112
+ const mx = Calculator.sub(x1, x2).div(2);
113
+ const my = Calculator.sub(y1, y2).div(2);
114
+ const x1_prime = Calculator.mul(phi.cos, mx).add(Calculator.mul(phi.sin, my));
115
+ const y1_prime = Calculator.mul(phi.sin, mx).neg().add(Calculator.mul(phi.cos, my));
116
+ return new Point([+x1_prime, +y1_prime]);
117
+ }
118
+ computeThetaRange() {
119
+ const { P0, P1, ellipse, sweepFlag } = this;
120
+ const theta1 = ellipse.computeThetaForPoint(P0);
121
+ const theta2 = ellipse.computeThetaForPoint(P1);
122
+ if (+theta2 === 0 && !sweepFlag.value) {
123
+ theta2.replace(360, 'degrees');
124
+ }
125
+ const thetaRange = [theta1, theta2].sort((a, b) => +Calculator.sub(a.radians, b.radians));
126
+ return thetaRange;
127
+ }
128
+ }
@@ -0,0 +1,39 @@
1
+ import { Calculator } from '../utilities/Calculator';
2
+ import { Ellipse } from './Ellipse';
3
+ import { Point } from '../abstracts/Point';
4
+ import { Angle } from '../abstracts/Angle';
5
+ export class Circle extends Ellipse {
6
+ _radius;
7
+ constructor(values) {
8
+ const [center, radius] = values;
9
+ const phi = new Angle(0, 'radians'); // Always 0 since circles don't change with rotation.
10
+ super([center, radius, radius.clone(), phi]);
11
+ this._radius = radius;
12
+ }
13
+ get isCircle() {
14
+ return true;
15
+ }
16
+ get radius() {
17
+ return this._radius;
18
+ }
19
+ get values() {
20
+ const [center, radius] = super.values;
21
+ return [center, radius];
22
+ }
23
+ set values(values) {
24
+ super.values = values;
25
+ }
26
+ clone() {
27
+ const values = this.values.map((value) => value.clone());
28
+ return new Circle(values);
29
+ }
30
+ computeCriticalPoints(ellipse = this) {
31
+ const { center, rx } = ellipse;
32
+ const radius = rx;
33
+ const first = new Point([center.x, +Calculator.add(center.y, +radius)]);
34
+ const second = new Point([+Calculator.add(center.x, +radius), center.y]);
35
+ const third = new Point([center.x, +Calculator.sub(center.y, +radius)]);
36
+ const fourth = new Point([+Calculator.sub(center.x, +radius), center.y]);
37
+ return [first, second, third, fourth];
38
+ }
39
+ }