@johnmorrisdotca/uchusen-rockets 0.19.1 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/LICENSES/Orbiter-MIT.txt +21 -0
- package/NOTICE.md +37 -1
- package/README.md +13 -6
- package/dist/assets/falcon-heavy-test-flight-apart.painted.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-apart.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-apart.thick.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-car.glb +0 -0
- package/dist/assets/falcon-heavy-test-flight-car.painted.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-car.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-car.thick.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-centre.glb +0 -0
- package/dist/assets/falcon-heavy-test-flight-centre.painted.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-centre.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-centre.thick.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-side-left.glb +0 -0
- package/dist/assets/falcon-heavy-test-flight-side-left.painted.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-side-left.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-side-left.thick.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-side-right.glb +0 -0
- package/dist/assets/falcon-heavy-test-flight-side-right.painted.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-side-right.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-side-right.thick.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-starman.glb +0 -0
- package/dist/assets/falcon-heavy-test-flight-starman.painted.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-starman.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-starman.thick.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight.glb +0 -0
- package/dist/assets/falcon-heavy-test-flight.painted.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight.thick.svg +1 -0
- package/dist/assets/models/roadster-first-generation-car.glb +0 -0
- package/dist/assets/models/roadster-second-generation-2-car.glb +0 -0
- package/dist/assets/models/roadster-second-generation-car.glb +0 -0
- package/dist/assets/roadster-first-generation-apart.painted.svg +1 -0
- package/dist/assets/roadster-first-generation-apart.svg +1 -0
- package/dist/assets/roadster-first-generation-apart.thick.svg +1 -0
- package/dist/assets/roadster-first-generation-car.glb +0 -0
- package/dist/assets/roadster-first-generation-car.painted.svg +1 -0
- package/dist/assets/roadster-first-generation-car.svg +1 -0
- package/dist/assets/roadster-first-generation-car.thick.svg +1 -0
- package/dist/assets/roadster-first-generation-starman.glb +0 -0
- package/dist/assets/roadster-first-generation-starman.painted.svg +1 -0
- package/dist/assets/roadster-first-generation-starman.svg +1 -0
- package/dist/assets/roadster-first-generation-starman.thick.svg +1 -0
- package/dist/assets/roadster-first-generation.glb +0 -0
- package/dist/assets/roadster-first-generation.painted.svg +1 -0
- package/dist/assets/roadster-first-generation.svg +1 -0
- package/dist/assets/roadster-first-generation.thick.svg +1 -0
- package/dist/assets/roadster-second-generation-2-apart.painted.svg +1 -0
- package/dist/assets/roadster-second-generation-2-apart.svg +1 -0
- package/dist/assets/roadster-second-generation-2-apart.thick.svg +1 -0
- package/dist/assets/roadster-second-generation-2-car.glb +0 -0
- package/dist/assets/roadster-second-generation-2-car.painted.svg +1 -0
- package/dist/assets/roadster-second-generation-2-car.svg +1 -0
- package/dist/assets/roadster-second-generation-2-car.thick.svg +1 -0
- package/dist/assets/roadster-second-generation-2-starman.glb +0 -0
- package/dist/assets/roadster-second-generation-2-starman.painted.svg +1 -0
- package/dist/assets/roadster-second-generation-2-starman.svg +1 -0
- package/dist/assets/roadster-second-generation-2-starman.thick.svg +1 -0
- package/dist/assets/roadster-second-generation-2.glb +0 -0
- package/dist/assets/roadster-second-generation-2.painted.svg +1 -0
- package/dist/assets/roadster-second-generation-2.svg +1 -0
- package/dist/assets/roadster-second-generation-2.thick.svg +1 -0
- package/dist/assets/roadster-second-generation-apart.painted.svg +1 -0
- package/dist/assets/roadster-second-generation-apart.svg +1 -0
- package/dist/assets/roadster-second-generation-apart.thick.svg +1 -0
- package/dist/assets/roadster-second-generation-car.glb +0 -0
- package/dist/assets/roadster-second-generation-car.painted.svg +1 -0
- package/dist/assets/roadster-second-generation-car.svg +1 -0
- package/dist/assets/roadster-second-generation-car.thick.svg +1 -0
- package/dist/assets/roadster-second-generation-starman.glb +0 -0
- package/dist/assets/roadster-second-generation-starman.painted.svg +1 -0
- package/dist/assets/roadster-second-generation-starman.svg +1 -0
- package/dist/assets/roadster-second-generation-starman.thick.svg +1 -0
- package/dist/assets/roadster-second-generation.glb +0 -0
- package/dist/assets/roadster-second-generation.painted.svg +1 -0
- package/dist/assets/roadster-second-generation.svg +1 -0
- package/dist/assets/roadster-second-generation.thick.svg +1 -0
- package/dist/data/countries.d.ts +51 -0
- package/dist/data/countries.js +91 -0
- package/dist/data/credits.d.ts +36 -0
- package/dist/data/credits.js +36 -0
- package/dist/data/falcon.js +26 -14
- package/dist/data/makers.js +1 -0
- package/dist/data/roadster.d.ts +13 -0
- package/dist/data/roadster.js +73 -0
- package/dist/data/sources.d.ts +56 -0
- package/dist/data/sources.js +7 -0
- package/dist/element/flag.d.ts +30 -0
- package/dist/element/flag.js +23 -0
- package/dist/element/scene.d.ts +7 -0
- package/dist/element/scene.js +35 -1
- package/dist/element.d.ts +5 -0
- package/dist/element.js +58 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/inspace.js +1 -0
- package/dist/model/registry.js +6 -4
- package/dist/model/roadster.d.ts +31 -0
- package/dist/model/roadster.js +112 -0
- package/dist/parts.d.ts +1 -1
- package/dist/physics/atmosphere.d.ts +182 -0
- package/dist/physics/atmosphere.js +246 -0
- package/dist/physics/attitude.d.ts +342 -0
- package/dist/physics/attitude.js +351 -0
- package/dist/physics/burns.d.ts +194 -0
- package/dist/physics/burns.js +157 -0
- package/dist/physics/elements.d.ts +133 -0
- package/dist/physics/elements.js +239 -0
- package/dist/physics/gravity.d.ts +53 -0
- package/dist/physics/gravity.js +78 -0
- package/dist/physics/index.d.ts +17 -0
- package/dist/physics/index.js +8 -0
- package/dist/physics/propagate.d.ts +110 -0
- package/dist/physics/propagate.js +122 -0
- package/dist/physics/rocket.d.ts +105 -0
- package/dist/physics/rocket.js +102 -0
- package/dist/physics/transfers.d.ts +128 -0
- package/dist/physics/transfers.js +155 -0
- package/dist/physics/vector.d.ts +20 -0
- package/dist/physics/vector.js +9 -0
- package/dist/react.d.ts +1 -1
- package/dist/rockets.d.ts +5 -0
- package/dist/rockets.js +9 -4
- package/dist/sun.d.ts +13 -0
- package/dist/sun.js +25 -0
- package/package.json +11 -1
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
// A craft's attitude: which way it points (a quaternion) and how it turns, moved forward in time by Euler's equations, torques from
|
|
2
|
+
// thrusters at points of the craft, and the autopilot's modes: kill the rotation, or turn to and hold a direction. Pure.
|
|
3
|
+
// Ported from Orbiter (https://github.com/orbitersim/orbiter): Euler's equations of RigidBody::EulerInv_full and the gravity-gradient
|
|
4
|
+
// torque of RigidBody::GetTorque (Src/Orbiter/Rigidbody.cpp), the angular half of RigidBody::RK4_LinAng (Src/Orbiter/BodyIntegrator.cpp),
|
|
5
|
+
// the whole of RK4_LinAng for the place and attitude together, a thruster's torque `ref x F` and a group's largest torque (Src/Orbiter/Vessel.cpp, UpdateThrustForces and MaxAngularMoment, summed per axis here), and the
|
|
6
|
+
// navcomp's kill-rotation and turn modes (Src/Orbiter/Vessel.cpp, UpdateThrustForces: 1/dt rate control, a tenth of it to kill the rotation, at most 5 degrees a second, slowing within 20 degrees
|
|
7
|
+
// of the target, each axis held to 40 per cent of its thrusters when turning). Copyright (c) 2000-2026 Martin Schweiger, MIT licence
|
|
8
|
+
// (LICENSES/Orbiter-MIT.txt). Changed in the port: a right-handed frame, so Orbiter's (I w) x w is w x (I w) here and its thruster torque
|
|
9
|
+
// crossp(F, ref) is ref x F; real moments of inertia (kg m^2) and torques (N m) in place of Orbiter's per kilogram; a step turns the
|
|
10
|
+
// quaternion by the exact rotation of its mean rate where Orbiter's Quaternion::Rotate takes a first-order step and renormalises; the turn
|
|
11
|
+
// mode's error is the rotation from the nose to the target (Orbiter's per-axis asin and atan2 are its small-angle form), and the roll is
|
|
12
|
+
// held to a chosen roof direction or damped, where Orbiter levels it against the radial direction.
|
|
13
|
+
import { add, addScaled, cross, dot, length, scale, sub } from "./vector.js";
|
|
14
|
+
const normalise = (q) => {
|
|
15
|
+
const l = Math.hypot(q[0], q[1], q[2], q[3]);
|
|
16
|
+
return [q[0] / l, q[1] / l, q[2] / l, q[3] / l];
|
|
17
|
+
};
|
|
18
|
+
const multiply = (a, b) => [
|
|
19
|
+
a[0] * b[0] - a[1] * b[1] - a[2] * b[2] - a[3] * b[3],
|
|
20
|
+
a[0] * b[1] + a[1] * b[0] + a[2] * b[3] - a[3] * b[2],
|
|
21
|
+
a[0] * b[2] - a[1] * b[3] + a[2] * b[0] + a[3] * b[1],
|
|
22
|
+
a[0] * b[3] + a[1] * b[2] - a[2] * b[1] + a[3] * b[0],
|
|
23
|
+
];
|
|
24
|
+
/**
|
|
25
|
+
* The quaternion of a turn by an angle about an axis.
|
|
26
|
+
* @param axis The axis, any length but not zero.
|
|
27
|
+
* @param angle The angle, radians, right-handed about the axis.
|
|
28
|
+
* @returns The unit quaternion.
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* import { quatFromAxisAngle, rotateVector } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
32
|
+
*
|
|
33
|
+
* console.log(rotateVector(quatFromAxisAngle([0, 0, 1], Math.PI / 2), [1, 0, 0]).map((x) => x.toFixed(3)));
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export function quatFromAxisAngle(axis, angle) {
|
|
37
|
+
const l = length(axis);
|
|
38
|
+
const s = Math.sin(angle / 2) / l;
|
|
39
|
+
return [Math.cos(angle / 2), axis[0] * s, axis[1] * s, axis[2] * s];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A vector of the craft's frame in the scene's frame.
|
|
43
|
+
* @param q The attitude.
|
|
44
|
+
* @param v The vector in the craft's frame.
|
|
45
|
+
* @returns The same vector in the scene's frame.
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { quatFromAxisAngle, rotateVector } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
49
|
+
*
|
|
50
|
+
* console.log(rotateVector(quatFromAxisAngle([1, 0, 0], Math.PI / 2), [0, 1, 0]).map((x) => x.toFixed(3)));
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export function rotateVector(q, v) {
|
|
54
|
+
const u = [q[1], q[2], q[3]];
|
|
55
|
+
const t = scale(cross(u, v), 2);
|
|
56
|
+
return add(addScaled(v, t, q[0]), cross(u, t));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A vector of the scene's frame in the craft's frame: the inverse of {@link rotateVector}.
|
|
60
|
+
* @param q The attitude.
|
|
61
|
+
* @param v The vector in the scene's frame.
|
|
62
|
+
* @returns The same vector in the craft's frame.
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* import { quatFromAxisAngle, unrotateVector } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
66
|
+
*
|
|
67
|
+
* console.log(unrotateVector(quatFromAxisAngle([1, 0, 0], Math.PI / 2), [0, 0, 1]).map((x) => x.toFixed(3)));
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export const unrotateVector = (q, v) => rotateVector([q[0], -q[1], -q[2], -q[3]], v);
|
|
71
|
+
/**
|
|
72
|
+
* Turns an attitude by a rotation given in the craft's own frame (a rotation vector: its direction the axis, its length the angle in radians), as Orbiter's `Quaternion::Rotate` does with the turn rate times the step.
|
|
73
|
+
* @param q The attitude.
|
|
74
|
+
* @param rotation The rotation, in the craft's frame.
|
|
75
|
+
* @returns The new attitude.
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* import { rotateVector, turnBy } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
79
|
+
*
|
|
80
|
+
* console.log(rotateVector(turnBy([1, 0, 0, 0], [0, 0, Math.PI]), [1, 0, 0]).map((x) => x.toFixed(3)));
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export function turnBy(q, rotation) {
|
|
84
|
+
const angle = length(rotation);
|
|
85
|
+
return angle > 0 ? normalise(multiply(q, quatFromAxisAngle(rotation, angle))) : q;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Euler's equations for a body with principal moments of inertia along its own axes: the angular acceleration a torque gives, `I^-1 (torque - w x I w)` (Orbiter's `RigidBody::EulerInv_full`).
|
|
89
|
+
* @param inertia The principal moments of inertia about the craft's x, y and z axes, kg m^2.
|
|
90
|
+
* @param torque The torque, in the craft's frame, N m.
|
|
91
|
+
* @param omega The turn rate, in the craft's frame, radians a second.
|
|
92
|
+
* @returns The angular acceleration, in the craft's frame, radians a second squared.
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* import { angularAcceleration } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
96
|
+
*
|
|
97
|
+
* console.log(angularAcceleration([1_000, 2_000, 2_500], [0, 0, 50], [0, 0, 0]));
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export function angularAcceleration(inertia, torque, omega) {
|
|
101
|
+
return [
|
|
102
|
+
(torque[0] - (inertia[2] - inertia[1]) * omega[1] * omega[2]) / inertia[0],
|
|
103
|
+
(torque[1] - (inertia[0] - inertia[2]) * omega[2] * omega[0]) / inertia[1],
|
|
104
|
+
(torque[2] - (inertia[1] - inertia[0]) * omega[0] * omega[1]) / inertia[2],
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The torque a body's gravity puts on a long craft (the gravity gradient), `3 gm / r^3 (u x I u)` with `u` the unit vector to the body in the craft's frame (Orbiter's `RigidBody::GetTorque`, without its damping). It turns a long craft to point at the body.
|
|
109
|
+
* @param gm The body's gravitational parameter, m^3/s^2.
|
|
110
|
+
* @param toBody The body's centre from the craft, in the craft's frame, metres.
|
|
111
|
+
* @param inertia The principal moments of inertia, kg m^2.
|
|
112
|
+
* @returns The torque, in the craft's frame, N m.
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* import { gravityGradientTorque } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
116
|
+
*
|
|
117
|
+
* console.log(gravityGradientTorque(3.986004418e14, [-4_800_000, 4_800_000, 0], [100_000, 10_000, 100_000]));
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export function gravityGradientTorque(gm, toBody, inertia) {
|
|
121
|
+
const r = length(toBody);
|
|
122
|
+
const u = scale(toBody, 1 / r);
|
|
123
|
+
return scale(cross(u, [inertia[0] * u[0], inertia[1] * u[1], inertia[2] * u[2]]), (3 * gm) / (r * r * r));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* One step of a craft's turning: the fourth-order Runge-Kutta step Orbiter takes for the angular state (the angular half of `RK4_LinAng`).
|
|
127
|
+
* @param spin The attitude and turn rate now.
|
|
128
|
+
* @param h The step, seconds.
|
|
129
|
+
* @param inertia The principal moments of inertia, kg m^2.
|
|
130
|
+
* @param torque What turns the craft.
|
|
131
|
+
* @returns The attitude and turn rate `h` seconds later.
|
|
132
|
+
* @example
|
|
133
|
+
* ```ts
|
|
134
|
+
* import { stepSpin } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
135
|
+
*
|
|
136
|
+
* const next = stepSpin({ t: 0, q: [1, 0, 0, 0], omega: [0, 0, 0] }, 1, [1_000, 1_000, 1_000], () => [0, 0, 10]);
|
|
137
|
+
* console.log(next.omega[2]);
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export function stepSpin(spin, h, inertia, torque) {
|
|
141
|
+
const next = stepCraft({ ...spin, position: [0, 0, 0], velocity: [0, 0, 0] }, h, { inertia, acceleration: () => [0, 0, 0], torque });
|
|
142
|
+
return { t: next.t, q: next.q, omega: next.omega };
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Turns a craft forward by a span of time, in steps no longer than `options.step`.
|
|
146
|
+
* @param start The attitude and turn rate at the start.
|
|
147
|
+
* @param seconds How long, seconds.
|
|
148
|
+
* @param options The step, the moments of inertia and what turns the craft (no torque when left out).
|
|
149
|
+
* @returns The attitude and turn rate at `start.t + seconds`.
|
|
150
|
+
* @example
|
|
151
|
+
* ```ts
|
|
152
|
+
* import { spinFor } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
153
|
+
*
|
|
154
|
+
* const tumbling = spinFor({ t: 0, q: [1, 0, 0, 0], omega: [0.1, 0.01, 0] }, 60, { step: 0.1, inertia: [1_000, 2_000, 3_000] });
|
|
155
|
+
* console.log(tumbling.omega.map((x) => x.toFixed(4)));
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
export function spinFor(start, seconds, options) {
|
|
159
|
+
if (!(options.step > 0))
|
|
160
|
+
throw new RangeError("step must be a positive number of seconds");
|
|
161
|
+
const count = Math.max(1, Math.ceil(Math.abs(seconds) / options.step - 1e-9));
|
|
162
|
+
const torque = options.torque ?? (() => [0, 0, 0]);
|
|
163
|
+
let spin = start;
|
|
164
|
+
for (let i = 0; i < count; i++)
|
|
165
|
+
spin = stepSpin(spin, seconds / count, options.inertia, torque);
|
|
166
|
+
return { ...spin, t: start.t + seconds };
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* What a set of thrusters does at given levels: the force and the torque on the craft (`position x force` for each, Orbiter's thruster torque) and the propellant they burn.
|
|
170
|
+
* @param thrusters The thrusters.
|
|
171
|
+
* @param levels Each one's level, 0 to 1, in the same order (missing ones are off).
|
|
172
|
+
* @returns The force (N) and torque (N m), in the craft's frame, and the mass flow (kg/s).
|
|
173
|
+
* @example
|
|
174
|
+
* ```ts
|
|
175
|
+
* import { thrusterPush } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
176
|
+
*
|
|
177
|
+
* const pair = [{ position: [1, 0, 0] as const, direction: [0, 1, 0] as const, thrust: 100, exhaustSpeed: 2_800 }, { position: [-1, 0, 0] as const, direction: [0, -1, 0] as const, thrust: 100, exhaustSpeed: 2_800 }];
|
|
178
|
+
* console.log(thrusterPush(pair, [1, 1]));
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
export function thrusterPush(thrusters, levels) {
|
|
182
|
+
let force = [0, 0, 0];
|
|
183
|
+
let torque = [0, 0, 0];
|
|
184
|
+
let massFlow = 0;
|
|
185
|
+
thrusters.forEach((thruster, i) => {
|
|
186
|
+
const level = Math.max(0, Math.min(1, levels[i] ?? 0));
|
|
187
|
+
if (level === 0)
|
|
188
|
+
return;
|
|
189
|
+
const push = scale(thruster.direction, (thruster.thrust * level) / length(thruster.direction));
|
|
190
|
+
force = add(force, push);
|
|
191
|
+
torque = add(torque, cross(thruster.position, push));
|
|
192
|
+
massFlow += (thruster.thrust * level) / thruster.exhaustSpeed;
|
|
193
|
+
});
|
|
194
|
+
return { force, torque, massFlow };
|
|
195
|
+
}
|
|
196
|
+
const groupOf = (torque) => {
|
|
197
|
+
const axis = [0, 1, 2].reduce((best, k) => (Math.abs(torque[k]) > Math.abs(torque[best]) ? k : best), 0);
|
|
198
|
+
return Math.abs(torque[axis]) > 1e-12 ? { axis, sign: torque[axis] > 0 ? 1 : -1 } : null;
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* The torque limits of a set of thrusters. Each thruster belongs to the group of the axis it turns the craft about most, as Orbiter's attitude groups (pitch up and down, yaw left and right, bank left and right) are set up.
|
|
202
|
+
* @param thrusters The thrusters.
|
|
203
|
+
* @returns The largest torque about each axis, each way.
|
|
204
|
+
* @example
|
|
205
|
+
* ```ts
|
|
206
|
+
* import { torqueLimits } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
207
|
+
*
|
|
208
|
+
* const pair = [{ position: [1, 0, 0] as const, direction: [0, 1, 0] as const, thrust: 100, exhaustSpeed: 2_800 }, { position: [-1, 0, 0] as const, direction: [0, -1, 0] as const, thrust: 100, exhaustSpeed: 2_800 }];
|
|
209
|
+
* console.log(torqueLimits(pair));
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
export function torqueLimits(thrusters) {
|
|
213
|
+
const positive = [0, 0, 0];
|
|
214
|
+
const negative = [0, 0, 0];
|
|
215
|
+
for (const thruster of thrusters) {
|
|
216
|
+
const torque = thrusterPush([thruster], [1]).torque;
|
|
217
|
+
const group = groupOf(torque);
|
|
218
|
+
if (group)
|
|
219
|
+
(group.sign > 0 ? positive : negative)[group.axis] += Math.abs(torque[group.axis]);
|
|
220
|
+
}
|
|
221
|
+
return { positive, negative };
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Fires the thrusters for a wanted torque: each attitude group at the level that gives its axis's share, held to full, as Orbiter fires a group at the level its navcomp asks for.
|
|
225
|
+
* @param thrusters The thrusters.
|
|
226
|
+
* @param torque The torque wanted, in the craft's frame, N m.
|
|
227
|
+
* @returns Each thruster's level (0 to 1), and what they do together: the force, the torque (which may differ a little from the one wanted, when a group also turns the craft about another axis) and the mass flow.
|
|
228
|
+
* @example
|
|
229
|
+
* ```ts
|
|
230
|
+
* import { fireFor } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
231
|
+
*
|
|
232
|
+
* const pair = [{ position: [1, 0, 0] as const, direction: [0, 1, 0] as const, thrust: 100, exhaustSpeed: 2_800 }, { position: [-1, 0, 0] as const, direction: [0, -1, 0] as const, thrust: 100, exhaustSpeed: 2_800 }];
|
|
233
|
+
* console.log(fireFor(pair, [0, 0, 50]).levels);
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
export function fireFor(thrusters, torque) {
|
|
237
|
+
const limits = torqueLimits(thrusters);
|
|
238
|
+
const levels = thrusters.map((thruster) => {
|
|
239
|
+
const group = groupOf(thrusterPush([thruster], [1]).torque);
|
|
240
|
+
if (!group || Math.sign(torque[group.axis]) !== group.sign)
|
|
241
|
+
return 0;
|
|
242
|
+
const limit = (group.sign > 0 ? limits.positive : limits.negative)[group.axis];
|
|
243
|
+
return limit > 0 ? Math.min(1, Math.abs(torque[group.axis]) / limit) : 0;
|
|
244
|
+
});
|
|
245
|
+
return { levels, ...thrusterPush(thrusters, levels) };
|
|
246
|
+
}
|
|
247
|
+
const clampAxis = (torque, limits, share) => {
|
|
248
|
+
const one = (k) => Math.max(-share * limits.negative[k], Math.min(share * limits.positive[k], torque[k]));
|
|
249
|
+
return [one(0), one(1), one(2)];
|
|
250
|
+
};
|
|
251
|
+
const perpendicular = (v) => {
|
|
252
|
+
const other = Math.abs(v[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0];
|
|
253
|
+
const p = cross(v, other);
|
|
254
|
+
return scale(p, 1 / length(p));
|
|
255
|
+
};
|
|
256
|
+
/** Orbiter's kill-rotation mode scales each group's level by ten times its largest torque: it asks for a tenth of the torque that would stop the turn in one step. */
|
|
257
|
+
const KILL_SHARE = 0.1;
|
|
258
|
+
/**
|
|
259
|
+
* The autopilot's kill-rotation mode (Orbiter's navcomp): a tenth of the torque that would stop the turning in one control step, `(-I w / dt + w x I w) / 10`, held to what the thrusters can give. Orbiter switches the mode off once the turn rate is below 1e-5 radians a second.
|
|
260
|
+
* @param spin The attitude and turn rate.
|
|
261
|
+
* @param inertia The principal moments of inertia, kg m^2.
|
|
262
|
+
* @param limits What the thrusters can give.
|
|
263
|
+
* @param dt The control step, seconds (the time until the autopilot looks again).
|
|
264
|
+
* @returns The torque to apply, in the craft's frame, N m.
|
|
265
|
+
* @example
|
|
266
|
+
* ```ts
|
|
267
|
+
* import { killRotation } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
268
|
+
*
|
|
269
|
+
* const limits = { positive: [200, 200, 200] as const, negative: [200, 200, 200] as const };
|
|
270
|
+
* console.log(killRotation({ t: 0, q: [1, 0, 0, 0], omega: [0.05, 0, 0] }, [1_000, 1_000, 1_000], limits, 0.1));
|
|
271
|
+
* ```
|
|
272
|
+
*/
|
|
273
|
+
export function killRotation(spin, inertia, limits, dt) {
|
|
274
|
+
const w = spin.omega;
|
|
275
|
+
const iw = [inertia[0] * w[0], inertia[1] * w[1], inertia[2] * w[2]];
|
|
276
|
+
return clampAxis(scale(add(scale(iw, -1 / dt), cross(w, iw)), KILL_SHARE), limits, 1);
|
|
277
|
+
}
|
|
278
|
+
/** Orbiter's turn modes: at most 5 degrees a second, slowing within 20 degrees of the target, each axis held to 40 per cent. */
|
|
279
|
+
const TURN_RATE = (5 * Math.PI) / 180;
|
|
280
|
+
const SLOW_WITHIN = (20 * Math.PI) / 180;
|
|
281
|
+
const TURN_SHARE = 0.4;
|
|
282
|
+
/**
|
|
283
|
+
* The autopilot's turn-and-hold mode (Orbiter's navcomp prograde, retrograde, normal and antinormal modes, for any direction): it asks for a turn rate towards the target of 5 degrees a second, less within 20 degrees of it, and gives the torque that reaches that rate in one control step, each axis held to 40 per cent of its thrusters. With a roof direction it also rolls the craft so that its roof axis points there; without one it stops the roll.
|
|
284
|
+
* @param spin The attitude and turn rate.
|
|
285
|
+
* @param inertia The principal moments of inertia, kg m^2.
|
|
286
|
+
* @param limits What the thrusters can give.
|
|
287
|
+
* @param dt The control step, seconds.
|
|
288
|
+
* @param target The direction to point the nose at, in the scene's frame.
|
|
289
|
+
* @param options The craft's nose and roof axes in its own frame (`[0, 1, 0]` and `[0, 0, 1]` when left out: this package's rockets stand along +y), and the roof's direction in the scene's frame if the roll is to be held.
|
|
290
|
+
* @returns The torque to apply, in the craft's frame, N m.
|
|
291
|
+
* @example
|
|
292
|
+
* ```ts
|
|
293
|
+
* import { holdDirection } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
294
|
+
*
|
|
295
|
+
* const limits = { positive: [200, 200, 200] as const, negative: [200, 200, 200] as const };
|
|
296
|
+
* console.log(holdDirection({ t: 0, q: [1, 0, 0, 0], omega: [0, 0, 0] }, [1_000, 1_000, 1_000], limits, 0.1, [1, 0, 0]));
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
export function holdDirection(spin, inertia, limits, dt, target, options = {}) {
|
|
300
|
+
const nose = scale(options.nose ?? [0, 1, 0], 1 / length(options.nose ?? [0, 1, 0]));
|
|
301
|
+
const towards = unrotateVector(spin.q, target);
|
|
302
|
+
const want = scale(towards, 1 / length(towards));
|
|
303
|
+
const axis = cross(nose, want);
|
|
304
|
+
const angle = Math.atan2(length(axis), dot(nose, want));
|
|
305
|
+
let error = length(axis) > 1e-15 ? scale(axis, angle / length(axis)) : angle > 1 ? scale(perpendicular(nose), Math.PI) : [0, 0, 0];
|
|
306
|
+
const n = nose;
|
|
307
|
+
if (options.roof) {
|
|
308
|
+
const roofAxis = options.roofAxis ?? [0, 0, 1];
|
|
309
|
+
const roofWanted = unrotateVector(spin.q, options.roof);
|
|
310
|
+
const flat = (v) => sub(v, scale(n, dot(v, n)));
|
|
311
|
+
const [have, get] = [flat(roofAxis), flat(roofWanted)];
|
|
312
|
+
if (length(have) > 1e-12 && length(get) > 1e-12)
|
|
313
|
+
error = add(error, scale(n, Math.atan2(dot(cross(have, get), n), dot(have, get))));
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
error = sub(error, scale(n, dot(error, n)));
|
|
317
|
+
}
|
|
318
|
+
const rate = (e) => Math.max(-TURN_RATE, Math.min(TURN_RATE, (TURN_RATE / SLOW_WITHIN) * e));
|
|
319
|
+
const wanted = [rate(error[0]), rate(error[1]), rate(error[2])];
|
|
320
|
+
const torque = (k) => (inertia[k] * (wanted[k] - spin.omega[k])) / dt;
|
|
321
|
+
return clampAxis([torque(0), torque(1), torque(2)], limits, TURN_SHARE);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* One step of a craft's place and attitude together: Orbiter's `RK4_LinAng`, the fourth-order Runge-Kutta step with the position, the speed, the attitude and the turn rate moved as one, so a push along the nose follows the turning nose.
|
|
325
|
+
* @param craft The craft now.
|
|
326
|
+
* @param h The step, seconds.
|
|
327
|
+
* @param forces What moves it.
|
|
328
|
+
* @returns The craft `h` seconds later.
|
|
329
|
+
* @example
|
|
330
|
+
* ```ts
|
|
331
|
+
* import { gravityAt, stepCraft } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
332
|
+
*
|
|
333
|
+
* const next = stepCraft({ t: 0, position: [6_778_000, 0, 0], velocity: [0, 7_669, 0], q: [1, 0, 0, 0], omega: [0, 0, 0.01] }, 1, { inertia: [1_000, 1_000, 1_000], acceleration: (_t, p) => gravityAt(p, [{ gm: 3.986004418e14, position: [0, 0, 0] }]) });
|
|
334
|
+
* console.log(next.t, next.omega);
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
export function stepCraft(craft, h, forces) {
|
|
338
|
+
const { t, position: p, velocity: v, q, omega: w } = craft;
|
|
339
|
+
const torque = forces.torque ?? (() => [0, 0, 0]);
|
|
340
|
+
const moments = (tt, pp, vv, qq, ww) => [forces.acceleration(tt, pp, vv, qq), angularAcceleration(forces.inertia, torque(tt, qq, ww), ww)];
|
|
341
|
+
const h2 = h / 2;
|
|
342
|
+
const [a0, b0] = moments(t, p, v, q, w);
|
|
343
|
+
const [pa, va, qa, wa] = [addScaled(p, v, h2), addScaled(v, a0, h2), turnBy(q, scale(w, h2)), addScaled(w, b0, h2)];
|
|
344
|
+
const [a1, b1] = moments(t + h2, pa, va, qa, wa);
|
|
345
|
+
const [pb, vb, qb, wb] = [addScaled(p, va, h2), addScaled(v, a1, h2), turnBy(q, scale(wa, h2)), addScaled(w, b1, h2)];
|
|
346
|
+
const [a2, b2] = moments(t + h2, pb, vb, qb, wb);
|
|
347
|
+
const [pc, vc, qc, wc] = [addScaled(p, vb, h), addScaled(v, a2, h), turnBy(q, scale(wb, h)), addScaled(w, b2, h)];
|
|
348
|
+
const [a3, b3] = moments(t + h, pc, vc, qc, wc);
|
|
349
|
+
const mean = (x0, x1, x2, x3) => scale(add(add(x0, x3), scale(add(x1, x2), 2)), h / 6);
|
|
350
|
+
return { t: t + h, position: add(p, mean(v, va, vb, vc)), velocity: add(v, mean(a0, a1, a2, a3)), q: turnBy(q, mean(w, wa, wb, wc)), omega: add(w, mean(b0, b1, b2, b3)) };
|
|
351
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import type { StateVector } from "./elements.ts";
|
|
2
|
+
import { type Acceleration, type Method, type State } from "./propagate.ts";
|
|
3
|
+
import { type Engine } from "./rocket.ts";
|
|
4
|
+
import { type Vec3 } from "./vector.ts";
|
|
5
|
+
/**
|
|
6
|
+
* Which way a burn pushes. The named ones follow the craft's motion round the body it orbits: `prograde` along its speed, `retrograde` against it, `normal` along the orbit's angular momentum (to the north of a west-to-east orbit), `antinormal` the other way, `radial-out` away from the body and `radial-in` towards it. A vector is a fixed direction in the frame.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import type { Direction } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
11
|
+
*
|
|
12
|
+
* const raise: Direction = "prograde";
|
|
13
|
+
* const fixed: Direction = [1, 0, 0];
|
|
14
|
+
* console.log(raise, fixed);
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export type Direction = "prograde" | "retrograde" | "normal" | "antinormal" | "radial-out" | "radial-in" | Vec3;
|
|
18
|
+
/**
|
|
19
|
+
* The unit vector a direction points along, for a craft at a position and speed relative to the body it orbits.
|
|
20
|
+
* @param direction The direction.
|
|
21
|
+
* @param position The craft's position from the body's centre, metres.
|
|
22
|
+
* @param velocity The craft's speed relative to the body, metres a second.
|
|
23
|
+
* @returns A unit vector in the frame.
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { directionOf } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
27
|
+
*
|
|
28
|
+
* console.log(directionOf("normal", [7_000_000, 0, 0], [0, 7_500, 0]));
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function directionOf(direction: Direction, position: Vec3, velocity: Vec3): Vec3;
|
|
32
|
+
/**
|
|
33
|
+
* Where the body the craft orbits is, and how fast it goes, at a time: the named directions are taken from it. Left out, it stands still at the frame's origin.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* import type { Centre } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
38
|
+
*
|
|
39
|
+
* const earthAtRest: Centre = () => ({ position: [0, 0, 0], velocity: [0, 0, 0] });
|
|
40
|
+
* console.log(earthAtRest(0).position);
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export type Centre = (t: number) => StateVector;
|
|
44
|
+
/**
|
|
45
|
+
* A change of speed all at once (an impulsive burn), as a planning tool treats a short burn.
|
|
46
|
+
* @param state The craft before the burn.
|
|
47
|
+
* @param dv The change of speed, metres a second.
|
|
48
|
+
* @param direction Which way.
|
|
49
|
+
* @param centre Where the body the craft orbits is; left out, at the origin and still.
|
|
50
|
+
* @returns The craft just after: the same place and time, the new speed.
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* import { elementsOf, impulse } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
54
|
+
*
|
|
55
|
+
* const after = impulse({ t: 0, position: [6_778_000, 0, 0], velocity: [0, 7_669, 0] }, 2_400, "prograde");
|
|
56
|
+
* console.log(((elementsOf(3.986004418e14, after.position, after.velocity).apoapsis - 6_378_137) / 1000).toFixed(0), "km apogee");
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare function impulse(state: State, dv: number, direction: Direction, centre?: Centre): State;
|
|
60
|
+
/**
|
|
61
|
+
* When to start a burn so that it is centred on a planned point, as Orbiter's plane-alignment display times one: the point's time less half the burn.
|
|
62
|
+
* @param centreTime When the planned point is reached, seconds.
|
|
63
|
+
* @param seconds How long the burn lasts, seconds.
|
|
64
|
+
* @returns When to start, seconds.
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* import { burnStart } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
68
|
+
*
|
|
69
|
+
* console.log(burnStart(1_000, 120));
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export declare const burnStart: (centreTime: number, seconds: number) => number;
|
|
73
|
+
/**
|
|
74
|
+
* One burn of an engine at a steady throttle: for a time, or until it has given a change of speed (the rocket equation says how long).
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* import { exhaustSpeedOf, type Burn } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
79
|
+
*
|
|
80
|
+
* const circularise: Burn = { engine: { thrust: 91_189, exhaustSpeed: exhaustSpeedOf(314) }, direction: "prograde", dv: 1_500 };
|
|
81
|
+
* console.log(circularise.dv);
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export interface Burn {
|
|
85
|
+
engine: Engine;
|
|
86
|
+
direction: Direction;
|
|
87
|
+
/** 0 to 1; 1 when left out. */
|
|
88
|
+
throttle?: number;
|
|
89
|
+
/** How long, seconds. Give this or `dv`. */
|
|
90
|
+
seconds?: number;
|
|
91
|
+
/** The change of speed the engine gives, metres a second (not counting gravity's pull meanwhile). */
|
|
92
|
+
dv?: number;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* How a burn or a plan is flown: the step, the gravity of the scene, the body the directions are taken from, and the method.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* import { gravityAt, type FlyOptions } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
100
|
+
*
|
|
101
|
+
* const options: FlyOptions = { step: 5, gravity: (_t, p) => gravityAt(p, [{ gm: 3.986004418e14, position: [0, 0, 0] }]) };
|
|
102
|
+
* console.log(options.step);
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export interface FlyOptions {
|
|
106
|
+
/** The longest step, seconds. */
|
|
107
|
+
step: number;
|
|
108
|
+
/** Gravity (and anything else that pulls) at a time, place and speed. */
|
|
109
|
+
gravity: Acceleration;
|
|
110
|
+
/** The body the craft orbits, for the named directions; at the origin and still when left out. */
|
|
111
|
+
centre?: Centre;
|
|
112
|
+
/** The craft's mass with no propellant, kilograms: a burn that would go below it stops there. 0 when left out, and then a burn that would use all the mass is refused, as its change of speed has no end. */
|
|
113
|
+
dryMass?: number;
|
|
114
|
+
/** For the burns; `rk4` when left out, as a push along the speed depends on the speed. */
|
|
115
|
+
burnMethod?: Method;
|
|
116
|
+
/** For the coasts between burns; `sy4` when left out. */
|
|
117
|
+
coastMethod?: Method;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* What a burn did: where the craft is at its end, its mass, how long it burned, the speed the engine gave and the propellant used, and whether it ran out.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* import { exhaustSpeedOf, flyBurn, gravityAt, type BurnResult } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
125
|
+
*
|
|
126
|
+
* const result: BurnResult = flyBurn({ t: 0, position: [6_778_000, 0, 0], velocity: [0, 7_669, 0] }, 20_000, { engine: { thrust: 91_189, exhaustSpeed: exhaustSpeedOf(314) }, direction: "prograde", seconds: 60 }, { step: 1, gravity: (_t, p) => gravityAt(p, [{ gm: 3.986004418e14, position: [0, 0, 0] }]) });
|
|
127
|
+
* console.log(result.dv.toFixed(1), "m/s for", result.propellant.toFixed(0), "kg");
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export interface BurnResult {
|
|
131
|
+
state: State;
|
|
132
|
+
mass: number;
|
|
133
|
+
seconds: number;
|
|
134
|
+
dv: number;
|
|
135
|
+
propellant: number;
|
|
136
|
+
/** True when the burn stopped early because the propellant ran out (`dryMass`). */
|
|
137
|
+
short: boolean;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Flies one burn from a state: gravity and the engine's thrust together, the mass falling at the engine's flow, the thrust kept along its direction as the craft moves.
|
|
141
|
+
* @param start The craft when the engine lights.
|
|
142
|
+
* @param mass Its mass then, kilograms.
|
|
143
|
+
* @param burn The engine, the direction, and how long or how much.
|
|
144
|
+
* @param options The step, the gravity, the centre and the dry mass.
|
|
145
|
+
* @returns Where the craft is when the engine stops, and what the burn did.
|
|
146
|
+
* @throws {RangeError} When the burn has no length, or would use all the mass with no `dryMass` above zero.
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* import { exhaustSpeedOf, flyBurn, gravityAt } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
150
|
+
*
|
|
151
|
+
* const earth = [{ gm: 3.986004418e14, position: [0, 0, 0] as const }];
|
|
152
|
+
* const raise = flyBurn({ t: 0, position: [6_778_000, 0, 0], velocity: [0, 7_669, 0] }, 20_000, { engine: { thrust: 91_189, exhaustSpeed: exhaustSpeedOf(314) }, direction: "prograde", dv: 100 }, { step: 1, gravity: (_t, p) => gravityAt(p, earth) });
|
|
153
|
+
* console.log(raise.seconds.toFixed(1), "s,", raise.mass.toFixed(0), "kg left");
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export declare function flyBurn(start: State, mass: number, burn: Burn, options: FlyOptions): BurnResult;
|
|
157
|
+
/**
|
|
158
|
+
* A burn in a plan: the burn, and the time its middle is planned for.
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```ts
|
|
162
|
+
* import { exhaustSpeedOf, type PlannedBurn } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
163
|
+
*
|
|
164
|
+
* const departure: PlannedBurn = { at: 1_200, engine: { thrust: 91_189, exhaustSpeed: exhaustSpeedOf(314) }, direction: "prograde", dv: 2_400 };
|
|
165
|
+
* console.log(departure.at);
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
export interface PlannedBurn extends Burn {
|
|
169
|
+
/** When the middle of the burn falls, seconds. */
|
|
170
|
+
at: number;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Flies a plan: coasts to each burn, starting it so that its middle falls at its planned time, flies it, and coasts on to the end.
|
|
174
|
+
* @param start The craft at the start.
|
|
175
|
+
* @param mass Its mass then, kilograms.
|
|
176
|
+
* @param burns The planned burns, in any order; they must not overlap.
|
|
177
|
+
* @param end The time to stop, seconds (after the last burn).
|
|
178
|
+
* @param options The step, the gravity, the centre, the dry mass and the methods.
|
|
179
|
+
* @returns The craft at `end`, its mass, and what each burn did, in time order.
|
|
180
|
+
* @example
|
|
181
|
+
* ```ts
|
|
182
|
+
* import { elementsOf, exhaustSpeedOf, flyPlan, gravityAt } from "@johnmorrisdotca/uchusen-rockets/physics";
|
|
183
|
+
*
|
|
184
|
+
* const earth = [{ gm: 3.986004418e14, position: [0, 0, 0] as const }];
|
|
185
|
+
* const engine = { thrust: 91_189, exhaustSpeed: exhaustSpeedOf(314) };
|
|
186
|
+
* const flown = flyPlan({ t: 0, position: [6_778_000, 0, 0], velocity: [0, 7_669, 0] }, 20_000, [{ at: 600, engine, direction: "prograde", dv: 100 }], 3_600, { step: 5, gravity: (_t, p) => gravityAt(p, earth) });
|
|
187
|
+
* console.log(((elementsOf(3.986004418e14, flown.state.position, flown.state.velocity).apoapsis - 6_378_137) / 1000).toFixed(0), "km apogee");
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
export declare function flyPlan(start: State, mass: number, burns: readonly PlannedBurn[], end: number, options: FlyOptions): {
|
|
191
|
+
state: State;
|
|
192
|
+
mass: number;
|
|
193
|
+
burns: BurnResult[];
|
|
194
|
+
};
|