init
This commit is contained in:
234
src/engine/rocketDesignCalcs.js
Normal file
234
src/engine/rocketDesignCalcs.js
Normal file
@@ -0,0 +1,234 @@
|
||||
// Pure calculation functions for rocket vehicle design (no React)
|
||||
|
||||
const G0 = 9.80665 // m/s² standard gravity
|
||||
|
||||
/**
|
||||
* Calculate full rocket geometry and performance from engine data and rocket inputs.
|
||||
*
|
||||
* @param {object} engineData — results section from engine JSON (allThermo, nozzleGeometry, etc.)
|
||||
* @param {object} rocketInputs — {
|
||||
* outerRadius, // m — vehicle outer radius
|
||||
* burnTime, // s
|
||||
* arrangement, // 'coaxial' | 'tandem'
|
||||
* innerPropellant, // 'fuel' | 'ox' (coaxial only — which propellant goes in inner tank)
|
||||
* rhoFuel, // kg/m³
|
||||
* rhoOx, // kg/m³
|
||||
* payloadMass, // kg
|
||||
* payloadBayLength, // m
|
||||
* structMassFraction, // 0–1 (dry mass fraction of propellant mass)
|
||||
* }
|
||||
* @returns {object|null}
|
||||
*/
|
||||
export function calcRocketGeometry(engineData, rocketInputs) {
|
||||
if (!engineData || !rocketInputs) return null
|
||||
|
||||
const {
|
||||
outerRadius,
|
||||
burnTime,
|
||||
arrangement,
|
||||
innerPropellant,
|
||||
rhoFuel,
|
||||
rhoOx,
|
||||
payloadMass,
|
||||
payloadBayLength,
|
||||
structMassFraction,
|
||||
} = rocketInputs
|
||||
|
||||
const Isp = engineData?.allThermo?.Isp
|
||||
const F = engineData?.allThermo?.F
|
||||
const mdot = engineData?.allThermo?.mdot
|
||||
const OF = engineData?.allThermo?.OF
|
||||
|
||||
// Flow rates: use direct values if solved, otherwise derive from mdot + OF
|
||||
let mdot_f = engineData?.allThermo?.mdot_f
|
||||
let mdot_ox = engineData?.allThermo?.mdot_ox
|
||||
if (mdot && OF && isFinite(mdot) && isFinite(OF) && OF > 0) {
|
||||
if (!mdot_f || !isFinite(mdot_f)) mdot_f = mdot / (1 + OF)
|
||||
if (!mdot_ox || !isFinite(mdot_ox)) mdot_ox = mdot * OF / (1 + OF)
|
||||
}
|
||||
|
||||
// Require at minimum: radius, propellant densities, and flow rates from an engine
|
||||
if (
|
||||
!outerRadius || outerRadius <= 0 ||
|
||||
!rhoFuel || !rhoOx ||
|
||||
!mdot_f || !mdot_ox ||
|
||||
!isFinite(mdot_f) || !isFinite(mdot_ox)
|
||||
) return null
|
||||
|
||||
const R = outerRadius
|
||||
const tb = (burnTime && burnTime > 0) ? burnTime : 30
|
||||
|
||||
// ── Propellant volumes ──────────────────────────────────────────────
|
||||
const m_fuel = mdot_f * tb
|
||||
const m_ox = mdot_ox * tb
|
||||
const V_fuel = m_fuel / rhoFuel
|
||||
const V_ox = m_ox / rhoOx
|
||||
const V_prop = V_fuel + V_ox
|
||||
|
||||
// ── Nose cone (ogive approximation: height = 2R) ────────────────────
|
||||
const L_nose = 2 * R
|
||||
|
||||
// ── Payload bay ─────────────────────────────────────────────────────
|
||||
const L_payload = payloadBayLength ?? 0
|
||||
|
||||
// ── Engine section (from nozzle geometry if available) ──────────────
|
||||
const ng = engineData?.nozzleGeometry
|
||||
const cg = engineData?.chamberGeometry
|
||||
const L_engine = (ng?.Ln ?? 0) + (cg?.Lc ?? 0)
|
||||
|
||||
// ── Tank geometry ───────────────────────────────────────────────────
|
||||
let L_tank_fuel, L_tank_ox, L_tank, r_inner
|
||||
|
||||
if (arrangement === 'coaxial') {
|
||||
// Both tanks share the same axial section; total volume in annulus + inner cylinder
|
||||
// Inner tank radius from the smaller propellant volume
|
||||
// Outer tank occupies the remaining annular area
|
||||
const V_inner = innerPropellant === 'fuel' ? V_fuel : V_ox
|
||||
const V_outer = innerPropellant === 'fuel' ? V_ox : V_fuel
|
||||
|
||||
// Solve for tank length using outer volume first (conservative — outer is usually larger)
|
||||
// V_outer = π (R² - r_inner²) L_tank and V_inner = π r_inner² L_tank
|
||||
// From V_inner / (V_inner + V_outer) = r_inner² / R²
|
||||
// → r_inner = R √(V_inner / V_prop_total)
|
||||
r_inner = R * Math.sqrt(V_inner / V_prop)
|
||||
// Guard: inner radius must be smaller than outer
|
||||
if (r_inner >= R) r_inner = R * 0.7
|
||||
|
||||
L_tank = V_prop / (Math.PI * R * R)
|
||||
L_tank_fuel = arrangement === 'coaxial' ? L_tank : null
|
||||
L_tank_ox = arrangement === 'coaxial' ? L_tank : null
|
||||
} else {
|
||||
// Tandem: stacked cylinders
|
||||
L_tank_fuel = V_fuel / (Math.PI * R * R)
|
||||
L_tank_ox = V_ox / (Math.PI * R * R)
|
||||
L_tank = L_tank_fuel + L_tank_ox
|
||||
r_inner = null
|
||||
}
|
||||
|
||||
// ── Total vehicle length ────────────────────────────────────────────
|
||||
const totalLength = L_nose + L_payload + L_tank + L_engine
|
||||
|
||||
// ── Mass budget ─────────────────────────────────────────────────────
|
||||
const m_prop = m_fuel + m_ox
|
||||
const m_struct = m_prop * (structMassFraction ?? 0.10)
|
||||
const m_payload = payloadMass ?? 0
|
||||
const m_dry = m_struct + m_payload
|
||||
const m_wet = m_prop + m_dry
|
||||
|
||||
const massRatio = m_wet / m_dry
|
||||
|
||||
// ── Tsiolkovsky delta-v ─────────────────────────────────────────────
|
||||
const deltaV = Isp && isFinite(Isp) && massRatio > 1
|
||||
? Isp * G0 * Math.log(massRatio)
|
||||
: null
|
||||
|
||||
// ── TWR at liftoff ──────────────────────────────────────────────────
|
||||
const TWR = F && isFinite(F) && m_wet > 0
|
||||
? F / (m_wet * G0)
|
||||
: null
|
||||
|
||||
return {
|
||||
// Dimensions
|
||||
outerRadius: R,
|
||||
L_nose,
|
||||
L_payload,
|
||||
L_tank,
|
||||
L_tank_fuel,
|
||||
L_tank_ox,
|
||||
L_engine,
|
||||
totalLength,
|
||||
r_inner,
|
||||
// Mass
|
||||
m_fuel,
|
||||
m_ox,
|
||||
V_fuel,
|
||||
V_ox,
|
||||
m_prop,
|
||||
m_struct,
|
||||
m_payload,
|
||||
m_dry,
|
||||
m_wet,
|
||||
// Performance
|
||||
massRatio,
|
||||
deltaV,
|
||||
TWR,
|
||||
// Config
|
||||
arrangement,
|
||||
innerPropellant: arrangement === 'coaxial' ? innerPropellant : null,
|
||||
// Nozzle geometry for 3D model (null → fall back to a generic flare)
|
||||
nozzleExitRadius: ng?.re ?? null,
|
||||
nozzleThroatRadius: ng?.rt ?? null,
|
||||
chamberLength: cg?.Lc ?? 0,
|
||||
nozzleLength: ng?.Ln ?? 0,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of { key, label, ok, value } requirement objects so the UI
|
||||
* can show exactly what is missing before the calc can run.
|
||||
*/
|
||||
export function diagnoseRocketInputs(engineCalcData, rocketInputs) {
|
||||
const t = engineCalcData?.allThermo ?? {}
|
||||
const mdot = t.mdot
|
||||
const OF = t.OF
|
||||
const mdot_f_raw = t.mdot_f
|
||||
const mdot_ox_raw = t.mdot_ox
|
||||
|
||||
// Derived flow rates (same logic as calcRocketGeometry)
|
||||
let mdot_f = mdot_f_raw
|
||||
let mdot_ox = mdot_ox_raw
|
||||
if (mdot && OF && isFinite(mdot) && isFinite(OF) && OF > 0) {
|
||||
if (!mdot_f || !isFinite(mdot_f)) mdot_f = mdot / (1 + OF)
|
||||
if (!mdot_ox || !isFinite(mdot_ox)) mdot_ox = mdot * OF / (1 + OF)
|
||||
}
|
||||
|
||||
const R = rocketInputs?.outerRadius
|
||||
const bt = rocketInputs?.burnTime
|
||||
|
||||
const fmt = v => (v != null && isFinite(v)) ? v.toPrecision(4) : null
|
||||
|
||||
return [
|
||||
{
|
||||
key: 'mdot',
|
||||
label: 'Total mass flow (ṁ)',
|
||||
ok: !!(mdot && isFinite(mdot)),
|
||||
value: fmt(mdot),
|
||||
hint: 'Enter ṁ or Thrust + Isp on the Engine page',
|
||||
},
|
||||
{
|
||||
key: 'OF',
|
||||
label: 'O/F ratio',
|
||||
ok: !!(OF && isFinite(OF) && OF > 0),
|
||||
value: fmt(OF),
|
||||
hint: 'Enter O/F ratio on the Engine page',
|
||||
},
|
||||
{
|
||||
key: 'mdot_f',
|
||||
label: 'Fuel flow rate (ṁ_f)',
|
||||
ok: !!(mdot_f && isFinite(mdot_f)),
|
||||
value: fmt(mdot_f),
|
||||
hint: mdot && OF ? 'Derived from ṁ + O/F' : 'Needs ṁ and O/F both solved',
|
||||
},
|
||||
{
|
||||
key: 'mdot_ox',
|
||||
label: 'Oxidizer flow rate (ṁ_ox)',
|
||||
ok: !!(mdot_ox && isFinite(mdot_ox)),
|
||||
value: fmt(mdot_ox),
|
||||
hint: mdot && OF ? 'Derived from ṁ + O/F' : 'Needs ṁ and O/F both solved',
|
||||
},
|
||||
{
|
||||
key: 'outerRadius',
|
||||
label: 'Outer diameter',
|
||||
ok: !!(R && R > 0),
|
||||
value: R ? `${(R * 2000).toFixed(0)} mm` : null,
|
||||
hint: 'Enter outer diameter in Vehicle Geometry section',
|
||||
},
|
||||
{
|
||||
key: 'burnTime',
|
||||
label: 'Burn time',
|
||||
ok: true, // always ok — falls back to 30 s
|
||||
value: bt ? `${bt} s` : '30 s (default)',
|
||||
hint: null,
|
||||
},
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user