@interstellar-tools/equations 0.1.1

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 (78) hide show
  1. package/README.md +18 -0
  2. package/dist/__tests__/compute-angle.int.spec.d.ts +2 -0
  3. package/dist/__tests__/compute-angle.int.spec.d.ts.map +1 -0
  4. package/dist/__tests__/compute-angle.int.spec.js +62 -0
  5. package/dist/__tests__/compute-angle.int.spec.js.map +1 -0
  6. package/dist/__tests__/compute-mean-anomaly.int.spec.d.ts +2 -0
  7. package/dist/__tests__/compute-mean-anomaly.int.spec.d.ts.map +1 -0
  8. package/dist/__tests__/compute-mean-anomaly.int.spec.js +175 -0
  9. package/dist/__tests__/compute-mean-anomaly.int.spec.js.map +1 -0
  10. package/dist/__tests__/eccentric-to-true-anomaly.spec.d.ts +2 -0
  11. package/dist/__tests__/eccentric-to-true-anomaly.spec.d.ts.map +1 -0
  12. package/dist/__tests__/eccentric-to-true-anomaly.spec.js +33 -0
  13. package/dist/__tests__/eccentric-to-true-anomaly.spec.js.map +1 -0
  14. package/dist/__tests__/solve-kepler-bisection.spec.d.ts +2 -0
  15. package/dist/__tests__/solve-kepler-bisection.spec.d.ts.map +1 -0
  16. package/dist/__tests__/solve-kepler-bisection.spec.js +41 -0
  17. package/dist/__tests__/solve-kepler-bisection.spec.js.map +1 -0
  18. package/dist/__tests__/solve-kepler-high-eccentricity.spec.d.ts +2 -0
  19. package/dist/__tests__/solve-kepler-high-eccentricity.spec.d.ts.map +1 -0
  20. package/dist/__tests__/solve-kepler-high-eccentricity.spec.js +81 -0
  21. package/dist/__tests__/solve-kepler-high-eccentricity.spec.js.map +1 -0
  22. package/dist/__tests__/solve-kepler-newton-raphson.spec.d.ts +2 -0
  23. package/dist/__tests__/solve-kepler-newton-raphson.spec.d.ts.map +1 -0
  24. package/dist/__tests__/solve-kepler-newton-raphson.spec.js +50 -0
  25. package/dist/__tests__/solve-kepler-newton-raphson.spec.js.map +1 -0
  26. package/dist/__tests__/solve-kepler.int.spec.d.ts +2 -0
  27. package/dist/__tests__/solve-kepler.int.spec.d.ts.map +1 -0
  28. package/dist/__tests__/solve-kepler.int.spec.js +76 -0
  29. package/dist/__tests__/solve-kepler.int.spec.js.map +1 -0
  30. package/dist/__tests__/true-anomaly-to-mean-anomaly.int.spec.d.ts +2 -0
  31. package/dist/__tests__/true-anomaly-to-mean-anomaly.int.spec.d.ts.map +1 -0
  32. package/dist/__tests__/true-anomaly-to-mean-anomaly.int.spec.js +46 -0
  33. package/dist/__tests__/true-anomaly-to-mean-anomaly.int.spec.js.map +1 -0
  34. package/dist/__tests__/wrap-angle.spec.d.ts +2 -0
  35. package/dist/__tests__/wrap-angle.spec.d.ts.map +1 -0
  36. package/dist/__tests__/wrap-angle.spec.js +60 -0
  37. package/dist/__tests__/wrap-angle.spec.js.map +1 -0
  38. package/dist/compute-angle.d.ts +69 -0
  39. package/dist/compute-angle.d.ts.map +1 -0
  40. package/dist/compute-angle.js +79 -0
  41. package/dist/compute-angle.js.map +1 -0
  42. package/dist/compute-mean-anomaly.d.ts +47 -0
  43. package/dist/compute-mean-anomaly.d.ts.map +1 -0
  44. package/dist/compute-mean-anomaly.js +86 -0
  45. package/dist/compute-mean-anomaly.js.map +1 -0
  46. package/dist/eccentric-to-true-anomaly.d.ts +43 -0
  47. package/dist/eccentric-to-true-anomaly.d.ts.map +1 -0
  48. package/dist/eccentric-to-true-anomaly.js +63 -0
  49. package/dist/eccentric-to-true-anomaly.js.map +1 -0
  50. package/dist/index.d.ts +16 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +16 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/solve-kepler-bisection.d.ts +75 -0
  55. package/dist/solve-kepler-bisection.d.ts.map +1 -0
  56. package/dist/solve-kepler-bisection.js +94 -0
  57. package/dist/solve-kepler-bisection.js.map +1 -0
  58. package/dist/solve-kepler-high-eccentricity.d.ts +99 -0
  59. package/dist/solve-kepler-high-eccentricity.d.ts.map +1 -0
  60. package/dist/solve-kepler-high-eccentricity.js +150 -0
  61. package/dist/solve-kepler-high-eccentricity.js.map +1 -0
  62. package/dist/solve-kepler-newton-raphson.d.ts +87 -0
  63. package/dist/solve-kepler-newton-raphson.d.ts.map +1 -0
  64. package/dist/solve-kepler-newton-raphson.js +118 -0
  65. package/dist/solve-kepler-newton-raphson.js.map +1 -0
  66. package/dist/solve-kepler.d.ts +82 -0
  67. package/dist/solve-kepler.d.ts.map +1 -0
  68. package/dist/solve-kepler.js +99 -0
  69. package/dist/solve-kepler.js.map +1 -0
  70. package/dist/true-anomaly-to-mean-anomaly.d.ts +74 -0
  71. package/dist/true-anomaly-to-mean-anomaly.d.ts.map +1 -0
  72. package/dist/true-anomaly-to-mean-anomaly.js +91 -0
  73. package/dist/true-anomaly-to-mean-anomaly.js.map +1 -0
  74. package/dist/wrap-angle.d.ts +82 -0
  75. package/dist/wrap-angle.d.ts.map +1 -0
  76. package/dist/wrap-angle.js +97 -0
  77. package/dist/wrap-angle.js.map +1 -0
  78. package/package.json +58 -0
@@ -0,0 +1,76 @@
1
+ import assert from 'node:assert/strict';
2
+ import test, { describe } from 'node:test';
3
+ import { solveKepler } from '../solve-kepler';
4
+ const EPSILON = 1e-8; // Floating-point tolerance
5
+ const assertApproxEqual = (actual, expected) => {
6
+ assert.ok(Math.abs(actual - expected) < EPSILON, `Expected ~${expected}, got ${actual}`);
7
+ };
8
+ describe('solveKepler', () => {
9
+ test('Circular Orbit (e = 0)', () => {
10
+ const M = Math.PI / 4; // 45 degrees
11
+ const e = 0.0; // Circular orbit
12
+ const result = solveKepler(M, e);
13
+ assertApproxEqual(result, M); // E = M for circular orbits
14
+ });
15
+ test('Small Eccentricity (e = 0.1)', () => {
16
+ const M = Math.PI / 4;
17
+ const e = 0.1;
18
+ const expectedE = 0.8612648848681754;
19
+ const result = solveKepler(M, e);
20
+ assertApproxEqual(result, expectedE);
21
+ });
22
+ test('Moderate Eccentricity (e = 0.5)', () => {
23
+ const M = Math.PI / 2;
24
+ const e = 0.5;
25
+ const expectedE = 2.02097993808977;
26
+ const result = solveKepler(M, e);
27
+ assertApproxEqual(result, expectedE);
28
+ });
29
+ test('High Eccentricity (e = 0.8)', () => {
30
+ const M = Math.PI / 6;
31
+ const e = 0.8;
32
+ const expectedE = 1.2929083458551878;
33
+ const result = solveKepler(M, e);
34
+ assertApproxEqual(result, expectedE);
35
+ });
36
+ test('Nearly Parabolic Orbit (e = 0.99)', () => {
37
+ const M = Math.PI / 4;
38
+ const e = 0.99;
39
+ const expectedE = 1.758085607716896;
40
+ const result = solveKepler(M, e);
41
+ assertApproxEqual(result, expectedE);
42
+ });
43
+ test('Mean Anomaly at 0', () => {
44
+ const M = 0;
45
+ const e = 0.5;
46
+ const result = solveKepler(M, e);
47
+ assertApproxEqual(result, 0); // E = 0 when M = 0
48
+ });
49
+ test('Mean Anomaly at π', () => {
50
+ const M = Math.PI;
51
+ const e = 0.5;
52
+ const expectedE = Math.PI; // At M = π, E should also be π
53
+ const result = solveKepler(M, e);
54
+ assert.equal(result, expectedE);
55
+ });
56
+ test('Mean Anomaly at 2π', () => {
57
+ const M = 2 * Math.PI;
58
+ const e = 0.5;
59
+ const result = solveKepler(M, e);
60
+ assertApproxEqual(result, 0); // Should wrap to 0
61
+ });
62
+ test('Convergence with max iterations', () => {
63
+ const M = Math.PI / 3;
64
+ const e = 0.7;
65
+ const result = solveKepler(M, e, 100); // Force more iterations
66
+ assert.ok(result >= 0 && result < 2 * Math.PI, `Expected result in range [0, 2π], got ${result}`);
67
+ });
68
+ test('Invalid Eccentricity (e < 0) Throws Error', () => {
69
+ assert.throws(() => solveKepler(Math.PI / 4, -0.1), RangeError);
70
+ });
71
+ test('Invalid Eccentricity (e >= 1) Throws Error', () => {
72
+ assert.throws(() => solveKepler(Math.PI / 4, 1), RangeError);
73
+ assert.throws(() => solveKepler(Math.PI / 4, 1.1), RangeError);
74
+ });
75
+ });
76
+ //# sourceMappingURL=solve-kepler.int.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"solve-kepler.int.spec.js","sourceRoot":"","sources":["../../src/__tests__/solve-kepler.int.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=true-anomaly-to-mean-anomaly.int.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"true-anomaly-to-mean-anomaly.int.spec.d.ts","sourceRoot":"","sources":["../../src/__tests__/true-anomaly-to-mean-anomaly.int.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,46 @@
1
+ import assert from 'node:assert/strict';
2
+ import test, { describe } from 'node:test';
3
+ import { trueAnomalyToMeanAnomaly } from '../true-anomaly-to-mean-anomaly';
4
+ import { wrapAngle } from '../wrap-angle';
5
+ describe('trueAnomalyToMeanAnomaly', () => {
6
+ const EPSILON = 1e-10; // Floating-point tolerance
7
+ const assertApproxEqual = (actual, expected) => {
8
+ assert.ok(Math.abs(actual - expected) < EPSILON, `Expected ~${expected}, got ${actual}`);
9
+ };
10
+ test('Circular Orbit (e = 0)', () => {
11
+ const result = trueAnomalyToMeanAnomaly(Math.PI / 3, 0); // e = 0 (should be identity)
12
+ assertApproxEqual(result, wrapAngle(Math.PI / 3));
13
+ });
14
+ test('Zero True Anomaly (V = 0)', () => {
15
+ const result = trueAnomalyToMeanAnomaly(0, 0.5);
16
+ assertApproxEqual(result, 0);
17
+ });
18
+ test('True Anomaly at π/2 (90°)', () => {
19
+ const result = trueAnomalyToMeanAnomaly(Math.PI / 2, 0.5);
20
+ const expectedM = 0.6141848493043778; // Corrected expected value
21
+ assertApproxEqual(result, expectedM);
22
+ });
23
+ test('True Anomaly at π (Apoapsis)', () => {
24
+ const result = trueAnomalyToMeanAnomaly(Math.PI, 0.5);
25
+ const expectedM = wrapAngle(Math.PI); // Mean anomaly should be equal to π
26
+ assertApproxEqual(result, expectedM);
27
+ });
28
+ test('True Anomaly at 3π/2 (270°)', () => {
29
+ const result = trueAnomalyToMeanAnomaly((3 * Math.PI) / 2, 0.5);
30
+ const expectedM = -0.6141848493043782; // Corrected expected value
31
+ assertApproxEqual(result, expectedM);
32
+ });
33
+ test('True Anomaly at 2π (Periapsis)', () => {
34
+ const result = trueAnomalyToMeanAnomaly(2 * Math.PI, 0.5);
35
+ assertApproxEqual(result, 0); // Should return to zero
36
+ });
37
+ test('Eccentricity Out of Bounds Throws Error', () => {
38
+ assert.throws(() => trueAnomalyToMeanAnomaly(Math.PI / 3, -0.1), RangeError);
39
+ assert.throws(() => trueAnomalyToMeanAnomaly(Math.PI / 3, 1.1), RangeError);
40
+ });
41
+ test('Large Eccentricity (e close to 1)', () => {
42
+ const result = trueAnomalyToMeanAnomaly(Math.PI / 4, 0.99);
43
+ assert.ok(result >= 0 && result < 2 * Math.PI, `Expected result in range [0, 2π], got ${result}`);
44
+ });
45
+ });
46
+ //# sourceMappingURL=true-anomaly-to-mean-anomaly.int.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"true-anomaly-to-mean-anomaly.int.spec.js","sourceRoot":"","sources":["../../src/__tests__/true-anomaly-to-mean-anomaly.int.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=wrap-angle.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap-angle.spec.d.ts","sourceRoot":"","sources":["../../src/__tests__/wrap-angle.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,60 @@
1
+ import assert from 'node:assert';
2
+ import { describe, test } from 'node:test';
3
+ import { wrapAngle } from '../wrap-angle';
4
+ const EPSILON = 1e-10; // Small tolerance for floating-point comparisons
5
+ const assertApproxEqual = (actual, expected) => {
6
+ assert.ok(Math.abs(actual - expected) < EPSILON, `Expected ~${expected}, got ${actual}`);
7
+ };
8
+ describe('wrapAngle', () => {
9
+ test('angles already in range [-2π, 2π]', () => {
10
+ assert.equal(wrapAngle(0), 0);
11
+ assert.equal(wrapAngle(Math.PI), Math.PI);
12
+ assert.equal(wrapAngle(-Math.PI), -Math.PI);
13
+ assert.equal(wrapAngle(1.5 * Math.PI), 1.5 * Math.PI);
14
+ assert.equal(wrapAngle(-1.5 * Math.PI), -1.5 * Math.PI);
15
+ });
16
+ test('negative and retrograde angles', () => {
17
+ assertApproxEqual(wrapAngle(-2 * Math.PI), 0);
18
+ assertApproxEqual(wrapAngle(-3 * Math.PI), -Math.PI);
19
+ assertApproxEqual(wrapAngle(-4 * Math.PI), 0);
20
+ assertApproxEqual(wrapAngle(-5 * Math.PI), -Math.PI);
21
+ assertApproxEqual(wrapAngle(-6 * Math.PI), 0);
22
+ });
23
+ test('angles greater than 2π', () => {
24
+ assertApproxEqual(wrapAngle(2 * Math.PI), 0);
25
+ assertApproxEqual(wrapAngle(3 * Math.PI), Math.PI);
26
+ assertApproxEqual(wrapAngle(4 * Math.PI), 0);
27
+ assertApproxEqual(wrapAngle(5 * Math.PI), Math.PI);
28
+ assertApproxEqual(wrapAngle(6 * Math.PI), 0);
29
+ });
30
+ test('extreme values', () => {
31
+ assertApproxEqual(wrapAngle(100 * Math.PI), 0);
32
+ assertApproxEqual(wrapAngle(-100 * Math.PI), 0);
33
+ assertApproxEqual(wrapAngle(1000), wrapAngle(1000 % (2 * Math.PI)));
34
+ assertApproxEqual(wrapAngle(-1000), wrapAngle(-1000 % (2 * Math.PI)));
35
+ });
36
+ test('boundary values', () => {
37
+ assertApproxEqual(wrapAngle(2 * Math.PI), 0);
38
+ assertApproxEqual(wrapAngle(-2 * Math.PI), 0);
39
+ assertApproxEqual(wrapAngle(2 * Math.PI + EPSILON), EPSILON);
40
+ assertApproxEqual(wrapAngle(-2 * Math.PI - EPSILON), -EPSILON);
41
+ });
42
+ test('floating-point drift prevention', () => {
43
+ assertApproxEqual(wrapAngle(Math.PI + EPSILON / 2), Math.PI + EPSILON / 2);
44
+ assertApproxEqual(wrapAngle(-Math.PI - EPSILON / 2), -Math.PI - EPSILON / 2);
45
+ assertApproxEqual(wrapAngle(2 * Math.PI - EPSILON / 2), -EPSILON / 2);
46
+ assertApproxEqual(wrapAngle(-2 * Math.PI + EPSILON / 2), EPSILON / 2);
47
+ });
48
+ test('large positive and negative angles', () => {
49
+ assertApproxEqual(wrapAngle(1e6 * Math.PI), 0);
50
+ assertApproxEqual(wrapAngle(-1e6 * Math.PI), 0);
51
+ assertApproxEqual(wrapAngle(1e9), wrapAngle(1e9 % (2 * Math.PI)));
52
+ assertApproxEqual(wrapAngle(-1e9), wrapAngle(-1e9 % (2 * Math.PI)));
53
+ });
54
+ test('angles near zero', () => {
55
+ assertApproxEqual(wrapAngle(EPSILON), EPSILON);
56
+ assertApproxEqual(wrapAngle(-EPSILON), -EPSILON);
57
+ assertApproxEqual(wrapAngle(0), 0);
58
+ });
59
+ });
60
+ //# sourceMappingURL=wrap-angle.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap-angle.spec.js","sourceRoot":"","sources":["../../src/__tests__/wrap-angle.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ import type { CelestialBodyType, Radians, TimeStepInterface } from '@interstellar-tools/types';
2
+ /**
3
+ * Computes the orbital angle (true anomaly, $ν$) of a celestial body for a given date and time step.
4
+ *
5
+ * **Mathematical Explanation:**
6
+ *
7
+ * This function determines the **true anomaly** ($ν$) based on the time elapsed since the
8
+ * **J2000 epoch** (January 1, 2000) and the provided time step. The calculation follows these steps:
9
+ *
10
+ * **Step 1: Compute Time Elapsed Since J2000**
11
+ *
12
+ * The number of days since **J2000** is computed as:
13
+ * $$ \Delta T = \frac{t - t_{J2000}}{\text{MILLISECONDS\_PER\_DAY}} $$
14
+ * where:
15
+ * - $t$ is the current date in milliseconds.
16
+ * - $t_{J2000}$ is **J2000** (2000-01-01T00:00:00Z).
17
+ * - **MILLISECONDS_PER_DAY** is the number of milliseconds in one day.
18
+ *
19
+ * **Step 2: Compute the Mean Anomaly ($M$)**
20
+ *
21
+ * The **mean anomaly** is calculated as:
22
+ * $$ M = M_0 + n \cdot (\Delta T + \text{timeStep}) $$
23
+ * where:
24
+ * - $M_0$ is the initial mean anomaly at **J2000**.
25
+ * - $n$ is the mean motion (orbital angular velocity).
26
+ * - $\text{timeStep}$ is the time step in days.
27
+ *
28
+ * **Step 3: Solve Kepler’s Equation for Eccentric Anomaly ($E$)**
29
+ *
30
+ * Kepler’s equation:
31
+ * $$ M = E - e \sin(E) $$
32
+ * is solved numerically to obtain the **eccentric anomaly** ($E$).
33
+ *
34
+ * **Step 4: Convert Eccentric Anomaly ($E$) to True Anomaly ($ν$)**
35
+ *
36
+ * Using the relation:
37
+ * $$ ν = 2 \tan^{-1} \left( \sqrt{\frac{1+e}{1-e}} \tan\left(\frac{E}{2}\right) \right) $$
38
+ *
39
+ * **Step 5: Adjust the True Anomaly for Certain Bodies**
40
+ * - For **comets**, the final **true anomaly** is adjusted by subtracting the **eccentricity** ($e$).
41
+ * - For **stars and other celestial objects**, the computed **true anomaly** is used directly.
42
+ *
43
+ * @param {CelestialBodyType} body - The celestial body for which to compute the true anomaly.
44
+ * @param {TimeStepInterface} timeStep - The time step in days since the last frame.
45
+ * @returns {Radians} The computed true anomaly in radians.
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * import { computeAngle } from './compute-angle';
50
+ *
51
+ * const earth: CelestialBodyType = {
52
+ * name: 'Earth',
53
+ * e: 0.0167, // Orbital eccentricity
54
+ * angle: 0, // Initial angle
55
+ * };
56
+ *
57
+ * const timeStep: TimeStepInterface = { days: 1 }; // Time step of 1 day
58
+ * const angle: Radians = computeAngle(earth, timeStep);
59
+ * console.log(angle); // Output: Computed true anomaly in radians
60
+ * ```
61
+ *
62
+ * @see [Kepler's Equation (Wikipedia)](https://en.wikipedia.org/wiki/Kepler%27s_equation)
63
+ * @see [True Anomaly (Wikipedia)](https://en.wikipedia.org/wiki/True_anomaly)
64
+ * @see [Mean Anomaly (Wikipedia)](https://en.wikipedia.org/wiki/Mean_anomaly)
65
+ * @see [Orbital Mechanics (NASA)](https://solarsystem.nasa.gov/basics/chapter2-2/)
66
+ * @category Angle
67
+ */
68
+ export declare const computeAngle: (body: CelestialBodyType, timeStep: TimeStepInterface) => Radians;
69
+ //# sourceMappingURL=compute-angle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compute-angle.d.ts","sourceRoot":"","sources":["../src/compute-angle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,OAAO,EACP,iBAAiB,EAClB,MAAM,2BAA2B,CAAC;AAMnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,eAAO,MAAM,YAAY,GACvB,MAAM,iBAAiB,EACvB,UAAU,iBAAiB,KAC1B,OASF,CAAC"}
@@ -0,0 +1,79 @@
1
+ import { computeMeanAnomaly } from './compute-mean-anomaly';
2
+ import { eccentricToTrueAnomaly } from './eccentric-to-true-anomaly';
3
+ import { solveKepler } from './solve-kepler';
4
+ /**
5
+ * Computes the orbital angle (true anomaly, $ν$) of a celestial body for a given date and time step.
6
+ *
7
+ * **Mathematical Explanation:**
8
+ *
9
+ * This function determines the **true anomaly** ($ν$) based on the time elapsed since the
10
+ * **J2000 epoch** (January 1, 2000) and the provided time step. The calculation follows these steps:
11
+ *
12
+ * **Step 1: Compute Time Elapsed Since J2000**
13
+ *
14
+ * The number of days since **J2000** is computed as:
15
+ * $$ \Delta T = \frac{t - t_{J2000}}{\text{MILLISECONDS\_PER\_DAY}} $$
16
+ * where:
17
+ * - $t$ is the current date in milliseconds.
18
+ * - $t_{J2000}$ is **J2000** (2000-01-01T00:00:00Z).
19
+ * - **MILLISECONDS_PER_DAY** is the number of milliseconds in one day.
20
+ *
21
+ * **Step 2: Compute the Mean Anomaly ($M$)**
22
+ *
23
+ * The **mean anomaly** is calculated as:
24
+ * $$ M = M_0 + n \cdot (\Delta T + \text{timeStep}) $$
25
+ * where:
26
+ * - $M_0$ is the initial mean anomaly at **J2000**.
27
+ * - $n$ is the mean motion (orbital angular velocity).
28
+ * - $\text{timeStep}$ is the time step in days.
29
+ *
30
+ * **Step 3: Solve Kepler’s Equation for Eccentric Anomaly ($E$)**
31
+ *
32
+ * Kepler’s equation:
33
+ * $$ M = E - e \sin(E) $$
34
+ * is solved numerically to obtain the **eccentric anomaly** ($E$).
35
+ *
36
+ * **Step 4: Convert Eccentric Anomaly ($E$) to True Anomaly ($ν$)**
37
+ *
38
+ * Using the relation:
39
+ * $$ ν = 2 \tan^{-1} \left( \sqrt{\frac{1+e}{1-e}} \tan\left(\frac{E}{2}\right) \right) $$
40
+ *
41
+ * **Step 5: Adjust the True Anomaly for Certain Bodies**
42
+ * - For **comets**, the final **true anomaly** is adjusted by subtracting the **eccentricity** ($e$).
43
+ * - For **stars and other celestial objects**, the computed **true anomaly** is used directly.
44
+ *
45
+ * @param {CelestialBodyType} body - The celestial body for which to compute the true anomaly.
46
+ * @param {TimeStepInterface} timeStep - The time step in days since the last frame.
47
+ * @returns {Radians} The computed true anomaly in radians.
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * import { computeAngle } from './compute-angle';
52
+ *
53
+ * const earth: CelestialBodyType = {
54
+ * name: 'Earth',
55
+ * e: 0.0167, // Orbital eccentricity
56
+ * angle: 0, // Initial angle
57
+ * };
58
+ *
59
+ * const timeStep: TimeStepInterface = { days: 1 }; // Time step of 1 day
60
+ * const angle: Radians = computeAngle(earth, timeStep);
61
+ * console.log(angle); // Output: Computed true anomaly in radians
62
+ * ```
63
+ *
64
+ * @see [Kepler's Equation (Wikipedia)](https://en.wikipedia.org/wiki/Kepler%27s_equation)
65
+ * @see [True Anomaly (Wikipedia)](https://en.wikipedia.org/wiki/True_anomaly)
66
+ * @see [Mean Anomaly (Wikipedia)](https://en.wikipedia.org/wiki/Mean_anomaly)
67
+ * @see [Orbital Mechanics (NASA)](https://solarsystem.nasa.gov/basics/chapter2-2/)
68
+ * @category Angle
69
+ */
70
+ export const computeAngle = (body, timeStep) => {
71
+ // Compute mean anomaly (M) with time step
72
+ const M = computeMeanAnomaly(body, timeStep);
73
+ // Solve Kepler's equation for eccentric anomaly (E)
74
+ const E = solveKepler(M, body.e);
75
+ // Convert eccentric anomaly (E) to true anomaly (ν)
76
+ const V = eccentricToTrueAnomaly(E, body.e);
77
+ return V;
78
+ };
79
+ //# sourceMappingURL=compute-angle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compute-angle.js","sourceRoot":"","sources":["../src/compute-angle.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,IAAuB,EACvB,QAA2B,EAClB,EAAE;IACX,0CAA0C;IAC1C,MAAM,CAAC,GAAY,kBAAkB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACtD,oDAAoD;IACpD,MAAM,CAAC,GAAY,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1C,oDAAoD;IACpD,MAAM,CAAC,GAAY,sBAAsB,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAErD,OAAO,CAAC,CAAC;AACX,CAAC,CAAC"}
@@ -0,0 +1,47 @@
1
+ import type { CelestialBodyType, Radians, TemporalInterface } from '@interstellar-tools/types';
2
+ /**
3
+ * Computes the **mean anomaly** ($M$) of a celestial body for a given time step.
4
+ *
5
+ * **Mathematical Explanation:**
6
+ *
7
+ * The **mean anomaly** is a measure of the position of an orbiting body as if it moved
8
+ * with uniform angular motion along a circular orbit with the same period as the actual
9
+ * elliptical orbit. It is computed as:
10
+ *
11
+ * $$ M = M_0 + n \cdot \Delta T $$
12
+ *
13
+ * where:
14
+ * - $M_0$ is the **initial mean anomaly** (converted from true anomaly if necessary).
15
+ * - $n$ is the **mean motion**, given by:
16
+ * $$ n = \frac{2\pi}{P} $$
17
+ * where $P$ is the orbital period in days.
18
+ * - $\Delta T$ is the time step.
19
+ *
20
+ * @param {CelestialBodyType} body - The celestial body for which to compute the mean anomaly.
21
+ * @param {TemporalInterface} timeStep - The time step over which to compute the change.
22
+ * @returns {Radians} The computed mean anomaly in radians.
23
+ *
24
+ * @throws {RangeError} If the body's eccentricity is outside the range $0 \leq e < 1$.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * import { computeMeanAnomaly } from './compute-mean-anomaly';
29
+ *
30
+ * const mars: CelestialBodyType = {
31
+ * name: 'Mars',
32
+ * e: 0.0934, // Eccentricity of Mars' orbit
33
+ * angle: 1.047, // Initial true anomaly (in radians)
34
+ * period: { value: 687, unit: 'day' }, // Orbital period in days
35
+ * };
36
+ *
37
+ * const timeStep: TemporalInterface = { value: 1, unit: 'day' }; // 1-day step
38
+ * const meanAnomaly = computeMeanAnomaly(mars, timeStep);
39
+ * console.log(meanAnomaly); // Output: Computed mean anomaly in radians
40
+ * ```
41
+ *
42
+ * @see [Mean Anomaly (Wikipedia)](https://en.wikipedia.org/wiki/Mean_anomaly)
43
+ * @see [Orbital Mechanics (NASA)](https://solarsystem.nasa.gov/basics/chapter2-2/)
44
+ * @category Anomaly
45
+ */
46
+ export declare const computeMeanAnomaly: (body: CelestialBodyType, timeStep: TemporalInterface) => Radians;
47
+ //# sourceMappingURL=compute-mean-anomaly.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compute-mean-anomaly.d.ts","sourceRoot":"","sources":["../src/compute-mean-anomaly.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EACjB,OAAO,EACP,iBAAiB,EAClB,MAAM,2BAA2B,CAAC;AAkBnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,eAAO,MAAM,kBAAkB,GAC7B,MAAM,iBAAiB,EACvB,UAAU,iBAAiB,KAC1B,OAoCF,CAAC"}
@@ -0,0 +1,86 @@
1
+ import { TWO_PI } from '@interstellar-tools/constants';
2
+ import { convertTemporalUnit } from '@interstellar-tools/temporal';
3
+ import { trueAnomalyToMeanAnomaly } from './true-anomaly-to-mean-anomaly';
4
+ import { wrapAngle } from './wrap-angle';
5
+ const EPSILON = 1e-15; // Increased tolerance for high eccentricity
6
+ /**
7
+ * Compares two floating-point numbers for equality within a tolerance.
8
+ *
9
+ * @param {number} a - First number to compare.
10
+ * @param {number} b - Second number to compare.
11
+ * @param {number} [epsilon=EPSILON] - Tolerance for floating-point precision.
12
+ * @returns {boolean} `true` if values are approximately equal.
13
+ */
14
+ const areEqual = (a, b, epsilon = EPSILON) => {
15
+ return Math.abs(a - b) < epsilon * Math.max(1, Math.abs(a), Math.abs(b));
16
+ };
17
+ /**
18
+ * Computes the **mean anomaly** ($M$) of a celestial body for a given time step.
19
+ *
20
+ * **Mathematical Explanation:**
21
+ *
22
+ * The **mean anomaly** is a measure of the position of an orbiting body as if it moved
23
+ * with uniform angular motion along a circular orbit with the same period as the actual
24
+ * elliptical orbit. It is computed as:
25
+ *
26
+ * $$ M = M_0 + n \cdot \Delta T $$
27
+ *
28
+ * where:
29
+ * - $M_0$ is the **initial mean anomaly** (converted from true anomaly if necessary).
30
+ * - $n$ is the **mean motion**, given by:
31
+ * $$ n = \frac{2\pi}{P} $$
32
+ * where $P$ is the orbital period in days.
33
+ * - $\Delta T$ is the time step.
34
+ *
35
+ * @param {CelestialBodyType} body - The celestial body for which to compute the mean anomaly.
36
+ * @param {TemporalInterface} timeStep - The time step over which to compute the change.
37
+ * @returns {Radians} The computed mean anomaly in radians.
38
+ *
39
+ * @throws {RangeError} If the body's eccentricity is outside the range $0 \leq e < 1$.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * import { computeMeanAnomaly } from './compute-mean-anomaly';
44
+ *
45
+ * const mars: CelestialBodyType = {
46
+ * name: 'Mars',
47
+ * e: 0.0934, // Eccentricity of Mars' orbit
48
+ * angle: 1.047, // Initial true anomaly (in radians)
49
+ * period: { value: 687, unit: 'day' }, // Orbital period in days
50
+ * };
51
+ *
52
+ * const timeStep: TemporalInterface = { value: 1, unit: 'day' }; // 1-day step
53
+ * const meanAnomaly = computeMeanAnomaly(mars, timeStep);
54
+ * console.log(meanAnomaly); // Output: Computed mean anomaly in radians
55
+ * ```
56
+ *
57
+ * @see [Mean Anomaly (Wikipedia)](https://en.wikipedia.org/wiki/Mean_anomaly)
58
+ * @see [Orbital Mechanics (NASA)](https://solarsystem.nasa.gov/basics/chapter2-2/)
59
+ * @category Anomaly
60
+ */
61
+ export const computeMeanAnomaly = (body, timeStep) => {
62
+ const { e, angle, period } = body;
63
+ if (e < 0 || e >= 1) {
64
+ throw new RangeError(`Invalid eccentricity: ${e}. Eccentricity must be in the range [0, 1].`);
65
+ }
66
+ // Convert period to days and compute mean motion
67
+ const periodInDays = convertTemporalUnit(period, 'd').value;
68
+ const meanMotion = TWO_PI / Math.abs(periodInDays);
69
+ // Convert true anomaly (V) to mean anomaly (M0) if necessary
70
+ const M0 = e === 0 ? angle : trueAnomalyToMeanAnomaly(angle, e);
71
+ if (timeStep.value === 0) {
72
+ return M0;
73
+ }
74
+ // Clamping small time steps to prevent unnecessary updates
75
+ const minTimeStep = 1e-5; // Minimum allowed time step
76
+ const maxTimeStepFactor = e >= 0.9 ? 1 : 10; // Stricter limit for highly eccentric orbits
77
+ const clampedDeltaT = Math.max(minTimeStep, Math.min(timeStep.value, maxTimeStepFactor * Math.abs(periodInDays)));
78
+ // Compute new mean anomaly with hysteresis filter and sign preservation
79
+ const prevM = M0;
80
+ let newM = wrapAngle(M0 + meanMotion * clampedDeltaT);
81
+ if (areEqual(newM, prevM)) {
82
+ newM = prevM;
83
+ }
84
+ return newM;
85
+ };
86
+ //# sourceMappingURL=compute-mean-anomaly.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compute-mean-anomaly.js","sourceRoot":"","sources":["../src/compute-mean-anomaly.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAOnE,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,4CAA4C;AACnE;;;;;;;GAOG;AACH,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,UAAkB,OAAO,EAAW,EAAE;IAC5E,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3E,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,IAAuB,EACvB,QAA2B,EAClB,EAAE;IACX,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAElC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,UAAU,CAClB,yBAAyB,CAAC,6CAA6C,CACxE,CAAC;IACJ,CAAC;IAED,iDAAiD;IACjD,MAAM,YAAY,GAAG,mBAAmB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC;IAC5D,MAAM,UAAU,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IACnD,6DAA6D;IAC7D,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,wBAAwB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAEhE,IAAI,QAAQ,CAAC,KAAK,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,2DAA2D;IAC3D,MAAM,WAAW,GAAG,IAAI,CAAC,CAAC,4BAA4B;IACtD,MAAM,iBAAiB,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,6CAA6C;IAC1F,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAC5B,WAAW,EACX,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CACrE,CAAC;IACF,wEAAwE;IACxE,MAAM,KAAK,GAAG,EAAE,CAAC;IAEjB,IAAI,IAAI,GAAG,SAAS,CAAC,EAAE,GAAG,UAAU,GAAG,aAAa,CAAC,CAAC;IAEtD,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;QAC1B,IAAI,GAAG,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC,CAAC"}
@@ -0,0 +1,43 @@
1
+ import { Radians } from '@interstellar-tools/types';
2
+ /**
3
+ * Converts **Eccentric Anomaly** ($E$) to **True Anomaly** ($V$) for an orbit.
4
+ *
5
+ * **Mathematical Explanation:**
6
+ *
7
+ * The **eccentric anomaly** ($E$) and the **true anomaly** ($V$) are related through:
8
+ * $$
9
+ * \tan \frac{V}{2} = \sqrt{\frac{1+e}{1-e}} \tan \frac{E}{2}
10
+ * $$
11
+ * where:
12
+ * - $E$ is the **eccentric anomaly** (in radians).
13
+ * - $V$ is the **true anomaly** (in radians).
14
+ * - $e$ is the **orbital eccentricity** ($0 \leq e < 1$ for elliptical orbits).
15
+ *
16
+ * This function handles:
17
+ * - **Circular orbits** ($e = 0$): True anomaly is equal to eccentric anomaly.
18
+ * - **Parabolic orbits** ($e = 1$): Uses the special case:
19
+ * $$
20
+ * V = 2 \tan^{-1} \left(\frac{E}{2}\right)
21
+ * $$
22
+ * - **Elliptical orbits** ($0 < e < 1$): Uses the standard conversion equation.
23
+ *
24
+ * Additionally, numerical stability is ensured when $E \approx \pi$.
25
+ *
26
+ * @param {Radians} E - Eccentric anomaly ($E$) in radians.
27
+ * @param {number} e - Orbital eccentricity ($0 \leq e < 1$).
28
+ * @returns {Radians} True anomaly ($V$) in radians.
29
+ *
30
+ * @throws {RangeError} If **eccentricity** is out of the range $0 \leq e \leq 1$.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * console.log(eccentricToTrueAnomaly(1.0, 0.5)); // Output: True anomaly in radians
35
+ * console.log(eccentricToTrueAnomaly(Math.PI, 0)); // Output: Math.PI (circular orbit)
36
+ * console.log(eccentricToTrueAnomaly(0, 1)); // Output: 0 (parabolic orbit)
37
+ * ```
38
+ *
39
+ * @see [True Anomaly (Wikipedia)](https://en.wikipedia.org/wiki/True_anomaly)
40
+ * @category Anomaly
41
+ */
42
+ export declare const eccentricToTrueAnomaly: (E: Radians, e: number) => Radians;
43
+ //# sourceMappingURL=eccentric-to-true-anomaly.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eccentric-to-true-anomaly.d.ts","sourceRoot":"","sources":["../src/eccentric-to-true-anomaly.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,eAAO,MAAM,sBAAsB,GAAI,GAAG,OAAO,EAAE,GAAG,MAAM,KAAG,OAiC9D,CAAC"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Converts **Eccentric Anomaly** ($E$) to **True Anomaly** ($V$) for an orbit.
3
+ *
4
+ * **Mathematical Explanation:**
5
+ *
6
+ * The **eccentric anomaly** ($E$) and the **true anomaly** ($V$) are related through:
7
+ * $$
8
+ * \tan \frac{V}{2} = \sqrt{\frac{1+e}{1-e}} \tan \frac{E}{2}
9
+ * $$
10
+ * where:
11
+ * - $E$ is the **eccentric anomaly** (in radians).
12
+ * - $V$ is the **true anomaly** (in radians).
13
+ * - $e$ is the **orbital eccentricity** ($0 \leq e < 1$ for elliptical orbits).
14
+ *
15
+ * This function handles:
16
+ * - **Circular orbits** ($e = 0$): True anomaly is equal to eccentric anomaly.
17
+ * - **Parabolic orbits** ($e = 1$): Uses the special case:
18
+ * $$
19
+ * V = 2 \tan^{-1} \left(\frac{E}{2}\right)
20
+ * $$
21
+ * - **Elliptical orbits** ($0 < e < 1$): Uses the standard conversion equation.
22
+ *
23
+ * Additionally, numerical stability is ensured when $E \approx \pi$.
24
+ *
25
+ * @param {Radians} E - Eccentric anomaly ($E$) in radians.
26
+ * @param {number} e - Orbital eccentricity ($0 \leq e < 1$).
27
+ * @returns {Radians} True anomaly ($V$) in radians.
28
+ *
29
+ * @throws {RangeError} If **eccentricity** is out of the range $0 \leq e \leq 1$.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * console.log(eccentricToTrueAnomaly(1.0, 0.5)); // Output: True anomaly in radians
34
+ * console.log(eccentricToTrueAnomaly(Math.PI, 0)); // Output: Math.PI (circular orbit)
35
+ * console.log(eccentricToTrueAnomaly(0, 1)); // Output: 0 (parabolic orbit)
36
+ * ```
37
+ *
38
+ * @see [True Anomaly (Wikipedia)](https://en.wikipedia.org/wiki/True_anomaly)
39
+ * @category Anomaly
40
+ */
41
+ export const eccentricToTrueAnomaly = (E, e) => {
42
+ // Validate eccentricity range
43
+ if (e < 0 || e > 1) {
44
+ throw new RangeError(`Invalid eccentricity: ${e}. Eccentricity must be in the range [0, 1].`);
45
+ }
46
+ // Handle circular orbit (e = 0)
47
+ if (e === 0) {
48
+ return E; // True anomaly equals eccentric anomaly for circular orbits
49
+ }
50
+ // Handle parabolic orbit (e = 1)
51
+ if (e === 1) {
52
+ return 2 * Math.atan(E / 2); // Special formula for parabolic orbits
53
+ }
54
+ // Handle numerical instability near E = π
55
+ const epsilon = 1e-10; // Small threshold for numerical stability
56
+ if (Math.abs(Math.cos(E / 2)) < epsilon) {
57
+ return Math.PI; // True anomaly is π when E = π
58
+ }
59
+ // Convert eccentric anomaly (E) to true anomaly (V) for elliptical orbits
60
+ return (2 *
61
+ Math.atan2(Math.sqrt(1 + e) * Math.sin(E / 2), Math.sqrt(1 - e) * Math.cos(E / 2)));
62
+ };
63
+ //# sourceMappingURL=eccentric-to-true-anomaly.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eccentric-to-true-anomaly.js","sourceRoot":"","sources":["../src/eccentric-to-true-anomaly.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAU,EAAE,CAAS,EAAW,EAAE;IACvE,8BAA8B;IAC9B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,UAAU,CAClB,yBAAyB,CAAC,6CAA6C,CACxE,CAAC;IACJ,CAAC;IAED,gCAAgC;IAChC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACZ,OAAO,CAAC,CAAC,CAAC,4DAA4D;IACxE,CAAC;IAED,iCAAiC;IACjC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,uCAAuC;IACtE,CAAC;IAED,0CAA0C;IAC1C,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,0CAA0C;IAEjE,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC;QACxC,OAAO,IAAI,CAAC,EAAE,CAAC,CAAC,+BAA+B;IACjD,CAAC;IAED,0EAA0E;IAC1E,OAAO,CACL,CAAC;QACD,IAAI,CAAC,KAAK,CACR,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,EAClC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CACnC,CACF,CAAC;AACJ,CAAC,CAAC"}