@johnmorrisdotca/uchusen-rockets 0.19.0 → 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 (130) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSES/Orbiter-MIT.txt +21 -0
  3. package/NOTICE.md +37 -1
  4. package/README.md +14 -7
  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/probe.d.ts +15 -0
  93. package/dist/element/probe.js +46 -0
  94. package/dist/element/scene.d.ts +7 -0
  95. package/dist/element/scene.js +38 -10
  96. package/dist/element.d.ts +7 -0
  97. package/dist/element.js +74 -20
  98. package/dist/index.d.ts +3 -0
  99. package/dist/index.js +2 -0
  100. package/dist/inspace.js +1 -0
  101. package/dist/model/registry.js +6 -4
  102. package/dist/model/roadster.d.ts +31 -0
  103. package/dist/model/roadster.js +112 -0
  104. package/dist/parts.d.ts +1 -1
  105. package/dist/physics/atmosphere.d.ts +182 -0
  106. package/dist/physics/atmosphere.js +246 -0
  107. package/dist/physics/attitude.d.ts +342 -0
  108. package/dist/physics/attitude.js +351 -0
  109. package/dist/physics/burns.d.ts +194 -0
  110. package/dist/physics/burns.js +157 -0
  111. package/dist/physics/elements.d.ts +133 -0
  112. package/dist/physics/elements.js +239 -0
  113. package/dist/physics/gravity.d.ts +53 -0
  114. package/dist/physics/gravity.js +78 -0
  115. package/dist/physics/index.d.ts +17 -0
  116. package/dist/physics/index.js +8 -0
  117. package/dist/physics/propagate.d.ts +110 -0
  118. package/dist/physics/propagate.js +122 -0
  119. package/dist/physics/rocket.d.ts +105 -0
  120. package/dist/physics/rocket.js +102 -0
  121. package/dist/physics/transfers.d.ts +128 -0
  122. package/dist/physics/transfers.js +155 -0
  123. package/dist/physics/vector.d.ts +20 -0
  124. package/dist/physics/vector.js +9 -0
  125. package/dist/react.d.ts +1 -1
  126. package/dist/rockets.d.ts +5 -0
  127. package/dist/rockets.js +9 -4
  128. package/dist/sun.d.ts +13 -0
  129. package/dist/sun.js +25 -0
  130. package/package.json +11 -1
@@ -0,0 +1,182 @@
1
+ import { type Vec3 } from "./vector.ts";
2
+ /**
3
+ * One layer of a layered atmosphere: from its base up, the temperature changes at a fixed rate with geopotential height (or stays the same), and the pressure and density follow.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { EARTH_ATMOSPHERE, type AtmosphereLayer } from "@johnmorrisdotca/uchusen-rockets/physics";
8
+ *
9
+ * const ground: AtmosphereLayer = EARTH_ATMOSPHERE.layers[0]!;
10
+ * console.log(ground.temperature, ground.lapse);
11
+ * ```
12
+ */
13
+ export interface AtmosphereLayer {
14
+ /** The layer's base, geopotential metres. */
15
+ base: number;
16
+ /** Temperature at the base, kelvin. */
17
+ temperature: number;
18
+ /** How the temperature changes going up, kelvin a geopotential metre (0 for a layer of one temperature). */
19
+ lapse: number;
20
+ /** Pressure at the base, pascals. */
21
+ pressure: number;
22
+ /** Density at the base, kg/m^3. */
23
+ density: number;
24
+ }
25
+ /**
26
+ * A layered model of a planet's air, as Orbiter's 2006 models are.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * import { MARS_ATMOSPHERE, type AtmosphereModel } from "@johnmorrisdotca/uchusen-rockets/physics";
31
+ *
32
+ * const mars: AtmosphereModel = MARS_ATMOSPHERE;
33
+ * console.log(mars.name, mars.limit);
34
+ * ```
35
+ */
36
+ export interface AtmosphereModel {
37
+ name: string;
38
+ /** The radius the geopotential height is worked from, metres. */
39
+ radius: number;
40
+ /** Standard gravity over the gas constant, g0 / R, kelvin a metre. */
41
+ g0R: number;
42
+ /** The specific gas constant, J/(kg K). */
43
+ gasConstant: number;
44
+ /** The ratio of specific heats. */
45
+ gamma: number;
46
+ /** Above this height (geometric metres) the model has no air. */
47
+ limit: number;
48
+ /** The layers, lowest first. */
49
+ layers: readonly AtmosphereLayer[];
50
+ }
51
+ /**
52
+ * The air at a height: temperature (K), pressure (Pa), density (kg/m^3) and the speed of sound (m/s). All zero where there is no air.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * import { atmosphereAt, EARTH_ATMOSPHERE, type Air } from "@johnmorrisdotca/uchusen-rockets/physics";
57
+ *
58
+ * const air: Air = atmosphereAt(EARTH_ATMOSPHERE, 10_000);
59
+ * console.log(air.density.toFixed(3));
60
+ * ```
61
+ */
62
+ export interface Air {
63
+ temperature: number;
64
+ pressure: number;
65
+ density: number;
66
+ speedOfSound: number;
67
+ }
68
+ /**
69
+ * The Earth's air as Orbiter 2006 models it (Orbiter's EarthAtm2006: "a simple static atmosphere model", its own description, ending at 200 km).
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * import { atmosphereAt, EARTH_ATMOSPHERE } from "@johnmorrisdotca/uchusen-rockets/physics";
74
+ *
75
+ * console.log(atmosphereAt(EARTH_ATMOSPHERE, 0).pressure);
76
+ * ```
77
+ */
78
+ export declare const EARTH_ATMOSPHERE: AtmosphereModel;
79
+ /**
80
+ * Mars's air as Orbiter 2006 models it (Orbiter's MarsAtm2006), ending at 100 km.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * import { atmosphereAt, MARS_ATMOSPHERE } from "@johnmorrisdotca/uchusen-rockets/physics";
85
+ *
86
+ * console.log(atmosphereAt(MARS_ATMOSPHERE, 0).pressure);
87
+ * ```
88
+ */
89
+ export declare const MARS_ATMOSPHERE: AtmosphereModel;
90
+ /**
91
+ * Venus's air as Orbiter 2006 models it (Orbiter's VenusAtm2006), ending at 200 km.
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * import { atmosphereAt, VENUS_ATMOSPHERE } from "@johnmorrisdotca/uchusen-rockets/physics";
96
+ *
97
+ * console.log(atmosphereAt(VENUS_ATMOSPHERE, 0).temperature);
98
+ * ```
99
+ */
100
+ export declare const VENUS_ATMOSPHERE: AtmosphereModel;
101
+ /**
102
+ * 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.
103
+ * @param model The model.
104
+ * @param altitude The geometric height above the reference radius, metres (below 0 the lowest layer goes on down).
105
+ * @returns The air there; all zero above the model's limit, as Orbiter has no air there.
106
+ * @example
107
+ * ```ts
108
+ * import { atmosphereAt, EARTH_ATMOSPHERE } from "@johnmorrisdotca/uchusen-rockets/physics";
109
+ *
110
+ * const air = atmosphereAt(EARTH_ATMOSPHERE, 30_000);
111
+ * console.log(air.temperature.toFixed(1), "K,", air.pressure.toFixed(0), "Pa,", air.density.toFixed(4), "kg/m^3");
112
+ * ```
113
+ */
114
+ export declare function atmosphereAt(model: AtmosphereModel, altitude: number): Air;
115
+ /**
116
+ * 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.
117
+ * @param altitude The geometric height above sea level, metres, from -5,000 to 86,000.
118
+ * @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.
119
+ * @example
120
+ * ```ts
121
+ * import { standardAtmosphere } from "@johnmorrisdotca/uchusen-rockets/physics";
122
+ *
123
+ * const air = standardAtmosphere(11_000);
124
+ * console.log(air?.temperature.toFixed(3), "K,", air?.pressure.toFixed(0), "Pa");
125
+ * ```
126
+ */
127
+ export declare function standardAtmosphere(altitude: number): Air | null;
128
+ /**
129
+ * 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`.
130
+ * @param position The craft's position from the planet's centre, metres.
131
+ * @param velocity Its speed relative to the planet's centre, metres a second.
132
+ * @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).
133
+ * @returns The velocity through the air, metres a second.
134
+ * @example
135
+ * ```ts
136
+ * import { airVelocity } from "@johnmorrisdotca/uchusen-rockets/physics";
137
+ *
138
+ * console.log(airVelocity([6_778_000, 0, 0], [0, 7_669, 0], [0, 0, 7.292115e-5]));
139
+ * ```
140
+ */
141
+ export declare const airVelocity: (position: Vec3, velocity: Vec3, spin: Vec3) => Vec3;
142
+ /**
143
+ * The dynamic pressure of the air on a craft, `rho v^2 / 2` (Orbiter's `dynp`).
144
+ * @param density The air's density, kg/m^3.
145
+ * @param airspeed The speed through the air, metres a second.
146
+ * @returns The dynamic pressure, pascals.
147
+ * @example
148
+ * ```ts
149
+ * import { dynamicPressure } from "@johnmorrisdotca/uchusen-rockets/physics";
150
+ *
151
+ * console.log(dynamicPressure(0.4, 400));
152
+ * ```
153
+ */
154
+ export declare const dynamicPressure: (density: number, airspeed: number) => number;
155
+ /**
156
+ * The Mach number: the speed through the air over the speed of sound there (Orbiter's `atmM`).
157
+ * @param airspeed The speed through the air, metres a second.
158
+ * @param air The air, from {@link atmosphereAt} or {@link standardAtmosphere}.
159
+ * @returns The Mach number; 0 where there is no air.
160
+ * @example
161
+ * ```ts
162
+ * import { atmosphereAt, EARTH_ATMOSPHERE, machNumber } from "@johnmorrisdotca/uchusen-rockets/physics";
163
+ *
164
+ * console.log(machNumber(600, atmosphereAt(EARTH_ATMOSPHERE, 11_000)).toFixed(2));
165
+ * ```
166
+ */
167
+ export declare const machNumber: (airspeed: number, air: Air) => number;
168
+ /**
169
+ * 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).
170
+ * @param density The air's density, kg/m^3.
171
+ * @param air The velocity through the air, from {@link airVelocity}, metres a second.
172
+ * @param dragArea The drag coefficient times the reference area, `Cd A`, m^2.
173
+ * @param mass The craft's mass, kilograms.
174
+ * @returns The acceleration, metres a second squared.
175
+ * @example
176
+ * ```ts
177
+ * import { atmosphereAt, dragAcceleration, EARTH_ATMOSPHERE } from "@johnmorrisdotca/uchusen-rockets/physics";
178
+ *
179
+ * console.log(dragAcceleration(atmosphereAt(EARTH_ATMOSPHERE, 60_000).density, [0, 7_000, 0], 1.3 * 12, 9_000));
180
+ * ```
181
+ */
182
+ export declare function dragAcceleration(density: number, air: Vec3, dragArea: number, mass: number): Vec3;
@@ -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
+ }