Better accuracy
This commit is contained in:
@@ -1,6 +1,46 @@
|
||||
// Pure calculation functions for rocket vehicle design (no React)
|
||||
|
||||
import { STRUCTURAL_MATERIALS } from './knowledgebaseData.js'
|
||||
|
||||
const G0 = 9.80665 // m/s² standard gravity
|
||||
const R_HE = 2077 // J/kg/K — specific gas constant for helium
|
||||
const T_PRESSURANT = 293 // K — standard pressurant storage temperature
|
||||
|
||||
/**
|
||||
* Generate nose cone profile points (r vs x) for a given shape.
|
||||
* All shapes share L = 2R.
|
||||
*
|
||||
* @param {string} shape — 'conical' | 'tangentOgive' | 'vonKarman'
|
||||
* @param {number} R — base radius (m)
|
||||
* @param {number} L — nose length (m)
|
||||
* @param {number} n — number of points (default 24)
|
||||
* @returns {Array<{x, r}>} profile points from tip (x=0) to base (x=L)
|
||||
*/
|
||||
function noseConeProfile(shape, R, L, n = 24) {
|
||||
const points = []
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
const x = (i / (n - 1)) * L
|
||||
let r
|
||||
|
||||
if (shape === 'tangentOgive') {
|
||||
// Tangent ogive: ρ = (R² + L²) / (2R)
|
||||
const rho = (R * R + L * L) / (2 * R)
|
||||
r = Math.sqrt(rho * rho - (L - x) * (L - x)) - (rho - R)
|
||||
} else if (shape === 'vonKarman') {
|
||||
// Von Kármán (LD-Haack): θ = acos(1 − 2x/L), r = (R/√π) * √(θ − sin(2θ)/2)
|
||||
const theta = Math.acos(1 - 2 * x / L)
|
||||
r = (R / Math.sqrt(Math.PI)) * Math.sqrt(theta - Math.sin(2 * theta) / 2)
|
||||
} else {
|
||||
// Default conical: r(x) = R * (x / L)
|
||||
r = R * (x / L)
|
||||
}
|
||||
|
||||
points.push({ x, r: Math.max(0, r) })
|
||||
}
|
||||
|
||||
return points
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate full rocket geometry and performance from engine data and rocket inputs.
|
||||
@@ -15,7 +55,13 @@ const G0 = 9.80665 // m/s² standard gravity
|
||||
* rhoOx, // kg/m³
|
||||
* payloadMass, // kg
|
||||
* payloadBayLength, // m
|
||||
* structMassFraction, // 0–1 (dry mass fraction of propellant mass)
|
||||
* tankMaterialId, // 'al_6061_t6' | 'ss_304' | ... (structural material)
|
||||
* tankSafetyFactor, // safety factor for hoop stress
|
||||
* feedSystem, // 'pressure_fed' | 'pump_fed'
|
||||
* ullagePercent, // % extra volume above propellant for gases
|
||||
* otherStructFraction, // fraction of propellant mass for non-tank structure
|
||||
* engineDryMass, // kg (from engine structural results, null if not available)
|
||||
* noseConeShape, // 'conical' | 'tangentOgive' | 'vonKarman'
|
||||
* }
|
||||
* @returns {object|null}
|
||||
*/
|
||||
@@ -31,13 +77,20 @@ export function calcRocketGeometry(engineData, rocketInputs) {
|
||||
rhoOx,
|
||||
payloadMass,
|
||||
payloadBayLength,
|
||||
structMassFraction,
|
||||
tankMaterialId,
|
||||
tankSafetyFactor,
|
||||
feedSystem,
|
||||
ullagePercent,
|
||||
otherStructFraction,
|
||||
engineDryMass,
|
||||
noseConeShape,
|
||||
} = rocketInputs
|
||||
|
||||
const Isp = engineData?.allThermo?.Isp
|
||||
const F = engineData?.allThermo?.F
|
||||
const mdot = engineData?.allThermo?.mdot
|
||||
const OF = engineData?.allThermo?.OF
|
||||
const p0 = engineData?.allThermo?.p0 ?? 6.9e6 // Chamber pressure, default 6.9 MPa
|
||||
|
||||
// Flow rates: use direct values if solved, otherwise derive from mdot + OF
|
||||
let mdot_f = engineData?.allThermo?.mdot_f
|
||||
@@ -65,8 +118,9 @@ export function calcRocketGeometry(engineData, rocketInputs) {
|
||||
const V_ox = m_ox / rhoOx
|
||||
const V_prop = V_fuel + V_ox
|
||||
|
||||
// ── Nose cone (ogive approximation: height = 2R) ────────────────────
|
||||
// ── Nose cone (height = 2R) ───────────────────────────────────────────
|
||||
const L_nose = 2 * R
|
||||
const noseProfilePoints = noseConeProfile(noseConeShape ?? 'conical', R, L_nose, 24)
|
||||
|
||||
// ── Payload bay ─────────────────────────────────────────────────────
|
||||
const L_payload = payloadBayLength ?? 0
|
||||
@@ -76,33 +130,90 @@ export function calcRocketGeometry(engineData, rocketInputs) {
|
||||
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
|
||||
// ── Tank geometry with domes and ullage ──────────────────────────────
|
||||
let L_tank_fuel, L_tank_fuel_cyl, L_tank_ox, L_tank_ox_cyl, L_tank, r_inner, m_tank_fuel, m_tank_ox, m_tank_total
|
||||
let t_wall = null
|
||||
|
||||
const ullage = ullagePercent ?? 5
|
||||
const materialId = tankMaterialId ?? 'al_6061_t6'
|
||||
const material = STRUCTURAL_MATERIALS.find(m => m.id === materialId) || STRUCTURAL_MATERIALS[0]
|
||||
const SF = tankSafetyFactor ?? 2.0
|
||||
|
||||
// Tank pressure (Pa)
|
||||
const p_tank = feedSystem === 'pump_fed' ? 2e6 : p0 * 1.2
|
||||
|
||||
// Hoop stress wall thickness (same for all tanks at same pressure and radius)
|
||||
t_wall = (p_tank * R) / (material.yieldStrength / SF)
|
||||
|
||||
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
|
||||
// Coaxial: hemispherical domes only on outer tank; inner tank is annular (flat bulkheads)
|
||||
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
|
||||
// Outer tank: account for dome volume
|
||||
const V_dome_outer = (4 / 3) * Math.PI * R * R * R
|
||||
const V_eff_outer = V_outer * (1 + ullage / 100)
|
||||
const L_tank_cyl_outer = Math.max(0, (V_eff_outer - V_dome_outer) / (Math.PI * R * R))
|
||||
|
||||
// Inner annular tank (no domes): full volume in cylindrical section
|
||||
const A_annulus = Math.PI * (R * R - r_inner * r_inner)
|
||||
const L_tank_cyl_inner = V_inner / A_annulus
|
||||
|
||||
// Total outer tank length
|
||||
const L_tank_outer = L_tank_cyl_outer + 2 * R
|
||||
|
||||
// Both tanks are coaxial, so effective length is the longer one
|
||||
L_tank = Math.max(L_tank_outer, L_tank_cyl_inner)
|
||||
|
||||
// For compatibility with tandem display
|
||||
L_tank_fuel = L_tank
|
||||
L_tank_fuel_cyl = L_tank_cyl_outer
|
||||
L_tank_ox = L_tank
|
||||
L_tank_ox_cyl = L_tank_cyl_inner
|
||||
|
||||
// Coaxial tank mass: outer tank with domes + inner annular section
|
||||
const m_cyl_outer = 2 * Math.PI * R * t_wall * L_tank_cyl_outer * material.density
|
||||
const m_dome_outer = 4 * Math.PI * R * R * t_wall * material.density
|
||||
const m_tank_outer = m_cyl_outer + m_dome_outer
|
||||
|
||||
const m_cyl_inner = 2 * Math.PI * r_inner * t_wall * L_tank_cyl_inner * material.density
|
||||
const m_tank_inner = m_cyl_inner // Inner annular tank, no domes
|
||||
|
||||
m_tank_total = m_tank_outer + m_tank_inner
|
||||
} else {
|
||||
// Tandem: stacked cylinders
|
||||
L_tank_fuel = V_fuel / (Math.PI * R * R)
|
||||
L_tank_ox = V_ox / (Math.PI * R * R)
|
||||
// Tandem: both tanks have hemispherical domes
|
||||
// Effective volumes include ullage
|
||||
const V_eff_fuel = V_fuel * (1 + ullage / 100)
|
||||
const V_eff_ox = V_ox * (1 + ullage / 100)
|
||||
|
||||
// Dome volume for each tank
|
||||
const V_dome_one = (4 / 3) * Math.PI * R * R * R
|
||||
|
||||
// Cylindrical sections
|
||||
L_tank_fuel_cyl = Math.max(0, (V_eff_fuel - V_dome_one) / (Math.PI * R * R))
|
||||
L_tank_ox_cyl = Math.max(0, (V_eff_ox - V_dome_one) / (Math.PI * R * R))
|
||||
|
||||
// Total tank lengths including domes
|
||||
L_tank_fuel = L_tank_fuel_cyl + 2 * R
|
||||
L_tank_ox = L_tank_ox_cyl + 2 * R
|
||||
L_tank = L_tank_fuel + L_tank_ox
|
||||
r_inner = null
|
||||
|
||||
// Tank mass: cylindrical + dome shells (two hemispheres = full sphere surface area)
|
||||
m_tank_fuel = (2 * Math.PI * R * t_wall * L_tank_fuel_cyl * material.density) + (4 * Math.PI * R * R * t_wall * material.density)
|
||||
m_tank_ox = (2 * Math.PI * R * t_wall * L_tank_ox_cyl * material.density) + (4 * Math.PI * R * R * t_wall * material.density)
|
||||
m_tank_total = m_tank_fuel + m_tank_ox
|
||||
}
|
||||
|
||||
// ── Pressurant system (pressure-fed only) ─────────────────────────────
|
||||
let m_pressurant = 0
|
||||
if (feedSystem === 'pressure_fed') {
|
||||
const m_He = (p_tank * V_prop) / (R_HE * T_PRESSURANT)
|
||||
const m_bottle = m_He * 4 // Conservative bottle mass estimate
|
||||
m_pressurant = m_He + m_bottle
|
||||
}
|
||||
|
||||
// ── Total vehicle length ────────────────────────────────────────────
|
||||
@@ -110,9 +221,11 @@ export function calcRocketGeometry(engineData, rocketInputs) {
|
||||
|
||||
// ── Mass budget ─────────────────────────────────────────────────────
|
||||
const m_prop = m_fuel + m_ox
|
||||
const m_struct = m_prop * (structMassFraction ?? 0.10)
|
||||
const m_other_struct = m_prop * (otherStructFraction ?? 0.05)
|
||||
const m_struct = m_tank_total + m_other_struct
|
||||
const m_payload = payloadMass ?? 0
|
||||
const m_dry = m_struct + m_payload
|
||||
const m_engine = engineDryMass ?? 0
|
||||
const m_dry = m_struct + m_payload + m_engine + m_pressurant
|
||||
const m_wet = m_prop + m_dry
|
||||
|
||||
const massRatio = m_wet / m_dry
|
||||
@@ -135,6 +248,8 @@ export function calcRocketGeometry(engineData, rocketInputs) {
|
||||
L_tank,
|
||||
L_tank_fuel,
|
||||
L_tank_ox,
|
||||
L_tank_fuel_cyl,
|
||||
L_tank_ox_cyl,
|
||||
L_engine,
|
||||
totalLength,
|
||||
r_inner,
|
||||
@@ -146,8 +261,13 @@ export function calcRocketGeometry(engineData, rocketInputs) {
|
||||
m_prop,
|
||||
m_struct,
|
||||
m_payload,
|
||||
m_engine,
|
||||
m_dry,
|
||||
m_wet,
|
||||
m_tank_total,
|
||||
m_pressurant,
|
||||
// Tank structure
|
||||
t_wall,
|
||||
// Performance
|
||||
massRatio,
|
||||
deltaV,
|
||||
@@ -160,6 +280,8 @@ export function calcRocketGeometry(engineData, rocketInputs) {
|
||||
nozzleThroatRadius: ng?.rt ?? null,
|
||||
chamberLength: cg?.Lc ?? 0,
|
||||
nozzleLength: ng?.Ln ?? 0,
|
||||
// Nose cone profile for 3D model
|
||||
noseProfilePoints,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user