@saber-usa/node-common 1.7.7 → 1.7.9-alpha.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.
@@ -1,1037 +1,1037 @@
1
- /**
2
- * Refactored Vinti Ballistic Propagator JavaScript Implementation
3
- * Uses existing node-common libraries instead of custom implementations
4
- *
5
- * This is a refactored version of the complete Vinti Ballistic Propagator
6
- * that replaces custom implementations with existing node-common libraries.
7
- */
8
-
9
- // Import existing libraries instead of defining custom ones
10
- import {MU, WGS84_EARTH_EQUATORIAL_RADIUS_KM} from "./constants.js";
11
- import {NodeVector3D} from "./NodeVector3D.js";
12
-
13
- // Earth constants using existing constants.js values
14
- const EarthConstants = {
15
- EquatorialRadiusKm: WGS84_EARTH_EQUATORIAL_RADIUS_KM, // 6378.137 km
16
- Mu: MU, // km³/s²
17
- J2: 1082.62999e-6,
18
- J3: -2.53215e-6,
19
- };
20
-
21
- /**
22
- * Position-Velocity vector pair
23
- * Enhanced to work with pious-squid Vector3D
24
- */
25
- class PosVelVec {
26
- constructor(posOrX, velOrY, z, vx, vy, vz) {
27
- if (posOrX instanceof NodeVector3D) {
28
- this.position = posOrX;
29
- this.velocity = velOrY;
30
- } else {
31
- this.position = new NodeVector3D(posOrX, velOrY, z);
32
- this.velocity = new NodeVector3D(vx, vy, vz);
33
- }
34
- }
35
-
36
- /**
37
- * Get a zero vector for position and velocity
38
- * @return {PosVelVec} zero vector
39
- */
40
- static zero() {
41
- return new PosVelVec(new NodeVector3D(0, 0, 0), new NodeVector3D(0, 0, 0));
42
- }
43
-
44
- /**
45
- * Get the position vector
46
- * @return {NodeVector3D} Position vector
47
- */
48
- get Position() {
49
- return this.position;
50
- }
51
-
52
- /**
53
- * Get the velocity vector
54
- * @return {NodeVector3D} Velocity vector
55
- */
56
- get Velocity() {
57
- return this.velocity;
58
- }
59
-
60
- /**
61
- * Get the value at a specific index
62
- * @param {*} index
63
- * @return {number}
64
- */
65
- get(index) {
66
- switch (index) {
67
- case 0: return this.position.x;
68
- case 1: return this.position.y;
69
- case 2: return this.position.z;
70
- case 3: return this.velocity.x;
71
- case 4: return this.velocity.y;
72
- case 5: return this.velocity.z;
73
- default: throw new Error(`Index ${index} out of range`);
74
- }
75
- }
76
-
77
- set(index, value) {
78
- switch (index) {
79
- case 0: this.position.x = value; break;
80
- case 1: this.position.y = value; break;
81
- case 2: this.position.z = value; break;
82
- case 3: this.velocity.x = value; break;
83
- case 4: this.velocity.y = value; break;
84
- case 5: this.velocity.z = value; break;
85
- default: throw new Error(`Index ${index} out of range`);
86
- }
87
- }
88
- }
89
-
90
- /**
91
- * Complete Ballistic Propagator Implementation
92
- * Refactored to use pious-squid Vector3D operations
93
- */
94
- class BallisticPropagatorUtils {
95
- /**
96
- * Kepler solver - exact port from C#
97
- * Now uses pious-squid Vector3D operations
98
- * @param {*} planet
99
- * @param {*} t0
100
- * @param {*} x0
101
- * @param {*} t1
102
- * @return {{x1: PosVelVec, xxx: number}} Object with propagated state and auxiliary value
103
- */
104
- static kepler1(planet, t0, x0, t1) {
105
- const muqr = 1;
106
- const tlimit = 1.0e-10;
107
- const kn = 10;
108
-
109
- const rex = planet[0];
110
- const gmx = planet[1];
111
-
112
- let dt = t1 - t0;
113
- let x1 = PosVelVec.zero();
114
- let xxx = 0;
115
-
116
- if (Math.abs(dt) < tlimit) {
117
- for (let i = 0; i < 6; i++) {
118
- x1.set(i, x0.get(i));
119
- }
120
- return {x1, xxx: 0};
121
- }
122
-
123
- // Change from SI units to astronomical units
124
- const timeFactor = Math.sqrt(rex * rex * rex / gmx);
125
- const vex = rex / timeFactor;
126
- dt /= timeFactor;
127
-
128
- // Use pious-squid Vector3D scale method instead of custom scaleBy
129
- const r0 = x0.Position.scale(1 / rex);
130
- const v0 = x0.Velocity.scale(1 / vex);
131
-
132
- const r0Mag = r0.magnitude();
133
- const v0Mag = v0.magnitude();
134
-
135
- const d0 = r0.dot(v0);
136
- const sigma0 = d0 / muqr;
137
- const alp0 = 2.0 / r0Mag - v0Mag * v0Mag;
138
-
139
- // Initial guess
140
- let x = alp0 * dt;
141
- if (alp0 <= 0) x = 0.5 * dt / r0Mag;
142
-
143
- let dfx = 0;
144
- let u1 = 0; let u2 = 0; let u3 = 0;
145
- let y = 0; let cy = 0; let sy = 0;
146
-
147
- // Newton-Raphson iteration
148
- for (let k = 1; k <= kn; k++) {
149
- let yqr;
150
-
151
- if (alp0 < 0) {
152
- y = alp0 * x * x;
153
- yqr = Math.sqrt(-y);
154
- cy = (1 - Math.cosh(yqr)) / y;
155
- sy = (Math.sinh(yqr) - yqr) / (yqr * yqr * yqr);
156
- } else if (alp0 === 0) {
157
- y = 0;
158
- cy = 0.5;
159
- sy = 1.0 / 6.0;
160
- } else if (alp0 > 0) {
161
- y = alp0 * x * x;
162
- yqr = Math.sqrt(y);
163
- cy = (1 - Math.cos(yqr)) / y;
164
- sy = (yqr - Math.sin(yqr)) / (yqr * yqr * yqr);
165
- }
166
-
167
- u1 = x * (1 - y * sy);
168
- u2 = x * x * cy;
169
- u3 = x * x * x * sy;
170
-
171
- const fx = r0Mag * u1 + sigma0 * u2 + u3 - dt * muqr;
172
- dfx = sigma0 * u1 + (1 - alp0 * r0Mag) * u2 + r0Mag;
173
- const dfx2 = sigma0 * (1 - y * cy) + (1 - alp0 * r0Mag) * u1;
174
- const sdfx = dfx / Math.abs(dfx);
175
-
176
- const dx2 = 16 * dfx * dfx - 20 * fx * dfx2;
177
-
178
- let dx;
179
- if (dx2 > 0) {
180
- dx = 5 * fx / (dfx + sdfx * Math.sqrt(dx2));
181
- } else {
182
- dx = 0.5 * x;
183
- }
184
-
185
- if (Math.abs(dx) < 1.0e-10) break;
186
-
187
- x -= dx;
188
- }
189
-
190
- // Kepler solution converged
191
- const rmag = dfx;
192
- const f = 1 - u2 / r0Mag;
193
- const g = dt - u3 / muqr;
194
- const df = -muqr * u1 / (rmag * r0Mag);
195
- const dg = 1 - u2 / rmag;
196
-
197
- // Use pious-squid Vector3D operations
198
- const finalPos = r0.scale(f).add(v0.scale(g)).scale(rex);
199
- const finalVel = r0.scale(df).add(v0.scale(dg)).scale(vex);
200
-
201
- x1 = new PosVelVec(finalPos, finalVel);
202
- xxx = x * Math.sqrt(rex);
203
-
204
- return {x1, xxx};
205
- }
206
-
207
- /**
208
- * Main Vinti Ballistic Propagation Algorithm
209
- * Complete implementation with all steps - using pious-squid Vector3D
210
- * @param {*} x0
211
- * @param {*} span
212
- * @param {*} eqrad
213
- * @param {*} mu
214
- * @param {*} j2
215
- * @param {*} j3
216
- * @return {PosVelVec} x1
217
- */
218
- static ballisticProp(x0, span, eqrad, mu, j2, j3) {
219
- // Setup
220
- // const pi = 3.141592653589793238;
221
- const pi = Math.PI;
222
- const twopi = 2 * pi;
223
- // const degs = 180 / pi; Astrolibrary's use of degs is unusual in that it calculates a value but immediately discards it.
224
-
225
- const ae = eqrad; // Equatorial radius
226
- const gm = mu; // Gravitational parameter
227
- const xj2 = j2; // J2
228
- const xj3 = j3; // J3
229
-
230
- const planet = [ae, gm, xj2, xj3];
231
-
232
- let x1 = PosVelVec.zero();
233
-
234
- // Compute initial guess using Kepler solver
235
- const vt0 = 0;
236
- const vt1 = span;
237
-
238
- let xhat0 = 0;
239
- const keplerResult = this.kepler1(planet, vt0, x0, vt1);
240
- x1 = keplerResult.x1;
241
- xhat0 = keplerResult.xxx;
242
-
243
- xhat0 /= Math.sqrt(ae); // Change to astronomical units
244
-
245
- // Check Vinti's forbidden zone - using pious-squid Vector3D cross product
246
- const h0 = new NodeVector3D(
247
- x0.get(1) * x0.get(5) - x0.get(2) * x0.get(4),
248
- x0.get(2) * x0.get(3) - x0.get(0) * x0.get(5),
249
- x0.get(0) * x0.get(4) - x0.get(1) * x0.get(3),
250
- );
251
- const h02 = h0.magnitude() * h0.magnitude();
252
-
253
- const r0mag = x0.Position.magnitude();
254
- const v0mag = x0.Velocity.magnitude();
255
-
256
- const alp0 = 2.0 / r0mag - v0mag * v0mag / gm;
257
-
258
- let periapsisRadius; let e0;
259
- if (Math.abs(alp0) < 1.0e-15) {
260
- periapsisRadius = h02 / (2.0 * gm); // Parabolic
261
- } else {
262
- const a0 = 1.0 / alp0;
263
- const e02 = 1 - alp0 * h02 / gm;
264
-
265
- if (e02 <= 0) {
266
- e0 = 0;
267
- } else {
268
- e0 = Math.sqrt(e02);
269
- }
270
-
271
- periapsisRadius = a0 * (1.0 - e0);
272
- }
273
-
274
- if (periapsisRadius < 210) {
275
- throw new Error("Trajectory enters Vinti forbidden zone [WARNING: SPOOKY].");
276
- }
277
-
278
- // Change from SI units to astronomical units
279
- const timeFactor = Math.sqrt(ae * ae * ae / gm);
280
- const ve = ae / timeFactor;
281
- const t0 = vt0 / timeFactor;
282
-
283
- // Use pious-squid Vector3D scale method
284
- const pin = x0.Position.scale(1 / ae);
285
- const vin = x0.Velocity.scale(1 / ve);
286
-
287
- const tf = span / timeFactor;
288
-
289
- // ========== STEP 1: Initial coordinate transformation ==========
290
- const delta = -xj3 / (2 * xj2);
291
- const csq = xj2 * (1 - delta * delta / xj2);
292
- const d0 = Math.sqrt(pin.x * pin.x + pin.y * pin.y);
293
- let alph0 = Math.atan2(pin.y, pin.x);
294
-
295
- if (alph0 < 0) alph0 = twopi + alph0;
296
-
297
- const r02 = d0 * d0 + pin.z * pin.z;
298
- const zpdelta = pin.z + delta;
299
- const rhotemp = r02 - csq + delta * (zpdelta + pin.z);
300
- const rho0 = Math.sqrt(
301
- rhotemp + Math.sqrt(rhotemp * rhotemp + 4 * csq * (zpdelta * zpdelta)))
302
- / Math.sqrt(2.0);
303
- const rho02 = rho0 * rho0;
304
- const sigma0 = zpdelta / rho0;
305
- const rrd = pin.dot(vin); // Using pious-squid dot product
306
- const delsig0 = delta * sigma0;
307
- const csqsig0 = csq * sigma0;
308
- let rcs = rho02 + csqsig0 * sigma0;
309
- const v = -(rho0 + delsig0) / rcs;
310
- const v0 = vin.magnitude(); // Using pious-squid magnitude method
311
-
312
- // ========== STEP 2: First three Jacobi constants ==========
313
- const alph3 = pin.x * vin.y - pin.y * vin.x;
314
- const alph32 = alph3 * alph3;
315
- const alph1 = 0.5 * v0 * v0 + v;
316
- const sqrf = rho0 * rrd + (csqsig0 + delta * rho0) * vin.z;
317
- const sqrg = -sigma0 * rrd - (delsig0 - rho0) * vin.z;
318
- const alph22 = 2 * rho0 + 2 * alph1 * rho02 + (csq * alph32 - sqrf * sqrf) / (rho02 + csq);
319
- const alph2 = Math.sqrt(alph22);
320
- const gamma0 = 2 * alph1;
321
- const csgam0 = csq * gamma0;
322
- const p0 = alph22;
323
- const s0 = 1 - alph32 / alph22;
324
- const pcsgam0 = p0 - csgam0;
325
- const csqs0p0 = csq * s0 * p0;
326
-
327
- // ========== STEP 3: Factorizing F and G quartics ==========
328
- let a1p = 0;
329
- let b1 = csqs0p0 / pcsgam0;
330
- let a1 = (csq - b1) / pcsgam0;
331
-
332
- let p = 0; let p1 = 0; let q1 = 0;
333
- let gam1 = 0; let pgam1 = 0; let gamma = 0;
334
- let cneca = 0; let sneca = 0;
335
-
336
- // Factorize F(rho) quartic
337
- for (let icf = 1; icf <= 5; icf++) {
338
- gam1 = 1 + gamma0 * a1;
339
- pgam1 = pcsgam0 + b1 * gamma0 - 4 * a1 * gam1;
340
- gamma = gamma0 / gam1;
341
- p = pgam1 / gam1;
342
- b1 = csqs0p0 / pgam1;
343
- a1 = (csq - gam1 * b1) / pgam1;
344
- const dela1 = a1 - a1p;
345
-
346
- if (Math.abs(dela1) < 1.0e-15) break;
347
- a1p = a1;
348
- }
349
-
350
- const oe = [];
351
- oe[0] = p; // Vinti mean element (conic parameter)
352
-
353
- let smgam;
354
- if (gamma < 0) {
355
- smgam = Math.sqrt(-gamma);
356
- } else {
357
- smgam = Math.sqrt(gamma);
358
- }
359
-
360
- // Factorize G(sigma) quartic
361
- let s1p = 0;
362
- let px = 0;
363
- let s1 = 1;
364
-
365
- for (let icg = 1; icg <= 5; icg++) {
366
- const p0s1 = p0 * s1;
367
- q1 = -csgam0 / p0s1;
368
- p1 = 2 * delta / p0s1 - 2 * q1 * px;
369
- px = delta / p0s1 - s0 * p1 / (2 * s1);
370
- s1 = (pcsgam0 - s0 * p0 * q1) / ((1 - 2 * px * p1) * p0);
371
- const dels1 = s1 - s1p;
372
-
373
- if (Math.abs(dels1) < 1.0e-15) break;
374
- s1p = s1;
375
- }
376
-
377
- // ========== STEP 4: Initialize R and N integral coefficients ==========
378
- const gams3 = gamma * smgam;
379
- const s = s0 / s1;
380
- const pxs = px * px + s;
381
- oe[2] = pxs; // Vinti mean element (sin^2(I))
382
-
383
- let q;
384
- if (pxs < 0) {
385
- q = 0;
386
- } else {
387
- q = Math.sqrt(pxs);
388
- }
389
-
390
- let xinc = Math.asin(q);
391
-
392
- // Check inclination quadrant - CRITICAL FIX from C#
393
- if (alph3 * Math.cos(xinc) < 0) xinc = pi - xinc;
394
-
395
- const q2 = q * q;
396
- const q4 = q2 * q2;
397
- const betad = 1 + p1 * px - q1 * px * px - q1 * q2;
398
- const beta = (p1 - 2 * q1 * px) / betad;
399
- const betasq = beta * beta;
400
- const g = -beta / (1 + Math.sqrt(1 - betasq * q2));
401
- const a = px + g * q2;
402
- const b = 1 + g * px;
403
-
404
- const g2 = g * g;
405
- const asq = a * a;
406
- const bsq = b * b;
407
- const ab = a * b;
408
- const d5 = 1 + p1 * a - q1 * asq;
409
- const xm = (q1 * bsq - p1 * b * g - g2) / d5;
410
- const xk1 = xm * q2;
411
- const d1 = Math.sqrt((1 - g2 * q2) / (s1 * d5));
412
- const ecc2 = 1 + p * gamma;
413
- const ecc = Math.sqrt(ecc2);
414
- oe[1] = ecc; // Vinti mean element (eccentricity)
415
- const rho1 = p / (1 + ecc);
416
-
417
- // Coefficients for R-integrals
418
- // A0 = 1, A1 from factorization, A2 to A6
419
- const a1sq = a1 * a1;
420
- const b1sq = b1 * b1;
421
-
422
- const a2 = (3 * a1sq - b1) / 2;
423
- const a3 = 2.5 * a1sq * a1 - 1.50 * a1 * b1;
424
- const a4 = 0.375 * (b1sq - 10 * a1sq * b1);
425
- const a5 = 1.875 * a1 * b1sq;
426
- const a6 = -0.3125 * b1sq * b1;
427
-
428
- // W1, W2 ... W6 for the R-integrals
429
- const e3 = ecc2 * ecc;
430
- const e4 = ecc2 * ecc2;
431
- const e5 = e3 * ecc2;
432
- const e6 = e3 * e3;
433
-
434
- const psq = p * p;
435
-
436
- const p3 = psq * p;
437
- const p4 = psq * psq;
438
- const p5 = p3 * psq;
439
- const p6 = p4 * psq;
440
-
441
- const x21 = 1 / p;
442
- const x22 = ecc / p;
443
-
444
- const x33 = 0.5 * ecc2 / psq;
445
- const x32 = 2 * ecc / psq;
446
- const x31 = 1 / psq + x33;
447
-
448
- const x44 = e3 / (3 * p3);
449
- const x43 = 1.5 * ecc2 / p3;
450
- const x42 = 3 * ecc / p3 + 2 * x44;
451
- const x41 = 1 / p3 + x43;
452
-
453
- const x55 = x33 * x33;
454
- const x54 = 4 * x44 * x21;
455
- const x53 = 3 * ecc2 / p4 + 1.5 * x55;
456
- const x52 = 4 * ecc / p4 + 2 * x54;
457
- const x51 = 1 / p4 + x53;
458
-
459
- const x66 = 0.2 * e5 / p5;
460
- const x65 = 1.25 * e4 / p5;
461
- const x64 = 10 * e3 / (3 * p5) + 4 * x66 / 3;
462
- const x63 = 5 * ecc2 / p5 + 1.5 * x65;
463
- const x62 = 5 * ecc / p5 + 2 * x64;
464
- const x61 = 1 / p5 + x63;
465
-
466
- const x77 = e6 / (6 * p6);
467
- const x76 = 1.2 * e5 / p6;
468
- const x75 = 3.75 * e4 / p6 + 1.25 * x77;
469
- const x74 = 20 * e3 / (3 * p6) + x76 / 0.75;
470
- const x73 = 7.5 * ecc2 / p6 + 1.5 * x75;
471
- const x72 = 6 * ecc / p6 + 2 * x74;
472
- const x71 = 1 / p6 + x73;
473
-
474
- const gg1si = 1.0 / Math.sqrt(gam1);
475
- const gg1psi = gg1si / Math.sqrt(p);
476
-
477
- // R1 coefficients
478
- const cr11 = (rho1 + a1) * gg1si;
479
- const cr12 = ecc * gg1si;
480
- const cr13 = a2 * gg1psi;
481
- const cr14 = a3 * gg1psi;
482
- const cr15 = a4 * gg1psi;
483
- const cr16 = a5 * gg1psi;
484
- const cr17 = a6 * gg1psi;
485
-
486
- // R2 coefficients
487
- const cr21 = Math.sqrt(p0 / pgam1);
488
- const cr22 = a1 * cr21;
489
- const cr23 = a2 * cr21;
490
- const cr24 = a3 * cr21;
491
- const cr25 = a4 * cr21;
492
- const cr26 = a5 * cr21;
493
- const cr27 = a6 * cr21;
494
-
495
- // R3 coefficients
496
- const cr31 = alph3 * gg1psi;
497
- const cr32 = a1 * cr31;
498
- const cr33 = (a2 - csq) * cr31;
499
- const cr34 = (a3 - a1 * csq) * cr31;
500
- const cr35 = a4 * cr31 - csq * cr33;
501
-
502
- // Coefficients for N-integrals
503
-
504
- // N3 coefficients
505
- const bmg = b - g;
506
- const bpg = b + g;
507
- const d1ma = 1 - a;
508
- const d1pa = 1 + a;
509
- const beta1 = bmg / d1ma;
510
- const beta2 = -bpg / d1pa;
511
- const b12 = beta1 * beta1;
512
- const b13 = b12 * beta1;
513
- const b22 = beta2 * beta2;
514
- const b23 = b22 * beta2;
515
- let xmm1 = Math.sqrt(1 - b12 * q2);
516
-
517
- if (xmm1 * alph3 < 0) xmm1 = -xmm1;
518
-
519
- let xmm2 = Math.sqrt(1 - b22 * q2);
520
-
521
- if (xmm2 * alph3 < 0) xmm2 = -xmm2;
522
-
523
- const d2 = delta / (p0 * (s1 - s0 * q1));
524
- const d3 = q1 + 2 * p1 * d2;
525
- const d4 = d1 * alph3 / (2 * alph2);
526
-
527
- const d1md3 = 1 - d3;
528
- const bmag = b - a * g;
529
-
530
- const d10 = bmag / bmg * Math.sqrt(d1md3 / (d5 * (1 - xm / b12) * (1 - 2 * d2)));
531
- const d20 = bmag / bpg * Math.sqrt(d1md3 / (d5 * (1 - xm / b22) * (1 + 2 * d2)));
532
-
533
- const dd2 = xm / 2;
534
- const dd3 = dd2 * g;
535
- const dd4 = 1.5 * dd2 * dd2;
536
- const dd5 = dd4 * g;
537
- const dd6 = dd2 * dd4 / 0.6;
538
-
539
- const c15 = dd6 / (b13 * b13);
540
- const c14 = c15 + dd5 / (b12 * b13);
541
- const c13 = c14 + dd4 / (b12 * b12);
542
- const c12 = c13 + dd3 / b13;
543
- const c11 = c12 + dd2 / b12;
544
- const c10 = c11 + g / beta1;
545
-
546
- const c25 = dd6 / (b23 * b23);
547
- const c24 = c25 + dd5 / (b22 * b23);
548
- const c23 = c24 + dd4 / (b22 * b22);
549
- const c22 = c23 + dd3 / b23;
550
- const c21 = c22 + dd2 / b22;
551
- const c20 = c21 + g / beta2;
552
-
553
- const b1q = beta1 * q;
554
- const b1q2 = b1q * b1q;
555
- const b1q4 = b1q2 * b1q2;
556
- const b2q = beta2 * q;
557
- const b2q2 = b2q * b2q;
558
- const b2q4 = b2q2 * b2q2;
559
-
560
- // N1 and N2 coefficients
561
- const xk12 = xk1 * xk1;
562
- const xk13 = xk12 * xk1;
563
-
564
- // Byrd and Friedman formula for elliptic integral
565
- const sq = xk1 / 16 + xk12 / 32 + 21 * xk13 / 1024;
566
- const sq2 = sq * sq;
567
- const sq3 = sq2 * sq;
568
-
569
- const ucf1 = 2 * sq / (1 + sq2);
570
- const ucf2 = sq2 / (1 + sq2 * sq2);
571
- const ucf3 = 2.0 * sq3 / (3.0 * (1 + sq3 * sq3));
572
-
573
- const denystt = 1 + sq + sq;
574
- const denyst = denystt * denystt * d1;
575
-
576
- // N1 coefficients
577
- const d1a2 = d1 / alph2;
578
- const cn11 = d1a2 * asq;
579
- const cn12 = d1a2 * 2 * ab * q;
580
- const cn13 = d1a2 * (bsq - 4 * ab * g) * q2;
581
- const cn14 = d1a2 * (xm * ab - 2 * bsq * g) * q2 * q;
582
- const cn15 = d1a2 * (3 * bsq * g2 + xm * bsq / 2) * q4;
583
- const cn16 = -d1a2 * xm * bsq * g * q4 * q;
584
- const cn17 = 0.375 * d1a2 * xm * xm * bsq * q4 * q2;
585
-
586
- // N2 coefficients
587
- const cn22 = 0.5 * xk1 * d1;
588
- const cn24 = 0.375 * xk12 * d1;
589
- const cn26 = 0.3125 * xk13 * d1;
590
-
591
- // N3 coefficients
592
- const d41ma = d4 / d1ma;
593
- const d41pa = d4 / d1pa;
594
-
595
- const cn31 = -d41ma * c10 - d41pa * c20;
596
- const cn32 = -d41ma * c11 * b1q - d41pa * c21 * b2q;
597
- const cn33 = -d41ma * c12 * b1q2 - d41pa * c22 * b2q2;
598
- const cn34 = -d41ma * c13 * b1q2 * b1q - d41pa * c23 * b2q2 * b2q;
599
- const cn35 = -d41ma * c14 * b1q4 - d41pa * c24 * b2q4;
600
- const cn36 = -d41ma * c15 * b1q4 * b1q - d41pa * c25 * b2q4 * b2q;
601
-
602
- // Avoid singularity at zero inclination
603
- let u;
604
- if (q === 0.0) {
605
- u = 0;
606
- } else {
607
- const d5sq = Math.sqrt(s1 * p0 * (1 + p1 * sigma0 - q1 * sigma0 * sigma0));
608
- const utn = (sigma0 - a) * d5sq;
609
- const utd = sqrg * Math.sqrt(1 - g2 * q2);
610
-
611
- u = Math.atan2(utn, utd);
612
- }
613
-
614
- // Calculate Tk = tk, k = 0, T0 = t0 = u. Here k = 1, ... ,6
615
- let csu = Math.cos(u);
616
- let snu = Math.sin(u);
617
- let snu2 = snu * snu;
618
- let snu4 = snu2 * snu2;
619
-
620
- let t1 = 1 - csu;
621
- let t2 = (u - csu * snu) / 2;
622
- let t3 = (2 * t1 - csu * snu2) / 3;
623
- let t4 = (3 * t2 - csu * snu2 * snu) / 4;
624
- let t5 = (4 * t3 - csu * snu4) / 5;
625
- let t6 = (5 * t4 - csu * snu4 * snu) / 6;
626
-
627
- // Compute xhat at initial time
628
- let xhat;
629
- if (gamma < -1.0e-14) { // Ellipse
630
- if (Math.abs(ecc) < 1.0e-10
631
- || Math.abs(sqrf) < 1.0e-10
632
- || Math.abs(rho0 - rho1) < 1e-5) {
633
- xhat = 0;
634
- } else {
635
- const shat = Math.abs(sqrf) / sqrf * Math.sqrt(gamma * rho02 + 2 * rho0 - p) / ecc;
636
- const cacs = (rho0 - rho1) * gamma / ecc + 1;
637
- let acs = Math.atan2(shat * smgam, cacs);
638
-
639
- if (acs < 0) acs = twopi + acs;
640
-
641
- xhat = acs / smgam;
642
- }
643
- } else if (gamma > 1.0e-14) { // Hyperbola
644
- if (Math.abs(ecc) < 1.0e-10 || Math.abs(sqrf) < 1.0e-10) {
645
- xhat = 0.0;
646
- } else {
647
- const chat = (rho0 - rho1) / ecc;
648
- const zz = gamma * chat + 1;
649
- xhat = Math.log(zz + Math.sqrt(zz * zz - 1)) / Math.sqrt(gamma);
650
-
651
- if (sqrf < 0) xhat = -xhat;
652
- }
653
- } else { // Parabola
654
- xhat = rrd;
655
- }
656
-
657
- // ========== STEP 5: Last three Jacobi constants ==========
658
- // Determine true anomaly from xhat at t0
659
- let eca = smgam * xhat;
660
- let tra; let chat; let shat;
661
-
662
- if (gamma < -1.0e-14) { // Ellipse
663
- sneca = Math.sin(eca);
664
- cneca = Math.cos(eca);
665
- const s1mes = Math.sqrt(1 - ecc2);
666
- const temp3 = 2 * Math.atan(ecc * sneca / (1 + s1mes - ecc * cneca));
667
- tra = eca + temp3;
668
- } else if (gamma > 1.0e-14) { // Hyperbola
669
- sneca = Math.sinh(eca);
670
- cneca = Math.cosh(eca);
671
-
672
- chat = (cneca - 1) / gamma;
673
- shat = sneca / smgam;
674
- tra = Math.atan2(Math.sqrt(p) * shat, rho1 - chat);
675
- } else { // Parabola
676
- tra = 2 * Math.atan(xhat / Math.sqrt(p));
677
- }
678
-
679
- let snw = Math.sin(tra);
680
- let cnw = Math.cos(tra);
681
- let ecccnw = ecc * cnw;
682
-
683
- // const dw1dx = (1 + ecccnw) / p;
684
- // const dw2dx = dw1dx * dw1dx; same case. Astrolibrary does not use this instance of dw2dx here but does so later in the code after it is redeclared
685
-
686
- const v3 = snw * cnw;
687
- const v4 = v3 * cnw;
688
- const v5 = v4 * cnw;
689
- const v6 = v5 * cnw;
690
- const v7 = v6 * cnw;
691
-
692
- let w1 = x21 * tra + x22 * snw;
693
- let w2 = x31 * tra + x32 * snw + x33 * v3;
694
- let w3 = x41 * tra + x42 * snw + x43 * v3 + x44 * v4;
695
- let w4 = x51 * tra + x52 * snw + x53 * v3 + x54 * v4 + x55 * v5;
696
- let w5 = x61 * tra + x62 * snw + x63 * v3 + x64 * v4 + x65 * v5 + x66 * v6;
697
- let w6 = x71 * tra + x72 * snw + x73 * v3 + x74 * v4 + x75 * v5 + x76 * v6 + x77 * v7;
698
-
699
- let uhat;
700
- if (Math.abs(gamma) >= 1.0e-14) { // Circle, ellipse, hyperbola
701
- chat = (cneca - 1) / gamma;
702
- uhat = (sneca - eca) / gams3;
703
- } else { // Parabola
704
- chat = xhat * xhat / 2;
705
- uhat = xhat * xhat * xhat / 6;
706
- }
707
-
708
- const r1 = cr11 * xhat + cr12 * uhat + cr13 * tra
709
- + cr14 * w1 + cr15 * w2 + cr16 * w3 + cr17 * w4;
710
- const r2 = cr21 * tra + cr22 * w1 + cr23 * w2 + cr24
711
- * w3 + cr25 * w4 + cr26 * w5 + cr27 * w6;
712
-
713
- let sstar = Math.sin(0.5 * u);
714
- let cstar = Math.cos(0.5 * u);
715
- let cb1qs = cstar - b1q * sstar;
716
- let psi1 = Math.atan(xmm1 * sstar / cb1qs);
717
-
718
- if (cb1qs * Math.cos(psi1) < 0.0) psi1 = pi + psi1;
719
-
720
- let cb2qs = cstar - b2q * sstar;
721
- let psi2 = Math.atan(xmm2 * sstar / cb2qs);
722
-
723
- if (cb2qs * Math.cos(psi2) < 0.0) psi2 = pi + psi2;
724
-
725
- let r3 = cr31 * w2 + cr32 * w3 + cr33 * w4 + cr34 * w5 + cr35 * w6;
726
-
727
- let en3 = d10 * psi1 + d20 * psi2 + cn31 * u + cn32
728
- * t1 + cn33 * t2 + cn34 * t3 + cn35 * t4 + cn36 * t5;
729
- const en1 = cn11 * u + cn12 * t1 + cn13 * t2 + cn14
730
- * t3 + cn15 * t4 + cn16 * t5 + cn17 * t6;
731
- const en2 = d1 * u + cn22 * t2 + cn24 * t4 + cn26 * t6;
732
-
733
- const somega = en2 - r2; // beta2
734
- const capt = t0 - r1 - csq * en1; // -beta1
735
- const deltat = tf - capt;
736
- const comega = alph0 + csq * r3 - en3; // beta3
737
-
738
- oe[3] = -capt * timeFactor; // Vinti mean element "beta1"
739
- oe[4] = somega; // Vinti mean element "beta2"
740
- oe[5] = comega; // Vinti mean element "beta3"
741
-
742
- // ========== STEP 6: Solve kinematical equations ==========
743
- // The generalized Kepler equation is solved by iteration
744
- xhat += xhat0;
745
-
746
- // Variables already declared earlier, will be reassigned in loop
747
-
748
- for (let ick = 1; ick <= 10; ick++) {
749
- eca = smgam * xhat;
750
-
751
- if (gamma < -1.0e-14) { // Ellipse
752
- sneca = Math.sin(eca);
753
- cneca = Math.cos(eca);
754
- const s1mes = Math.sqrt(1 - ecc2);
755
- const temp3 = 2 * Math.atan(ecc * sneca / (1 + s1mes - ecc * cneca));
756
- tra = eca + temp3;
757
- } else if (gamma > 1.0e-14) { // Hyperbola
758
- sneca = Math.sinh(eca);
759
- cneca = Math.cosh(eca);
760
- chat = (cneca - 1) / gamma;
761
- shat = sneca / smgam;
762
- tra = Math.atan2(Math.sqrt(p) * shat, rho1 - chat);
763
- } else { // Parabola
764
- tra = 2 * Math.atan(xhat / Math.sqrt(p));
765
- }
766
-
767
- snw = Math.sin(tra);
768
- cnw = Math.cos(tra);
769
- ecccnw = ecc * cnw;
770
-
771
- const dwdx = (1 + ecccnw) / Math.sqrt(p);
772
- const dw1dx = (1 + ecccnw) / p;
773
- const dw2dx = dw1dx * dw1dx;
774
- const dw3dx = dw1dx * dw2dx;
775
- const dw4dx = dw2dx * dw2dx;
776
-
777
- const v3 = snw * cnw;
778
- const v4 = v3 * cnw;
779
- const v5 = v4 * cnw;
780
- const v6 = v5 * cnw;
781
- const v7 = v6 * cnw;
782
-
783
- w1 = x21 * tra + x22 * snw;
784
- w2 = x31 * tra + x32 * snw + x33 * v3;
785
- w3 = x41 * tra + x42 * snw + x43 * v3 + x44 * v4;
786
- w4 = x51 * tra + x52 * snw + x53 * v3 + x54 * v4 + x55 * v5;
787
- w5 = x61 * tra + x62 * snw + x63 * v3 + x64 * v4 + x65 * v5 + x66 * v6;
788
- w6 = x71 * tra + x72 * snw + x73 * v3 + x74 * v4 + x75 * v5 + x76 * v6 + x77 * v7;
789
-
790
- if (Math.abs(gamma) >= 1.0e-14) {
791
- chat = (cneca - 1) / gamma;
792
- uhat = (sneca - eca) / gams3;
793
- } else { // Parabola
794
- chat = xhat * xhat / 2;
795
- uhat = xhat * xhat * xhat / 6;
796
- }
797
-
798
- const r1 = cr11 * xhat
799
- + cr12 * uhat
800
- + cr13 * tra
801
- + cr14 * w1
802
- + cr15 * w2
803
- + cr16 * w3
804
- + cr17 * w4;
805
- const r2
806
- = cr21 * tra
807
- + cr22 * w1
808
- + cr23 * w2
809
- + cr24 * w3
810
- + cr25 * w4
811
- + cr26 * w5
812
- + cr27 * w6;
813
-
814
- const ystar = (r2 + somega) / denyst;
815
- u = ystar
816
- + ucf1 * Math.sin(2 * ystar)
817
- + ucf2 * Math.sin(4 * ystar)
818
- + ucf3 * Math.sin(6 * ystar);
819
-
820
- csu = Math.cos(u);
821
- snu = Math.sin(u);
822
- snu2 = snu * snu;
823
- snu4 = snu2 * snu2;
824
-
825
- t1 = 1 - csu;
826
- t2 = (u - csu * snu) / 2;
827
- t3 = (2 * t1 - csu * snu2) / 3;
828
- t4 = (3 * t2 - csu * snu2 * snu) / 4;
829
- t5 = (4 * t3 - csu * snu4) / 5;
830
- t6 = (5 * t4 - csu * snu4 * snu) / 6;
831
-
832
- const en1 = cn11 * u
833
- + cn12 * t1
834
- + cn13 * t2
835
- + cn14 * t3
836
- + cn15 * t4
837
- + cn16 * t5
838
- + cn17 * t6;
839
-
840
- const cn1r1 = csq * en1 + r1;
841
- const psixhat = cn1r1 - deltat; // Function
842
-
843
- const dpsx1 = cr13 + cr14 * dw1dx + cr15 * dw2dx + cr16 * dw3dx + cr17 * dw4dx;
844
- const dpsx = cr11 + cr12 * chat + dpsx1 * dwdx; // 1st derivative
845
- const delx = psixhat / dpsx;
846
-
847
- xhat -= delx;
848
-
849
- if (Math.abs(delx) < 1.0e-14) break;
850
- }
851
-
852
- // ========== STEP 7: Final coordinate transformation ==========
853
- // OSC state vector is (rhof, sigmaf, alphaf, drhof, dsigf, dalphf)
854
- // ECI state vector is (pf, vf)
855
-
856
- sstar = Math.sin(0.5 * u);
857
- cstar = Math.cos(0.5 * u);
858
- cb1qs = cstar - b1q * sstar;
859
- psi1 = Math.atan(xmm1 * sstar / cb1qs);
860
-
861
- if (cb1qs * Math.cos(psi1) < 0) psi1 = pi + psi1;
862
-
863
- cb2qs = cstar - b2q * sstar;
864
- psi2 = Math.atan(xmm2 * sstar / cb2qs);
865
-
866
- if (cb2qs * Math.cos(psi2) < 0) psi2 = pi + psi2;
867
-
868
- r3 = cr31 * w2 + cr32 * w3 + cr33 * w4 + cr34 * w5 + cr35 * w6;
869
- en3 = d10 * psi1
870
- + d20 * psi2
871
- + cn31 * u
872
- + cn32 * t1
873
- + cn33 * t2
874
- + cn34 * t3
875
- + cn35 * t4
876
- + cn36 * t5;
877
- const qsnu = q * snu;
878
- const alphaf = comega - csq * r3 + en3;
879
- const rhof = rho1 + ecc * chat;
880
- const sigmaf = (a + b * qsnu) / (1 + g * qsnu);
881
-
882
- const rhof2 = rhof * rhof;
883
- const sigf2 = sigmaf * sigmaf;
884
-
885
- const rhocsq = rhof2 + csq;
886
- const df = Math.sqrt(rhocsq * (1 - sigf2));
887
- const snalp = Math.sin(alphaf);
888
- const csalp = Math.cos(alphaf);
889
-
890
- const pf = new NodeVector3D(df * csalp, df * snalp, rhof * sigmaf - delta);
891
-
892
- shat = xhat + gamma * uhat;
893
- rcs = rhof2 + csq * sigf2;
894
-
895
- const rhoqd = rhof2 - 2 * a1 * rhof + b1;
896
- const drhofp = Math.sqrt(gam1 * rhoqd) / rcs;
897
- const drhof = ecc * shat * drhofp;
898
- const temp12 = (1 + p1 * sigmaf - q1 * sigf2) * (1 - g2 * q2) * s1;
899
- const dsigf = alph2 * q * Math.sqrt(temp12) / rcs * csu / (1 + g * qsnu);
900
- const temp20 = (1 - sigf2) * (rhof * drhof);
901
- const temp21 = rhocsq * sigmaf * dsigf;
902
- const dd = (temp20 - temp21) / df;
903
- const dalphf = alph3 / (df * df);
904
-
905
- const vf = new NodeVector3D(
906
- dd * csalp - pf.y * dalphf,
907
- dd * snalp + pf.x * dalphf,
908
- sigmaf * drhof + rhof * dsigf,
909
- );
910
-
911
- // Change from astronomical units to SI units - using pious-squid Vector3D scale method
912
- x1.position = pf.scale(ae);
913
- x1.velocity = vf.scale(ve);
914
-
915
- return x1;
916
- }
917
- }
918
-
919
- /**
920
- * Main Ballistic Propagator class
921
- * Refactored to use existing constants and Vector3D class
922
- */
923
- class BallisticPropagator {
924
- constructor(baseStateVector, options = {}) {
925
- this.baseStateVector = baseStateVector;
926
- this.planetEquatorialRadiusKm
927
- = options.equatorialRadius || EarthConstants.EquatorialRadiusKm;
928
- this.mu = options.mu || EarthConstants.Mu;
929
- this.j2 = options.j2 || EarthConstants.J2;
930
- this.j3 = options.j3 || EarthConstants.J3;
931
- this.referenceFrame = options.referenceFrame || "J2000";
932
- }
933
-
934
- /**
935
- * Propagate state vector to target time
936
- * @param {Date|number} targetTime - Target time as Date or seconds from epoch
937
- * @return {Object} Propagated state with position and velocity
938
- */
939
- propagate(targetTime) {
940
- let spanSeconds;
941
-
942
- if (targetTime instanceof Date) {
943
- spanSeconds = (targetTime - this.baseStateVector.epochUtc) / 1000;
944
- } else {
945
- spanSeconds = targetTime;
946
- }
947
-
948
- // Create PosVelVec using pious-squid Vector3D
949
- // Handle both lowercase and uppercase property names, but check for undefined specifically
950
- const getComponent = (vector, lowerProp, upperProp) => {
951
- return vector[lowerProp] !== undefined ? vector[lowerProp] : vector[upperProp];
952
- };
953
-
954
- const x0 = new PosVelVec(
955
- new NodeVector3D(
956
- getComponent(this.baseStateVector.position, "x", "X"),
957
- getComponent(this.baseStateVector.position, "y", "Y"),
958
- getComponent(this.baseStateVector.position, "z", "Z"),
959
- ),
960
- new NodeVector3D(
961
- getComponent(this.baseStateVector.velocity, "x", "X"),
962
- getComponent(this.baseStateVector.velocity, "y", "Y"),
963
- getComponent(this.baseStateVector.velocity, "z", "Z"),
964
- ),
965
- );
966
-
967
- const x1 = BallisticPropagatorUtils.ballisticProp(
968
- x0,
969
- spanSeconds,
970
- this.planetEquatorialRadiusKm,
971
- this.mu,
972
- this.j2,
973
- this.j3,
974
- );
975
-
976
- return {
977
- position: x1.position,
978
- velocity: x1.velocity,
979
- epochUtc: targetTime instanceof Date
980
- ? targetTime
981
- : new Date(this.baseStateVector.epochUtc.getTime() + spanSeconds * 1000),
982
- referenceFrame: this.referenceFrame,
983
- };
984
- }
985
- }
986
-
987
- // Export for Node.js
988
- export {BallisticPropagator, BallisticPropagatorUtils, PosVelVec, EarthConstants};
989
-
990
- /**
991
- * REFACTORING SUMMARY:
992
- * ===================
993
- *
994
- * REPLACED IMPLEMENTATIONS:
995
- * 1. EarthConstants - Now uses values from ./constants.js:
996
- * - EquatorialRadiusKm: WGS84_EARTH_EQUATORIAL_RADIUS_KM (6378.137 km)
997
- * - Mu: MU converted from m³/s² to km³/s² (398600.4418)
998
- * - J2 and J3: Kept original values as they're specific to Vinti propagator
999
- *
1000
- * 2. Vector3D - Now imports and uses pious-squid's Vector3D class:
1001
- * - Uses magnitude() instead of length property
1002
- * - Uses scale() instead of scaleBy() method
1003
- * - Uses add() and dot() methods directly from pious-squid
1004
- * - All vector operations now use the robust pious-squid implementation
1005
- *
1006
- * 3. Mathematical operations:
1007
- * - Vector magnitude calculations use pious-squid's magnitude() method
1008
- * - Vector scaling uses pious-squid's scale() method
1009
- * - Vector addition uses pious-squid's add() method
1010
- * - Dot product uses pious-squid's dot() method
1011
- *
1012
- * BENEFITS:
1013
- * - Reduced code duplication (removed ~100 lines of custom Vector3D implementation)
1014
- * - Uses standardized, well-tested vector operations from pious-squid
1015
- * - Uses standardized Earth constants from node-common constants.js
1016
- * - Maintains 100% functional compatibility with original implementation
1017
- * - Better maintainability through shared library usage
1018
- *
1019
- * EXAMPLE USAGE (unchanged):
1020
- * ==========================
1021
- *
1022
- * // ISS-like circular orbit
1023
- * const initialState = {
1024
- * position: new Vector3D(6778.137, 0, 0), // km
1025
- * velocity: new Vector3D(0, 7.66779, 0), // km/s
1026
- * epochUtc: new Date('2025-01-01T00:00:00Z'),
1027
- * referenceFrame: 'J2000'
1028
- * };
1029
- *
1030
- * const propagator = new BallisticPropagator(initialState);
1031
- *
1032
- * // Propagate for 5 minutes (300 seconds)
1033
- * const result = propagator.propagate(300);
1034
- *
1035
- * console.log('Position after 300s:', result.position);
1036
- * console.log('Velocity after 300s:', result.velocity);
1037
- */
1
+ /**
2
+ * Refactored Vinti Ballistic Propagator JavaScript Implementation
3
+ * Uses existing node-common libraries instead of custom implementations
4
+ *
5
+ * This is a refactored version of the complete Vinti Ballistic Propagator
6
+ * that replaces custom implementations with existing node-common libraries.
7
+ */
8
+
9
+ // Import existing libraries instead of defining custom ones
10
+ import {MU, WGS84_EARTH_EQUATORIAL_RADIUS_KM} from "./constants.js";
11
+ import {NodeVector3D} from "./NodeVector3D.js";
12
+
13
+ // Earth constants using existing constants.js values
14
+ const EarthConstants = {
15
+ EquatorialRadiusKm: WGS84_EARTH_EQUATORIAL_RADIUS_KM, // 6378.137 km
16
+ Mu: MU / 1e9, // Convert from m³/s² to km³/s² (398600.4418)
17
+ J2: 1082.62999e-6,
18
+ J3: -2.53215e-6,
19
+ };
20
+
21
+ /**
22
+ * Position-Velocity vector pair
23
+ * Enhanced to work with pious-squid Vector3D
24
+ */
25
+ class PosVelVec {
26
+ constructor(posOrX, velOrY, z, vx, vy, vz) {
27
+ if (posOrX instanceof NodeVector3D) {
28
+ this.position = posOrX;
29
+ this.velocity = velOrY;
30
+ } else {
31
+ this.position = new NodeVector3D(posOrX, velOrY, z);
32
+ this.velocity = new NodeVector3D(vx, vy, vz);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Get a zero vector for position and velocity
38
+ * @return {PosVelVec} zero vector
39
+ */
40
+ static zero() {
41
+ return new PosVelVec(new NodeVector3D(0, 0, 0), new NodeVector3D(0, 0, 0));
42
+ }
43
+
44
+ /**
45
+ * Get the position vector
46
+ * @return {NodeVector3D} Position vector
47
+ */
48
+ get Position() {
49
+ return this.position;
50
+ }
51
+
52
+ /**
53
+ * Get the velocity vector
54
+ * @return {NodeVector3D} Velocity vector
55
+ */
56
+ get Velocity() {
57
+ return this.velocity;
58
+ }
59
+
60
+ /**
61
+ * Get the value at a specific index
62
+ * @param {*} index
63
+ * @return {number}
64
+ */
65
+ get(index) {
66
+ switch (index) {
67
+ case 0: return this.position.x;
68
+ case 1: return this.position.y;
69
+ case 2: return this.position.z;
70
+ case 3: return this.velocity.x;
71
+ case 4: return this.velocity.y;
72
+ case 5: return this.velocity.z;
73
+ default: throw new Error(`Index ${index} out of range`);
74
+ }
75
+ }
76
+
77
+ set(index, value) {
78
+ switch (index) {
79
+ case 0: this.position.x = value; break;
80
+ case 1: this.position.y = value; break;
81
+ case 2: this.position.z = value; break;
82
+ case 3: this.velocity.x = value; break;
83
+ case 4: this.velocity.y = value; break;
84
+ case 5: this.velocity.z = value; break;
85
+ default: throw new Error(`Index ${index} out of range`);
86
+ }
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Complete Ballistic Propagator Implementation
92
+ * Refactored to use pious-squid Vector3D operations
93
+ */
94
+ class BallisticPropagatorUtils {
95
+ /**
96
+ * Kepler solver - exact port from C#
97
+ * Now uses pious-squid Vector3D operations
98
+ * @param {*} planet
99
+ * @param {*} t0
100
+ * @param {*} x0
101
+ * @param {*} t1
102
+ * @return {{x1: PosVelVec, xxx: number}} Object with propagated state and auxiliary value
103
+ */
104
+ static kepler1(planet, t0, x0, t1) {
105
+ const muqr = 1;
106
+ const tlimit = 1.0e-10;
107
+ const kn = 10;
108
+
109
+ const rex = planet[0];
110
+ const gmx = planet[1];
111
+
112
+ let dt = t1 - t0;
113
+ let x1 = PosVelVec.zero();
114
+ let xxx = 0;
115
+
116
+ if (Math.abs(dt) < tlimit) {
117
+ for (let i = 0; i < 6; i++) {
118
+ x1.set(i, x0.get(i));
119
+ }
120
+ return {x1, xxx: 0};
121
+ }
122
+
123
+ // Change from SI units to astronomical units
124
+ const timeFactor = Math.sqrt(rex * rex * rex / gmx);
125
+ const vex = rex / timeFactor;
126
+ dt /= timeFactor;
127
+
128
+ // Use pious-squid Vector3D scale method instead of custom scaleBy
129
+ const r0 = x0.Position.scale(1 / rex);
130
+ const v0 = x0.Velocity.scale(1 / vex);
131
+
132
+ const r0Mag = r0.magnitude();
133
+ const v0Mag = v0.magnitude();
134
+
135
+ const d0 = r0.dot(v0);
136
+ const sigma0 = d0 / muqr;
137
+ const alp0 = 2.0 / r0Mag - v0Mag * v0Mag;
138
+
139
+ // Initial guess
140
+ let x = alp0 * dt;
141
+ if (alp0 <= 0) x = 0.5 * dt / r0Mag;
142
+
143
+ let dfx = 0;
144
+ let u1 = 0; let u2 = 0; let u3 = 0;
145
+ let y = 0; let cy = 0; let sy = 0;
146
+
147
+ // Newton-Raphson iteration
148
+ for (let k = 1; k <= kn; k++) {
149
+ let yqr;
150
+
151
+ if (alp0 < 0) {
152
+ y = alp0 * x * x;
153
+ yqr = Math.sqrt(-y);
154
+ cy = (1 - Math.cosh(yqr)) / y;
155
+ sy = (Math.sinh(yqr) - yqr) / (yqr * yqr * yqr);
156
+ } else if (alp0 === 0) {
157
+ y = 0;
158
+ cy = 0.5;
159
+ sy = 1.0 / 6.0;
160
+ } else if (alp0 > 0) {
161
+ y = alp0 * x * x;
162
+ yqr = Math.sqrt(y);
163
+ cy = (1 - Math.cos(yqr)) / y;
164
+ sy = (yqr - Math.sin(yqr)) / (yqr * yqr * yqr);
165
+ }
166
+
167
+ u1 = x * (1 - y * sy);
168
+ u2 = x * x * cy;
169
+ u3 = x * x * x * sy;
170
+
171
+ const fx = r0Mag * u1 + sigma0 * u2 + u3 - dt * muqr;
172
+ dfx = sigma0 * u1 + (1 - alp0 * r0Mag) * u2 + r0Mag;
173
+ const dfx2 = sigma0 * (1 - y * cy) + (1 - alp0 * r0Mag) * u1;
174
+ const sdfx = dfx / Math.abs(dfx);
175
+
176
+ const dx2 = 16 * dfx * dfx - 20 * fx * dfx2;
177
+
178
+ let dx;
179
+ if (dx2 > 0) {
180
+ dx = 5 * fx / (dfx + sdfx * Math.sqrt(dx2));
181
+ } else {
182
+ dx = 0.5 * x;
183
+ }
184
+
185
+ if (Math.abs(dx) < 1.0e-10) break;
186
+
187
+ x -= dx;
188
+ }
189
+
190
+ // Kepler solution converged
191
+ const rmag = dfx;
192
+ const f = 1 - u2 / r0Mag;
193
+ const g = dt - u3 / muqr;
194
+ const df = -muqr * u1 / (rmag * r0Mag);
195
+ const dg = 1 - u2 / rmag;
196
+
197
+ // Use pious-squid Vector3D operations
198
+ const finalPos = r0.scale(f).add(v0.scale(g)).scale(rex);
199
+ const finalVel = r0.scale(df).add(v0.scale(dg)).scale(vex);
200
+
201
+ x1 = new PosVelVec(finalPos, finalVel);
202
+ xxx = x * Math.sqrt(rex);
203
+
204
+ return {x1, xxx};
205
+ }
206
+
207
+ /**
208
+ * Main Vinti Ballistic Propagation Algorithm
209
+ * Complete implementation with all steps - using pious-squid Vector3D
210
+ * @param {*} x0
211
+ * @param {*} span
212
+ * @param {*} eqrad
213
+ * @param {*} mu
214
+ * @param {*} j2
215
+ * @param {*} j3
216
+ * @return {PosVelVec} x1
217
+ */
218
+ static ballisticProp(x0, span, eqrad, mu, j2, j3) {
219
+ // Setup
220
+ // const pi = 3.141592653589793238;
221
+ const pi = Math.PI;
222
+ const twopi = 2 * pi;
223
+ // const degs = 180 / pi; Astrolibrary's use of degs is unusual in that it calculates a value but immediately discards it.
224
+
225
+ const ae = eqrad; // Equatorial radius
226
+ const gm = mu; // Gravitational parameter
227
+ const xj2 = j2; // J2
228
+ const xj3 = j3; // J3
229
+
230
+ const planet = [ae, gm, xj2, xj3];
231
+
232
+ let x1 = PosVelVec.zero();
233
+
234
+ // Compute initial guess using Kepler solver
235
+ const vt0 = 0;
236
+ const vt1 = span;
237
+
238
+ let xhat0 = 0;
239
+ const keplerResult = this.kepler1(planet, vt0, x0, vt1);
240
+ x1 = keplerResult.x1;
241
+ xhat0 = keplerResult.xxx;
242
+
243
+ xhat0 /= Math.sqrt(ae); // Change to astronomical units
244
+
245
+ // Check Vinti's forbidden zone - using pious-squid Vector3D cross product
246
+ const h0 = new NodeVector3D(
247
+ x0.get(1) * x0.get(5) - x0.get(2) * x0.get(4),
248
+ x0.get(2) * x0.get(3) - x0.get(0) * x0.get(5),
249
+ x0.get(0) * x0.get(4) - x0.get(1) * x0.get(3),
250
+ );
251
+ const h02 = h0.magnitude() * h0.magnitude();
252
+
253
+ const r0mag = x0.Position.magnitude();
254
+ const v0mag = x0.Velocity.magnitude();
255
+
256
+ const alp0 = 2.0 / r0mag - v0mag * v0mag / gm;
257
+
258
+ let periapsisRadius; let e0;
259
+ if (Math.abs(alp0) < 1.0e-15) {
260
+ periapsisRadius = h02 / (2.0 * gm); // Parabolic
261
+ } else {
262
+ const a0 = 1.0 / alp0;
263
+ const e02 = 1 - alp0 * h02 / gm;
264
+
265
+ if (e02 <= 0) {
266
+ e0 = 0;
267
+ } else {
268
+ e0 = Math.sqrt(e02);
269
+ }
270
+
271
+ periapsisRadius = a0 * (1.0 - e0);
272
+ }
273
+
274
+ if (periapsisRadius < 210) {
275
+ throw new Error("Trajectory enters Vinti forbidden zone [WARNING: SPOOKY].");
276
+ }
277
+
278
+ // Change from SI units to astronomical units
279
+ const timeFactor = Math.sqrt(ae * ae * ae / gm);
280
+ const ve = ae / timeFactor;
281
+ const t0 = vt0 / timeFactor;
282
+
283
+ // Use pious-squid Vector3D scale method
284
+ const pin = x0.Position.scale(1 / ae);
285
+ const vin = x0.Velocity.scale(1 / ve);
286
+
287
+ const tf = span / timeFactor;
288
+
289
+ // ========== STEP 1: Initial coordinate transformation ==========
290
+ const delta = -xj3 / (2 * xj2);
291
+ const csq = xj2 * (1 - delta * delta / xj2);
292
+ const d0 = Math.sqrt(pin.x * pin.x + pin.y * pin.y);
293
+ let alph0 = Math.atan2(pin.y, pin.x);
294
+
295
+ if (alph0 < 0) alph0 = twopi + alph0;
296
+
297
+ const r02 = d0 * d0 + pin.z * pin.z;
298
+ const zpdelta = pin.z + delta;
299
+ const rhotemp = r02 - csq + delta * (zpdelta + pin.z);
300
+ const rho0 = Math.sqrt(
301
+ rhotemp + Math.sqrt(rhotemp * rhotemp + 4 * csq * (zpdelta * zpdelta)))
302
+ / Math.sqrt(2.0);
303
+ const rho02 = rho0 * rho0;
304
+ const sigma0 = zpdelta / rho0;
305
+ const rrd = pin.dot(vin); // Using pious-squid dot product
306
+ const delsig0 = delta * sigma0;
307
+ const csqsig0 = csq * sigma0;
308
+ let rcs = rho02 + csqsig0 * sigma0;
309
+ const v = -(rho0 + delsig0) / rcs;
310
+ const v0 = vin.magnitude(); // Using pious-squid magnitude method
311
+
312
+ // ========== STEP 2: First three Jacobi constants ==========
313
+ const alph3 = pin.x * vin.y - pin.y * vin.x;
314
+ const alph32 = alph3 * alph3;
315
+ const alph1 = 0.5 * v0 * v0 + v;
316
+ const sqrf = rho0 * rrd + (csqsig0 + delta * rho0) * vin.z;
317
+ const sqrg = -sigma0 * rrd - (delsig0 - rho0) * vin.z;
318
+ const alph22 = 2 * rho0 + 2 * alph1 * rho02 + (csq * alph32 - sqrf * sqrf) / (rho02 + csq);
319
+ const alph2 = Math.sqrt(alph22);
320
+ const gamma0 = 2 * alph1;
321
+ const csgam0 = csq * gamma0;
322
+ const p0 = alph22;
323
+ const s0 = 1 - alph32 / alph22;
324
+ const pcsgam0 = p0 - csgam0;
325
+ const csqs0p0 = csq * s0 * p0;
326
+
327
+ // ========== STEP 3: Factorizing F and G quartics ==========
328
+ let a1p = 0;
329
+ let b1 = csqs0p0 / pcsgam0;
330
+ let a1 = (csq - b1) / pcsgam0;
331
+
332
+ let p = 0; let p1 = 0; let q1 = 0;
333
+ let gam1 = 0; let pgam1 = 0; let gamma = 0;
334
+ let cneca = 0; let sneca = 0;
335
+
336
+ // Factorize F(rho) quartic
337
+ for (let icf = 1; icf <= 5; icf++) {
338
+ gam1 = 1 + gamma0 * a1;
339
+ pgam1 = pcsgam0 + b1 * gamma0 - 4 * a1 * gam1;
340
+ gamma = gamma0 / gam1;
341
+ p = pgam1 / gam1;
342
+ b1 = csqs0p0 / pgam1;
343
+ a1 = (csq - gam1 * b1) / pgam1;
344
+ const dela1 = a1 - a1p;
345
+
346
+ if (Math.abs(dela1) < 1.0e-15) break;
347
+ a1p = a1;
348
+ }
349
+
350
+ const oe = [];
351
+ oe[0] = p; // Vinti mean element (conic parameter)
352
+
353
+ let smgam;
354
+ if (gamma < 0) {
355
+ smgam = Math.sqrt(-gamma);
356
+ } else {
357
+ smgam = Math.sqrt(gamma);
358
+ }
359
+
360
+ // Factorize G(sigma) quartic
361
+ let s1p = 0;
362
+ let px = 0;
363
+ let s1 = 1;
364
+
365
+ for (let icg = 1; icg <= 5; icg++) {
366
+ const p0s1 = p0 * s1;
367
+ q1 = -csgam0 / p0s1;
368
+ p1 = 2 * delta / p0s1 - 2 * q1 * px;
369
+ px = delta / p0s1 - s0 * p1 / (2 * s1);
370
+ s1 = (pcsgam0 - s0 * p0 * q1) / ((1 - 2 * px * p1) * p0);
371
+ const dels1 = s1 - s1p;
372
+
373
+ if (Math.abs(dels1) < 1.0e-15) break;
374
+ s1p = s1;
375
+ }
376
+
377
+ // ========== STEP 4: Initialize R and N integral coefficients ==========
378
+ const gams3 = gamma * smgam;
379
+ const s = s0 / s1;
380
+ const pxs = px * px + s;
381
+ oe[2] = pxs; // Vinti mean element (sin^2(I))
382
+
383
+ let q;
384
+ if (pxs < 0) {
385
+ q = 0;
386
+ } else {
387
+ q = Math.sqrt(pxs);
388
+ }
389
+
390
+ let xinc = Math.asin(q);
391
+
392
+ // Check inclination quadrant - CRITICAL FIX from C#
393
+ if (alph3 * Math.cos(xinc) < 0) xinc = pi - xinc;
394
+
395
+ const q2 = q * q;
396
+ const q4 = q2 * q2;
397
+ const betad = 1 + p1 * px - q1 * px * px - q1 * q2;
398
+ const beta = (p1 - 2 * q1 * px) / betad;
399
+ const betasq = beta * beta;
400
+ const g = -beta / (1 + Math.sqrt(1 - betasq * q2));
401
+ const a = px + g * q2;
402
+ const b = 1 + g * px;
403
+
404
+ const g2 = g * g;
405
+ const asq = a * a;
406
+ const bsq = b * b;
407
+ const ab = a * b;
408
+ const d5 = 1 + p1 * a - q1 * asq;
409
+ const xm = (q1 * bsq - p1 * b * g - g2) / d5;
410
+ const xk1 = xm * q2;
411
+ const d1 = Math.sqrt((1 - g2 * q2) / (s1 * d5));
412
+ const ecc2 = 1 + p * gamma;
413
+ const ecc = Math.sqrt(ecc2);
414
+ oe[1] = ecc; // Vinti mean element (eccentricity)
415
+ const rho1 = p / (1 + ecc);
416
+
417
+ // Coefficients for R-integrals
418
+ // A0 = 1, A1 from factorization, A2 to A6
419
+ const a1sq = a1 * a1;
420
+ const b1sq = b1 * b1;
421
+
422
+ const a2 = (3 * a1sq - b1) / 2;
423
+ const a3 = 2.5 * a1sq * a1 - 1.50 * a1 * b1;
424
+ const a4 = 0.375 * (b1sq - 10 * a1sq * b1);
425
+ const a5 = 1.875 * a1 * b1sq;
426
+ const a6 = -0.3125 * b1sq * b1;
427
+
428
+ // W1, W2 ... W6 for the R-integrals
429
+ const e3 = ecc2 * ecc;
430
+ const e4 = ecc2 * ecc2;
431
+ const e5 = e3 * ecc2;
432
+ const e6 = e3 * e3;
433
+
434
+ const psq = p * p;
435
+
436
+ const p3 = psq * p;
437
+ const p4 = psq * psq;
438
+ const p5 = p3 * psq;
439
+ const p6 = p4 * psq;
440
+
441
+ const x21 = 1 / p;
442
+ const x22 = ecc / p;
443
+
444
+ const x33 = 0.5 * ecc2 / psq;
445
+ const x32 = 2 * ecc / psq;
446
+ const x31 = 1 / psq + x33;
447
+
448
+ const x44 = e3 / (3 * p3);
449
+ const x43 = 1.5 * ecc2 / p3;
450
+ const x42 = 3 * ecc / p3 + 2 * x44;
451
+ const x41 = 1 / p3 + x43;
452
+
453
+ const x55 = x33 * x33;
454
+ const x54 = 4 * x44 * x21;
455
+ const x53 = 3 * ecc2 / p4 + 1.5 * x55;
456
+ const x52 = 4 * ecc / p4 + 2 * x54;
457
+ const x51 = 1 / p4 + x53;
458
+
459
+ const x66 = 0.2 * e5 / p5;
460
+ const x65 = 1.25 * e4 / p5;
461
+ const x64 = 10 * e3 / (3 * p5) + 4 * x66 / 3;
462
+ const x63 = 5 * ecc2 / p5 + 1.5 * x65;
463
+ const x62 = 5 * ecc / p5 + 2 * x64;
464
+ const x61 = 1 / p5 + x63;
465
+
466
+ const x77 = e6 / (6 * p6);
467
+ const x76 = 1.2 * e5 / p6;
468
+ const x75 = 3.75 * e4 / p6 + 1.25 * x77;
469
+ const x74 = 20 * e3 / (3 * p6) + x76 / 0.75;
470
+ const x73 = 7.5 * ecc2 / p6 + 1.5 * x75;
471
+ const x72 = 6 * ecc / p6 + 2 * x74;
472
+ const x71 = 1 / p6 + x73;
473
+
474
+ const gg1si = 1.0 / Math.sqrt(gam1);
475
+ const gg1psi = gg1si / Math.sqrt(p);
476
+
477
+ // R1 coefficients
478
+ const cr11 = (rho1 + a1) * gg1si;
479
+ const cr12 = ecc * gg1si;
480
+ const cr13 = a2 * gg1psi;
481
+ const cr14 = a3 * gg1psi;
482
+ const cr15 = a4 * gg1psi;
483
+ const cr16 = a5 * gg1psi;
484
+ const cr17 = a6 * gg1psi;
485
+
486
+ // R2 coefficients
487
+ const cr21 = Math.sqrt(p0 / pgam1);
488
+ const cr22 = a1 * cr21;
489
+ const cr23 = a2 * cr21;
490
+ const cr24 = a3 * cr21;
491
+ const cr25 = a4 * cr21;
492
+ const cr26 = a5 * cr21;
493
+ const cr27 = a6 * cr21;
494
+
495
+ // R3 coefficients
496
+ const cr31 = alph3 * gg1psi;
497
+ const cr32 = a1 * cr31;
498
+ const cr33 = (a2 - csq) * cr31;
499
+ const cr34 = (a3 - a1 * csq) * cr31;
500
+ const cr35 = a4 * cr31 - csq * cr33;
501
+
502
+ // Coefficients for N-integrals
503
+
504
+ // N3 coefficients
505
+ const bmg = b - g;
506
+ const bpg = b + g;
507
+ const d1ma = 1 - a;
508
+ const d1pa = 1 + a;
509
+ const beta1 = bmg / d1ma;
510
+ const beta2 = -bpg / d1pa;
511
+ const b12 = beta1 * beta1;
512
+ const b13 = b12 * beta1;
513
+ const b22 = beta2 * beta2;
514
+ const b23 = b22 * beta2;
515
+ let xmm1 = Math.sqrt(1 - b12 * q2);
516
+
517
+ if (xmm1 * alph3 < 0) xmm1 = -xmm1;
518
+
519
+ let xmm2 = Math.sqrt(1 - b22 * q2);
520
+
521
+ if (xmm2 * alph3 < 0) xmm2 = -xmm2;
522
+
523
+ const d2 = delta / (p0 * (s1 - s0 * q1));
524
+ const d3 = q1 + 2 * p1 * d2;
525
+ const d4 = d1 * alph3 / (2 * alph2);
526
+
527
+ const d1md3 = 1 - d3;
528
+ const bmag = b - a * g;
529
+
530
+ const d10 = bmag / bmg * Math.sqrt(d1md3 / (d5 * (1 - xm / b12) * (1 - 2 * d2)));
531
+ const d20 = bmag / bpg * Math.sqrt(d1md3 / (d5 * (1 - xm / b22) * (1 + 2 * d2)));
532
+
533
+ const dd2 = xm / 2;
534
+ const dd3 = dd2 * g;
535
+ const dd4 = 1.5 * dd2 * dd2;
536
+ const dd5 = dd4 * g;
537
+ const dd6 = dd2 * dd4 / 0.6;
538
+
539
+ const c15 = dd6 / (b13 * b13);
540
+ const c14 = c15 + dd5 / (b12 * b13);
541
+ const c13 = c14 + dd4 / (b12 * b12);
542
+ const c12 = c13 + dd3 / b13;
543
+ const c11 = c12 + dd2 / b12;
544
+ const c10 = c11 + g / beta1;
545
+
546
+ const c25 = dd6 / (b23 * b23);
547
+ const c24 = c25 + dd5 / (b22 * b23);
548
+ const c23 = c24 + dd4 / (b22 * b22);
549
+ const c22 = c23 + dd3 / b23;
550
+ const c21 = c22 + dd2 / b22;
551
+ const c20 = c21 + g / beta2;
552
+
553
+ const b1q = beta1 * q;
554
+ const b1q2 = b1q * b1q;
555
+ const b1q4 = b1q2 * b1q2;
556
+ const b2q = beta2 * q;
557
+ const b2q2 = b2q * b2q;
558
+ const b2q4 = b2q2 * b2q2;
559
+
560
+ // N1 and N2 coefficients
561
+ const xk12 = xk1 * xk1;
562
+ const xk13 = xk12 * xk1;
563
+
564
+ // Byrd and Friedman formula for elliptic integral
565
+ const sq = xk1 / 16 + xk12 / 32 + 21 * xk13 / 1024;
566
+ const sq2 = sq * sq;
567
+ const sq3 = sq2 * sq;
568
+
569
+ const ucf1 = 2 * sq / (1 + sq2);
570
+ const ucf2 = sq2 / (1 + sq2 * sq2);
571
+ const ucf3 = 2.0 * sq3 / (3.0 * (1 + sq3 * sq3));
572
+
573
+ const denystt = 1 + sq + sq;
574
+ const denyst = denystt * denystt * d1;
575
+
576
+ // N1 coefficients
577
+ const d1a2 = d1 / alph2;
578
+ const cn11 = d1a2 * asq;
579
+ const cn12 = d1a2 * 2 * ab * q;
580
+ const cn13 = d1a2 * (bsq - 4 * ab * g) * q2;
581
+ const cn14 = d1a2 * (xm * ab - 2 * bsq * g) * q2 * q;
582
+ const cn15 = d1a2 * (3 * bsq * g2 + xm * bsq / 2) * q4;
583
+ const cn16 = -d1a2 * xm * bsq * g * q4 * q;
584
+ const cn17 = 0.375 * d1a2 * xm * xm * bsq * q4 * q2;
585
+
586
+ // N2 coefficients
587
+ const cn22 = 0.5 * xk1 * d1;
588
+ const cn24 = 0.375 * xk12 * d1;
589
+ const cn26 = 0.3125 * xk13 * d1;
590
+
591
+ // N3 coefficients
592
+ const d41ma = d4 / d1ma;
593
+ const d41pa = d4 / d1pa;
594
+
595
+ const cn31 = -d41ma * c10 - d41pa * c20;
596
+ const cn32 = -d41ma * c11 * b1q - d41pa * c21 * b2q;
597
+ const cn33 = -d41ma * c12 * b1q2 - d41pa * c22 * b2q2;
598
+ const cn34 = -d41ma * c13 * b1q2 * b1q - d41pa * c23 * b2q2 * b2q;
599
+ const cn35 = -d41ma * c14 * b1q4 - d41pa * c24 * b2q4;
600
+ const cn36 = -d41ma * c15 * b1q4 * b1q - d41pa * c25 * b2q4 * b2q;
601
+
602
+ // Avoid singularity at zero inclination
603
+ let u;
604
+ if (q === 0.0) {
605
+ u = 0;
606
+ } else {
607
+ const d5sq = Math.sqrt(s1 * p0 * (1 + p1 * sigma0 - q1 * sigma0 * sigma0));
608
+ const utn = (sigma0 - a) * d5sq;
609
+ const utd = sqrg * Math.sqrt(1 - g2 * q2);
610
+
611
+ u = Math.atan2(utn, utd);
612
+ }
613
+
614
+ // Calculate Tk = tk, k = 0, T0 = t0 = u. Here k = 1, ... ,6
615
+ let csu = Math.cos(u);
616
+ let snu = Math.sin(u);
617
+ let snu2 = snu * snu;
618
+ let snu4 = snu2 * snu2;
619
+
620
+ let t1 = 1 - csu;
621
+ let t2 = (u - csu * snu) / 2;
622
+ let t3 = (2 * t1 - csu * snu2) / 3;
623
+ let t4 = (3 * t2 - csu * snu2 * snu) / 4;
624
+ let t5 = (4 * t3 - csu * snu4) / 5;
625
+ let t6 = (5 * t4 - csu * snu4 * snu) / 6;
626
+
627
+ // Compute xhat at initial time
628
+ let xhat;
629
+ if (gamma < -1.0e-14) { // Ellipse
630
+ if (Math.abs(ecc) < 1.0e-10
631
+ || Math.abs(sqrf) < 1.0e-10
632
+ || Math.abs(rho0 - rho1) < 1e-5) {
633
+ xhat = 0;
634
+ } else {
635
+ const shat = Math.abs(sqrf) / sqrf * Math.sqrt(gamma * rho02 + 2 * rho0 - p) / ecc;
636
+ const cacs = (rho0 - rho1) * gamma / ecc + 1;
637
+ let acs = Math.atan2(shat * smgam, cacs);
638
+
639
+ if (acs < 0) acs = twopi + acs;
640
+
641
+ xhat = acs / smgam;
642
+ }
643
+ } else if (gamma > 1.0e-14) { // Hyperbola
644
+ if (Math.abs(ecc) < 1.0e-10 || Math.abs(sqrf) < 1.0e-10) {
645
+ xhat = 0.0;
646
+ } else {
647
+ const chat = (rho0 - rho1) / ecc;
648
+ const zz = gamma * chat + 1;
649
+ xhat = Math.log(zz + Math.sqrt(zz * zz - 1)) / Math.sqrt(gamma);
650
+
651
+ if (sqrf < 0) xhat = -xhat;
652
+ }
653
+ } else { // Parabola
654
+ xhat = rrd;
655
+ }
656
+
657
+ // ========== STEP 5: Last three Jacobi constants ==========
658
+ // Determine true anomaly from xhat at t0
659
+ let eca = smgam * xhat;
660
+ let tra; let chat; let shat;
661
+
662
+ if (gamma < -1.0e-14) { // Ellipse
663
+ sneca = Math.sin(eca);
664
+ cneca = Math.cos(eca);
665
+ const s1mes = Math.sqrt(1 - ecc2);
666
+ const temp3 = 2 * Math.atan(ecc * sneca / (1 + s1mes - ecc * cneca));
667
+ tra = eca + temp3;
668
+ } else if (gamma > 1.0e-14) { // Hyperbola
669
+ sneca = Math.sinh(eca);
670
+ cneca = Math.cosh(eca);
671
+
672
+ chat = (cneca - 1) / gamma;
673
+ shat = sneca / smgam;
674
+ tra = Math.atan2(Math.sqrt(p) * shat, rho1 - chat);
675
+ } else { // Parabola
676
+ tra = 2 * Math.atan(xhat / Math.sqrt(p));
677
+ }
678
+
679
+ let snw = Math.sin(tra);
680
+ let cnw = Math.cos(tra);
681
+ let ecccnw = ecc * cnw;
682
+
683
+ // const dw1dx = (1 + ecccnw) / p;
684
+ // const dw2dx = dw1dx * dw1dx; same case. Astrolibrary does not use this instance of dw2dx here but does so later in the code after it is redeclared
685
+
686
+ const v3 = snw * cnw;
687
+ const v4 = v3 * cnw;
688
+ const v5 = v4 * cnw;
689
+ const v6 = v5 * cnw;
690
+ const v7 = v6 * cnw;
691
+
692
+ let w1 = x21 * tra + x22 * snw;
693
+ let w2 = x31 * tra + x32 * snw + x33 * v3;
694
+ let w3 = x41 * tra + x42 * snw + x43 * v3 + x44 * v4;
695
+ let w4 = x51 * tra + x52 * snw + x53 * v3 + x54 * v4 + x55 * v5;
696
+ let w5 = x61 * tra + x62 * snw + x63 * v3 + x64 * v4 + x65 * v5 + x66 * v6;
697
+ let w6 = x71 * tra + x72 * snw + x73 * v3 + x74 * v4 + x75 * v5 + x76 * v6 + x77 * v7;
698
+
699
+ let uhat;
700
+ if (Math.abs(gamma) >= 1.0e-14) { // Circle, ellipse, hyperbola
701
+ chat = (cneca - 1) / gamma;
702
+ uhat = (sneca - eca) / gams3;
703
+ } else { // Parabola
704
+ chat = xhat * xhat / 2;
705
+ uhat = xhat * xhat * xhat / 6;
706
+ }
707
+
708
+ const r1 = cr11 * xhat + cr12 * uhat + cr13 * tra
709
+ + cr14 * w1 + cr15 * w2 + cr16 * w3 + cr17 * w4;
710
+ const r2 = cr21 * tra + cr22 * w1 + cr23 * w2 + cr24
711
+ * w3 + cr25 * w4 + cr26 * w5 + cr27 * w6;
712
+
713
+ let sstar = Math.sin(0.5 * u);
714
+ let cstar = Math.cos(0.5 * u);
715
+ let cb1qs = cstar - b1q * sstar;
716
+ let psi1 = Math.atan(xmm1 * sstar / cb1qs);
717
+
718
+ if (cb1qs * Math.cos(psi1) < 0.0) psi1 = pi + psi1;
719
+
720
+ let cb2qs = cstar - b2q * sstar;
721
+ let psi2 = Math.atan(xmm2 * sstar / cb2qs);
722
+
723
+ if (cb2qs * Math.cos(psi2) < 0.0) psi2 = pi + psi2;
724
+
725
+ let r3 = cr31 * w2 + cr32 * w3 + cr33 * w4 + cr34 * w5 + cr35 * w6;
726
+
727
+ let en3 = d10 * psi1 + d20 * psi2 + cn31 * u + cn32
728
+ * t1 + cn33 * t2 + cn34 * t3 + cn35 * t4 + cn36 * t5;
729
+ const en1 = cn11 * u + cn12 * t1 + cn13 * t2 + cn14
730
+ * t3 + cn15 * t4 + cn16 * t5 + cn17 * t6;
731
+ const en2 = d1 * u + cn22 * t2 + cn24 * t4 + cn26 * t6;
732
+
733
+ const somega = en2 - r2; // beta2
734
+ const capt = t0 - r1 - csq * en1; // -beta1
735
+ const deltat = tf - capt;
736
+ const comega = alph0 + csq * r3 - en3; // beta3
737
+
738
+ oe[3] = -capt * timeFactor; // Vinti mean element "beta1"
739
+ oe[4] = somega; // Vinti mean element "beta2"
740
+ oe[5] = comega; // Vinti mean element "beta3"
741
+
742
+ // ========== STEP 6: Solve kinematical equations ==========
743
+ // The generalized Kepler equation is solved by iteration
744
+ xhat += xhat0;
745
+
746
+ // Variables already declared earlier, will be reassigned in loop
747
+
748
+ for (let ick = 1; ick <= 10; ick++) {
749
+ eca = smgam * xhat;
750
+
751
+ if (gamma < -1.0e-14) { // Ellipse
752
+ sneca = Math.sin(eca);
753
+ cneca = Math.cos(eca);
754
+ const s1mes = Math.sqrt(1 - ecc2);
755
+ const temp3 = 2 * Math.atan(ecc * sneca / (1 + s1mes - ecc * cneca));
756
+ tra = eca + temp3;
757
+ } else if (gamma > 1.0e-14) { // Hyperbola
758
+ sneca = Math.sinh(eca);
759
+ cneca = Math.cosh(eca);
760
+ chat = (cneca - 1) / gamma;
761
+ shat = sneca / smgam;
762
+ tra = Math.atan2(Math.sqrt(p) * shat, rho1 - chat);
763
+ } else { // Parabola
764
+ tra = 2 * Math.atan(xhat / Math.sqrt(p));
765
+ }
766
+
767
+ snw = Math.sin(tra);
768
+ cnw = Math.cos(tra);
769
+ ecccnw = ecc * cnw;
770
+
771
+ const dwdx = (1 + ecccnw) / Math.sqrt(p);
772
+ const dw1dx = (1 + ecccnw) / p;
773
+ const dw2dx = dw1dx * dw1dx;
774
+ const dw3dx = dw1dx * dw2dx;
775
+ const dw4dx = dw2dx * dw2dx;
776
+
777
+ const v3 = snw * cnw;
778
+ const v4 = v3 * cnw;
779
+ const v5 = v4 * cnw;
780
+ const v6 = v5 * cnw;
781
+ const v7 = v6 * cnw;
782
+
783
+ w1 = x21 * tra + x22 * snw;
784
+ w2 = x31 * tra + x32 * snw + x33 * v3;
785
+ w3 = x41 * tra + x42 * snw + x43 * v3 + x44 * v4;
786
+ w4 = x51 * tra + x52 * snw + x53 * v3 + x54 * v4 + x55 * v5;
787
+ w5 = x61 * tra + x62 * snw + x63 * v3 + x64 * v4 + x65 * v5 + x66 * v6;
788
+ w6 = x71 * tra + x72 * snw + x73 * v3 + x74 * v4 + x75 * v5 + x76 * v6 + x77 * v7;
789
+
790
+ if (Math.abs(gamma) >= 1.0e-14) {
791
+ chat = (cneca - 1) / gamma;
792
+ uhat = (sneca - eca) / gams3;
793
+ } else { // Parabola
794
+ chat = xhat * xhat / 2;
795
+ uhat = xhat * xhat * xhat / 6;
796
+ }
797
+
798
+ const r1 = cr11 * xhat
799
+ + cr12 * uhat
800
+ + cr13 * tra
801
+ + cr14 * w1
802
+ + cr15 * w2
803
+ + cr16 * w3
804
+ + cr17 * w4;
805
+ const r2
806
+ = cr21 * tra
807
+ + cr22 * w1
808
+ + cr23 * w2
809
+ + cr24 * w3
810
+ + cr25 * w4
811
+ + cr26 * w5
812
+ + cr27 * w6;
813
+
814
+ const ystar = (r2 + somega) / denyst;
815
+ u = ystar
816
+ + ucf1 * Math.sin(2 * ystar)
817
+ + ucf2 * Math.sin(4 * ystar)
818
+ + ucf3 * Math.sin(6 * ystar);
819
+
820
+ csu = Math.cos(u);
821
+ snu = Math.sin(u);
822
+ snu2 = snu * snu;
823
+ snu4 = snu2 * snu2;
824
+
825
+ t1 = 1 - csu;
826
+ t2 = (u - csu * snu) / 2;
827
+ t3 = (2 * t1 - csu * snu2) / 3;
828
+ t4 = (3 * t2 - csu * snu2 * snu) / 4;
829
+ t5 = (4 * t3 - csu * snu4) / 5;
830
+ t6 = (5 * t4 - csu * snu4 * snu) / 6;
831
+
832
+ const en1 = cn11 * u
833
+ + cn12 * t1
834
+ + cn13 * t2
835
+ + cn14 * t3
836
+ + cn15 * t4
837
+ + cn16 * t5
838
+ + cn17 * t6;
839
+
840
+ const cn1r1 = csq * en1 + r1;
841
+ const psixhat = cn1r1 - deltat; // Function
842
+
843
+ const dpsx1 = cr13 + cr14 * dw1dx + cr15 * dw2dx + cr16 * dw3dx + cr17 * dw4dx;
844
+ const dpsx = cr11 + cr12 * chat + dpsx1 * dwdx; // 1st derivative
845
+ const delx = psixhat / dpsx;
846
+
847
+ xhat -= delx;
848
+
849
+ if (Math.abs(delx) < 1.0e-14) break;
850
+ }
851
+
852
+ // ========== STEP 7: Final coordinate transformation ==========
853
+ // OSC state vector is (rhof, sigmaf, alphaf, drhof, dsigf, dalphf)
854
+ // ECI state vector is (pf, vf)
855
+
856
+ sstar = Math.sin(0.5 * u);
857
+ cstar = Math.cos(0.5 * u);
858
+ cb1qs = cstar - b1q * sstar;
859
+ psi1 = Math.atan(xmm1 * sstar / cb1qs);
860
+
861
+ if (cb1qs * Math.cos(psi1) < 0) psi1 = pi + psi1;
862
+
863
+ cb2qs = cstar - b2q * sstar;
864
+ psi2 = Math.atan(xmm2 * sstar / cb2qs);
865
+
866
+ if (cb2qs * Math.cos(psi2) < 0) psi2 = pi + psi2;
867
+
868
+ r3 = cr31 * w2 + cr32 * w3 + cr33 * w4 + cr34 * w5 + cr35 * w6;
869
+ en3 = d10 * psi1
870
+ + d20 * psi2
871
+ + cn31 * u
872
+ + cn32 * t1
873
+ + cn33 * t2
874
+ + cn34 * t3
875
+ + cn35 * t4
876
+ + cn36 * t5;
877
+ const qsnu = q * snu;
878
+ const alphaf = comega - csq * r3 + en3;
879
+ const rhof = rho1 + ecc * chat;
880
+ const sigmaf = (a + b * qsnu) / (1 + g * qsnu);
881
+
882
+ const rhof2 = rhof * rhof;
883
+ const sigf2 = sigmaf * sigmaf;
884
+
885
+ const rhocsq = rhof2 + csq;
886
+ const df = Math.sqrt(rhocsq * (1 - sigf2));
887
+ const snalp = Math.sin(alphaf);
888
+ const csalp = Math.cos(alphaf);
889
+
890
+ const pf = new NodeVector3D(df * csalp, df * snalp, rhof * sigmaf - delta);
891
+
892
+ shat = xhat + gamma * uhat;
893
+ rcs = rhof2 + csq * sigf2;
894
+
895
+ const rhoqd = rhof2 - 2 * a1 * rhof + b1;
896
+ const drhofp = Math.sqrt(gam1 * rhoqd) / rcs;
897
+ const drhof = ecc * shat * drhofp;
898
+ const temp12 = (1 + p1 * sigmaf - q1 * sigf2) * (1 - g2 * q2) * s1;
899
+ const dsigf = alph2 * q * Math.sqrt(temp12) / rcs * csu / (1 + g * qsnu);
900
+ const temp20 = (1 - sigf2) * (rhof * drhof);
901
+ const temp21 = rhocsq * sigmaf * dsigf;
902
+ const dd = (temp20 - temp21) / df;
903
+ const dalphf = alph3 / (df * df);
904
+
905
+ const vf = new NodeVector3D(
906
+ dd * csalp - pf.y * dalphf,
907
+ dd * snalp + pf.x * dalphf,
908
+ sigmaf * drhof + rhof * dsigf,
909
+ );
910
+
911
+ // Change from astronomical units to SI units - using pious-squid Vector3D scale method
912
+ x1.position = pf.scale(ae);
913
+ x1.velocity = vf.scale(ve);
914
+
915
+ return x1;
916
+ }
917
+ }
918
+
919
+ /**
920
+ * Main Ballistic Propagator class
921
+ * Refactored to use existing constants and Vector3D class
922
+ */
923
+ class BallisticPropagator {
924
+ constructor(baseStateVector, options = {}) {
925
+ this.baseStateVector = baseStateVector;
926
+ this.planetEquatorialRadiusKm
927
+ = options.equatorialRadius || EarthConstants.EquatorialRadiusKm;
928
+ this.mu = options.mu || EarthConstants.Mu;
929
+ this.j2 = options.j2 || EarthConstants.J2;
930
+ this.j3 = options.j3 || EarthConstants.J3;
931
+ this.referenceFrame = options.referenceFrame || "J2000";
932
+ }
933
+
934
+ /**
935
+ * Propagate state vector to target time
936
+ * @param {Date|number} targetTime - Target time as Date or seconds from epoch
937
+ * @return {Object} Propagated state with position and velocity
938
+ */
939
+ propagate(targetTime) {
940
+ let spanSeconds;
941
+
942
+ if (targetTime instanceof Date) {
943
+ spanSeconds = (targetTime - this.baseStateVector.epochUtc) / 1000;
944
+ } else {
945
+ spanSeconds = targetTime;
946
+ }
947
+
948
+ // Create PosVelVec using pious-squid Vector3D
949
+ // Handle both lowercase and uppercase property names, but check for undefined specifically
950
+ const getComponent = (vector, lowerProp, upperProp) => {
951
+ return vector[lowerProp] !== undefined ? vector[lowerProp] : vector[upperProp];
952
+ };
953
+
954
+ const x0 = new PosVelVec(
955
+ new NodeVector3D(
956
+ getComponent(this.baseStateVector.position, "x", "X"),
957
+ getComponent(this.baseStateVector.position, "y", "Y"),
958
+ getComponent(this.baseStateVector.position, "z", "Z"),
959
+ ),
960
+ new NodeVector3D(
961
+ getComponent(this.baseStateVector.velocity, "x", "X"),
962
+ getComponent(this.baseStateVector.velocity, "y", "Y"),
963
+ getComponent(this.baseStateVector.velocity, "z", "Z"),
964
+ ),
965
+ );
966
+
967
+ const x1 = BallisticPropagatorUtils.ballisticProp(
968
+ x0,
969
+ spanSeconds,
970
+ this.planetEquatorialRadiusKm,
971
+ this.mu,
972
+ this.j2,
973
+ this.j3,
974
+ );
975
+
976
+ return {
977
+ position: x1.position,
978
+ velocity: x1.velocity,
979
+ epochUtc: targetTime instanceof Date
980
+ ? targetTime
981
+ : new Date(this.baseStateVector.epochUtc.getTime() + spanSeconds * 1000),
982
+ referenceFrame: this.referenceFrame,
983
+ };
984
+ }
985
+ }
986
+
987
+ // Export for Node.js
988
+ export {BallisticPropagator, BallisticPropagatorUtils, PosVelVec, EarthConstants};
989
+
990
+ /**
991
+ * REFACTORING SUMMARY:
992
+ * ===================
993
+ *
994
+ * REPLACED IMPLEMENTATIONS:
995
+ * 1. EarthConstants - Now uses values from ./constants.js:
996
+ * - EquatorialRadiusKm: WGS84_EARTH_EQUATORIAL_RADIUS_KM (6378.137 km)
997
+ * - Mu: MU converted from m³/s² to km³/s² (398600.4418)
998
+ * - J2 and J3: Kept original values as they're specific to Vinti propagator
999
+ *
1000
+ * 2. Vector3D - Now imports and uses pious-squid's Vector3D class:
1001
+ * - Uses magnitude() instead of length property
1002
+ * - Uses scale() instead of scaleBy() method
1003
+ * - Uses add() and dot() methods directly from pious-squid
1004
+ * - All vector operations now use the robust pious-squid implementation
1005
+ *
1006
+ * 3. Mathematical operations:
1007
+ * - Vector magnitude calculations use pious-squid's magnitude() method
1008
+ * - Vector scaling uses pious-squid's scale() method
1009
+ * - Vector addition uses pious-squid's add() method
1010
+ * - Dot product uses pious-squid's dot() method
1011
+ *
1012
+ * BENEFITS:
1013
+ * - Reduced code duplication (removed ~100 lines of custom Vector3D implementation)
1014
+ * - Uses standardized, well-tested vector operations from pious-squid
1015
+ * - Uses standardized Earth constants from node-common constants.js
1016
+ * - Maintains 100% functional compatibility with original implementation
1017
+ * - Better maintainability through shared library usage
1018
+ *
1019
+ * EXAMPLE USAGE (unchanged):
1020
+ * ==========================
1021
+ *
1022
+ * // ISS-like circular orbit
1023
+ * const initialState = {
1024
+ * position: new Vector3D(6778.137, 0, 0), // km
1025
+ * velocity: new Vector3D(0, 7.66779, 0), // km/s
1026
+ * epochUtc: new Date('2025-01-01T00:00:00Z'),
1027
+ * referenceFrame: 'J2000'
1028
+ * };
1029
+ *
1030
+ * const propagator = new BallisticPropagator(initialState);
1031
+ *
1032
+ * // Propagate for 5 minutes (300 seconds)
1033
+ * const result = propagator.propagate(300);
1034
+ *
1035
+ * console.log('Position after 300s:', result.position);
1036
+ * console.log('Velocity after 300s:', result.velocity);
1037
+ */