Better accuracy

This commit is contained in:
2026-03-04 19:40:46 +00:00
parent 6885801078
commit 6af56f478f
5 changed files with 371 additions and 71 deletions

View File

@@ -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, // 01 (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,
}
}