@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.
Files changed (128) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSES/Orbiter-MIT.txt +21 -0
  3. package/NOTICE.md +37 -1
  4. package/README.md +13 -6
  5. package/dist/assets/falcon-heavy-test-flight-apart.painted.svg +1 -0
  6. package/dist/assets/falcon-heavy-test-flight-apart.svg +1 -0
  7. package/dist/assets/falcon-heavy-test-flight-apart.thick.svg +1 -0
  8. package/dist/assets/falcon-heavy-test-flight-car.glb +0 -0
  9. package/dist/assets/falcon-heavy-test-flight-car.painted.svg +1 -0
  10. package/dist/assets/falcon-heavy-test-flight-car.svg +1 -0
  11. package/dist/assets/falcon-heavy-test-flight-car.thick.svg +1 -0
  12. package/dist/assets/falcon-heavy-test-flight-centre.glb +0 -0
  13. package/dist/assets/falcon-heavy-test-flight-centre.painted.svg +1 -0
  14. package/dist/assets/falcon-heavy-test-flight-centre.svg +1 -0
  15. package/dist/assets/falcon-heavy-test-flight-centre.thick.svg +1 -0
  16. package/dist/assets/falcon-heavy-test-flight-side-left.glb +0 -0
  17. package/dist/assets/falcon-heavy-test-flight-side-left.painted.svg +1 -0
  18. package/dist/assets/falcon-heavy-test-flight-side-left.svg +1 -0
  19. package/dist/assets/falcon-heavy-test-flight-side-left.thick.svg +1 -0
  20. package/dist/assets/falcon-heavy-test-flight-side-right.glb +0 -0
  21. package/dist/assets/falcon-heavy-test-flight-side-right.painted.svg +1 -0
  22. package/dist/assets/falcon-heavy-test-flight-side-right.svg +1 -0
  23. package/dist/assets/falcon-heavy-test-flight-side-right.thick.svg +1 -0
  24. package/dist/assets/falcon-heavy-test-flight-starman.glb +0 -0
  25. package/dist/assets/falcon-heavy-test-flight-starman.painted.svg +1 -0
  26. package/dist/assets/falcon-heavy-test-flight-starman.svg +1 -0
  27. package/dist/assets/falcon-heavy-test-flight-starman.thick.svg +1 -0
  28. package/dist/assets/falcon-heavy-test-flight.glb +0 -0
  29. package/dist/assets/falcon-heavy-test-flight.painted.svg +1 -0
  30. package/dist/assets/falcon-heavy-test-flight.svg +1 -0
  31. package/dist/assets/falcon-heavy-test-flight.thick.svg +1 -0
  32. package/dist/assets/models/roadster-first-generation-car.glb +0 -0
  33. package/dist/assets/models/roadster-second-generation-2-car.glb +0 -0
  34. package/dist/assets/models/roadster-second-generation-car.glb +0 -0
  35. package/dist/assets/roadster-first-generation-apart.painted.svg +1 -0
  36. package/dist/assets/roadster-first-generation-apart.svg +1 -0
  37. package/dist/assets/roadster-first-generation-apart.thick.svg +1 -0
  38. package/dist/assets/roadster-first-generation-car.glb +0 -0
  39. package/dist/assets/roadster-first-generation-car.painted.svg +1 -0
  40. package/dist/assets/roadster-first-generation-car.svg +1 -0
  41. package/dist/assets/roadster-first-generation-car.thick.svg +1 -0
  42. package/dist/assets/roadster-first-generation-starman.glb +0 -0
  43. package/dist/assets/roadster-first-generation-starman.painted.svg +1 -0
  44. package/dist/assets/roadster-first-generation-starman.svg +1 -0
  45. package/dist/assets/roadster-first-generation-starman.thick.svg +1 -0
  46. package/dist/assets/roadster-first-generation.glb +0 -0
  47. package/dist/assets/roadster-first-generation.painted.svg +1 -0
  48. package/dist/assets/roadster-first-generation.svg +1 -0
  49. package/dist/assets/roadster-first-generation.thick.svg +1 -0
  50. package/dist/assets/roadster-second-generation-2-apart.painted.svg +1 -0
  51. package/dist/assets/roadster-second-generation-2-apart.svg +1 -0
  52. package/dist/assets/roadster-second-generation-2-apart.thick.svg +1 -0
  53. package/dist/assets/roadster-second-generation-2-car.glb +0 -0
  54. package/dist/assets/roadster-second-generation-2-car.painted.svg +1 -0
  55. package/dist/assets/roadster-second-generation-2-car.svg +1 -0
  56. package/dist/assets/roadster-second-generation-2-car.thick.svg +1 -0
  57. package/dist/assets/roadster-second-generation-2-starman.glb +0 -0
  58. package/dist/assets/roadster-second-generation-2-starman.painted.svg +1 -0
  59. package/dist/assets/roadster-second-generation-2-starman.svg +1 -0
  60. package/dist/assets/roadster-second-generation-2-starman.thick.svg +1 -0
  61. package/dist/assets/roadster-second-generation-2.glb +0 -0
  62. package/dist/assets/roadster-second-generation-2.painted.svg +1 -0
  63. package/dist/assets/roadster-second-generation-2.svg +1 -0
  64. package/dist/assets/roadster-second-generation-2.thick.svg +1 -0
  65. package/dist/assets/roadster-second-generation-apart.painted.svg +1 -0
  66. package/dist/assets/roadster-second-generation-apart.svg +1 -0
  67. package/dist/assets/roadster-second-generation-apart.thick.svg +1 -0
  68. package/dist/assets/roadster-second-generation-car.glb +0 -0
  69. package/dist/assets/roadster-second-generation-car.painted.svg +1 -0
  70. package/dist/assets/roadster-second-generation-car.svg +1 -0
  71. package/dist/assets/roadster-second-generation-car.thick.svg +1 -0
  72. package/dist/assets/roadster-second-generation-starman.glb +0 -0
  73. package/dist/assets/roadster-second-generation-starman.painted.svg +1 -0
  74. package/dist/assets/roadster-second-generation-starman.svg +1 -0
  75. package/dist/assets/roadster-second-generation-starman.thick.svg +1 -0
  76. package/dist/assets/roadster-second-generation.glb +0 -0
  77. package/dist/assets/roadster-second-generation.painted.svg +1 -0
  78. package/dist/assets/roadster-second-generation.svg +1 -0
  79. package/dist/assets/roadster-second-generation.thick.svg +1 -0
  80. package/dist/data/countries.d.ts +51 -0
  81. package/dist/data/countries.js +91 -0
  82. package/dist/data/credits.d.ts +36 -0
  83. package/dist/data/credits.js +36 -0
  84. package/dist/data/falcon.js +26 -14
  85. package/dist/data/makers.js +1 -0
  86. package/dist/data/roadster.d.ts +13 -0
  87. package/dist/data/roadster.js +73 -0
  88. package/dist/data/sources.d.ts +56 -0
  89. package/dist/data/sources.js +7 -0
  90. package/dist/element/flag.d.ts +30 -0
  91. package/dist/element/flag.js +23 -0
  92. package/dist/element/scene.d.ts +7 -0
  93. package/dist/element/scene.js +35 -1
  94. package/dist/element.d.ts +5 -0
  95. package/dist/element.js +58 -2
  96. package/dist/index.d.ts +3 -0
  97. package/dist/index.js +2 -0
  98. package/dist/inspace.js +1 -0
  99. package/dist/model/registry.js +6 -4
  100. package/dist/model/roadster.d.ts +31 -0
  101. package/dist/model/roadster.js +112 -0
  102. package/dist/parts.d.ts +1 -1
  103. package/dist/physics/atmosphere.d.ts +182 -0
  104. package/dist/physics/atmosphere.js +246 -0
  105. package/dist/physics/attitude.d.ts +342 -0
  106. package/dist/physics/attitude.js +351 -0
  107. package/dist/physics/burns.d.ts +194 -0
  108. package/dist/physics/burns.js +157 -0
  109. package/dist/physics/elements.d.ts +133 -0
  110. package/dist/physics/elements.js +239 -0
  111. package/dist/physics/gravity.d.ts +53 -0
  112. package/dist/physics/gravity.js +78 -0
  113. package/dist/physics/index.d.ts +17 -0
  114. package/dist/physics/index.js +8 -0
  115. package/dist/physics/propagate.d.ts +110 -0
  116. package/dist/physics/propagate.js +122 -0
  117. package/dist/physics/rocket.d.ts +105 -0
  118. package/dist/physics/rocket.js +102 -0
  119. package/dist/physics/transfers.d.ts +128 -0
  120. package/dist/physics/transfers.js +155 -0
  121. package/dist/physics/vector.d.ts +20 -0
  122. package/dist/physics/vector.js +9 -0
  123. package/dist/react.d.ts +1 -1
  124. package/dist/rockets.d.ts +5 -0
  125. package/dist/rockets.js +9 -4
  126. package/dist/sun.d.ts +13 -0
  127. package/dist/sun.js +25 -0
  128. package/package.json +11 -1
@@ -0,0 +1,246 @@
1
+ // The air: temperature, pressure and density with height for the Earth, Mars and Venus, the 1976 U.S. Standard Atmosphere, and what the
2
+ // air does to a craft (its speed through the air, the dynamic pressure, the Mach number and the drag). Pure.
3
+ // Ported from Orbiter (https://github.com/orbitersim/orbiter): the layered models of Src/Celbody/Atmosphere/EarthAtm2006/EarthAtm2006.cpp,
4
+ // MarsAtm2006/MarsAtm2006.cpp and VenusAtm2006/VenusAtm2006.cpp (clbkConstants and clbkParams: each layer's base, temperature, lapse rate,
5
+ // pressure and density, the geopotential height from the planet's radius, and the height above which the model has no air), the speed of
6
+ // sound of Src/Orbiter/Planet.h (AtmSoundSpeed), the airspeed and dynamic pressure of Src/Orbiter/Vesselbase.cpp (air turning with the
7
+ // planet, no wind), and the drag of a drag element of Src/Orbiter/Vessel.cpp (CD times the dynamic pressure, against the airflow).
8
+ // Copyright (c) 2000-2026 Martin Schweiger, MIT licence (LICENSES/Orbiter-MIT.txt). Changed in the port: the three models are tables of
9
+ // layers read by one function in place of three nested if-chains, with the same numbers. Not ported: Orbiter's Jacchia 71-Gill model and
10
+ // the NRLMSISE-00 model, which need the Sun's activity, and NRLMSISE-00's C code, which is not Orbiter's own.
11
+ // Written here: standardAtmosphere, from the defining constants and equations of U.S. Standard Atmosphere, 1976 (NOAA, NASA and the U.S.
12
+ // Air Force, NASA-TM-X-74335), its tables 2, 3 and 4.
13
+ import { cross, length, scale, sub } from "./vector.js";
14
+ /**
15
+ * The Earth's air as Orbiter 2006 models it (Orbiter's EarthAtm2006: "a simple static atmosphere model", its own description, ending at 200 km).
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import { atmosphereAt, EARTH_ATMOSPHERE } from "@johnmorrisdotca/uchusen-rockets/physics";
20
+ *
21
+ * console.log(atmosphereAt(EARTH_ATMOSPHERE, 0).pressure);
22
+ * ```
23
+ */
24
+ export const EARTH_ATMOSPHERE = {
25
+ name: "Earth (Orbiter 2006)",
26
+ radius: 6.37101e6,
27
+ g0R: 0.0341643,
28
+ gasConstant: 286.91,
29
+ gamma: 1.4,
30
+ limit: 200e3,
31
+ layers: [
32
+ { base: 0, temperature: 288.16, lapse: -6.5e-3, pressure: 1.01325e5, density: 1.225 },
33
+ { base: 11e3, temperature: 216.66, lapse: 0, pressure: 2.26323e4, density: 0.36392 },
34
+ { base: 25e3, temperature: 216.66, lapse: 3e-3, pressure: 2488.726, density: 0.0400179 },
35
+ { base: 47e3, temperature: 282.66, lapse: 0, pressure: 120.4524, density: 0.00148459 },
36
+ { base: 53e3, temperature: 282.66, lapse: -4.5e-3, pressure: 58.32623, density: 0.000718878 },
37
+ { base: 79e3, temperature: 165.66, lapse: 0, pressure: 1.009601, density: 2.12319e-5 },
38
+ { base: 90e3, temperature: 165.66, lapse: 4e-3, pressure: 0.1044559, density: 2.19671e-6 },
39
+ { base: 105e3, temperature: 225.66, lapse: 0, pressure: 0.007454273, density: 1.15082e-7 },
40
+ ],
41
+ };
42
+ /**
43
+ * Mars's air as Orbiter 2006 models it (Orbiter's MarsAtm2006), ending at 100 km.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * import { atmosphereAt, MARS_ATMOSPHERE } from "@johnmorrisdotca/uchusen-rockets/physics";
48
+ *
49
+ * console.log(atmosphereAt(MARS_ATMOSPHERE, 0).pressure);
50
+ * ```
51
+ */
52
+ export const MARS_ATMOSPHERE = {
53
+ name: "Mars (Orbiter 2006)",
54
+ radius: 3.38992e6,
55
+ g0R: 0.0197275,
56
+ gasConstant: 188.92,
57
+ gamma: 1.2941,
58
+ limit: 100e3,
59
+ layers: [
60
+ { base: 0, temperature: 195, lapse: 2.5e-3, pressure: 610, density: 0.02 },
61
+ { base: 2e3, temperature: 200, lapse: 0, pressure: 4.99534e2, density: 1.59687e-2 },
62
+ { base: 4e3, temperature: 200, lapse: -2e-3, pressure: 4.101e2, density: 1.31097e-2 },
63
+ { base: 14e3, temperature: 180, lapse: 0, pressure: 1.45061e2, density: 5.1524e-3 },
64
+ { base: 20e3, temperature: 180, lapse: -1.5e-3, pressure: 7.51564e1, density: 2.66947e-3 },
65
+ { base: 30e3, temperature: 165, lapse: 0, pressure: 2.39322e1, density: 9.27323e-4 },
66
+ ],
67
+ };
68
+ /**
69
+ * Venus's air as Orbiter 2006 models it (Orbiter's VenusAtm2006), ending at 200 km.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * import { atmosphereAt, VENUS_ATMOSPHERE } from "@johnmorrisdotca/uchusen-rockets/physics";
74
+ *
75
+ * console.log(atmosphereAt(VENUS_ATMOSPHERE, 0).temperature);
76
+ * ```
77
+ */
78
+ export const VENUS_ATMOSPHERE = {
79
+ name: "Venus (Orbiter 2006)",
80
+ radius: 6.05184e6,
81
+ g0R: 0.0469506,
82
+ gasConstant: 188.92,
83
+ gamma: 1.2857,
84
+ limit: 200e3,
85
+ layers: [
86
+ { base: 0, temperature: 750, lapse: -0.009, pressure: 9.2e6, density: 65 },
87
+ { base: 30e3, temperature: 480, lapse: -0.00833, pressure: 8.967687e5, density: 9.89979 },
88
+ { base: 60e3, temperature: 230, lapse: 0, pressure: 1.421879e4, density: 3.274408e-1 },
89
+ { base: 70e3, temperature: 230, lapse: -0.0025, pressure: 1.846393e3, density: 4.252010e-2 },
90
+ { base: 90e3, temperature: 180, lapse: 0, pressure: 1.849556e1, density: 5.442431e-4 },
91
+ ],
92
+ };
93
+ const NO_AIR = { temperature: 0, pressure: 0, density: 0, speedOfSound: 0 };
94
+ /**
95
+ * The air of a layered model at a height above the surface (Orbiter's `clbkParams`): the geometric height is turned into geopotential height, the layer below it is found, and the temperature, pressure and density follow from that layer.
96
+ * @param model The model.
97
+ * @param altitude The geometric height above the reference radius, metres (below 0 the lowest layer goes on down).
98
+ * @returns The air there; all zero above the model's limit, as Orbiter has no air there.
99
+ * @example
100
+ * ```ts
101
+ * import { atmosphereAt, EARTH_ATMOSPHERE } from "@johnmorrisdotca/uchusen-rockets/physics";
102
+ *
103
+ * const air = atmosphereAt(EARTH_ATMOSPHERE, 30_000);
104
+ * console.log(air.temperature.toFixed(1), "K,", air.pressure.toFixed(0), "Pa,", air.density.toFixed(4), "kg/m^3");
105
+ * ```
106
+ */
107
+ export function atmosphereAt(model, altitude) {
108
+ if (altitude > model.limit)
109
+ return NO_AIR;
110
+ const h = (altitude * model.radius) / (model.radius + altitude);
111
+ const layers = model.layers;
112
+ let layer = layers[0];
113
+ for (const candidate of layers)
114
+ if (h > candidate.base)
115
+ layer = candidate;
116
+ let temperature;
117
+ let e;
118
+ let t = 1;
119
+ if (layer.lapse === 0) {
120
+ temperature = layer.temperature;
121
+ e = Math.exp((-model.g0R / layer.temperature) * (h - layer.base));
122
+ }
123
+ else {
124
+ temperature = (h - layer.base) * layer.lapse + layer.temperature;
125
+ t = temperature / layer.temperature;
126
+ e = Math.pow(t, -model.g0R / layer.lapse);
127
+ }
128
+ return { temperature, pressure: layer.pressure * e, density: (layer.density * e) / t, speedOfSound: Math.sqrt(model.gamma * model.gasConstant * temperature) };
129
+ }
130
+ // U.S. Standard Atmosphere, 1976: table 2 (adopted constants), table 3 (sea-level air) and table 4 (the layers to 86 km).
131
+ const R_STAR = 8.31432e3;
132
+ const G0 = 9.80665;
133
+ const R0 = 6.356766e6;
134
+ const T0 = 288.15;
135
+ const P0 = 1.01325e5;
136
+ const GAMMA = 1.4;
137
+ const AIR = [
138
+ [28.0134, 0.78084],
139
+ [31.9988, 0.209476],
140
+ [39.948, 0.00934],
141
+ [44.00995, 0.000314],
142
+ [20.183, 0.00001818],
143
+ [4.0026, 0.00000524],
144
+ [83.8, 0.00000114],
145
+ [131.3, 0.000000087],
146
+ [16.04303, 0.000002],
147
+ [2.01594, 0.0000005],
148
+ ];
149
+ const M0 = AIR.reduce((sum, [m, f]) => sum + m * f, 0);
150
+ const BASES = [0, 11e3, 20e3, 32e3, 47e3, 51e3, 71e3];
151
+ const LAPSES = [-6.5e-3, 0, 1e-3, 2.8e-3, 0, -2.8e-3, -2e-3];
152
+ const TOP = 84.852e3;
153
+ const STANDARD_LAYERS = [];
154
+ {
155
+ let temperature = T0;
156
+ let pressure = P0;
157
+ BASES.forEach((base, b) => {
158
+ STANDARD_LAYERS.push({ base, lapse: LAPSES[b], temperature, pressure });
159
+ const next = BASES[b + 1] ?? TOP;
160
+ const end = temperature + LAPSES[b] * (next - base);
161
+ pressure = LAPSES[b] === 0 ? pressure * Math.exp((-G0 * M0 * (next - base)) / (R_STAR * temperature)) : pressure * Math.pow(temperature / end, (G0 * M0) / (R_STAR * LAPSES[b]));
162
+ temperature = end;
163
+ });
164
+ }
165
+ /**
166
+ * The 1976 U.S. Standard Atmosphere from 5 km below sea level to 86 km, worked from its defining constants and equations (its tables 2, 3 and 4): seven layers of fixed temperature gradient in geopotential height, the sea-level air's mean molecular weight from its table 3, the hydrostatic equation and the gas law.
167
+ * @param altitude The geometric height above sea level, metres, from -5,000 to 86,000.
168
+ * @returns The air there (the temperature is the molecular-scale temperature, the same as the kinetic temperature below 80 km), or null outside those heights.
169
+ * @example
170
+ * ```ts
171
+ * import { standardAtmosphere } from "@johnmorrisdotca/uchusen-rockets/physics";
172
+ *
173
+ * const air = standardAtmosphere(11_000);
174
+ * console.log(air?.temperature.toFixed(3), "K,", air?.pressure.toFixed(0), "Pa");
175
+ * ```
176
+ */
177
+ export function standardAtmosphere(altitude) {
178
+ if (!(altitude >= -5_000 && altitude <= 86_000))
179
+ return null;
180
+ const h = (R0 * altitude) / (R0 + altitude);
181
+ let layer = STANDARD_LAYERS[0];
182
+ for (const candidate of STANDARD_LAYERS)
183
+ if (h >= candidate.base)
184
+ layer = candidate;
185
+ const temperature = layer.temperature + layer.lapse * (h - layer.base);
186
+ const pressure = layer.lapse === 0 ? layer.pressure * Math.exp((-G0 * M0 * (h - layer.base)) / (R_STAR * layer.temperature)) : layer.pressure * Math.pow(layer.temperature / temperature, (G0 * M0) / (R_STAR * layer.lapse));
187
+ return { temperature, pressure, density: (pressure * M0) / (R_STAR * temperature), speedOfSound: Math.sqrt((GAMMA * R_STAR * temperature) / M0) };
188
+ }
189
+ /**
190
+ * A craft's speed through the air, for air that turns with its planet and no wind (Orbiter's airspeed): its speed relative to the planet less the speed of the air where it is, `v - w x r`.
191
+ * @param position The craft's position from the planet's centre, metres.
192
+ * @param velocity Its speed relative to the planet's centre, metres a second.
193
+ * @param spin The planet's turn rate as a vector along its pole, radians a second (`[0, 0, 7.292115e-5]` for the Earth in an equatorial frame).
194
+ * @returns The velocity through the air, metres a second.
195
+ * @example
196
+ * ```ts
197
+ * import { airVelocity } from "@johnmorrisdotca/uchusen-rockets/physics";
198
+ *
199
+ * console.log(airVelocity([6_778_000, 0, 0], [0, 7_669, 0], [0, 0, 7.292115e-5]));
200
+ * ```
201
+ */
202
+ export const airVelocity = (position, velocity, spin) => sub(velocity, cross(spin, position));
203
+ /**
204
+ * The dynamic pressure of the air on a craft, `rho v^2 / 2` (Orbiter's `dynp`).
205
+ * @param density The air's density, kg/m^3.
206
+ * @param airspeed The speed through the air, metres a second.
207
+ * @returns The dynamic pressure, pascals.
208
+ * @example
209
+ * ```ts
210
+ * import { dynamicPressure } from "@johnmorrisdotca/uchusen-rockets/physics";
211
+ *
212
+ * console.log(dynamicPressure(0.4, 400));
213
+ * ```
214
+ */
215
+ export const dynamicPressure = (density, airspeed) => 0.5 * density * airspeed * airspeed;
216
+ /**
217
+ * The Mach number: the speed through the air over the speed of sound there (Orbiter's `atmM`).
218
+ * @param airspeed The speed through the air, metres a second.
219
+ * @param air The air, from {@link atmosphereAt} or {@link standardAtmosphere}.
220
+ * @returns The Mach number; 0 where there is no air.
221
+ * @example
222
+ * ```ts
223
+ * import { atmosphereAt, EARTH_ATMOSPHERE, machNumber } from "@johnmorrisdotca/uchusen-rockets/physics";
224
+ *
225
+ * console.log(machNumber(600, atmosphereAt(EARTH_ATMOSPHERE, 11_000)).toFixed(2));
226
+ * ```
227
+ */
228
+ export const machNumber = (airspeed, air) => (air.speedOfSound > 0 ? airspeed / air.speedOfSound : 0);
229
+ /**
230
+ * The drag on a craft as an acceleration: `-(rho |v| v / 2) Cd A / m`, against its velocity through the air (Orbiter's drag element: its drag coefficient times the dynamic pressure, along the airflow).
231
+ * @param density The air's density, kg/m^3.
232
+ * @param air The velocity through the air, from {@link airVelocity}, metres a second.
233
+ * @param dragArea The drag coefficient times the reference area, `Cd A`, m^2.
234
+ * @param mass The craft's mass, kilograms.
235
+ * @returns The acceleration, metres a second squared.
236
+ * @example
237
+ * ```ts
238
+ * import { atmosphereAt, dragAcceleration, EARTH_ATMOSPHERE } from "@johnmorrisdotca/uchusen-rockets/physics";
239
+ *
240
+ * console.log(dragAcceleration(atmosphereAt(EARTH_ATMOSPHERE, 60_000).density, [0, 7_000, 0], 1.3 * 12, 9_000));
241
+ * ```
242
+ */
243
+ export function dragAcceleration(density, air, dragArea, mass) {
244
+ const speed = length(air);
245
+ return speed > 0 ? scale(air, (-0.5 * density * speed * dragArea) / mass) : [0, 0, 0];
246
+ }
@@ -0,0 +1,342 @@
1
+ import { type Vec3 } from "./vector.ts";
2
+ /**
3
+ * An attitude as a unit quaternion `[w, x, y, z]`: the rotation that takes a vector of the craft's own frame into the scene's frame.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { rotateVector, type Quat } from "@johnmorrisdotca/uchusen-rockets/physics";
8
+ *
9
+ * const level: Quat = [1, 0, 0, 0];
10
+ * console.log(rotateVector(level, [0, 1, 0]));
11
+ * ```
12
+ */
13
+ export type Quat = readonly [number, number, number, number];
14
+ /**
15
+ * The quaternion of a turn by an angle about an axis.
16
+ * @param axis The axis, any length but not zero.
17
+ * @param angle The angle, radians, right-handed about the axis.
18
+ * @returns The unit quaternion.
19
+ * @example
20
+ * ```ts
21
+ * import { quatFromAxisAngle, rotateVector } from "@johnmorrisdotca/uchusen-rockets/physics";
22
+ *
23
+ * console.log(rotateVector(quatFromAxisAngle([0, 0, 1], Math.PI / 2), [1, 0, 0]).map((x) => x.toFixed(3)));
24
+ * ```
25
+ */
26
+ export declare function quatFromAxisAngle(axis: Vec3, angle: number): Quat;
27
+ /**
28
+ * A vector of the craft's frame in the scene's frame.
29
+ * @param q The attitude.
30
+ * @param v The vector in the craft's frame.
31
+ * @returns The same vector in the scene's frame.
32
+ * @example
33
+ * ```ts
34
+ * import { quatFromAxisAngle, rotateVector } from "@johnmorrisdotca/uchusen-rockets/physics";
35
+ *
36
+ * console.log(rotateVector(quatFromAxisAngle([1, 0, 0], Math.PI / 2), [0, 1, 0]).map((x) => x.toFixed(3)));
37
+ * ```
38
+ */
39
+ export declare function rotateVector(q: Quat, v: Vec3): Vec3;
40
+ /**
41
+ * A vector of the scene's frame in the craft's frame: the inverse of {@link rotateVector}.
42
+ * @param q The attitude.
43
+ * @param v The vector in the scene's frame.
44
+ * @returns The same vector in the craft's frame.
45
+ * @example
46
+ * ```ts
47
+ * import { quatFromAxisAngle, unrotateVector } from "@johnmorrisdotca/uchusen-rockets/physics";
48
+ *
49
+ * console.log(unrotateVector(quatFromAxisAngle([1, 0, 0], Math.PI / 2), [0, 0, 1]).map((x) => x.toFixed(3)));
50
+ * ```
51
+ */
52
+ export declare const unrotateVector: (q: Quat, v: Vec3) => Vec3;
53
+ /**
54
+ * 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.
55
+ * @param q The attitude.
56
+ * @param rotation The rotation, in the craft's frame.
57
+ * @returns The new attitude.
58
+ * @example
59
+ * ```ts
60
+ * import { rotateVector, turnBy } from "@johnmorrisdotca/uchusen-rockets/physics";
61
+ *
62
+ * console.log(rotateVector(turnBy([1, 0, 0, 0], [0, 0, Math.PI]), [1, 0, 0]).map((x) => x.toFixed(3)));
63
+ * ```
64
+ */
65
+ export declare function turnBy(q: Quat, rotation: Vec3): Quat;
66
+ /**
67
+ * 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`).
68
+ * @param inertia The principal moments of inertia about the craft's x, y and z axes, kg m^2.
69
+ * @param torque The torque, in the craft's frame, N m.
70
+ * @param omega The turn rate, in the craft's frame, radians a second.
71
+ * @returns The angular acceleration, in the craft's frame, radians a second squared.
72
+ * @example
73
+ * ```ts
74
+ * import { angularAcceleration } from "@johnmorrisdotca/uchusen-rockets/physics";
75
+ *
76
+ * console.log(angularAcceleration([1_000, 2_000, 2_500], [0, 0, 50], [0, 0, 0]));
77
+ * ```
78
+ */
79
+ export declare function angularAcceleration(inertia: Vec3, torque: Vec3, omega: Vec3): Vec3;
80
+ /**
81
+ * 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.
82
+ * @param gm The body's gravitational parameter, m^3/s^2.
83
+ * @param toBody The body's centre from the craft, in the craft's frame, metres.
84
+ * @param inertia The principal moments of inertia, kg m^2.
85
+ * @returns The torque, in the craft's frame, N m.
86
+ * @example
87
+ * ```ts
88
+ * import { gravityGradientTorque } from "@johnmorrisdotca/uchusen-rockets/physics";
89
+ *
90
+ * console.log(gravityGradientTorque(3.986004418e14, [-4_800_000, 4_800_000, 0], [100_000, 10_000, 100_000]));
91
+ * ```
92
+ */
93
+ export declare function gravityGradientTorque(gm: number, toBody: Vec3, inertia: Vec3): Vec3;
94
+ /**
95
+ * A craft's attitude and turn rate at a time. The turn rate is in the craft's own frame, radians a second.
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * import type { Spin } from "@johnmorrisdotca/uchusen-rockets/physics";
100
+ *
101
+ * const still: Spin = { t: 0, q: [1, 0, 0, 0], omega: [0, 0, 0] };
102
+ * console.log(still.omega);
103
+ * ```
104
+ */
105
+ export interface Spin {
106
+ t: number;
107
+ q: Quat;
108
+ omega: Vec3;
109
+ }
110
+ /**
111
+ * What turns the craft: the torque in its own frame (N m) at a time, attitude and turn rate.
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * import type { Torque } from "@johnmorrisdotca/uchusen-rockets/physics";
116
+ *
117
+ * const steady: Torque = () => [0, 0, 10];
118
+ * console.log(steady(0, [1, 0, 0, 0], [0, 0, 0]));
119
+ * ```
120
+ */
121
+ export type Torque = (t: number, q: Quat, omega: Vec3) => Vec3;
122
+ /**
123
+ * 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`).
124
+ * @param spin The attitude and turn rate now.
125
+ * @param h The step, seconds.
126
+ * @param inertia The principal moments of inertia, kg m^2.
127
+ * @param torque What turns the craft.
128
+ * @returns The attitude and turn rate `h` seconds later.
129
+ * @example
130
+ * ```ts
131
+ * import { stepSpin } from "@johnmorrisdotca/uchusen-rockets/physics";
132
+ *
133
+ * const next = stepSpin({ t: 0, q: [1, 0, 0, 0], omega: [0, 0, 0] }, 1, [1_000, 1_000, 1_000], () => [0, 0, 10]);
134
+ * console.log(next.omega[2]);
135
+ * ```
136
+ */
137
+ export declare function stepSpin(spin: Spin, h: number, inertia: Vec3, torque: Torque): Spin;
138
+ /**
139
+ * Turns a craft forward by a span of time, in steps no longer than `options.step`.
140
+ * @param start The attitude and turn rate at the start.
141
+ * @param seconds How long, seconds.
142
+ * @param options The step, the moments of inertia and what turns the craft (no torque when left out).
143
+ * @returns The attitude and turn rate at `start.t + seconds`.
144
+ * @example
145
+ * ```ts
146
+ * import { spinFor } from "@johnmorrisdotca/uchusen-rockets/physics";
147
+ *
148
+ * 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] });
149
+ * console.log(tumbling.omega.map((x) => x.toFixed(4)));
150
+ * ```
151
+ */
152
+ export declare function spinFor(start: Spin, seconds: number, options: {
153
+ step: number;
154
+ inertia: Vec3;
155
+ torque?: Torque;
156
+ }): Spin;
157
+ /**
158
+ * One thruster: where it is and which way it pushes, in the craft's frame, its thrust and exhaust speed.
159
+ *
160
+ * @example
161
+ * ```ts
162
+ * import type { Thruster } from "@johnmorrisdotca/uchusen-rockets/physics";
163
+ *
164
+ * const quad: Thruster = { position: [2, 10, 0], direction: [0, 0, 1], thrust: 445, exhaustSpeed: 2_800 };
165
+ * console.log(quad.thrust);
166
+ * ```
167
+ */
168
+ export interface Thruster {
169
+ /** Where its push acts, from the craft's centre of mass, metres. */
170
+ position: Vec3;
171
+ /** Which way it pushes the craft, a unit vector (the exhaust goes the other way). */
172
+ direction: Vec3;
173
+ /** Thrust at full level, newtons. */
174
+ thrust: number;
175
+ /** Exhaust speed, metres a second. */
176
+ exhaustSpeed: number;
177
+ }
178
+ /**
179
+ * 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.
180
+ * @param thrusters The thrusters.
181
+ * @param levels Each one's level, 0 to 1, in the same order (missing ones are off).
182
+ * @returns The force (N) and torque (N m), in the craft's frame, and the mass flow (kg/s).
183
+ * @example
184
+ * ```ts
185
+ * import { thrusterPush } from "@johnmorrisdotca/uchusen-rockets/physics";
186
+ *
187
+ * 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 }];
188
+ * console.log(thrusterPush(pair, [1, 1]));
189
+ * ```
190
+ */
191
+ export declare function thrusterPush(thrusters: readonly Thruster[], levels: readonly number[]): {
192
+ force: Vec3;
193
+ torque: Vec3;
194
+ massFlow: number;
195
+ };
196
+ /**
197
+ * The largest torque a craft's thrusters can give about each of its axes, each way: for each axis and sense, the thrusters that turn the craft that way, all at full (Orbiter's `MaxAngularMoment` of the attitude groups).
198
+ *
199
+ * @example
200
+ * ```ts
201
+ * import type { TorqueLimits } from "@johnmorrisdotca/uchusen-rockets/physics";
202
+ *
203
+ * const limits: TorqueLimits = { positive: [400, 400, 200], negative: [400, 400, 200] };
204
+ * console.log(limits.positive[2]);
205
+ * ```
206
+ */
207
+ export interface TorqueLimits {
208
+ /** About +x, +y and +z, N m (0 or more). */
209
+ positive: Vec3;
210
+ /** About -x, -y and -z, N m (0 or more). */
211
+ negative: Vec3;
212
+ }
213
+ /**
214
+ * 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.
215
+ * @param thrusters The thrusters.
216
+ * @returns The largest torque about each axis, each way.
217
+ * @example
218
+ * ```ts
219
+ * import { torqueLimits } from "@johnmorrisdotca/uchusen-rockets/physics";
220
+ *
221
+ * 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 }];
222
+ * console.log(torqueLimits(pair));
223
+ * ```
224
+ */
225
+ export declare function torqueLimits(thrusters: readonly Thruster[]): TorqueLimits;
226
+ /**
227
+ * 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.
228
+ * @param thrusters The thrusters.
229
+ * @param torque The torque wanted, in the craft's frame, N m.
230
+ * @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.
231
+ * @example
232
+ * ```ts
233
+ * import { fireFor } from "@johnmorrisdotca/uchusen-rockets/physics";
234
+ *
235
+ * 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 }];
236
+ * console.log(fireFor(pair, [0, 0, 50]).levels);
237
+ * ```
238
+ */
239
+ export declare function fireFor(thrusters: readonly Thruster[], torque: Vec3): {
240
+ levels: number[];
241
+ force: Vec3;
242
+ torque: Vec3;
243
+ massFlow: number;
244
+ };
245
+ /**
246
+ * 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.
247
+ * @param spin The attitude and turn rate.
248
+ * @param inertia The principal moments of inertia, kg m^2.
249
+ * @param limits What the thrusters can give.
250
+ * @param dt The control step, seconds (the time until the autopilot looks again).
251
+ * @returns The torque to apply, in the craft's frame, N m.
252
+ * @example
253
+ * ```ts
254
+ * import { killRotation } from "@johnmorrisdotca/uchusen-rockets/physics";
255
+ *
256
+ * const limits = { positive: [200, 200, 200] as const, negative: [200, 200, 200] as const };
257
+ * console.log(killRotation({ t: 0, q: [1, 0, 0, 0], omega: [0.05, 0, 0] }, [1_000, 1_000, 1_000], limits, 0.1));
258
+ * ```
259
+ */
260
+ export declare function killRotation(spin: Spin, inertia: Vec3, limits: TorqueLimits, dt: number): Vec3;
261
+ /**
262
+ * 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.
263
+ * @param spin The attitude and turn rate.
264
+ * @param inertia The principal moments of inertia, kg m^2.
265
+ * @param limits What the thrusters can give.
266
+ * @param dt The control step, seconds.
267
+ * @param target The direction to point the nose at, in the scene's frame.
268
+ * @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.
269
+ * @returns The torque to apply, in the craft's frame, N m.
270
+ * @example
271
+ * ```ts
272
+ * import { holdDirection } from "@johnmorrisdotca/uchusen-rockets/physics";
273
+ *
274
+ * const limits = { positive: [200, 200, 200] as const, negative: [200, 200, 200] as const };
275
+ * 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]));
276
+ * ```
277
+ */
278
+ export declare function holdDirection(spin: Spin, inertia: Vec3, limits: TorqueLimits, dt: number, target: Vec3, options?: {
279
+ nose?: Vec3;
280
+ roofAxis?: Vec3;
281
+ roof?: Vec3;
282
+ }): Vec3;
283
+ /**
284
+ * A craft whose place and attitude move together: where it is, how fast it goes, which way it points and how it turns.
285
+ *
286
+ * @example
287
+ * ```ts
288
+ * import type { Craft } from "@johnmorrisdotca/uchusen-rockets/physics";
289
+ *
290
+ * const parked: Craft = { t: 0, position: [6_778_000, 0, 0], velocity: [0, 7_669, 0], q: [1, 0, 0, 0], omega: [0, 0, 0] };
291
+ * console.log(parked.q);
292
+ * ```
293
+ */
294
+ export interface Craft {
295
+ t: number;
296
+ position: Vec3;
297
+ velocity: Vec3;
298
+ q: Quat;
299
+ omega: Vec3;
300
+ }
301
+ /**
302
+ * What moves a craft: its acceleration in the scene's frame (gravity, and an engine's thrust along the nose, which depends on the attitude) and the torque in its own frame.
303
+ *
304
+ * @example
305
+ * ```ts
306
+ * import { gravityAt, rotateVector, type CraftForces } from "@johnmorrisdotca/uchusen-rockets/physics";
307
+ *
308
+ * const earth = [{ gm: 3.986004418e14, position: [0, 0, 0] as const }];
309
+ * const pushAlongNose: CraftForces = {
310
+ * inertia: [1_000, 1_000, 1_000],
311
+ * acceleration: (_t, p, _v, q) => {
312
+ * const g = gravityAt(p, earth);
313
+ * const push = rotateVector(q, [0, 0.1, 0]);
314
+ * return [g[0] + push[0], g[1] + push[1], g[2] + push[2]];
315
+ * },
316
+ * torque: () => [0, 0, 1],
317
+ * };
318
+ * console.log(pushAlongNose.inertia);
319
+ * ```
320
+ */
321
+ export interface CraftForces {
322
+ /** The principal moments of inertia, kg m^2. */
323
+ inertia: Vec3;
324
+ acceleration: (t: number, position: Vec3, velocity: Vec3, q: Quat) => Vec3;
325
+ /** No torque when left out. */
326
+ torque?: Torque;
327
+ }
328
+ /**
329
+ * 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.
330
+ * @param craft The craft now.
331
+ * @param h The step, seconds.
332
+ * @param forces What moves it.
333
+ * @returns The craft `h` seconds later.
334
+ * @example
335
+ * ```ts
336
+ * import { gravityAt, stepCraft } from "@johnmorrisdotca/uchusen-rockets/physics";
337
+ *
338
+ * 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] }]) });
339
+ * console.log(next.t, next.omega);
340
+ * ```
341
+ */
342
+ export declare function stepCraft(craft: Craft, h: number, forces: CraftForces): Craft;