Trajectories
This commit is contained in:
137
README.md
137
README.md
@@ -1,57 +1,122 @@
|
|||||||
# RocketTools - Rocket Propulsion Engineering Calculator
|
# RocketTools — Rocketry Equation Solver & Design Suite
|
||||||
|
|
||||||
RocketTools is a web-based application that provides engineering calculators and tools for rocket propulsion. Built with React and Vite, it serves both students learning aerospace engineering concepts and professional engineers performing complex propulsion calculations.
|
A modern web-based rocket engine and vehicle design calculator built with **React + Vite**, featuring constraint-based equation solving, 3D modeling, and trajectory simulation.
|
||||||
|
|
||||||
## Project Status
|
## Features
|
||||||
|
|
||||||
✅ Documentation Finalized and Approved
|
### 🧮 Solver
|
||||||
✅ Core Functionality Implemented
|
- **Constraint propagation solver** for rocket equations
|
||||||
🚧 Additional Features in Development
|
- Drag-and-drop variable palette with live computation
|
||||||
|
- Automatically solves for unknowns given constraints
|
||||||
|
- Covers thrust, Isp, chamber conditions, nozzle geometry, performance metrics
|
||||||
|
|
||||||
## Key Features
|
### 🔥 Engine Design
|
||||||
|
- **Chamber & nozzle calculations** (pressure, temperature, area ratios)
|
||||||
|
- **Ablative material erosion** with pressure-dependent rates
|
||||||
|
- **Structural sizing** (hoop stress, wall thickness, mass budget)
|
||||||
|
- **Injector & coolant design** options
|
||||||
|
- Export/import engine specs as JSON
|
||||||
|
|
||||||
- **Equation Solver**: Drag-and-drop interface for rocketry variables with automatic solving
|
### 🚀 Rocket Design
|
||||||
- **Engine Designer**: 3D visualization of engine models using React Three Fiber
|
- **Vehicle geometry** with configurable tank layouts (tandem, coaxial)
|
||||||
- **Knowledge Base**: Reference materials for fuels and oxidizers
|
- **Tank structural analysis** (domes, ullage, pressure-fed/pump-fed)
|
||||||
- **Export Capabilities**: Save results in ODT and JSON formats
|
- **Nose cone profiles** (conical, tangent ogive, Von Kármán)
|
||||||
|
- **Mass budget** integration (payload, structure, engines, pressurant)
|
||||||
|
- **3D visualization** with interactive model
|
||||||
|
|
||||||
## Project Documentation
|
### 📈 Trajectory Simulation
|
||||||
|
- **RK4 integrator** for realistic flight paths
|
||||||
|
- **Atmospheric model** (US Standard piecewise)
|
||||||
|
- **Drag & gravity** effects
|
||||||
|
- **Pitch program** (vertical hold → gravity turn)
|
||||||
|
- **Event detection** (liftoff, MECO, Max Q, apogee, landing)
|
||||||
|
- **Playback controls** with timeline scrubber
|
||||||
|
|
||||||
All project documentation has been finalized and approved:
|
### 📚 Knowledgebase
|
||||||
|
- **Fuels & oxidizers** library with propellant properties
|
||||||
|
- **Ablative materials** with pressure exponents
|
||||||
|
- **Structural materials** with yield strengths
|
||||||
|
- **Equation reference** with derivations
|
||||||
|
|
||||||
- [Project Overview (Final)](PROJECT_OVERVIEW_FINAL.md)
|
## Quick Start
|
||||||
- [Project Structure (Final)](PROJECT_STRUCTURE_FINAL.md)
|
|
||||||
- [Approved Consolidated Document](ROCKETTOOLS_PROJECT_APPROVED.md)
|
### Installation
|
||||||
- [Stakeholder Overview](PROJECT_DESCRIPTION_STAKEHOLDER.md)
|
```bash
|
||||||
- [Consolidated Refined Documentation](ROCKETTOOLS_CONSOLIDATED_REFINED.md)
|
npm install
|
||||||
- [Consolidated Refined Documentation](ROCKETTOOLS_CONSOLIDATED_REFINED.md)
|
```
|
||||||
|
|
||||||
|
### Development
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
Opens at `http://localhost:5173`
|
||||||
|
|
||||||
|
### Build
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
## Technology Stack
|
## Technology Stack
|
||||||
|
|
||||||
- React 19 with Vite
|
- **Framework**: React 19 + React Router
|
||||||
- React Three Fiber for 3D visualization
|
- **Build**: Vite 7.3
|
||||||
- DnD Kit for drag-and-drop functionality
|
- **Styling**: Tailwind CSS v4
|
||||||
- TailwindCSS for styling
|
- **3D Graphics**: Three.js + @react-three/fiber
|
||||||
- React Router for navigation
|
- **UI**: @dnd-kit (drag-and-drop), @react-three/drei
|
||||||
|
|
||||||
## Development Setup
|
## Core Architecture
|
||||||
|
|
||||||
```bash
|
See [ARCHITECTURE.md](docs/ARCHITECTURE.md) for detailed:
|
||||||
npm install
|
- Module organization
|
||||||
npm run dev
|
- Data flow patterns
|
||||||
|
- Solver algorithm
|
||||||
|
- 3D rendering pipeline
|
||||||
|
|
||||||
|
## Feature Documentation
|
||||||
|
|
||||||
|
Comprehensive guides for each module:
|
||||||
|
|
||||||
|
- **[Solver Guide](docs/SOLVER.md)** — Constraint propagation system, equation definitions
|
||||||
|
- **[Engine Design](docs/ENGINE_DESIGN.md)** — Combustion, ablation, structural analysis
|
||||||
|
- **[Rocket Design](docs/ROCKET_DESIGN.md)** — Tank geometry, mass budget, 3D modeling
|
||||||
|
- **[Trajectory Simulation](docs/TRAJECTORY.md)** — RK4 integration, atmosphere, events
|
||||||
|
- **[Knowledgebase](docs/KNOWLEDGEBASE.md)** — Fuels, materials, reference equations
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── pages/ # Route pages
|
||||||
|
├── components/ # React components
|
||||||
|
├── hooks/ # Custom React hooks (state management)
|
||||||
|
├── engine/ # Core calculations & algorithms
|
||||||
|
│ ├── variables.js # Variable definitions
|
||||||
|
│ ├── equations.js # Equation library
|
||||||
|
│ ├── solver.js # Constraint solver
|
||||||
|
│ ├── engineDesignCalcs.js # Engine calculations
|
||||||
|
│ ├── rocketDesignCalcs.js # Vehicle calculations
|
||||||
|
│ ├── trajectoryCalcs.js # Trajectory simulation
|
||||||
|
│ └── knowledgebaseData.js # Material databases
|
||||||
|
└── App.jsx # Router configuration
|
||||||
```
|
```
|
||||||
|
|
||||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
## Development Tips
|
||||||
|
|
||||||
Currently, two official plugins are available:
|
### Adding an Equation
|
||||||
|
1. Define variables in `src/engine/variables.js`
|
||||||
|
2. Add equation + solver in `src/engine/equations.js`
|
||||||
|
3. Automatically appears in solver palette
|
||||||
|
|
||||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
### Modifying Materials
|
||||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
- Edit `src/engine/knowledgebaseData.js`
|
||||||
|
- Changes auto-reflect via hot reload
|
||||||
|
|
||||||
## React Compiler
|
### Extending 3D Models
|
||||||
|
- Rocket: `src/components/rocket/RocketModel3D.jsx`
|
||||||
|
- Trajectory: `src/components/trajectory/TrajectoryPlot.jsx`
|
||||||
|
|
||||||
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
## License
|
||||||
|
|
||||||
## Expanding the ESLint configuration
|
Open-source. See LICENSE for details.
|
||||||
|
|
||||||
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
|
**Built with ❤️ for rocketry engineers and enthusiasts.**
|
||||||
|
|||||||
308
docs/ARCHITECTURE.md
Normal file
308
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
# RocketTools Architecture
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
RocketTools is a single-page application (SPA) built with **React + Vite**, organized into logical layers:
|
||||||
|
|
||||||
|
1. **Pages** — Route-level components
|
||||||
|
2. **Components** — Reusable UI elements
|
||||||
|
3. **Hooks** — State management & business logic
|
||||||
|
4. **Engine** — Core calculations & algorithms
|
||||||
|
5. **App.jsx** — Router configuration
|
||||||
|
|
||||||
|
## Layer Responsibilities
|
||||||
|
|
||||||
|
### Pages (`src/pages/`)
|
||||||
|
|
||||||
|
Each page is a route-level component that:
|
||||||
|
- Uses one or more custom hooks for state
|
||||||
|
- Composes domain-specific components
|
||||||
|
- Manages local UI state (modals, tabs, etc.)
|
||||||
|
|
||||||
|
**Pages:**
|
||||||
|
- `Home.jsx` — Landing page with feature overview
|
||||||
|
- `Solver.jsx` — Equation solver workspace
|
||||||
|
- `EnginePage.jsx` — Engine design tools
|
||||||
|
- `RocketPage.jsx` — Vehicle design tools
|
||||||
|
- `TrajectoryPage.jsx` — Flight simulation
|
||||||
|
- `DocsPage.jsx` — In-app documentation
|
||||||
|
- `KnowledgebaseEquationsPage.jsx` — Equation reference
|
||||||
|
- `KnowledgebaseFuelsPage.jsx` — Propellant database
|
||||||
|
- `KnowledgebaseAblativesPage.jsx` — Ablative materials
|
||||||
|
|
||||||
|
### Components (`src/components/`)
|
||||||
|
|
||||||
|
Pure, composable UI elements that accept props and render JSX.
|
||||||
|
|
||||||
|
**Solver Components:**
|
||||||
|
- `Workspace.jsx` — Drop zone for constraint cards
|
||||||
|
- `VariablePalette.jsx` — Draggable variable list
|
||||||
|
- `VariableCard.jsx` — Input field with unit conversion
|
||||||
|
- `ResultsPanel.jsx` — Results table display
|
||||||
|
|
||||||
|
**Engine/Rocket Components:**
|
||||||
|
- `rocket/RocketModel3D.jsx` — Three.js 3D scene
|
||||||
|
- `trajectory/TrajectoryPlot.jsx` — Canvas flight path chart
|
||||||
|
- `trajectory/TimelineBar.jsx` — Playback controls
|
||||||
|
|
||||||
|
### Hooks (`src/hooks/`)
|
||||||
|
|
||||||
|
Custom React hooks encapsulate domain state & logic.
|
||||||
|
|
||||||
|
**State Hooks:**
|
||||||
|
```
|
||||||
|
useSolver()
|
||||||
|
├─ variables: Map<id, value>
|
||||||
|
├─ constraints: Map<id, constraint>
|
||||||
|
├─ dispatch(action)
|
||||||
|
└─ results: Map<id, solvedValue>
|
||||||
|
|
||||||
|
useEngineDesign()
|
||||||
|
├─ engineInputs: {chamberPressure, fuels, ...}
|
||||||
|
├─ engineResults: {thrust, isp, ...}
|
||||||
|
├─ setEngineInputs()
|
||||||
|
└─ structureResults: {wallThickness, mass, ...}
|
||||||
|
|
||||||
|
useRocketDesign()
|
||||||
|
├─ rocketInputs: {outerRadius, tankConfig, ...}
|
||||||
|
├─ rocketResults: {totalMass, twr, ...}
|
||||||
|
└─ setRocketInputs()
|
||||||
|
|
||||||
|
useTrajectory()
|
||||||
|
├─ config: {vehicle, engine, atmosphere, ...}
|
||||||
|
├─ playback: {time, speed, isPlaying}
|
||||||
|
├─ events: [{type, time, data}]
|
||||||
|
└─ runSimulation()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Engine (`src/engine/`)
|
||||||
|
|
||||||
|
Pure JavaScript modules with **zero React dependencies**. These can be:
|
||||||
|
- Unit tested independently
|
||||||
|
- Reused in Node.js or other environments
|
||||||
|
- Debugged without UI overhead
|
||||||
|
|
||||||
|
**Module Breakdown:**
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `variables.js` | Variable catalog (name, unit, type) |
|
||||||
|
| `equations.js` | Equation definitions + solvers |
|
||||||
|
| `solver.js` | Constraint propagation algorithm |
|
||||||
|
| `numerics.js` | Math utilities (bisection, Mach) |
|
||||||
|
| `engineDesignCalcs.js` | Combustion, ablation, structure |
|
||||||
|
| `rocketDesignCalcs.js` | Tank geometry, mass budget |
|
||||||
|
| `trajectoryCalcs.js` | RK4 integrator, event detection |
|
||||||
|
| `knowledgebaseData.js` | Material & fuel databases |
|
||||||
|
|
||||||
|
## Data Flow Patterns
|
||||||
|
|
||||||
|
### Solver Workflow
|
||||||
|
|
||||||
|
```
|
||||||
|
User Input
|
||||||
|
↓
|
||||||
|
[VariableCard updates constraint]
|
||||||
|
↓
|
||||||
|
dispatch(setConstraint) → useSolver
|
||||||
|
↓
|
||||||
|
solver.solve() → [greedy propagation]
|
||||||
|
↓
|
||||||
|
useSolver state updates
|
||||||
|
↓
|
||||||
|
[VariableCard/ResultsPanel re-render]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Engine Design Workflow
|
||||||
|
|
||||||
|
```
|
||||||
|
User Input (EnginePage)
|
||||||
|
↓
|
||||||
|
useEngineDesign.setEngineInputs()
|
||||||
|
↓
|
||||||
|
engineDesignCalcs.calcCombustion()
|
||||||
|
+ engineDesignCalcs.calcCooling()
|
||||||
|
+ engineDesignCalcs.calcStructure()
|
||||||
|
↓
|
||||||
|
useSolver state updates (memoized)
|
||||||
|
↓
|
||||||
|
[EnginePage, RocketPage re-render]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rocket Design Workflow
|
||||||
|
|
||||||
|
```
|
||||||
|
User Input (RocketPage)
|
||||||
|
↓
|
||||||
|
useRocketDesign.setRocketInputs()
|
||||||
|
↓
|
||||||
|
rocketDesignCalcs.calcTankGeometry()
|
||||||
|
+ rocketDesignCalcs.calcMassBudget()
|
||||||
|
+ rocketDesignCalcs.noseConeProfile()
|
||||||
|
↓
|
||||||
|
useRocketDesign state updates (memoized)
|
||||||
|
↓
|
||||||
|
[RocketModel3D, RocketPage re-render]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Trajectory Simulation Workflow
|
||||||
|
|
||||||
|
```
|
||||||
|
User Config (TrajectoryPage)
|
||||||
|
↓
|
||||||
|
useTrajectory.runSimulation()
|
||||||
|
↓
|
||||||
|
trajectoryCalcs.runSimulation()
|
||||||
|
[RK4 integrator loop]
|
||||||
|
[Event detection]
|
||||||
|
↓
|
||||||
|
states[] + events[] returned
|
||||||
|
↓
|
||||||
|
useTrajectory state updates
|
||||||
|
↓
|
||||||
|
[TrajectoryPlot, TimelineBar re-render]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Design Decisions
|
||||||
|
|
||||||
|
### 1. Separation of Concerns
|
||||||
|
- **Engine layer**: Pure math, no React
|
||||||
|
- **Hooks layer**: State management, memoization, side effects
|
||||||
|
- **Component layer**: UI rendering only
|
||||||
|
- **Page layer**: Route composition, layout
|
||||||
|
|
||||||
|
**Benefit**: Engine code is testable, reusable, and maintainable independently of React.
|
||||||
|
|
||||||
|
### 2. Constraint Solver as Core
|
||||||
|
The solver uses **greedy constraint propagation**:
|
||||||
|
- User sets constraint on a variable
|
||||||
|
- Solver finds equations referencing that variable
|
||||||
|
- Solves for unknowns using math or bisection
|
||||||
|
- Propagates newly-computed values (recursive)
|
||||||
|
|
||||||
|
**Benefit**: Intuitive UI (users see automatic computation), flexible equation network.
|
||||||
|
|
||||||
|
### 3. Three.js via React Three Fiber
|
||||||
|
Rather than direct Three.js imperative API:
|
||||||
|
- Use `<Canvas>`, `<Mesh>`, etc. as React components
|
||||||
|
- Hooks like `useFrame()` for animations
|
||||||
|
- Drei helpers for common geometries
|
||||||
|
|
||||||
|
**Benefit**: Declarative, composable, integrates with React state.
|
||||||
|
|
||||||
|
### 4. Canvas for Trajectory Plotting
|
||||||
|
HTML5 Canvas instead of SVG:
|
||||||
|
- Handles 1000s of points efficiently
|
||||||
|
- Responsive to window resize
|
||||||
|
- Custom click/hover interactions
|
||||||
|
|
||||||
|
**Benefit**: Performance, flexibility.
|
||||||
|
|
||||||
|
## Memoization Strategy
|
||||||
|
|
||||||
|
Use `useMemo()` to avoid re-computation:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Hook definition
|
||||||
|
const engineResults = useMemo(() => {
|
||||||
|
return calcCombustion(engineInputs) // only runs when engineInputs changes
|
||||||
|
}, [engineInputs])
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rule**: Memoize expensive calculations; skip for cheap operations.
|
||||||
|
|
||||||
|
## State Management (No Redux)
|
||||||
|
|
||||||
|
React hooks provide sufficient state management for this app:
|
||||||
|
- `useState()` for local UI state
|
||||||
|
- Custom hooks + `useCallback()` for complex logic
|
||||||
|
- `useMemo()` for expensive computations
|
||||||
|
- Props drilling is acceptable for this size
|
||||||
|
|
||||||
|
**Alternative**: Could migrate to Redux/Zustand if app grows significantly.
|
||||||
|
|
||||||
|
## 3D Rendering Pipeline
|
||||||
|
|
||||||
|
```
|
||||||
|
RocketModel3D.jsx (R3F Canvas)
|
||||||
|
↓
|
||||||
|
[useRocketDesign hook provides geometry]
|
||||||
|
↓
|
||||||
|
TankSection, NoseCone components
|
||||||
|
↓
|
||||||
|
THREE.CylinderGeometry, SphereGeometry, LatheGeometry
|
||||||
|
↓
|
||||||
|
Material (MeshStandardMaterial)
|
||||||
|
↓
|
||||||
|
Lighting, OrbitControls
|
||||||
|
↓
|
||||||
|
Canvas rendered to DOM
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
No tests currently, but recommended:
|
||||||
|
|
||||||
|
**Unit Tests** (Jest):
|
||||||
|
```javascript
|
||||||
|
// src/engine/__tests__/solver.test.js
|
||||||
|
import { solve } from '../solver.js'
|
||||||
|
test('solves linear equation', () => {
|
||||||
|
const eqs = [{var: 'x', fn: (x) => 2*x - 5}]
|
||||||
|
const result = solve(eqs, {y: 10})
|
||||||
|
expect(result.x).toBeCloseTo(2.5)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Integration Tests** (React Testing Library):
|
||||||
|
```javascript
|
||||||
|
// src/pages/__tests__/Solver.test.jsx
|
||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import Solver from '../Solver'
|
||||||
|
test('Solver page renders', () => {
|
||||||
|
render(<Solver />)
|
||||||
|
expect(screen.getByText(/Equation Solver/i)).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
1. **Memoization** — Expensive calculations in `useMemo()`
|
||||||
|
2. **Lazy Loading** — Routes loaded on-demand (React Router)
|
||||||
|
3. **Canvas Rendering** — 60 FPS for trajectory animation
|
||||||
|
4. **Variable Updates** — Solver propagation is greedy (stops early)
|
||||||
|
|
||||||
|
**Potential Bottlenecks:**
|
||||||
|
- Large variable networks (100+ equations)
|
||||||
|
- Complex 3D geometries (reduce polygon count)
|
||||||
|
- Trajectory with 1000s of timesteps (reduce accuracy or cache)
|
||||||
|
|
||||||
|
## File Organization Best Practices
|
||||||
|
|
||||||
|
- **One component per file** (except tiny helpers)
|
||||||
|
- **Colocate tests** with source (`__tests__` subfolder)
|
||||||
|
- **Group by domain** (rocket, trajectory, solver) not by type
|
||||||
|
- **Keep hooks small** (< 50 lines of logic)
|
||||||
|
- **Engine code** has zero dependencies on React or UI
|
||||||
|
|
||||||
|
## Extension Points
|
||||||
|
|
||||||
|
### Adding a New Page
|
||||||
|
1. Create `src/pages/NewPage.jsx`
|
||||||
|
2. Add route in `App.jsx`
|
||||||
|
3. Add NavLink if needed
|
||||||
|
|
||||||
|
### Adding a New Feature Module
|
||||||
|
1. Create hook: `src/hooks/useNewFeature.js`
|
||||||
|
2. Create components: `src/components/NewFeature*.jsx`
|
||||||
|
3. Add calculations to `src/engine/` if needed
|
||||||
|
4. Create page: `src/pages/NewFeaturePage.jsx`
|
||||||
|
|
||||||
|
### Adding Material Properties
|
||||||
|
1. Edit `src/engine/knowledgebaseData.js`
|
||||||
|
2. Add to `FUELS`, `ABLATIVES`, or `STRUCTURAL_MATERIALS` array
|
||||||
|
3. Auto-appears in UI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated**: 2025-02 | **Status**: Current (v3)
|
||||||
496
docs/CONTRIBUTING.md
Normal file
496
docs/CONTRIBUTING.md
Normal file
@@ -0,0 +1,496 @@
|
|||||||
|
# Contributing Guide
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- Node.js 18+
|
||||||
|
- npm or yarn
|
||||||
|
- Basic understanding of React, JavaScript
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone/navigate to project
|
||||||
|
cd rocketry
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Start dev server
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Open browser
|
||||||
|
# http://localhost:5173
|
||||||
|
```
|
||||||
|
|
||||||
|
### Project Structure Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── engine/ # Pure math, zero React deps
|
||||||
|
├── hooks/ # State management
|
||||||
|
├── components/ # UI components
|
||||||
|
├── pages/ # Route pages
|
||||||
|
└── App.jsx # Router
|
||||||
|
```
|
||||||
|
|
||||||
|
See [ARCHITECTURE.md](ARCHITECTURE.md) for detailed breakdown.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Tasks
|
||||||
|
|
||||||
|
### Adding a New Equation
|
||||||
|
|
||||||
|
**File**: `src/engine/equations.js`
|
||||||
|
|
||||||
|
1. **Check if variables exist** in `variables.js`
|
||||||
|
- If not, add them with `id`, `name`, `unit`, `category`, `type`
|
||||||
|
|
||||||
|
2. **Add equation object**:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
id: 'myEquation',
|
||||||
|
name: 'My Equation Name',
|
||||||
|
variables: ['var1', 'var2', 'var3'],
|
||||||
|
equation: 'var1 = var2 * var3', // for reference
|
||||||
|
solvers: {
|
||||||
|
var1: (inputs) => inputs.var2 * inputs.var3,
|
||||||
|
var2: (inputs) => inputs.var1 / inputs.var3,
|
||||||
|
var3: (inputs) => inputs.var1 / inputs.var2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Test manually**
|
||||||
|
- Go to Solver page
|
||||||
|
- Drag variables onto workspace
|
||||||
|
- Set constraints, verify solver computes correct values
|
||||||
|
|
||||||
|
### For Transcendental Equations (Bisection)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { bisection } from '../numerics.js'
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 'machEquation',
|
||||||
|
solvers: {
|
||||||
|
machNumber: (inputs) => {
|
||||||
|
const fn = (M) => isentropicAreaRatio(M, inputs.gamma) - inputs.areaRatio
|
||||||
|
return bisection(fn, 0.1, 10, 1e-6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding a Material to Knowledgebase
|
||||||
|
|
||||||
|
**File**: `src/engine/knowledgebaseData.js`
|
||||||
|
|
||||||
|
**For fuels/oxidizers:**
|
||||||
|
```javascript
|
||||||
|
FUELS.push({
|
||||||
|
id: 'newFuel',
|
||||||
|
name: 'New Fuel',
|
||||||
|
formula: 'C8H18', // typical hydrocarbon
|
||||||
|
density: 800, // kg/m³
|
||||||
|
molecularWeight: 114,
|
||||||
|
properties: {
|
||||||
|
combustionTemp: 3500,
|
||||||
|
gamma: 1.25,
|
||||||
|
charVelocity: 1500 // m/s
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**For ablatives:**
|
||||||
|
```javascript
|
||||||
|
ABLATIVES.push({
|
||||||
|
id: 'newAblative',
|
||||||
|
name: 'New Ablative',
|
||||||
|
density: 1400,
|
||||||
|
erosionRate: 0.020, // inch/s @ 300 psi
|
||||||
|
pressureExponent: 0.35, // power-law exponent
|
||||||
|
tempLimit: 2100, // K
|
||||||
|
notes: 'Best for chamber walls'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**For structural materials:**
|
||||||
|
```javascript
|
||||||
|
STRUCTURAL_MATERIALS.push({
|
||||||
|
id: 'newMaterial',
|
||||||
|
name: 'New Material',
|
||||||
|
density: 4500, // kg/m³
|
||||||
|
yieldStrength: 900, // MPa
|
||||||
|
youngModulus: 200, // GPa
|
||||||
|
meltingPoint: 1800, // K
|
||||||
|
cte: 12e-6, // 1/K
|
||||||
|
notes: 'Good for hot sections'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Changes auto-appear in UI (hot reload).
|
||||||
|
|
||||||
|
### Adding a New Page
|
||||||
|
|
||||||
|
1. **Create page component** (`src/pages/NewPage.jsx`):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
export default function NewPage() {
|
||||||
|
return (
|
||||||
|
<div className="flex-1 overflow-auto p-6">
|
||||||
|
<h1 className="text-3xl font-bold mb-4">New Feature</h1>
|
||||||
|
{/* Your content */}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Add route** in `src/App.jsx`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import NewPage from './pages/NewPage.jsx'
|
||||||
|
|
||||||
|
// In Routes:
|
||||||
|
<Route path="/new-feature" element={<NewPage />} />
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Add navigation link** in header if needed:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
<NavLink
|
||||||
|
to="/new-feature"
|
||||||
|
className={/* classes */}
|
||||||
|
>
|
||||||
|
New Feature
|
||||||
|
</NavLink>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding a 3D Component
|
||||||
|
|
||||||
|
**File**: `src/components/new3d/NewComponent.jsx`
|
||||||
|
|
||||||
|
Use React Three Fiber (`@react-three/fiber`):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { Canvas } from '@react-three/fiber'
|
||||||
|
import { OrbitControls } from '@react-three/drei'
|
||||||
|
|
||||||
|
export default function NewComponent() {
|
||||||
|
return (
|
||||||
|
<Canvas>
|
||||||
|
<ambientLight intensity={0.6} />
|
||||||
|
<directionalLight position={[5, 5, 5]} />
|
||||||
|
<mesh>
|
||||||
|
<boxGeometry args={[1, 1, 1]} />
|
||||||
|
<meshStandardMaterial color="red" />
|
||||||
|
</mesh>
|
||||||
|
<OrbitControls />
|
||||||
|
</Canvas>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Style & Conventions
|
||||||
|
|
||||||
|
### Naming
|
||||||
|
|
||||||
|
- **Components**: PascalCase (`VariableCard.jsx`)
|
||||||
|
- **Files**: match component name or use kebab-case for utilities
|
||||||
|
- **Variables/functions**: camelCase
|
||||||
|
- **Constants**: UPPER_SNAKE_CASE
|
||||||
|
- **React hooks**: `useFeatureName`
|
||||||
|
|
||||||
|
### File Organization
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 1. Imports (grouped)
|
||||||
|
import React, { useState, useMemo } from 'react'
|
||||||
|
import { helper } from '../utils'
|
||||||
|
import './styles.css'
|
||||||
|
|
||||||
|
// 2. Types/Constants
|
||||||
|
const DEFAULT_VALUE = 10
|
||||||
|
|
||||||
|
// 3. Component
|
||||||
|
export default function MyComponent({ prop1, prop2 }) {
|
||||||
|
// Hooks
|
||||||
|
const [state, setState] = useState(0)
|
||||||
|
|
||||||
|
// Computed values
|
||||||
|
const memoized = useMemo(() => {
|
||||||
|
return expensiveCalculation()
|
||||||
|
}, [deps])
|
||||||
|
|
||||||
|
// Event handlers
|
||||||
|
const handleClick = () => { /* ... */ }
|
||||||
|
|
||||||
|
// JSX
|
||||||
|
return <div>...</div>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Styling
|
||||||
|
|
||||||
|
- Use **Tailwind CSS** classes
|
||||||
|
- Keep dark theme (slate-950 background)
|
||||||
|
- Responsive: mobile-first, use Tailwind breakpoints
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
<div className="px-4 py-2 rounded-md bg-slate-800 hover:bg-slate-700 transition-colors">
|
||||||
|
Click me
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Comments
|
||||||
|
|
||||||
|
- Add only for **why**, not **what**
|
||||||
|
- Keep code self-documenting
|
||||||
|
- Avoid comments for obvious logic
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ✅ Good: explains intent
|
||||||
|
// Pressure correction for temperature variation
|
||||||
|
const correctedRate = baseRate * Math.pow(temp / refTemp, exponent)
|
||||||
|
|
||||||
|
// ❌ Bad: obvious from code
|
||||||
|
const x = x + 1 // increment x
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Currently no test suite. Recommended additions:
|
||||||
|
|
||||||
|
### Unit Tests (Jest)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// src/engine/__tests__/solver.test.js
|
||||||
|
import { solve } from '../solver'
|
||||||
|
|
||||||
|
test('solves linear equation', () => {
|
||||||
|
const eqs = [{
|
||||||
|
id: 'eq1',
|
||||||
|
variables: ['x', 'y'],
|
||||||
|
solvers: {
|
||||||
|
x: (inputs) => inputs.y * 2
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
const result = solve(eqs, { y: 5 })
|
||||||
|
expect(result.x).toBe(10)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Component Tests (React Testing Library)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// src/components/__tests__/VariableCard.test.jsx
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react'
|
||||||
|
import VariableCard from '../VariableCard'
|
||||||
|
|
||||||
|
test('renders input field', () => {
|
||||||
|
render(<VariableCard variable={{name: 'thrust'}} />)
|
||||||
|
expect(screen.getByDisplayValue('0')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('updates on input change', () => {
|
||||||
|
const { getByDisplayValue } = render(<VariableCard />)
|
||||||
|
fireEvent.change(getByDisplayValue('0'), {target: {value: '100'}})
|
||||||
|
expect(getByDisplayValue('100')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Testing Checklist
|
||||||
|
|
||||||
|
- [ ] Solver computes correct values
|
||||||
|
- [ ] Engine design produces reasonable thrust/Isp
|
||||||
|
- [ ] Rocket design mass budget is sensible
|
||||||
|
- [ ] Trajectory plot shows reasonable path
|
||||||
|
- [ ] 3D models render without errors
|
||||||
|
- [ ] UI is responsive on mobile
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
### Memoization
|
||||||
|
|
||||||
|
Use `useMemo()` for expensive calculations:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const results = useMemo(() => {
|
||||||
|
return engineDesignCalcs.calcCombustion(inputs) // expensive
|
||||||
|
}, [inputs])
|
||||||
|
```
|
||||||
|
|
||||||
|
### Avoid Unnecessary Re-renders
|
||||||
|
|
||||||
|
- Lift state up only when needed
|
||||||
|
- Use `useCallback()` for event handlers passed to children
|
||||||
|
- Consider `React.memo()` for pure components
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const VariableCard = React.memo(({ variable, onChange }) => {
|
||||||
|
// Component only re-renders if variable or onChange changes
|
||||||
|
return <input onChange={onChange} />
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Canvas Performance
|
||||||
|
|
||||||
|
- Limit trajectory states to ~5000 points
|
||||||
|
- Use fixed timestep (don't go below 0.01 s)
|
||||||
|
- Reduce Three.js geometry complexity if needed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Git Workflow
|
||||||
|
|
||||||
|
### Branching
|
||||||
|
|
||||||
|
- `main` — production-ready code
|
||||||
|
- `feature/*` — new features
|
||||||
|
- `fix/*` — bug fixes
|
||||||
|
- `docs/*` — documentation updates
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
Keep commits atomic and well-described:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git commit -m "Add pressure-corrected ablation model
|
||||||
|
|
||||||
|
- Implement power-law pressure exponent for erosion rate
|
||||||
|
- Add pressure factor display in Engine page
|
||||||
|
- Update knowledgebase with exponent values
|
||||||
|
- Update docs with pressure correction formula"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pull Requests
|
||||||
|
|
||||||
|
Include:
|
||||||
|
- What changed and why
|
||||||
|
- How to test
|
||||||
|
- Any breaking changes
|
||||||
|
- Related issues
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Debugging Tips
|
||||||
|
|
||||||
|
### Browser DevTools
|
||||||
|
|
||||||
|
**Console:**
|
||||||
|
```javascript
|
||||||
|
// Log solver state
|
||||||
|
console.log('Constraints:', solverState.constraints)
|
||||||
|
console.log('Results:', solverState.results)
|
||||||
|
```
|
||||||
|
|
||||||
|
**React DevTools Extension:**
|
||||||
|
- Inspect component props/state
|
||||||
|
- Profile render performance
|
||||||
|
- Trace updates
|
||||||
|
|
||||||
|
### Hot Reload
|
||||||
|
|
||||||
|
Changes to `src/` automatically reload (Vite).
|
||||||
|
- Engine modules reload: refresh browser
|
||||||
|
- Components/pages: usually hot reload instantly
|
||||||
|
|
||||||
|
### Performance Profiling
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// In React component
|
||||||
|
console.time('calculation')
|
||||||
|
const result = expensiveFunction()
|
||||||
|
console.timeEnd('calculation') // logs elapsed time
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
### ❌ Don't: Mutate state directly
|
||||||
|
```javascript
|
||||||
|
state.value = 100 // Wrong!
|
||||||
|
setState({...state, value: 100}) // Correct
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ Don't: Create objects in dependencies
|
||||||
|
```javascript
|
||||||
|
const obj = {x: 1}
|
||||||
|
useMemo(() => calc(obj), [obj]) // Runs every render!
|
||||||
|
```
|
||||||
|
|
||||||
|
### ✅ Do: Memoize dependencies
|
||||||
|
```javascript
|
||||||
|
const obj = useMemo(() => ({x: 1}), [])
|
||||||
|
useMemo(() => calc(obj), [obj]) // Stable
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ Don't: Forget dependencies
|
||||||
|
```javascript
|
||||||
|
useEffect(() => {
|
||||||
|
doSomething(dep)
|
||||||
|
}, []) // Missing 'dep'!
|
||||||
|
```
|
||||||
|
|
||||||
|
### ✅ Do: Include all dependencies
|
||||||
|
```javascript
|
||||||
|
useEffect(() => {
|
||||||
|
doSomething(dep)
|
||||||
|
}, [dep])
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Asking for Help
|
||||||
|
|
||||||
|
**Before asking:**
|
||||||
|
1. Check existing documentation
|
||||||
|
2. Search similar equations/features
|
||||||
|
3. Review console for errors
|
||||||
|
4. Try simplifying the problem
|
||||||
|
|
||||||
|
**When asking:**
|
||||||
|
- Include error message
|
||||||
|
- Describe what you tried
|
||||||
|
- Provide minimal reproducible example
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Roadmap Ideas
|
||||||
|
|
||||||
|
Potential future features:
|
||||||
|
|
||||||
|
- **Multi-stage rockets** — sequential stage design
|
||||||
|
- **Thrust vector control** — pitch/yaw steering
|
||||||
|
- **Grid fin aerodynamics** — drag & control surfaces
|
||||||
|
- **Solid rocket motors** — grain geometry, regression
|
||||||
|
- **Regenerative cooling** — thermal analysis
|
||||||
|
- **Uncertainty analysis** — Monte Carlo simulations
|
||||||
|
- **Optimization** — automated mass/performance optimization
|
||||||
|
- **Data export** — CAD, simulation formats (GMAT, Basilisk)
|
||||||
|
- **Multiplayer** — collaborative design sessions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [React Docs](https://react.dev)
|
||||||
|
- [Tailwind CSS](https://tailwindcss.com)
|
||||||
|
- [Three.js Docs](https://threejs.org/docs)
|
||||||
|
- [React Three Fiber](https://docs.pmnd.rs/react-three-fiber)
|
||||||
|
- [Vite Docs](https://vitejs.dev)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated**: 2025-02 | **Status**: Current
|
||||||
394
docs/ENGINE_DESIGN.md
Normal file
394
docs/ENGINE_DESIGN.md
Normal file
@@ -0,0 +1,394 @@
|
|||||||
|
# Engine Design Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The **Engine Design** tool calculates rocket engine performance, material erosion, and structural requirements. It integrates combustion thermodynamics, ablative material degradation, and hoop stress analysis into a unified interface.
|
||||||
|
|
||||||
|
## Main Sections
|
||||||
|
|
||||||
|
### Combustion Design
|
||||||
|
Configure chamber, nozzle, and propellant properties to predict thrust, Isp, and exit conditions.
|
||||||
|
|
||||||
|
### Ablative Erosion
|
||||||
|
Model chamber liner erosion based on pressure-dependent rates and propellant choice.
|
||||||
|
|
||||||
|
### Structural Sizing
|
||||||
|
Calculate wall thicknesses and component masses using hoop stress formula.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Combustion Design
|
||||||
|
|
||||||
|
### Inputs
|
||||||
|
|
||||||
|
**Chamber:**
|
||||||
|
- `chamberPressure` (MPa) — combustion chamber pressure
|
||||||
|
- `nozzleDiameter` (mm) — nozzle throat diameter
|
||||||
|
- `expansionRatio` — nozzle exit / throat area ratio
|
||||||
|
- `divergenceAngle` (°) — nozzle divergence cone angle
|
||||||
|
|
||||||
|
**Propellant Selection:**
|
||||||
|
- `fuelType` — dropdown (RP1, Methane, Hydrogen, etc.)
|
||||||
|
- `oxidiserType` — dropdown (LOX, N2O4, etc.)
|
||||||
|
- **Note**: Propellant properties come from knowledgebase (T0, gamma, Mw, etc.)
|
||||||
|
|
||||||
|
**Design Options:**
|
||||||
|
- `injectorType` — impingement, shower-head, multi-element
|
||||||
|
- `coolingMethod` — none, regenerative (future)
|
||||||
|
- `nozzleShape` — conical (15-20°), bell curve (optional)
|
||||||
|
|
||||||
|
### Outputs (Computed)
|
||||||
|
|
||||||
|
**Performance:**
|
||||||
|
- `thrustVacuum` (kN) — vacuum thrust (accounting for exit pressure)
|
||||||
|
- `thrustSeaLevel` (kN) — sea level thrust (accounting for ambient pressure)
|
||||||
|
- `specificImpulseVacuum` (s) — Isp in vacuum
|
||||||
|
- `specificImpulseSeaLevel` (s) — Isp at sea level
|
||||||
|
- `characteristicVelocity` (m/s) — c* = P0·At / ṁ
|
||||||
|
|
||||||
|
**Combustion Conditions:**
|
||||||
|
- `chamberTemperature` (K) — adiabatic flame temperature
|
||||||
|
- `exitTemperature` (K) — nozzle exit temperature
|
||||||
|
- `machNumber` — exit Mach number (computed via bisection)
|
||||||
|
- `exitPressure` (MPa) — nozzle exit pressure
|
||||||
|
- `exitDensity` (kg/m³) — exhaust density at exit
|
||||||
|
|
||||||
|
**Nozzle Geometry:**
|
||||||
|
- `throatDiameter` (mm) — computed from chamber P, thrust, mass flow
|
||||||
|
- `exitDiameter` (mm) — exit diameter = throat × √(expansionRatio)
|
||||||
|
- `nozzleLength` (mm) — divergence section length
|
||||||
|
- `thrustCoefficient` — CF (thrust coefficient for flow)
|
||||||
|
|
||||||
|
**Flow Properties:**
|
||||||
|
- `massFlowRate` (kg/s) — propellant mass flow rate
|
||||||
|
- `exitVelocity` (m/s) — effective exhaust velocity
|
||||||
|
- `burnRate` (mm/s) — for propellant grain design
|
||||||
|
|
||||||
|
### Key Equations
|
||||||
|
|
||||||
|
**Thrust (from area and pressure):**
|
||||||
|
```
|
||||||
|
F = P0·At·CF + (Pe - P_ambient)·Ae
|
||||||
|
```
|
||||||
|
Where CF is thrust coefficient from isentropic relations.
|
||||||
|
|
||||||
|
**Specific Impulse:**
|
||||||
|
```
|
||||||
|
Isp = Ve / g0
|
||||||
|
Isp_sl = (Ve - (Pe - P_atm) / ṁ · Ae) / g0
|
||||||
|
```
|
||||||
|
|
||||||
|
**Exit Temperature (isentropic flow):**
|
||||||
|
```
|
||||||
|
Te = T0 · (Pe / P0)^((γ-1)/γ)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Mass Flow Rate (from energy balance):**
|
||||||
|
```
|
||||||
|
ṁ = P0 · At / c*
|
||||||
|
c* = √(2·(γ+1)/(γ-1) · R·T0) for ideal nozzle
|
||||||
|
```
|
||||||
|
|
||||||
|
**Characteristic Velocity:**
|
||||||
|
```
|
||||||
|
c* = √(2·(γ+1)/(γ-1) · R·T0 · (2/(γ+1))^((γ+1)/(γ-1)))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ablative Erosion (v2)
|
||||||
|
|
||||||
|
### Material Properties
|
||||||
|
|
||||||
|
Each ablative material in the knowledgebase has:
|
||||||
|
- **Density** (kg/m³) — ablator material density
|
||||||
|
- **Base Erosion Rate** (inch/s) — reference erosion at standard pressure (typically 300 psi)
|
||||||
|
- **Pressure Exponent** (n) — power-law sensitivity to pressure
|
||||||
|
|
||||||
|
### Pressure-Corrected Erosion
|
||||||
|
|
||||||
|
The key innovation of v2 is **pressure-dependent erosion rates**:
|
||||||
|
|
||||||
|
```
|
||||||
|
erosion_rate(P) = base_rate · (P / P_ref)^n
|
||||||
|
```
|
||||||
|
|
||||||
|
Where:
|
||||||
|
- `base_rate` — from material database (inch/s @ 300 psi)
|
||||||
|
- `P` — chamber pressure (Pa)
|
||||||
|
- `P_ref` — reference pressure (300 psi = 2.068 MPa)
|
||||||
|
- `n` — pressure exponent (material-specific)
|
||||||
|
|
||||||
|
### Pressure Exponents by Material Class
|
||||||
|
|
||||||
|
**Composites:**
|
||||||
|
- PAXS (polyester/glass): n = 0.38
|
||||||
|
- KFSI (silica/phenolic): n = 0.35
|
||||||
|
- Carbon phenolic: n = 0.32
|
||||||
|
|
||||||
|
**Elastomers:**
|
||||||
|
- ZIRCONIA (zirconia/silicone): n = 0.48
|
||||||
|
- Butyl rubber: n = 0.50
|
||||||
|
|
||||||
|
**Theory**: Lower n = less pressure-sensitive (better for high-P engines)
|
||||||
|
|
||||||
|
### Example Calculation
|
||||||
|
|
||||||
|
**Material:** PAXS, base_rate = 0.025 in/s, n = 0.38
|
||||||
|
|
||||||
|
**At different pressures:**
|
||||||
|
- 300 psi: rate = 0.025 × (300/300)^0.38 = 0.025 in/s
|
||||||
|
- 500 psi: rate = 0.025 × (500/300)^0.38 = 0.032 in/s
|
||||||
|
- 1000 psi: rate = 0.025 × (1000/300)^0.38 = 0.042 in/s
|
||||||
|
- 1500 psi: rate = 0.025 × (1500/300)^0.38 = 0.050 in/s
|
||||||
|
|
||||||
|
### Erosion Calculation
|
||||||
|
|
||||||
|
**Inputs:**
|
||||||
|
- Chamber pressure (Pa)
|
||||||
|
- Burn time (s)
|
||||||
|
- Ablative material (from dropdown)
|
||||||
|
- Initial liner thickness (mm)
|
||||||
|
|
||||||
|
**Process:**
|
||||||
|
1. Look up material properties (base_rate, n)
|
||||||
|
2. Calculate pressure factor: `(P / P_ref)^n`
|
||||||
|
3. Apply correction: `effective_rate = base_rate × factor`
|
||||||
|
4. Erosion depth: `erosion = effective_rate × burn_time`
|
||||||
|
5. Remaining thickness: `remaining = initial - erosion`
|
||||||
|
|
||||||
|
**Display:**
|
||||||
|
- Pressure factor (if not 1.0)
|
||||||
|
- Corrected erosion rate (inch/s)
|
||||||
|
- Erosion depth (inch)
|
||||||
|
- Remaining thickness (inch)
|
||||||
|
- **Warning** if remaining < 0.5 inch
|
||||||
|
|
||||||
|
### Thermal Analysis (Future)
|
||||||
|
|
||||||
|
Currently, erosion is purely mechanical. Future versions could include:
|
||||||
|
- Heat flux calculation
|
||||||
|
- Material melting/sublimation
|
||||||
|
- Thermal diffusion into structure
|
||||||
|
- Combined mechanical + thermal erosion
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Structural Sizing
|
||||||
|
|
||||||
|
### Material Selection
|
||||||
|
|
||||||
|
Choose from STRUCTURAL_MATERIALS array:
|
||||||
|
- **Aluminum 6061-T6**: Low density, corrosion-resistant
|
||||||
|
- **Stainless Steel 304**: High temp, corrosion-resistant
|
||||||
|
- **Inconel 718**: High strength at elevated T
|
||||||
|
- **Titanium 6-4**: Light, strong, expensive
|
||||||
|
- **CFRP (Carbon Fiber)**: Highest strength-to-weight
|
||||||
|
|
||||||
|
Each has: density (kg/m³), yield strength (MPa), Young's modulus, CTE, limits
|
||||||
|
|
||||||
|
### Hoop Stress Formula
|
||||||
|
|
||||||
|
For thin-walled cylinders:
|
||||||
|
```
|
||||||
|
σ_hoop = (P × r) / t
|
||||||
|
```
|
||||||
|
|
||||||
|
Solving for wall thickness with safety factor:
|
||||||
|
```
|
||||||
|
t = (P × r) / (σ_yield / SF)
|
||||||
|
```
|
||||||
|
|
||||||
|
Where:
|
||||||
|
- `P` — internal pressure (Pa)
|
||||||
|
- `r` — radius (m)
|
||||||
|
- `σ_yield` — material yield strength (Pa)
|
||||||
|
- `SF` — safety factor (typical: 2.0–4.0)
|
||||||
|
|
||||||
|
### Component Sizing
|
||||||
|
|
||||||
|
**Chamber:**
|
||||||
|
- Pressure: P0 (combustion pressure)
|
||||||
|
- Radius: determined by geometry
|
||||||
|
- Thickness: `t_chamber = (P0 × r) / (σ_y / SF)`
|
||||||
|
|
||||||
|
**Convergent Section (nozzle entrance):**
|
||||||
|
- Pressure: P0 (full chamber pressure)
|
||||||
|
- Radius: smaller than chamber (converging)
|
||||||
|
- Thickness: similar to chamber
|
||||||
|
|
||||||
|
**Throat (narrowest point):**
|
||||||
|
- Pressure: ~P0 (highest local stress)
|
||||||
|
- Radius: throat_radius (smallest)
|
||||||
|
- **Result**: highest stress → thickest wall
|
||||||
|
- Thickness: `t_throat = (P0 × r_throat) / (σ_y / SF)`
|
||||||
|
|
||||||
|
**Divergent Section (nozzle expansion):**
|
||||||
|
- Pressure: decreases from P0 to Pe
|
||||||
|
- Radius: increasing
|
||||||
|
- Thickness: typically uses Pe ≈ 0.1·P0 for design
|
||||||
|
- Lower thickness than chamber
|
||||||
|
|
||||||
|
**Exit (atmospheric nozzle):**
|
||||||
|
- Pressure: near ambient
|
||||||
|
- Thickness: minimal (can be thin-walled)
|
||||||
|
|
||||||
|
### Mass Calculation
|
||||||
|
|
||||||
|
**Cylindrical section:**
|
||||||
|
```
|
||||||
|
m = 2π·r·t·L·ρ
|
||||||
|
```
|
||||||
|
|
||||||
|
**Hemispherical dome (for tanks):**
|
||||||
|
```
|
||||||
|
m = 4π·r²·t·ρ
|
||||||
|
```
|
||||||
|
|
||||||
|
**Frustum (truncated cone):**
|
||||||
|
```
|
||||||
|
m ≈ π·(r1·L + r2·L)·t·ρ (simplified)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Total engine dry mass:**
|
||||||
|
```
|
||||||
|
m_dry = m_chamber + m_convergent + m_nozzle + m_injector_plate + m_misc
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI Layout
|
||||||
|
|
||||||
|
**Left Panel (Inputs):**
|
||||||
|
- Material dropdown
|
||||||
|
- Safety factor slider
|
||||||
|
- Pressure specifications (chamber, ambient)
|
||||||
|
|
||||||
|
**Right Panel (Results):**
|
||||||
|
- Wall thickness breakdown (chamber, throat, exit)
|
||||||
|
- Mass breakdown (chamber, convergent, divergent, injector, **total**)
|
||||||
|
- Material properties (yield, density, limit temp)
|
||||||
|
- Stress utilization (if known)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Workflow Example: Design LOX/RP1 Engine
|
||||||
|
|
||||||
|
### Step 1: Set Combustion Conditions
|
||||||
|
1. Select `fuelType = RP1`
|
||||||
|
2. Select `oxidiserType = LOX`
|
||||||
|
3. Set `chamberPressure = 200 bar`
|
||||||
|
4. Set `nozzleDiameter = 50 mm`
|
||||||
|
5. Set `expansionRatio = 8`
|
||||||
|
→ **Result**: Thrust ≈ 150 kN, Isp ≈ 310 s
|
||||||
|
|
||||||
|
### Step 2: Design Ablation
|
||||||
|
1. Select `ablativeMaterial = PAXS`
|
||||||
|
2. Set `initialLinertThickness = 10 mm`
|
||||||
|
3. Set `burnTime = 60 s`
|
||||||
|
→ **Result**: Erosion ≈ 1.5 mm, Remaining ≈ 8.5 mm (OK)
|
||||||
|
|
||||||
|
### Step 3: Size Structure
|
||||||
|
1. Select `structuralMaterial = Inconel718`
|
||||||
|
2. Set `safetyFactor = 3.0`
|
||||||
|
→ **Result**:
|
||||||
|
- Chamber wall: 2.1 mm
|
||||||
|
- Throat wall: 2.8 mm
|
||||||
|
- Divergent: 1.5 mm
|
||||||
|
- **Total dry mass**: 2.3 kg
|
||||||
|
|
||||||
|
### Step 4: Export
|
||||||
|
- **Export as JSON**: Download engine specs
|
||||||
|
- Import into rocket design tool
|
||||||
|
- Calculate vehicle TWR, delta-v, etc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Advanced Topics
|
||||||
|
|
||||||
|
### Regenerative Cooling
|
||||||
|
|
||||||
|
Not yet implemented, but conceptually:
|
||||||
|
- Propellant flows through cooling jackets before injection
|
||||||
|
- Removes heat from chamber walls
|
||||||
|
- Allows higher chamber pressure or thinner walls
|
||||||
|
- Adds complexity (adds ~0.5 kg per engine)
|
||||||
|
|
||||||
|
Implementation would add:
|
||||||
|
- Cooling jacket dimensions
|
||||||
|
- Heat transfer correlation (Dittus-Boelert, etc.)
|
||||||
|
- Pressure drop calculation
|
||||||
|
- Temperature rise of propellant
|
||||||
|
|
||||||
|
### Ablator Recession Modeling
|
||||||
|
|
||||||
|
Current model assumes uniform erosion. Advanced model would include:
|
||||||
|
- **Surface recession rate** (different from bulk erosion)
|
||||||
|
- **Spallation** (chunks breaking off at high pressure)
|
||||||
|
- **Chemical reaction** (oxidation, decomposition)
|
||||||
|
- **Thermal gradient** (erosion depends on depth)
|
||||||
|
|
||||||
|
This requires:
|
||||||
|
- Heat conduction equation (PDE)
|
||||||
|
- Boundary conditions (heat flux from combustion)
|
||||||
|
- Material properties (k, cp, melt point)
|
||||||
|
- Solver (e.g., finite difference)
|
||||||
|
|
||||||
|
### Combustion Instability
|
||||||
|
|
||||||
|
Not modeled currently, but factors affecting it:
|
||||||
|
- **Injector design** (pattern, element size, momentum ratio)
|
||||||
|
- **Chamber L* (characteristic length)** = V / At
|
||||||
|
- Higher L* = more residence time = higher c*
|
||||||
|
- Typical range: 30–60 inches
|
||||||
|
- **Baffle plates** (reduce acoustic resonance)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Knowledgebase Integration
|
||||||
|
|
||||||
|
### Accessing Propellant Data
|
||||||
|
- Knowledgebase → Fuels / Oxidisers
|
||||||
|
- View all available propellants
|
||||||
|
- Copy properties into engine design
|
||||||
|
|
||||||
|
### Accessing Material Data
|
||||||
|
- Knowledgebase → Structural Materials
|
||||||
|
- Compare yield strengths, densities, costs
|
||||||
|
- Select best for your application
|
||||||
|
|
||||||
|
### Ablative Materials
|
||||||
|
- Knowledgebase → Ablative Materials
|
||||||
|
- View pressure exponents
|
||||||
|
- Notes on applications (chamber, nozzle, etc.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Chamber pressure seems too high / low?
|
||||||
|
- Check propellant selection (wrong fuel/oxidizer?)
|
||||||
|
- Verify chamber geometry (radius, length)
|
||||||
|
- Check for typos in input values
|
||||||
|
|
||||||
|
### Erosion warning?
|
||||||
|
- Select ablator with lower pressure exponent (more stable)
|
||||||
|
- Reduce chamber pressure if possible
|
||||||
|
- Increase initial liner thickness
|
||||||
|
- Shorten burn time
|
||||||
|
|
||||||
|
### Structural mass too heavy?
|
||||||
|
- Switch to lighter material (Ti-6-4 vs SS)
|
||||||
|
- Reduce safety factor (if acceptable for design)
|
||||||
|
- Increase chamber pressure radius (less stress)
|
||||||
|
- Use thinner walls for non-critical sections
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- Huzel, D. K., & Huang, D. H. (1992). Modern engineering for design of liquid-propellant rocket engines. AIAA.
|
||||||
|
- NASA SP-273: Liquid Rocket Engine Combustion Instability. ([PDF](https://ntrs.nasa.gov/api/citations/19700006920/downloads/19700006920.pdf))
|
||||||
|
- Crespo, A., & Liñán, A. (1975). Asymptotic analysis of unsteady flame oscillations in liquid propellant rocket motors. SIAM Journal on Applied Mathematics, 29(3), 521–533.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated**: 2025-02 | **Status**: Current (v2 — Pressure-Corrected Ablation)
|
||||||
465
docs/KNOWLEDGEBASE.md
Normal file
465
docs/KNOWLEDGEBASE.md
Normal file
@@ -0,0 +1,465 @@
|
|||||||
|
# Knowledgebase Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The **Knowledgebase** is a reference library of propellants, materials, and engineering equations. It provides the properties needed for calculations in the Solver, Engine Designer, and Rocket Designer.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
The knowledgebase is organized into four categories:
|
||||||
|
|
||||||
|
1. **Fuels & Oxidizers** — Propellant properties
|
||||||
|
2. **Ablative Materials** — Thermal protection
|
||||||
|
3. **Structural Materials** — Engine & tank construction
|
||||||
|
4. **Equations** — Mathematical references
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fuels & Oxidizers
|
||||||
|
|
||||||
|
### What are They?
|
||||||
|
|
||||||
|
**Fuel**: Hydrocarbon or hydrogen source
|
||||||
|
- Examples: RP-1 (kerosene), methane, hydrogen
|
||||||
|
|
||||||
|
**Oxidizer**: Oxygen source
|
||||||
|
- Examples: Liquid oxygen (LOX), nitrogen tetroxide (N2O4)
|
||||||
|
|
||||||
|
### Key Properties
|
||||||
|
|
||||||
|
Each propellant entry contains:
|
||||||
|
|
||||||
|
**Chemical Properties:**
|
||||||
|
- Molecular formula (e.g., O₂ for oxygen)
|
||||||
|
- Molecular weight (g/mol)
|
||||||
|
- Density (kg/m³)
|
||||||
|
|
||||||
|
**Combustion Properties:**
|
||||||
|
- **Adiabatic flame temperature (Tc)** — peak temperature of combustion (K)
|
||||||
|
- Higher Tc = higher Isp
|
||||||
|
- Example: LOX/RP-1 ≈ 3750 K, LOX/LH2 ≈ 3900 K
|
||||||
|
|
||||||
|
- **Expansion ratio (gamma, γ)** — specific heat ratio
|
||||||
|
- γ = Cp / Cv
|
||||||
|
- Affects nozzle performance
|
||||||
|
- LOX/RP-1: γ ≈ 1.25, LOX/LH2: γ ≈ 1.26
|
||||||
|
|
||||||
|
- **Characteristic velocity (c*)** — ideal nozzle exit velocity
|
||||||
|
- Fundamental property of propellant
|
||||||
|
- Used to estimate Isp
|
||||||
|
|
||||||
|
**Performance Metrics:**
|
||||||
|
- **Specific impulse (vacuum, Isp_vac)** — seconds
|
||||||
|
- Higher = better
|
||||||
|
- Example: LOX/RP-1 ≈ 310–320 s, LOX/LH2 ≈ 450 s
|
||||||
|
|
||||||
|
- **Specific impulse (sea level, Isp_sl)** — reduced due to ambient pressure
|
||||||
|
- Lower than vacuum value
|
||||||
|
- Example: LOX/RP-1 ≈ 260 s at sea level
|
||||||
|
|
||||||
|
**Mixture Ratio:**
|
||||||
|
- **O/F ratio** — oxidizer mass per fuel mass
|
||||||
|
- Example: LOX/RP-1 optimal ≈ 2.5–2.8
|
||||||
|
- Higher ratio = more oxygen = higher T but lower mass fraction
|
||||||
|
- Lower ratio = more fuel = lower T but better mass fraction
|
||||||
|
|
||||||
|
### Common Propellant Pairs
|
||||||
|
|
||||||
|
| Fuel | Oxidizer | Isp (s) | Tc (K) | γ | Notes |
|
||||||
|
|------|----------|---------|-------|-------|-------|
|
||||||
|
| RP-1 | LOX | 310 | 3750 | 1.25 | Space-proven, storable |
|
||||||
|
| Methane | LOX | 330 | 3900 | 1.24 | Better impulse than RP-1 |
|
||||||
|
| Hydrogen | LOX | 450 | 3900 | 1.26 | Best impulse, cryogenic, low density |
|
||||||
|
| MMH | N2O4 | 290 | 3400 | 1.20 | Storable, hypergolic (ignites on contact) |
|
||||||
|
| Hydrazine | N2O4 | 310 | 3700 | 1.24 | Toxic but storable, high density |
|
||||||
|
|
||||||
|
### Storage Types
|
||||||
|
|
||||||
|
**Cryogenic:**
|
||||||
|
- Requires refrigeration
|
||||||
|
- LOX, LH2, LN2, LCH4
|
||||||
|
- Higher energy density
|
||||||
|
- Complex ground support needed
|
||||||
|
|
||||||
|
**Storable (Room Temperature):**
|
||||||
|
- RP-1, Methane (marginally), MMH, Hydrazine
|
||||||
|
- No cryogenic handling needed
|
||||||
|
- Lower energy density than cryo
|
||||||
|
- Easier integration
|
||||||
|
|
||||||
|
**Hypergolic:**
|
||||||
|
- Ignites spontaneously on contact (fuel + oxidizer)
|
||||||
|
- No ignition system needed
|
||||||
|
- Toxic, corrosive, more expensive
|
||||||
|
- Examples: MMH/N2O4, Hydrazine/N2O4
|
||||||
|
|
||||||
|
### Selection Criteria
|
||||||
|
|
||||||
|
**High Performance** → LOX/LH2 (Isp 450 s, but cryogenic complexity)
|
||||||
|
**Best Balance** → LOX/RP-1 (Isp 310 s, storable, proven)
|
||||||
|
**Storable Only** → MMH/N2O4 (Isp 290 s, no cryo needed)
|
||||||
|
**Simple & Solid** → RP-1/LOX or Methane/LOX
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ablative Materials
|
||||||
|
|
||||||
|
### Purpose
|
||||||
|
|
||||||
|
Ablative materials line the rocket engine chamber to withstand:
|
||||||
|
- **High temperature** (combustion products: 3500+ K)
|
||||||
|
- **High pressure** (200+ bar)
|
||||||
|
- **Erosion** from hot gas flow
|
||||||
|
|
||||||
|
### Key Properties
|
||||||
|
|
||||||
|
Each ablative material entry contains:
|
||||||
|
|
||||||
|
**Mechanical Properties:**
|
||||||
|
- Density (kg/m³)
|
||||||
|
- Tensile strength (MPa)
|
||||||
|
- Compressive strength (MPa)
|
||||||
|
|
||||||
|
**Thermal Properties:**
|
||||||
|
- Specific heat (J/kg·K)
|
||||||
|
- Thermal conductivity (W/m·K)
|
||||||
|
- Melting point (K)
|
||||||
|
|
||||||
|
**Erosion Properties:**
|
||||||
|
- **Base erosion rate** (inch/s @ 300 psi reference pressure)
|
||||||
|
- Ablative material mass removed per unit time
|
||||||
|
- Higher rate = thicker liner needed
|
||||||
|
- Example: PAXS ≈ 0.025 in/s
|
||||||
|
|
||||||
|
- **Pressure exponent (n)** — power-law sensitivity to pressure
|
||||||
|
- `rate(P) = base_rate × (P / P_ref)^n`
|
||||||
|
- Lower n = less pressure-sensitive (better for high P)
|
||||||
|
- Example: PAXS n = 0.38, KFSI n = 0.35
|
||||||
|
|
||||||
|
**Application Notes:**
|
||||||
|
- Where used: chamber, nozzle, injector
|
||||||
|
- Temperature limits
|
||||||
|
- Compatibility with propellants
|
||||||
|
|
||||||
|
### Material Classes
|
||||||
|
|
||||||
|
**Composites (Rigid):**
|
||||||
|
- PAXS (polyester + glass) — n = 0.38
|
||||||
|
- Common, cost-effective
|
||||||
|
- Moderate erosion rate
|
||||||
|
- Max temp ~2000 K
|
||||||
|
|
||||||
|
- KFSI (silica + phenolic) — n = 0.35
|
||||||
|
- Better than PAXS
|
||||||
|
- Lower erosion rate
|
||||||
|
- Higher temp capability
|
||||||
|
- More expensive
|
||||||
|
|
||||||
|
- Carbon-Phenolic — n = 0.32
|
||||||
|
- Excellent thermal performance
|
||||||
|
- Very low erosion rate
|
||||||
|
- Very expensive
|
||||||
|
- Used on high-end engines
|
||||||
|
|
||||||
|
**Elastomers (Flexible):**
|
||||||
|
- ZIRCONIA (zirconia + silicone) — n = 0.48
|
||||||
|
- Flexible (less brittle)
|
||||||
|
- Higher erosion rate
|
||||||
|
- Absorbs vibration
|
||||||
|
- Lower cost than phenolics
|
||||||
|
|
||||||
|
- Butyl Rubber — n = 0.50
|
||||||
|
- Very flexible
|
||||||
|
- High erosion rate
|
||||||
|
- Used for low-pressure applications
|
||||||
|
|
||||||
|
### Pressure Exponent Explanation
|
||||||
|
|
||||||
|
The power law `rate ∝ P^n` comes from **regression rate burning**:
|
||||||
|
|
||||||
|
- Composites: n ≈ 0.3–0.4
|
||||||
|
- Erosion driven by thermal decomposition
|
||||||
|
- Less pressure-dependent
|
||||||
|
- Preferred for high-pressure engines
|
||||||
|
|
||||||
|
- Elastomers: n ≈ 0.4–0.5
|
||||||
|
- Erosion driven by shear stress
|
||||||
|
- More pressure-dependent
|
||||||
|
- OK for moderate pressures
|
||||||
|
|
||||||
|
**High Pressure (>1000 psi):** Use composite with low n
|
||||||
|
**Moderate Pressure (500 psi):** Elastomer acceptable
|
||||||
|
**Low Pressure (<300 psi):** Elastomer preferred (lower cost)
|
||||||
|
|
||||||
|
### Selection Guidance
|
||||||
|
|
||||||
|
| Application | Material | Reason |
|
||||||
|
|-------------|----------|--------|
|
||||||
|
| High-perf liquid rocket | KFSI or Carbon-Phenolic | Low erosion rate, high pressure capable |
|
||||||
|
| Medium-perf liquid | PAXS | Good balance, cost-effective |
|
||||||
|
| Hybrid rocket | KFSI (fuel grain liner) | Low ablation to preserve geometry |
|
||||||
|
| Solid rocket | Composite | Erosion from particles & hot gas |
|
||||||
|
| Low-cost experiment | Butyl or PAXS | Adequate performance, lowest cost |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Structural Materials
|
||||||
|
|
||||||
|
### Purpose
|
||||||
|
|
||||||
|
Structural materials form the engine chamber and rocket tanks. They must:
|
||||||
|
- Withstand internal pressure (hoop stress)
|
||||||
|
- Resist corrosion from propellants
|
||||||
|
- Perform at operating temperature
|
||||||
|
- Balance weight vs. strength
|
||||||
|
|
||||||
|
### Key Properties
|
||||||
|
|
||||||
|
Each structural material entry contains:
|
||||||
|
|
||||||
|
**Mechanical Properties:**
|
||||||
|
- **Yield strength** (MPa) — stress at which permanent deformation begins
|
||||||
|
- Higher = thinner walls possible = lighter
|
||||||
|
- Example: Aluminum 6061-T6: 275 MPa, Inconel 718: 1240 MPa
|
||||||
|
|
||||||
|
- **Young's modulus** (GPa) — stiffness
|
||||||
|
- Higher = less deflection (stronger)
|
||||||
|
|
||||||
|
- **Density** (kg/m³) — weight per volume
|
||||||
|
- Lower = lighter vehicle
|
||||||
|
- Example: Al: 2700, Ti: 4430, SS: 8000
|
||||||
|
|
||||||
|
**Thermal Properties:**
|
||||||
|
- Melting point (K) — temperature limit
|
||||||
|
- Coefficient of thermal expansion (CTE)
|
||||||
|
- Thermal conductivity
|
||||||
|
|
||||||
|
**Other:**
|
||||||
|
- Cost (relative)
|
||||||
|
- Machinability
|
||||||
|
- Availability
|
||||||
|
|
||||||
|
### Material Comparison
|
||||||
|
|
||||||
|
| Material | Density | Yield | T_melt | Cost | Best For |
|
||||||
|
|----------|---------|-------|--------|------|----------|
|
||||||
|
| Al 6061-T6 | 2700 | 275 | 933 | 1× | Prototype, pressure-fed |
|
||||||
|
| SS 304 | 8000 | 215 | 1726 | 3× | Corrosion resistance |
|
||||||
|
| Inconel 718 | 8190 | 1240 | 1600 | 10× | High-performance engines |
|
||||||
|
| Ti-6-4 | 4430 | 880 | 1941 | 15× | Lightweight, space |
|
||||||
|
| CFRP | 1600 | 600+ | 700 | 20× | Lowest weight |
|
||||||
|
|
||||||
|
### Hoop Stress Calculation
|
||||||
|
|
||||||
|
For cylindrical pressure vessels:
|
||||||
|
```
|
||||||
|
σ_hoop = (P × r) / t
|
||||||
|
```
|
||||||
|
|
||||||
|
Setting equal to yield with safety factor:
|
||||||
|
```
|
||||||
|
t = (P × r) / (σ_yield / SF)
|
||||||
|
```
|
||||||
|
|
||||||
|
Higher yield strength → thinner walls → lighter mass
|
||||||
|
|
||||||
|
### Material Selection
|
||||||
|
|
||||||
|
**Pressure-Fed Engine (200+ bar, chamber):**
|
||||||
|
- **Inconel 718** — withstands high pressure & heat
|
||||||
|
- **Titanium** — lighter alternative
|
||||||
|
- **Aluminum** — cheap but needs cooling design
|
||||||
|
|
||||||
|
**Rocket Tanks (20–50 bar):**
|
||||||
|
- **Aluminum 6061** — standard, proven, affordable
|
||||||
|
- **Titanium** — if weight critical
|
||||||
|
- **CFRP** — lowest weight, highest cost
|
||||||
|
|
||||||
|
**Low-Pressure Systems:**
|
||||||
|
- **Aluminum** — sufficient, cheapest
|
||||||
|
- **Stainless Steel** — if corrosion concern
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Equations Reference
|
||||||
|
|
||||||
|
### Fundamental Relations
|
||||||
|
|
||||||
|
**Thrust (momentum equation):**
|
||||||
|
```
|
||||||
|
F = ṁ · Ve + (Pe - Pa) · Ae
|
||||||
|
```
|
||||||
|
- ṁ = mass flow rate
|
||||||
|
- Ve = exit velocity
|
||||||
|
- Pe = exit pressure
|
||||||
|
- Pa = ambient pressure
|
||||||
|
- Ae = exit area
|
||||||
|
|
||||||
|
**Specific Impulse:**
|
||||||
|
```
|
||||||
|
Isp = Ve / g0 (vacuum)
|
||||||
|
Isp = (Ve - (Pe - Pa) / ṁ · Ae) / g0 (sea level)
|
||||||
|
```
|
||||||
|
- g0 = 9.81 m/s² (gravitational acceleration)
|
||||||
|
|
||||||
|
**Rocket Equation (Tsiolkovsky):**
|
||||||
|
```
|
||||||
|
ΔV = Isp · g0 · ln(m_initial / m_final)
|
||||||
|
```
|
||||||
|
- ΔV = velocity change (delta-v)
|
||||||
|
- m_initial = wet mass
|
||||||
|
- m_final = dry mass
|
||||||
|
|
||||||
|
### Isentropic Flow (Nozzle Design)
|
||||||
|
|
||||||
|
**Temperature at exit:**
|
||||||
|
```
|
||||||
|
Te / T0 = (Pe / P0)^((γ-1)/γ)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Density at exit:**
|
||||||
|
```
|
||||||
|
ρe / ρ0 = (Pe / P0)^(1/γ)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Mach number from area ratio:**
|
||||||
|
```
|
||||||
|
A / A* = (1/M) · [(2/(γ+1)) · (1 + (γ-1)/2 · M²)]^((γ+1)/(2(γ-1)))
|
||||||
|
```
|
||||||
|
- A* = throat area
|
||||||
|
- A = arbitrary section area
|
||||||
|
- M = Mach number
|
||||||
|
- (requires numerical inversion via bisection)
|
||||||
|
|
||||||
|
**Characteristic velocity:**
|
||||||
|
```
|
||||||
|
c* = √[2 · (γ+1)/(γ-1) · R · T0 · (2/(γ+1))^((γ+1)/(γ-1))]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Thrust Coefficient
|
||||||
|
|
||||||
|
```
|
||||||
|
CF = √[2γ²/(γ-1) · (2/(γ+1))^((γ+1)/(γ-1)) · (1 - (Pe/P0)^((γ-1)/γ))]
|
||||||
|
+ (Pe - Pa)/(P0) · (Ae/At)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Chamber Thermodynamics
|
||||||
|
|
||||||
|
**Energy balance (no losses):**
|
||||||
|
```
|
||||||
|
Cp · (T0 - Te) = Ve² / 2
|
||||||
|
```
|
||||||
|
- Cp = specific heat at constant pressure
|
||||||
|
|
||||||
|
**Chemical equilibrium:**
|
||||||
|
Determine T0, γ from propellant properties and stoichiometry
|
||||||
|
(computed via NASA CEA code or thermodynamic tables)
|
||||||
|
|
||||||
|
### Drag & Atmosphere
|
||||||
|
|
||||||
|
**Drag force:**
|
||||||
|
```
|
||||||
|
Fd = 0.5 · ρ · v² · Cd · A
|
||||||
|
```
|
||||||
|
- ρ = air density (depends on altitude)
|
||||||
|
- v = velocity
|
||||||
|
- Cd = drag coefficient (~0.25 for rockets)
|
||||||
|
- A = reference area
|
||||||
|
|
||||||
|
**US Standard Atmosphere (piecewise):**
|
||||||
|
```
|
||||||
|
T(h) = T0 - L·h (troposphere, 0–11 km)
|
||||||
|
P(h) = P0 · (T(h)/T0)^(-g/(R·L))
|
||||||
|
ρ(h) = ρ0 · (T(h)/T0)^(-(g/(R·L) + 1))
|
||||||
|
```
|
||||||
|
- L = lapse rate ≈ 6.5 K/km
|
||||||
|
- R = gas constant
|
||||||
|
|
||||||
|
### Hoop Stress (Pressure Vessels)
|
||||||
|
|
||||||
|
**Thin-walled cylinder:**
|
||||||
|
```
|
||||||
|
σ_hoop = P·r / t
|
||||||
|
```
|
||||||
|
With safety factor:
|
||||||
|
```
|
||||||
|
t = P·r / (σ_yield / SF)
|
||||||
|
```
|
||||||
|
Mass:
|
||||||
|
```
|
||||||
|
m = 2π · r · t · L · ρ (cylinder)
|
||||||
|
m = 4π · r² · t · ρ (hemispherical dome)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mass Fraction
|
||||||
|
|
||||||
|
**Payload fraction:**
|
||||||
|
```
|
||||||
|
fp = m_payload / m_wet
|
||||||
|
```
|
||||||
|
|
||||||
|
**Structure fraction:**
|
||||||
|
```
|
||||||
|
fs = m_structure / m_wet
|
||||||
|
```
|
||||||
|
|
||||||
|
**Propellant fraction:**
|
||||||
|
```
|
||||||
|
fp = m_propellant / m_wet
|
||||||
|
```
|
||||||
|
|
||||||
|
Sum: `fp + fs + fpayload = 1`
|
||||||
|
|
||||||
|
Typical rockets: `fs = 0.10–0.20` (10–20%)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Using Knowledgebase in Design
|
||||||
|
|
||||||
|
### Importing Propellant Properties
|
||||||
|
|
||||||
|
1. **Solver**: Drag `chamberTemperature`, `expansionGamma` onto workspace
|
||||||
|
2. Check knowledgebase for propellant pair (LOX + RP-1)
|
||||||
|
3. Enter T0 from knowledgebase → solver computes Isp
|
||||||
|
4. Verify with reference Isp in database
|
||||||
|
|
||||||
|
### Cross-Checking Ablation
|
||||||
|
|
||||||
|
1. **Engine Design**: Select PAXS ablative
|
||||||
|
2. **Check Pressure Exponent**: 0.38 (from knowledgebase)
|
||||||
|
3. Verify erosion rate correction is applied
|
||||||
|
4. Compare remaining thickness to safety margin (>0.5 inch)
|
||||||
|
|
||||||
|
### Material Trade-Studies
|
||||||
|
|
||||||
|
1. **Rocket Design**: Compare materials
|
||||||
|
- Aluminum: 1000 kg tanks
|
||||||
|
- Titanium: 600 kg tanks (lighter, more expensive)
|
||||||
|
- CFRP: 350 kg tanks (lightest, most expensive)
|
||||||
|
2. Use mass to compute TWR, delta-v
|
||||||
|
3. Pick best balance for mission
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
- **User contributions**: Allow adding new materials/propellants
|
||||||
|
- **Temperature corrections**: Adjust properties with temperature
|
||||||
|
- **Material compatibility**: Show fuel/material interactions
|
||||||
|
- **Cost database**: Include material & propellant costs
|
||||||
|
- **References**: Link to papers & technical data sources
|
||||||
|
- **Lookup plots**: Interactive charts (Isp vs. O/F, etc.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- NASA SP-273: Liquid Rocket Engine Combustion Instability.
|
||||||
|
- Huzel, D. K., & Huang, D. H. (1992). Modern engineering for design of liquid-propellant rocket engines. AIAA.
|
||||||
|
- Rocket Propulsion Elements (8th ed.). Sutton & Biblarz.
|
||||||
|
- US Standard Atmosphere 1976. NASA TM-X-74335.
|
||||||
|
- CEA (Chemical Equilibrium with Applications). NASA Glenn Research Center.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated**: 2025-02 | **Status**: Current
|
||||||
243
docs/QUICKSTART.md
Normal file
243
docs/QUICKSTART.md
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
# Quick Start Guide
|
||||||
|
|
||||||
|
Get up and running with RocketTools in 5 minutes.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open **http://localhost:5173** in your browser.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Your First Rocket Design (10 minutes)
|
||||||
|
|
||||||
|
### Step 1: Design an Engine (3 min)
|
||||||
|
|
||||||
|
1. Click **Design → Engine**
|
||||||
|
2. Select propellants:
|
||||||
|
- Fuel: **RP-1**
|
||||||
|
- Oxidizer: **LOX**
|
||||||
|
3. Set chamber pressure: **200 bar**
|
||||||
|
4. Set nozzle diameter: **50 mm**
|
||||||
|
5. Set expansion ratio: **8**
|
||||||
|
6. **→ Results**: Thrust ~150 kN, Isp ~310 s, burn time 60 s
|
||||||
|
7. **Export as JSON** (save to file)
|
||||||
|
|
||||||
|
### Step 2: Design a Rocket (4 min)
|
||||||
|
|
||||||
|
1. Click **Design → Rocket**
|
||||||
|
2. **Import engine JSON** (from step 1)
|
||||||
|
3. Set tank configuration: **Tandem**
|
||||||
|
4. Set outer radius: **1.0 m**
|
||||||
|
5. Set propellant volume: **5000 L**
|
||||||
|
6. Set payload: **50 kg**
|
||||||
|
7. **→ Results**: Wet mass 1550 kg, dry mass 450 kg, TWR 10
|
||||||
|
8. Adjust nose cone shape: **Von Kármán** (optimal)
|
||||||
|
9. **Export as JSON** (save to file)
|
||||||
|
|
||||||
|
### Step 3: Simulate Flight (3 min)
|
||||||
|
|
||||||
|
1. Click **Design → Trajectory**
|
||||||
|
2. **Import rocket JSON** (from step 2)
|
||||||
|
3. **Import engine JSON** (from step 1)
|
||||||
|
4. Set pitch start altitude: **1000 m**
|
||||||
|
5. **Run Simulation**
|
||||||
|
6. **Watch the animation** (click Play button)
|
||||||
|
7. **Results**: ~5000 m downrange, 50 km apogee
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Pages Overview
|
||||||
|
|
||||||
|
### 🧮 Solver
|
||||||
|
Automatically solve rocket equations.
|
||||||
|
- Drag variables onto workspace
|
||||||
|
- Set constraints
|
||||||
|
- Solver computes unknowns
|
||||||
|
|
||||||
|
**Try this**: Set `thrust = 100000 N`, `massFlowRate = 100 kg/s`
|
||||||
|
→ Solver computes `exitVelocity = 1000 m/s`
|
||||||
|
|
||||||
|
### 🔥 Engine Design
|
||||||
|
Calculate combustion, erosion, and structure.
|
||||||
|
- Propellant selection
|
||||||
|
- Chamber pressure & nozzle geometry
|
||||||
|
- Ablative erosion prediction
|
||||||
|
- Wall thickness & mass
|
||||||
|
|
||||||
|
**Try this**: Increase chamber pressure from 200 → 500 bar
|
||||||
|
→ See thrust increase, wall thickness increase, mass increase
|
||||||
|
|
||||||
|
### 🚀 Rocket Design
|
||||||
|
Vehicle geometry and mass budget.
|
||||||
|
- Tank configuration (tandem/coaxial)
|
||||||
|
- Tank structure (hoop stress)
|
||||||
|
- Nose cone shapes
|
||||||
|
- Complete mass budget
|
||||||
|
- 3D visualization
|
||||||
|
|
||||||
|
**Try this**: Switch nose cone from Conical → Von Kármán
|
||||||
|
→ 3D model updates in real time
|
||||||
|
|
||||||
|
### 📈 Trajectory Simulation
|
||||||
|
Flight path prediction.
|
||||||
|
- RK4 physics integrator
|
||||||
|
- Atmosphere model (US Standard)
|
||||||
|
- Event detection (liftoff, apogee, landing)
|
||||||
|
- Playback controls
|
||||||
|
|
||||||
|
**Try this**: Run simulation, then scrub timeline to different times
|
||||||
|
→ See position, velocity, altitude change
|
||||||
|
|
||||||
|
### 📚 Knowledgebase
|
||||||
|
Material & equation references.
|
||||||
|
- **Fuels / Oxidizers** — propellant properties
|
||||||
|
- **Ablative Materials** — thermal protection specs
|
||||||
|
- **Equations** — physics reference
|
||||||
|
- **Structural Materials** — metal/composite properties
|
||||||
|
|
||||||
|
**Try this**: Look up LOX properties
|
||||||
|
→ Temperature: 3750 K, Isp: 310 s
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tips & Tricks
|
||||||
|
|
||||||
|
### Save Your Work
|
||||||
|
- Engine JSON → use for multiple rocket designs
|
||||||
|
- Rocket JSON → use for trajectory simulations
|
||||||
|
- Keep both files for reference
|
||||||
|
|
||||||
|
### Quick Iterations
|
||||||
|
1. Modify one parameter (e.g., chamber pressure)
|
||||||
|
2. Solver/simulator automatically updates
|
||||||
|
3. Check results
|
||||||
|
4. Adjust again
|
||||||
|
|
||||||
|
### Understanding Mass
|
||||||
|
- **Wet mass** = total (with fuel)
|
||||||
|
- **Dry mass** = structure + engine + payload (no fuel)
|
||||||
|
- **Propellant mass** = wet − dry
|
||||||
|
- **TWR** = thrust / weight
|
||||||
|
- > 5 is good for vertical launch
|
||||||
|
- < 2 struggles to lift off
|
||||||
|
|
||||||
|
### Understanding Isp
|
||||||
|
- **Higher Isp = more efficient** (goes farther on same fuel)
|
||||||
|
- Vacuum Isp > Sea-level Isp (less atmospheric pressure at altitude)
|
||||||
|
- LOX/RP-1: ~310 s (workhorse)
|
||||||
|
- LOX/H2: ~450 s (high-perf, but cryogenic)
|
||||||
|
- MMH/N2O4: ~290 s (storable, easier operations)
|
||||||
|
|
||||||
|
### Flight Path Basics
|
||||||
|
- Vehicle accelerates straight up (vertical hold)
|
||||||
|
- At ~1 km altitude, starts gravity turn (follows velocity)
|
||||||
|
- Thrust ends at ~60 s (Main Engine Cutoff)
|
||||||
|
- Coasts up to apogee (~50 km in example)
|
||||||
|
- Falls back down (lands ~5 km downrange)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Questions
|
||||||
|
|
||||||
|
**Q: How do I get higher apogee?**
|
||||||
|
- Increase fuel volume (larger tank)
|
||||||
|
- Reduce payload mass
|
||||||
|
- Higher Isp propellant (LOX/H2)
|
||||||
|
- More efficient rocket (lighter structure)
|
||||||
|
|
||||||
|
**Q: Why is my rocket too heavy?**
|
||||||
|
- Reduce tank radius (but check volume)
|
||||||
|
- Use stronger material (Al → Ti → CFRP)
|
||||||
|
- Lower safety factor (use 2.0 instead of 3.0)
|
||||||
|
- Reduce payload
|
||||||
|
|
||||||
|
**Q: Can I design multiple stages?**
|
||||||
|
- Not yet — current version is single-stage only
|
||||||
|
- Workaround: design each stage separately, estimate staging losses manually
|
||||||
|
|
||||||
|
**Q: How accurate are the simulations?**
|
||||||
|
- ±5–10% for well-designed vehicles
|
||||||
|
- Assumes no wind, perfect injection
|
||||||
|
- Doesn't include control/guidance errors
|
||||||
|
- Good for feasibility analysis, not flight prediction
|
||||||
|
|
||||||
|
**Q: Can I export to CAD?**
|
||||||
|
- Not yet — export rocket JSON for external processing
|
||||||
|
- 3D model data available in code (tank dimensions, nose cone profile)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Mistakes to Avoid
|
||||||
|
|
||||||
|
❌ **Too small tank**
|
||||||
|
- 5000 L seems big but isn't for LOX/RP-1 rockets
|
||||||
|
- Try 10,000+ L for real vehicle
|
||||||
|
|
||||||
|
❌ **Forgot engine burn time**
|
||||||
|
- Check engine design was properly simulated
|
||||||
|
- Engine JSON must include burn time
|
||||||
|
|
||||||
|
❌ **Wrong reference area for drag**
|
||||||
|
- Use actual cross-sectional area (πR²)
|
||||||
|
- Not wetted surface or planform area
|
||||||
|
|
||||||
|
❌ **Unrealistic safety factor**
|
||||||
|
- 2.0–3.0 is typical for engines
|
||||||
|
- < 1.5 is dangerous, > 5.0 is wasteful
|
||||||
|
|
||||||
|
❌ **Ignoring pressurant mass**
|
||||||
|
- Pressure-fed system helium adds ~100 kg
|
||||||
|
- Consider pump-fed for heavier vehicles
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Explore knowledgebase** — understand propellants & materials
|
||||||
|
2. **Read equations** — understand the physics
|
||||||
|
3. **Design variants** — try different tank sizes, materials
|
||||||
|
4. **Optimize** — see what makes vehicle lighter/faster
|
||||||
|
5. **Contribute** — add new equations or materials
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Learning Resources
|
||||||
|
|
||||||
|
- [Rocket Propulsion Elements](https://www.amazon.com/Rocket-Propulsion-Elements-Sutton-Biblarz/dp/0470080728) — textbook
|
||||||
|
- [NASA SP-273: Combustion Instability](https://ntrs.nasa.gov) — free PDF
|
||||||
|
- [Everyday Astronaut](https://www.youtube.com/c/EverydayAstronaut) — video explanations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
| Problem | Solution |
|
||||||
|
|---------|----------|
|
||||||
|
| Nothing shows up | Refresh browser (Ctrl+F5) |
|
||||||
|
| Numbers look wrong | Check input units (bar vs MPa, mm vs m) |
|
||||||
|
| 3D model doesn't render | WebGL must be enabled in browser |
|
||||||
|
| Solver stuck | Ensure enough constraints set for system |
|
||||||
|
| Simulation runs forever | Reduce timestep or check inputs for infinity |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Keyboard Shortcuts
|
||||||
|
|
||||||
|
| Shortcut | Action |
|
||||||
|
|----------|--------|
|
||||||
|
| **Space** | Play/pause trajectory |
|
||||||
|
| **→** | Jump forward 10 seconds |
|
||||||
|
| **←** | Jump backward 10 seconds |
|
||||||
|
| **Home** | Jump to start |
|
||||||
|
| **End** | Jump to end |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Happy rocket designing! 🚀**
|
||||||
|
|
||||||
|
Next, dive into [SOLVER.md](SOLVER.md) to master the equation solver.
|
||||||
509
docs/ROCKET_DESIGN.md
Normal file
509
docs/ROCKET_DESIGN.md
Normal file
@@ -0,0 +1,509 @@
|
|||||||
|
# Rocket Design Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The **Rocket Design** tool calculates vehicle mass budget, tank geometry, structural requirements, and generates 3D models. It integrates engine data from the engine designer and computes the complete rocket's performance characteristics.
|
||||||
|
|
||||||
|
## Main Sections
|
||||||
|
|
||||||
|
### Vehicle Geometry
|
||||||
|
Define tank configuration, dimensions, and structural layout.
|
||||||
|
|
||||||
|
### Tank Structure
|
||||||
|
Calculate wall thicknesses, masses, and pressurant requirements.
|
||||||
|
|
||||||
|
### Nose Cone Design
|
||||||
|
Select and visualize nose cone shapes (conical, ogive, Von Kármán).
|
||||||
|
|
||||||
|
### Mass Budget
|
||||||
|
Integrate all components (engines, tanks, payload, structure) to compute total mass and performance metrics.
|
||||||
|
|
||||||
|
### 3D Visualization
|
||||||
|
Interactive three.js model showing tanks, nose cone, and flight orientation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Vehicle Geometry
|
||||||
|
|
||||||
|
### Tank Configuration Options
|
||||||
|
|
||||||
|
**Tandem (Sequential):**
|
||||||
|
- Two cylindrical tanks stacked vertically
|
||||||
|
- Fuel (lower), Oxidizer (upper)
|
||||||
|
- Domes: hemispherical on both ends
|
||||||
|
- **Advantage**: Compact, simpler plumbing
|
||||||
|
- **Disadvantage**: Longer vehicle
|
||||||
|
|
||||||
|
**Coaxial (Concentric):**
|
||||||
|
- Outer tank (fuel) surrounds inner tank (oxidizer)
|
||||||
|
- Reduces diameter
|
||||||
|
- Inner tank: flat bulkheads (simpler)
|
||||||
|
- **Advantage**: Compact, low diameter
|
||||||
|
- **Disadvantage**: More complex, thermal management
|
||||||
|
|
||||||
|
**Single Tank:**
|
||||||
|
- One tank with both propellants mixed
|
||||||
|
- Rarely used for high-performance (lower Isp)
|
||||||
|
|
||||||
|
### Tank Dimensions
|
||||||
|
|
||||||
|
**Inputs:**
|
||||||
|
- `outerRadius` (mm) — outer tank radius
|
||||||
|
- `propellantVolume` (L) — total propellant quantity
|
||||||
|
- `tankConfiguration` — tandem, coaxial, single
|
||||||
|
- `ullagePercent` (%) — pressurant volume as % of propellant
|
||||||
|
|
||||||
|
**Key Relationship:**
|
||||||
|
```
|
||||||
|
Ullage volume = propellant volume × (ullage% / 100)
|
||||||
|
Effective volume = propellant volume × (1 + ullage%)
|
||||||
|
```
|
||||||
|
|
||||||
|
Ullage accommodates:
|
||||||
|
- Thermal expansion of propellant
|
||||||
|
- Gas bubble for pump inlet
|
||||||
|
- Margin for fill level uncertainty
|
||||||
|
|
||||||
|
### Tank Geometry (Tandem Case)
|
||||||
|
|
||||||
|
**Dome Volume (hemispherical):**
|
||||||
|
```
|
||||||
|
V_dome = (2/3) × π × R³ (one hemisphere)
|
||||||
|
V_domes = (4/3) × π × R³ (both domes)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cylindrical Section:**
|
||||||
|
```
|
||||||
|
L_cyl = (V_eff - V_domes) / (π × R²)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Total Tank Length:**
|
||||||
|
```
|
||||||
|
L_total = L_cyl + 2R (two dome heights)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
- R = 1.5 m, V_prop = 5000 L, ullage = 10%
|
||||||
|
- V_eff = 5000 × 1.1 = 5500 L
|
||||||
|
- V_domes = 4/3 × π × 1.5³ ≈ 14.1 m³ = 14100 L (larger than prop!)
|
||||||
|
- Need smaller R or accept small L_cyl
|
||||||
|
|
||||||
|
**Note**: For smaller rockets, domes dominate volume. Design must account for this.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tank Structure
|
||||||
|
|
||||||
|
### Pressure Sources
|
||||||
|
|
||||||
|
**Pressure-Fed System:**
|
||||||
|
```
|
||||||
|
P_tank = 1.2 × P0_chamber (pressure margin)
|
||||||
|
```
|
||||||
|
Typical: P0 = 200 bar → P_tank = 240 bar
|
||||||
|
|
||||||
|
**Pump-Fed System:**
|
||||||
|
```
|
||||||
|
P_tank = 2 MPa (minimum for turbopump inlet)
|
||||||
|
```
|
||||||
|
Much lower pressure (pump provides most acceleration)
|
||||||
|
|
||||||
|
**Selection affects:**
|
||||||
|
- Wall thicknesses (direct proportionality)
|
||||||
|
- Mass (linear with pressure and wall thickness)
|
||||||
|
- Propellant pump requirements
|
||||||
|
|
||||||
|
### Structural Material Properties
|
||||||
|
|
||||||
|
Available materials from knowledgebase:
|
||||||
|
- **Aluminum 6061-T6**: density 2700 kg/m³, σ_y = 275 MPa
|
||||||
|
- **Stainless Steel 304**: density 8000 kg/m³, σ_y = 215 MPa
|
||||||
|
- **Inconel 718**: density 8190 kg/m³, σ_y = 1240 MPa
|
||||||
|
- **Titanium 6-4**: density 4430 kg/m³, σ_y = 880 MPa
|
||||||
|
- **CFRP (Carbon Fiber)**: density 1600 kg/m³, σ_y = 600 MPa
|
||||||
|
|
||||||
|
**Selection factors:**
|
||||||
|
- Cost (Al < SS < Ti < CFRP)
|
||||||
|
- Weight (CFRP < Ti < Al < SS)
|
||||||
|
- Corrosion (SS, Ti, CFRP better; Al needs coating)
|
||||||
|
- Temperature (Inconel for hot engines; others at T < 150°C)
|
||||||
|
|
||||||
|
### Wall Thickness (Hoop Stress)
|
||||||
|
|
||||||
|
**Hoop stress formula:**
|
||||||
|
```
|
||||||
|
σ_hoop = (P × R) / t
|
||||||
|
```
|
||||||
|
|
||||||
|
**With safety factor:**
|
||||||
|
```
|
||||||
|
t = (P × R) / (σ_yield / SF)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cylindrical section mass:**
|
||||||
|
```
|
||||||
|
m_cyl = 2π × R × t × L_cyl × ρ_material
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dome mass (two hemispheres):**
|
||||||
|
```
|
||||||
|
m_domes = 4π × R² × t × ρ_material
|
||||||
|
```
|
||||||
|
|
||||||
|
**Total tank mass:**
|
||||||
|
```
|
||||||
|
m_tank = m_cyl + m_domes
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example Calculation
|
||||||
|
|
||||||
|
**Tandem LOX/RP1 Tank:**
|
||||||
|
- Radius: 1.0 m
|
||||||
|
- Pressure: 240 bar = 24 MPa
|
||||||
|
- Material: Aluminum 6061-T6
|
||||||
|
- σ_y = 275 MPa
|
||||||
|
- ρ = 2700 kg/m³
|
||||||
|
- Safety Factor: 2.5
|
||||||
|
|
||||||
|
**Wall Thickness:**
|
||||||
|
```
|
||||||
|
t = (24 MPa × 1.0 m) / (275 MPa / 2.5)
|
||||||
|
= 24 / 110
|
||||||
|
≈ 0.218 m = 2.18 mm
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cylindrical Section (assume L_cyl = 3 m):**
|
||||||
|
```
|
||||||
|
m_cyl = 2π × 1.0 × 0.00218 × 3.0 × 2700
|
||||||
|
≈ 110 kg
|
||||||
|
```
|
||||||
|
|
||||||
|
**Domes:**
|
||||||
|
```
|
||||||
|
m_domes = 4π × 1.0² × 0.00218 × 2700
|
||||||
|
≈ 74 kg
|
||||||
|
```
|
||||||
|
|
||||||
|
**Total tank mass:** ~184 kg
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pressurant System (Pressure-Fed Only)
|
||||||
|
|
||||||
|
### Helium Requirements
|
||||||
|
|
||||||
|
For pressure-fed engines, pressurant gas maintains tank pressure throughout burn.
|
||||||
|
|
||||||
|
**Ideal gas law:**
|
||||||
|
```
|
||||||
|
P × V = n × R × T
|
||||||
|
```
|
||||||
|
|
||||||
|
**Helium mass:**
|
||||||
|
```
|
||||||
|
m_He = (P_tank × V_prop) / (R_He × T)
|
||||||
|
```
|
||||||
|
|
||||||
|
Where:
|
||||||
|
- `P_tank` — tank pressure (Pa)
|
||||||
|
- `V_prop` — propellant volume (m³)
|
||||||
|
- `R_He` — helium specific gas constant = 2077 J/kg/K
|
||||||
|
- `T` — temperature (K), typically 293 K (20°C)
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
- P_tank = 24 MPa = 24×10⁶ Pa
|
||||||
|
- V_prop = 5 m³
|
||||||
|
- T = 293 K
|
||||||
|
|
||||||
|
```
|
||||||
|
m_He = (24×10⁶ × 5) / (2077 × 293)
|
||||||
|
≈ 20 kg
|
||||||
|
```
|
||||||
|
|
||||||
|
### Helium Bottle Mass
|
||||||
|
|
||||||
|
Helium stored in high-pressure bottle. Typical storage pressure: 250 bar.
|
||||||
|
|
||||||
|
**Bottle mass (conservative):**
|
||||||
|
```
|
||||||
|
m_bottle = 4 × m_He
|
||||||
|
```
|
||||||
|
|
||||||
|
Rule of thumb: bottle is 4 times the helium mass. (More sophisticated: use MEOP analysis.)
|
||||||
|
|
||||||
|
**Total pressurant system:**
|
||||||
|
```
|
||||||
|
m_pressurant = m_He + m_bottle
|
||||||
|
```
|
||||||
|
|
||||||
|
Example: 20 kg He → 80 kg bottle → 100 kg pressurant system
|
||||||
|
|
||||||
|
**Note**: This is a major mass penalty for pressure-fed systems. Pump-fed systems avoid this.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Nose Cone Design
|
||||||
|
|
||||||
|
### Available Profiles
|
||||||
|
|
||||||
|
Three aerodynamic nose cone shapes, all with L = 2 × R (length = 2 × base radius).
|
||||||
|
|
||||||
|
**Conical:**
|
||||||
|
- Simple linear profile
|
||||||
|
- `r(x) = R × (x / L)`
|
||||||
|
- Sharpest tip, moderate drag
|
||||||
|
|
||||||
|
**Tangent Ogive:**
|
||||||
|
- Smooth circular arc meeting base tangentially
|
||||||
|
- `ρ = (R² + L²) / (2R)` (radius of curvature)
|
||||||
|
- `r(x) = √(ρ² - (L - x)²) - (ρ - R)`
|
||||||
|
- Smooth, less drag than conical
|
||||||
|
- Common in rocketry
|
||||||
|
|
||||||
|
**Von Kármán:**
|
||||||
|
- Power law profile minimizing drag
|
||||||
|
- `θ = acos(1 - 2x/L)`
|
||||||
|
- `r(x) = (R / √π) × √(θ - sin(2θ)/2)`
|
||||||
|
- Theoretically optimal for transonic flight
|
||||||
|
- Used on high-performance rockets
|
||||||
|
|
||||||
|
**Selection Criteria:**
|
||||||
|
- **Conical**: Simplest to manufacture, sharp tip
|
||||||
|
- **Tangent Ogive**: Better aerodynamics, smoother base transition
|
||||||
|
- **Von Kármán**: Best drag coefficient (Cd ≈ 0.15 vs 0.25 conical)
|
||||||
|
|
||||||
|
### 3D Visualization
|
||||||
|
|
||||||
|
Nose cone is rendered using THREE.js `LatheGeometry`:
|
||||||
|
- Profile points sampled from mathematical formula
|
||||||
|
- Rotated around vertical axis to create 3D shape
|
||||||
|
- Interactive rotation/zoom in 3D viewer
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mass Budget
|
||||||
|
|
||||||
|
### Components
|
||||||
|
|
||||||
|
**Structure:**
|
||||||
|
- Tank walls and domes: `m_tanks`
|
||||||
|
- Other structure (nosecone, bay, interstage): `m_other`
|
||||||
|
- Engine dry mass (from engine designer): `m_engine`
|
||||||
|
|
||||||
|
**Propellant:**
|
||||||
|
- Fuel + oxidizer: `m_propellant`
|
||||||
|
|
||||||
|
**Pressurant (pressure-fed only):**
|
||||||
|
- Helium + bottle: `m_pressurant`
|
||||||
|
|
||||||
|
**Payload:**
|
||||||
|
- Avionics, recovery system, instruments: `m_payload`
|
||||||
|
|
||||||
|
### Calculation
|
||||||
|
|
||||||
|
**Dry mass (no propellant):**
|
||||||
|
```
|
||||||
|
m_dry = m_tanks + m_other + m_engine + m_pressurant + m_payload
|
||||||
|
```
|
||||||
|
|
||||||
|
**Wet mass (with propellant):**
|
||||||
|
```
|
||||||
|
m_wet = m_dry + m_propellant
|
||||||
|
```
|
||||||
|
|
||||||
|
**Payload fraction:**
|
||||||
|
```
|
||||||
|
fraction_payload = m_payload / m_wet
|
||||||
|
```
|
||||||
|
|
||||||
|
**Structure fraction:**
|
||||||
|
```
|
||||||
|
fraction_structure = m_tanks / m_wet
|
||||||
|
```
|
||||||
|
|
||||||
|
**Thrust-to-weight ratio:**
|
||||||
|
```
|
||||||
|
TWR = F_thrust / (m_wet × g)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example Mass Budget
|
||||||
|
|
||||||
|
Small LOX/RP1 rocket:
|
||||||
|
|
||||||
|
| Component | Mass (kg) | % |
|
||||||
|
|-----------|-----------|-----|
|
||||||
|
| Tanks (structure) | 200 | 14% |
|
||||||
|
| Engine | 50 | 3.5% |
|
||||||
|
| Pressurant (He + bottle) | 100 | 7% |
|
||||||
|
| Avionics + recovery | 30 | 2% |
|
||||||
|
| **Dry mass** | **380** | **26.5%** |
|
||||||
|
| Propellant (LOX + RP1) | 1050 | 73.5% |
|
||||||
|
| **Wet mass** | **1430** | **100%** |
|
||||||
|
|
||||||
|
**Performance:**
|
||||||
|
- TWR = 150 kN / (1430 kg × 9.81 m/s²) = 10.7
|
||||||
|
- Delta-v (no gravity loss): 310 s × ln(1430/380) ≈ 1300 m/s
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3D Model Components
|
||||||
|
|
||||||
|
### Tank Rendering
|
||||||
|
|
||||||
|
**Cylindrical body:**
|
||||||
|
```javascript
|
||||||
|
<TankSection
|
||||||
|
radius={1.0}
|
||||||
|
length={3.0}
|
||||||
|
position={[0, 0, 0]}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
Renders:
|
||||||
|
- Cylinder: `CylinderGeometry`
|
||||||
|
- Top dome: `SphereGeometry` (hemisphere)
|
||||||
|
- Bottom dome: `SphereGeometry` (hemisphere)
|
||||||
|
|
||||||
|
**Material:** MeshStandardMaterial with color (red = fuel, blue = oxidizer)
|
||||||
|
|
||||||
|
### Nose Cone Rendering
|
||||||
|
|
||||||
|
**LatheGeometry:**
|
||||||
|
- Takes profile curve (array of points)
|
||||||
|
- Rotates around Y-axis to create 3D surface
|
||||||
|
- Smooth, aerodynamic appearance
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```javascript
|
||||||
|
const profile = noseConeProfile('tangent-ogive', 0.5, 1.0, 24)
|
||||||
|
const geometry = new THREE.LatheGeometry(profile, 24)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scene Setup
|
||||||
|
|
||||||
|
**Lighting:**
|
||||||
|
- Ambient light (0.6 intensity)
|
||||||
|
- Directional light from top-right
|
||||||
|
- Shadows enabled for depth
|
||||||
|
|
||||||
|
**Camera:**
|
||||||
|
- Orbit controls (rotate, zoom, pan)
|
||||||
|
- Auto-fit to model bounds
|
||||||
|
- Perspective view (60° FOV)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Workflow: Design Complete Vehicle
|
||||||
|
|
||||||
|
### Step 1: Import Engine
|
||||||
|
1. Go to **Design > Engine**
|
||||||
|
2. Configure combustion (LOX/RP1, 200 bar, etc.)
|
||||||
|
3. Size structure (Inconel, 3.0 SF)
|
||||||
|
4. Export as JSON
|
||||||
|
5. Return to **Design > Rocket**
|
||||||
|
6. Upload engine JSON
|
||||||
|
→ **Result**: Engine dry mass auto-populated
|
||||||
|
|
||||||
|
### Step 2: Configure Tanks
|
||||||
|
1. Set tank configuration: **Tandem**
|
||||||
|
2. Set outer radius: **1.0 m**
|
||||||
|
3. Set propellant volume: **5000 L**
|
||||||
|
4. Set ullage: **10%**
|
||||||
|
→ **Result**: Calculated tank length, dome geometry
|
||||||
|
|
||||||
|
### Step 3: Design Tank Structure
|
||||||
|
1. Select material: **Aluminum 6061-T6**
|
||||||
|
2. Set safety factor: **2.5**
|
||||||
|
3. Select feed system: **Pressure-Fed**
|
||||||
|
→ **Result**: Wall thickness, tank mass, pressurant requirements
|
||||||
|
|
||||||
|
### Step 4: Set Payload
|
||||||
|
1. Enter payload mass: **50 kg** (avionics, recovery)
|
||||||
|
→ **Result**: Dry mass updated
|
||||||
|
|
||||||
|
### Step 5: View Mass Budget
|
||||||
|
→ **Result**:
|
||||||
|
- Wet mass: 1550 kg
|
||||||
|
- Dry mass: 450 kg
|
||||||
|
- TWR: 10.2
|
||||||
|
- Delta-v (vacuum): 1320 m/s
|
||||||
|
|
||||||
|
### Step 6: Adjust Nose Cone
|
||||||
|
1. Select shape: **Von Kármán**
|
||||||
|
→ **Result**: 3D model updates with optimal nose cone
|
||||||
|
|
||||||
|
### Step 7: Export & Simulate
|
||||||
|
1. Export vehicle JSON
|
||||||
|
2. Go to **Design > Trajectory**
|
||||||
|
3. Import vehicle & engine data
|
||||||
|
4. Run flight simulation
|
||||||
|
→ **Result**: Altitude, downrange, apogee, landing site
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Advanced Topics
|
||||||
|
|
||||||
|
### Multi-Stage Rockets
|
||||||
|
|
||||||
|
Not yet implemented, but conceptually:
|
||||||
|
- Stage 1: Large engines, heavy structure
|
||||||
|
- Stage 2: Smaller engines, lighter structure
|
||||||
|
- Interstage adapter: mass penalty
|
||||||
|
- Staging sequence: defined by velocity requirements
|
||||||
|
|
||||||
|
Implementation would require:
|
||||||
|
- Multiple vehicle definitions (or list of stages)
|
||||||
|
- Trajectory system that handles stage separation
|
||||||
|
- Cumulative mass and thrust calculations
|
||||||
|
|
||||||
|
### Composite Materials
|
||||||
|
|
||||||
|
CFRP offers best strength-to-weight but requires special analysis:
|
||||||
|
- **Fiber direction** — properties vary with orientation
|
||||||
|
- **Matrix** — epoxy, vinyl ester
|
||||||
|
- **Layup schedule** — [0°/90°/45°] typical
|
||||||
|
- **Microbuckling** — axial compression limit
|
||||||
|
- **Matrix cracking** — transverse tensile limit
|
||||||
|
|
||||||
|
Advanced model would include:
|
||||||
|
- Classical laminate theory (CLT)
|
||||||
|
- Ply-by-ply failure criteria (Tsai-Wu, Hashin)
|
||||||
|
- Buckling analysis (Timoshenko, finite element)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Tank too long?
|
||||||
|
- Reduce ullage percentage (lower to 5%)
|
||||||
|
- Increase radius (larger tank, shorter cylinder)
|
||||||
|
- Split into multiple smaller tanks
|
||||||
|
- Switch to coaxial configuration
|
||||||
|
|
||||||
|
### Vehicle too heavy?
|
||||||
|
- Switch to lighter material (CFRP)
|
||||||
|
- Reduce safety factor (if acceptable)
|
||||||
|
- Decrease payload mass
|
||||||
|
- Use pump-fed system (avoids pressurant)
|
||||||
|
|
||||||
|
### Nose cone looks wrong?
|
||||||
|
- Verify radius and length (L = 2R)
|
||||||
|
- Try different shape (Von Kármán usually best)
|
||||||
|
- Check 3D viewer isn't zoomed in too far
|
||||||
|
|
||||||
|
### 3D model not rendering?
|
||||||
|
- WebGL must be enabled
|
||||||
|
- Check browser console for Three.js errors
|
||||||
|
- Ensure geometry dimensions are reasonable (not NaN)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- NASA SP-8007: Structural Design and Test Factors of Safety for Spaceflight Hardware
|
||||||
|
- Huzel, D. K., & Huang, D. H. (1992). Modern engineering for design of liquid-propellant rocket engines.
|
||||||
|
- Rocket Propulsion Elements (8th ed.). by Sutton & Biblarz.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated**: 2025-02 | **Status**: Current (v3 — Domed Tanks)
|
||||||
319
docs/SOLVER.md
Normal file
319
docs/SOLVER.md
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
# Solver Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The **Equation Solver** is a constraint-based computational engine that automatically solves rocket propulsion equations. Rather than manually plugging numbers into formulas, users specify constraints (inputs), and the solver propagates solutions through an interconnected equation network.
|
||||||
|
|
||||||
|
**Key Concept**: Drag variables onto the workspace, set values, and the solver automatically computes unknowns.
|
||||||
|
|
||||||
|
## User Interface
|
||||||
|
|
||||||
|
### Variable Palette (Left)
|
||||||
|
- Searchable list of all variables
|
||||||
|
- Organized by category (thrust, Isp, chamber, etc.)
|
||||||
|
- Drag variables onto workspace
|
||||||
|
|
||||||
|
### Workspace (Center)
|
||||||
|
- Drop zone for constraint cards
|
||||||
|
- Cards show variable name, unit, input field
|
||||||
|
- Set constraint by entering value
|
||||||
|
- Results auto-compute below
|
||||||
|
|
||||||
|
### Results Panel (Right)
|
||||||
|
- Shows all computed values
|
||||||
|
- Organized by category
|
||||||
|
- Clickable to copy values
|
||||||
|
- Unit selector for each variable
|
||||||
|
|
||||||
|
## Constraint Propagation Algorithm
|
||||||
|
|
||||||
|
The solver implements **greedy propagation**:
|
||||||
|
|
||||||
|
### Step-by-Step
|
||||||
|
|
||||||
|
1. **User Input**: Sets constraint on variable `x`
|
||||||
|
2. **Find Equations**: Locates all equations referencing `x`
|
||||||
|
3. **Attempt Solve**: For each equation, tries to solve for unknowns
|
||||||
|
- If equation has 1 unknown: solve directly
|
||||||
|
- If equation has 2+ unknowns: skip (not enough constraints)
|
||||||
|
4. **Update State**: Newly-solved variables are added to known set
|
||||||
|
5. **Recurse**: Repeat step 2 with newly-known variables
|
||||||
|
6. **Stop**: When no new variables can be computed
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
**Given:**
|
||||||
|
- `thrust = massFlowRate * exitVelocity`
|
||||||
|
- `exitVelocity = sqrt(2 * Cp * (T0 - Te))`
|
||||||
|
- `massFlowRate = densityO2 * densityFuel * burnRate`
|
||||||
|
|
||||||
|
**User Constraints:**
|
||||||
|
- `thrust = 50000 N`
|
||||||
|
- `massFlowRate = 100 kg/s`
|
||||||
|
|
||||||
|
**Solver Flow:**
|
||||||
|
```
|
||||||
|
1. Set thrust = 50000
|
||||||
|
2. Find equations with thrust: Eq1
|
||||||
|
3. Eq1 has 2 unknowns (massFlowRate, exitVelocity) → skip
|
||||||
|
4. Set massFlowRate = 100
|
||||||
|
5. Find equations with massFlowRate: Eq1, Eq3
|
||||||
|
6. Eq1 now has 1 unknown (exitVelocity) → solve: exitVelocity = 500 m/s
|
||||||
|
7. Find equations with exitVelocity: Eq2
|
||||||
|
8. Eq2 has 2 unknowns (T0, Te) → skip
|
||||||
|
9. No new variables → done
|
||||||
|
```
|
||||||
|
|
||||||
|
## Variable Definitions
|
||||||
|
|
||||||
|
Variables are defined in `src/engine/variables.js` with metadata:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
id: 'thrust',
|
||||||
|
name: 'Thrust',
|
||||||
|
unit: 'N',
|
||||||
|
category: 'performance',
|
||||||
|
type: 'number'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Categories:**
|
||||||
|
- `thrust` — Force (N, lbf)
|
||||||
|
- `isp` — Specific impulse (s, m/s)
|
||||||
|
- `chamber` — Chamber conditions (P, T, density)
|
||||||
|
- `nozzle` — Nozzle geometry (area, diameter, angle)
|
||||||
|
- `propellant` — Fuel/oxidizer properties
|
||||||
|
- `flow` — Mass flow rate, burn rate
|
||||||
|
- `thermal` — Heat, temperature
|
||||||
|
- `structural` — Stress, strain, thickness
|
||||||
|
|
||||||
|
## Equation Library
|
||||||
|
|
||||||
|
Equations are defined in `src/engine/equations.js`. Each equation:
|
||||||
|
|
||||||
|
1. **Specifies variables** it relates
|
||||||
|
2. **Implements forward solver** (compute output given inputs)
|
||||||
|
3. **Implements per-variable solvers** (solve for each variable given others)
|
||||||
|
|
||||||
|
### Example Equation
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
id: 'thrustEquation',
|
||||||
|
name: 'Thrust (Momentum)',
|
||||||
|
variables: ['thrust', 'massFlowRate', 'exitVelocity'],
|
||||||
|
equation: 'thrust = massFlowRate * exitVelocity',
|
||||||
|
forward: (inputs) => {
|
||||||
|
return inputs.massFlowRate * inputs.exitVelocity
|
||||||
|
},
|
||||||
|
solvers: {
|
||||||
|
exitVelocity: (inputs) => {
|
||||||
|
return inputs.thrust / inputs.massFlowRate
|
||||||
|
},
|
||||||
|
thrust: (inputs) => {
|
||||||
|
return inputs.massFlowRate * inputs.exitVelocity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transcendental Equations (Bisection)
|
||||||
|
|
||||||
|
For equations with no closed-form solution (e.g., Mach from area ratio), the solver uses **bisection**:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// In numerics.js
|
||||||
|
function bisection(fn, target, min, max, tolerance) {
|
||||||
|
while (max - min > tolerance) {
|
||||||
|
const mid = (min + max) / 2
|
||||||
|
const fMid = fn(mid)
|
||||||
|
if (fMid < target) {
|
||||||
|
min = mid
|
||||||
|
} else {
|
||||||
|
max = mid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (min + max) / 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example: Finding Mach number from area ratio
|
||||||
|
```javascript
|
||||||
|
solvers: {
|
||||||
|
machNumber: (inputs) => {
|
||||||
|
const fn = (M) => isentropicAreaRatio(M, inputs.gamma)
|
||||||
|
return bisection(fn, inputs.areaRatio, 0.01, 10, 1e-6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Equations
|
||||||
|
|
||||||
|
### Thrust
|
||||||
|
```
|
||||||
|
thrust = mass_flow_rate * exit_velocity
|
||||||
|
thrust = chamber_pressure * throat_area * CF
|
||||||
|
```
|
||||||
|
|
||||||
|
### Specific Impulse
|
||||||
|
```
|
||||||
|
Isp = exit_velocity / g
|
||||||
|
Isp_vac = exit_velocity / g
|
||||||
|
Isp_sl = (exit_velocity - P_exit / mass_flow_rate * area_exit) / g
|
||||||
|
```
|
||||||
|
|
||||||
|
### Chamber Conditions (Isentropic Flow)
|
||||||
|
```
|
||||||
|
T_exit = T_chamber * (P_exit / P_chamber)^((gamma - 1) / gamma)
|
||||||
|
rho_exit = rho_chamber * (P_exit / P_chamber)^(1 / gamma)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nozzle Area Ratio
|
||||||
|
```
|
||||||
|
A_exit / A_throat = (2 / (gamma + 1)) * (1 + (gamma - 1)/2 * M_exit^2))^((gamma + 1) / (2 * (gamma - 1))) / M_exit
|
||||||
|
```
|
||||||
|
(requires bisection to invert for M)
|
||||||
|
|
||||||
|
### Characteristic Velocity
|
||||||
|
```
|
||||||
|
c_star = P_chamber * A_throat / mass_flow_rate
|
||||||
|
```
|
||||||
|
|
||||||
|
### Thrust Coefficient
|
||||||
|
```
|
||||||
|
CF = sqrt(2 * gamma^2 / (gamma - 1) * (2 / (gamma + 1))^((gamma + 1) / (gamma - 1)) * (1 - (P_exit / P_chamber)^((gamma - 1) / gamma)))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workflow Examples
|
||||||
|
|
||||||
|
### Example 1: Determine Exit Velocity from Isp
|
||||||
|
|
||||||
|
**Goal:** Calculate exit velocity given Isp
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Drag `specificImpulse` onto workspace
|
||||||
|
2. Set `specificImpulse = 300 s`
|
||||||
|
3. Solver computes `exitVelocity = 300 * 9.81 ≈ 2943 m/s`
|
||||||
|
|
||||||
|
### Example 2: Find Chamber Pressure from Thrust
|
||||||
|
|
||||||
|
**Goal:** Find chamber pressure needed for 50 kN thrust
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Drag `thrust`, `throatDiameter`, `massFlowRate` onto workspace
|
||||||
|
2. Set:
|
||||||
|
- `thrust = 50000 N`
|
||||||
|
- `throatDiameter = 0.1 m`
|
||||||
|
- `massFlowRate = 100 kg/s`
|
||||||
|
3. Solver finds equations with these variables
|
||||||
|
4. Uses thrust equation to find thrust coefficient needed
|
||||||
|
5. Inverts nozzle area ratio with bisection to find Mach
|
||||||
|
6. Uses isentropic relations to find chamber pressure
|
||||||
|
7. Displays `chamberPressure = X bar`
|
||||||
|
|
||||||
|
### Example 3: Design for Specific Isp
|
||||||
|
|
||||||
|
**Goal:** Find propellant combination for Isp = 350 s
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Drag `specificImpulse`, `chamberTemperature`, `exhaustGamma` onto workspace
|
||||||
|
2. Set `specificImpulse = 350 s`
|
||||||
|
3. Click on knowledgebase to find propellant with T0 ≈ 3500 K
|
||||||
|
4. Enter that temperature
|
||||||
|
5. Solver computes `exitVelocity`, then works backward to required conditions
|
||||||
|
6. Compare against available propellants
|
||||||
|
|
||||||
|
## Advanced Features
|
||||||
|
|
||||||
|
### Unit Conversion
|
||||||
|
Every variable card has a unit selector. Setting a variable automatically converts:
|
||||||
|
```
|
||||||
|
thrust = 50000 N → 11240.44 lbf
|
||||||
|
thrust = 11240.44 lbf → 50000 N
|
||||||
|
```
|
||||||
|
|
||||||
|
### Importing Engine Design
|
||||||
|
Engine design results can be fed into the solver:
|
||||||
|
- File → Import Engine JSON
|
||||||
|
- Automatically populates thrust, Isp, chamber pressure, etc.
|
||||||
|
- Enables end-to-end rocket design workflow
|
||||||
|
|
||||||
|
### Variable Categories
|
||||||
|
Filter variables by category to reduce clutter:
|
||||||
|
- Thrust (F, Isp, c*)
|
||||||
|
- Chamber (P, T, density)
|
||||||
|
- Nozzle (area, geometry)
|
||||||
|
- Propellant (fuel/oxidizer properties)
|
||||||
|
- Flow (mass flow, burn rate)
|
||||||
|
|
||||||
|
## Limitations & Gotchas
|
||||||
|
|
||||||
|
1. **No Circular Dependencies**: If equations form a cycle, solver may loop infinitely
|
||||||
|
- Solver stops after N iterations to prevent this
|
||||||
|
- Design equations to be acyclic where possible
|
||||||
|
|
||||||
|
2. **Insufficient Constraints**: If variables are underdetermined (more unknowns than equations)
|
||||||
|
- Solver computes what it can, leaves rest as unknown
|
||||||
|
- User must provide more constraints
|
||||||
|
|
||||||
|
3. **Overdetermined System**: If too many constraints (conflicting values)
|
||||||
|
- Solver uses first constraint encountered
|
||||||
|
- Other constraints ignored (may warn in future)
|
||||||
|
|
||||||
|
4. **Transcendental Equation Tolerance**: Bisection has numerical error
|
||||||
|
- Default tolerance: 1e-6
|
||||||
|
- Results accurate to ~6 significant figures
|
||||||
|
- Adjust in `numerics.js` if higher precision needed
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Solver not computing?
|
||||||
|
- Ensure at least one constraint is set
|
||||||
|
- Check that equation network connects the variables you're trying to solve
|
||||||
|
- Look for missing material properties (fuel, oxidizer data)
|
||||||
|
|
||||||
|
### Result seems wrong?
|
||||||
|
- Verify input units match variable definition
|
||||||
|
- Check variable category (is it the right variable?)
|
||||||
|
- Try solving step-by-step with intermediate variables
|
||||||
|
|
||||||
|
### Equation not available?
|
||||||
|
- Not all rocketry equations are implemented
|
||||||
|
- Submit a feature request to add more
|
||||||
|
- Or add the equation yourself in `src/engine/equations.js`
|
||||||
|
|
||||||
|
## Development Guide
|
||||||
|
|
||||||
|
### Adding a New Equation
|
||||||
|
|
||||||
|
1. **Define variables** in `variables.js` if needed
|
||||||
|
2. **Implement equation** in `equations.js`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
equations.push({
|
||||||
|
id: 'myEquation',
|
||||||
|
name: 'My Equation',
|
||||||
|
variables: ['var1', 'var2', 'var3'],
|
||||||
|
equation: 'var1 = var2 * var3',
|
||||||
|
solvers: {
|
||||||
|
var1: (inputs) => inputs.var2 * inputs.var3,
|
||||||
|
var2: (inputs) => inputs.var1 / inputs.var3,
|
||||||
|
var3: (inputs) => inputs.var1 / inputs.var2
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Test** by:
|
||||||
|
- Setting constraints manually
|
||||||
|
- Verifying solver produces correct result
|
||||||
|
- Testing with known values
|
||||||
|
|
||||||
|
### Debugging Solver
|
||||||
|
- Add console logs in `solver.js` propagation loop
|
||||||
|
- Log variable state at each iteration
|
||||||
|
- Verify equation network is correct
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated**: 2025-02 | **Status**: Current
|
||||||
503
docs/TRAJECTORY.md
Normal file
503
docs/TRAJECTORY.md
Normal file
@@ -0,0 +1,503 @@
|
|||||||
|
# Trajectory Simulation Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The **Trajectory Simulator** predicts rocket flight paths using physics-based 4th-order Runge-Kutta integration. It accounts for atmospheric drag, gravity, thrust, and user-defined events to compute complete vehicle trajectories from liftoff to landing.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
1. **Configure Vehicle**
|
||||||
|
- Import engine (from Engine Designer) or specify manually
|
||||||
|
- Enter vehicle mass (wet & dry), reference area, drag coefficient
|
||||||
|
|
||||||
|
2. **Configure Atmosphere**
|
||||||
|
- Select model (US Standard, exponential)
|
||||||
|
- Set sea-level conditions (temperature, pressure)
|
||||||
|
|
||||||
|
3. **Set Pitch Program**
|
||||||
|
- Vertical hold duration or altitude
|
||||||
|
- Then gravity turn (pitch follows velocity vector)
|
||||||
|
|
||||||
|
4. **Add Events** (optional)
|
||||||
|
- Guidance commands (pitch changes)
|
||||||
|
- Jettison (drop mass during flight)
|
||||||
|
- Markers (annotations)
|
||||||
|
|
||||||
|
5. **Run Simulation**
|
||||||
|
- Click "Run Simulation"
|
||||||
|
- Wait for RK4 integration to complete
|
||||||
|
|
||||||
|
6. **Playback**
|
||||||
|
- Watch animation (1×, 5×, 10× speed)
|
||||||
|
- Scrub timeline to jump to time
|
||||||
|
- Click on plot to jump to position
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Physics Model
|
||||||
|
|
||||||
|
### State Vector
|
||||||
|
|
||||||
|
At each timestep, we track:
|
||||||
|
```
|
||||||
|
state = [x, y, vx, vy, m]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `x` — downrange distance (m)
|
||||||
|
- `y` — altitude above sea level (m)
|
||||||
|
- `vx` — horizontal velocity (m/s, positive = downrange)
|
||||||
|
- `vy` — vertical velocity (m/s, positive = up)
|
||||||
|
- `m` — vehicle mass (kg)
|
||||||
|
|
||||||
|
### Forces
|
||||||
|
|
||||||
|
**Thrust:**
|
||||||
|
```
|
||||||
|
F_thrust = thrust(t) × cos(pitch_angle) [horizontal component]
|
||||||
|
F_thrust = thrust(t) × sin(pitch_angle) [vertical component]
|
||||||
|
```
|
||||||
|
|
||||||
|
Direction depends on pitch program (see below).
|
||||||
|
|
||||||
|
**Gravity (altitude-dependent):**
|
||||||
|
```
|
||||||
|
g(h) = g0 × (R_earth / (R_earth + h))²
|
||||||
|
```
|
||||||
|
|
||||||
|
Where:
|
||||||
|
- `g0` = 9.81 m/s²
|
||||||
|
- `R_earth` = 6.371×10⁶ m
|
||||||
|
- `h` = altitude (m)
|
||||||
|
|
||||||
|
**Drag:**
|
||||||
|
```
|
||||||
|
F_drag = 0.5 × ρ(h) × v² × Cd × A_ref
|
||||||
|
```
|
||||||
|
|
||||||
|
Opposed to velocity direction:
|
||||||
|
```
|
||||||
|
F_drag_x = -F_drag × (vx / v)
|
||||||
|
F_drag_y = -F_drag × (vy / v)
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `v = √(vx² + vy²)` is total velocity.
|
||||||
|
|
||||||
|
### Atmosphere
|
||||||
|
|
||||||
|
**US Standard Model (default):**
|
||||||
|
- Piecewise linear layers
|
||||||
|
- Troposphere: 0–8500 m (temperature decreases 6.5 K/km)
|
||||||
|
- Stratosphere: 8500–15000 m (isothermal at 216.5 K)
|
||||||
|
- Higher: exponential decay
|
||||||
|
|
||||||
|
**Exponential Model:**
|
||||||
|
```
|
||||||
|
ρ(h) = ρ0 × exp(-h / H)
|
||||||
|
```
|
||||||
|
|
||||||
|
Where:
|
||||||
|
- `ρ0` = sea-level density (1.225 kg/m³)
|
||||||
|
- `H` = scale height (~8500 m)
|
||||||
|
|
||||||
|
**Pressure & Temperature:**
|
||||||
|
- US Standard uses table lookup
|
||||||
|
- Temperature affects gas density (lower T = higher ρ at same P)
|
||||||
|
|
||||||
|
### Equations of Motion
|
||||||
|
|
||||||
|
**Acceleration:**
|
||||||
|
```
|
||||||
|
a_x = (F_thrust_x + F_drag_x) / m
|
||||||
|
a_y = (F_thrust_y + F_drag_y - m×g(h)) / m
|
||||||
|
```
|
||||||
|
|
||||||
|
**Mass Change (during burn):**
|
||||||
|
```
|
||||||
|
dm/dt = -mass_flow_rate
|
||||||
|
```
|
||||||
|
|
||||||
|
Mass only changes during engine burn phase (after liftoff, before MECO).
|
||||||
|
|
||||||
|
### Numerical Integration (RK4)
|
||||||
|
|
||||||
|
The integrator advances state by small timesteps (default: dt = 0.05 s):
|
||||||
|
|
||||||
|
```
|
||||||
|
k1 = derivatives(t, state)
|
||||||
|
k2 = derivatives(t + dt/2, state + k1×dt/2)
|
||||||
|
k3 = derivatives(t + dt/2, state + k2×dt/2)
|
||||||
|
k4 = derivatives(t + dt, state + k3×dt)
|
||||||
|
|
||||||
|
state_new = state + (k1 + 2×k2 + 2×k3 + k4) × dt/6
|
||||||
|
```
|
||||||
|
|
||||||
|
**Accuracy**: RK4 is 4th-order accurate; typical error scales as O(dt⁵).
|
||||||
|
|
||||||
|
**Advantages:**
|
||||||
|
- More accurate than Euler method
|
||||||
|
- Reasonable computational speed
|
||||||
|
- Standard in trajectory software
|
||||||
|
|
||||||
|
**Limitations:**
|
||||||
|
- No adaptive timestep (dt is fixed)
|
||||||
|
- Cannot handle stiff equations
|
||||||
|
- Small dt needed for accuracy (0.01–0.1 s typical)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
|
||||||
|
### Vehicle Configuration
|
||||||
|
|
||||||
|
**From Engine Designer (auto-populated if imported):**
|
||||||
|
- `thrust` — engine thrust curve or constant
|
||||||
|
- `isp` — specific impulse (vacuum)
|
||||||
|
- `mdot` — mass flow rate (kg/s)
|
||||||
|
- `burnTime` — engine burn duration (s)
|
||||||
|
- `dryMass` — engine dry mass (kg)
|
||||||
|
|
||||||
|
**Vehicle Properties (manual input):**
|
||||||
|
- `wetMass` — total mass with propellant (kg)
|
||||||
|
- `dryMass` — mass without propellant (kg)
|
||||||
|
- **Note**: Used after engine shutdown
|
||||||
|
- `referenceArea` — cross-sectional area for drag (m²)
|
||||||
|
- `dragCoefficient` — Cd (typically 0.2–0.3 for rockets)
|
||||||
|
- `payloadMass` — payload (kg, contributes to dry mass)
|
||||||
|
|
||||||
|
### Atmospheric Conditions
|
||||||
|
|
||||||
|
- `atmosphereModel` — US Standard or Exponential
|
||||||
|
- `seaLevelTemperature` — T0 (K), default 288.15 K (15°C)
|
||||||
|
- `seaLevelPressure` — P0 (Pa), default 101325 Pa
|
||||||
|
- `windSpeed` — not yet implemented
|
||||||
|
|
||||||
|
### Pitch Program
|
||||||
|
|
||||||
|
**Vertical Hold Phase:**
|
||||||
|
- `pitchStartAlt` — altitude to begin gravity turn (m)
|
||||||
|
- Duration: liftoff until altitude > pitchStartAlt
|
||||||
|
- Pitch angle: 90° (straight up)
|
||||||
|
|
||||||
|
**Gravity Turn Phase:**
|
||||||
|
- Pitch angle follows velocity vector
|
||||||
|
- `pitch = atan2(vy, vx)`
|
||||||
|
- Vehicle points along flight path (min drag)
|
||||||
|
|
||||||
|
**Advanced Programs (future):**
|
||||||
|
- Custom pitch schedule vs. time
|
||||||
|
- Thrust vector control
|
||||||
|
- Angle of attack constraints
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Outputs
|
||||||
|
|
||||||
|
### Trajectory States
|
||||||
|
|
||||||
|
Array of `state` vectors at each timestep:
|
||||||
|
```javascript
|
||||||
|
[
|
||||||
|
{time: 0, x: 0, y: 0, vx: 0, vy: 0.1, m: 2000},
|
||||||
|
{time: 0.05, x: 0, y: 0.005, vx: 0.2, vy: 15.0, m: 1999.5},
|
||||||
|
...
|
||||||
|
{time: 60, x: 5000, y: 50000, vx: 2000, vy: 0, m: 800}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Total states: ~1200 for 60 second flight (0.05 s dt)
|
||||||
|
|
||||||
|
### Auto-Detected Events
|
||||||
|
|
||||||
|
**Liftoff:**
|
||||||
|
- First time `y > 0` and `vy > 0`
|
||||||
|
- Time, altitude, velocity logged
|
||||||
|
|
||||||
|
**Main Engine Cutoff (MECO):**
|
||||||
|
- When `burnTime` expires
|
||||||
|
- Last time mass changes
|
||||||
|
- Marks end of powered flight
|
||||||
|
|
||||||
|
**Max Q (Dynamic Pressure):**
|
||||||
|
- Maximum value of `q = 0.5 × ρ × v²`
|
||||||
|
- Often a structural design point
|
||||||
|
- Time, altitude, velocity, q value
|
||||||
|
|
||||||
|
**Apogee:**
|
||||||
|
- Highest altitude reached
|
||||||
|
- First time `vy` changes from positive to negative
|
||||||
|
- Last point of ascending flight
|
||||||
|
|
||||||
|
**Landing:**
|
||||||
|
- First time `y ≤ 0` after apogee
|
||||||
|
- Final altitude, downrange, velocity
|
||||||
|
|
||||||
|
### User-Defined Events
|
||||||
|
|
||||||
|
**Guidance (Pitch Command):**
|
||||||
|
```javascript
|
||||||
|
{type: 'guidance', time: 30, pitchAngle: 45}
|
||||||
|
```
|
||||||
|
At t=30s, change pitch to 45° (overrides gravity turn)
|
||||||
|
|
||||||
|
**Jettison (Mass Drop):**
|
||||||
|
```javascript
|
||||||
|
{type: 'jettison', time: 25, massDropped: 50}
|
||||||
|
```
|
||||||
|
Drop 50 kg (e.g., fairings) at t=25s
|
||||||
|
- Reduces drag & inertia
|
||||||
|
- Affects apogee & downrange
|
||||||
|
|
||||||
|
**Marker (Annotation):**
|
||||||
|
```javascript
|
||||||
|
{type: 'marker', time: 15, label: 'Separation'}
|
||||||
|
```
|
||||||
|
Just marks event on timeline; no physics change
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Workflow: Simulate LOX/RP1 Rocket
|
||||||
|
|
||||||
|
### Step 1: Design Engine
|
||||||
|
1. Go to **Design > Engine**
|
||||||
|
2. LOX/RP1, 200 bar → ~150 kN thrust, 310 s Isp, 60 s burn
|
||||||
|
3. Export JSON
|
||||||
|
|
||||||
|
### Step 2: Design Rocket
|
||||||
|
1. Go to **Design > Rocket**
|
||||||
|
2. Import engine JSON (gets thrust, Isp, burn time)
|
||||||
|
3. Configure tanks (5000 L, tandem)
|
||||||
|
4. Set payload (50 kg)
|
||||||
|
→ **Result**: Wet mass 1550 kg, dry mass 450 kg
|
||||||
|
|
||||||
|
### Step 3: Run Trajectory
|
||||||
|
1. Go to **Design > Trajectory**
|
||||||
|
2. Import rocket JSON (gets wet/dry mass, reference area)
|
||||||
|
3. Import engine JSON (gets thrust, Isp, burn time)
|
||||||
|
4. Set pitch start altitude: 1000 m
|
||||||
|
5. Click "Run Simulation"
|
||||||
|
→ **Result**: ~5000 m downrange, 50 km apogee, 180 s flight time
|
||||||
|
|
||||||
|
### Step 4: Analyze Results
|
||||||
|
- **Plot**: Shows altitude vs. downrange (parabolic path)
|
||||||
|
- **Timeline**: Mark apogee, MECO, Max Q
|
||||||
|
- **Playback**: Watch animation in real time
|
||||||
|
- **Scrub**: Jump to specific times to see position/velocity
|
||||||
|
|
||||||
|
### Step 5: Optimize
|
||||||
|
- Try different masses (remove payload → higher apogee)
|
||||||
|
- Try different pitch programs (start gravity turn later → longer range)
|
||||||
|
- Try jettison events (drop fairings at MECO → save mass)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Event System
|
||||||
|
|
||||||
|
### Event Structure
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
type: 'liftoff' | 'meco' | 'maxQ' | 'apogee' | 'landing' | 'guidance' | 'jettison' | 'marker',
|
||||||
|
time: number, // seconds
|
||||||
|
altitude?: number, // meters
|
||||||
|
downrange?: number, // meters
|
||||||
|
velocity?: number, // m/s
|
||||||
|
label?: string, // display name
|
||||||
|
value?: any // event-specific data
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Auto-Detected Events (Read-Only)
|
||||||
|
|
||||||
|
Computed during simulation; user cannot edit.
|
||||||
|
|
||||||
|
**Liftoff**: `{type: 'liftoff', time: 0.05, altitude: 0.1, velocity: 1.5}`
|
||||||
|
|
||||||
|
**Max Q**: `{type: 'maxQ', time: 5.2, altitude: 1000, q: 50000}`
|
||||||
|
|
||||||
|
**Apogee**: `{type: 'apogee', time: 120, altitude: 50000, velocity: 0}`
|
||||||
|
|
||||||
|
**Landing**: `{type: 'landing', time: 180, altitude: 0, downrange: 5000, velocity: -50}`
|
||||||
|
|
||||||
|
### User Events
|
||||||
|
|
||||||
|
**Guidance Command:**
|
||||||
|
```javascript
|
||||||
|
{type: 'guidance', time: 30, pitchAngle: 45, label: 'Pitch over'}
|
||||||
|
```
|
||||||
|
- Changes pitch at specified time
|
||||||
|
- Overrides gravity turn
|
||||||
|
- Can be used for coast phase burn starts, etc.
|
||||||
|
|
||||||
|
**Jettison:**
|
||||||
|
```javascript
|
||||||
|
{type: 'jettison', time: 25, massDropped: 50, label: 'Fairing separation'}
|
||||||
|
```
|
||||||
|
- Removes mass from trajectory
|
||||||
|
- Affects drag & inertia
|
||||||
|
- Typically after Max Q (fairing drag not needed)
|
||||||
|
|
||||||
|
**Marker:**
|
||||||
|
```javascript
|
||||||
|
{type: 'marker', time: 60, label: 'Engine cutoff'}
|
||||||
|
```
|
||||||
|
- Pure annotation
|
||||||
|
- No physics effect
|
||||||
|
- Useful for documentation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3D Visualization
|
||||||
|
|
||||||
|
### Trajectory Plot
|
||||||
|
|
||||||
|
HTML5 Canvas rendering of flight path:
|
||||||
|
|
||||||
|
**Axes:**
|
||||||
|
- X: downrange (0 to max)
|
||||||
|
- Y: altitude (0 to apogee + margin)
|
||||||
|
- Aspect ratio: ~2:1 (wider than tall)
|
||||||
|
|
||||||
|
**Elements:**
|
||||||
|
- Trajectory curve (light blue line)
|
||||||
|
- Event markers (circles at key points)
|
||||||
|
- Grid (light gray, 10 km spacing)
|
||||||
|
- Labels (altitude, downrange at axis)
|
||||||
|
- Legend (event types & colors)
|
||||||
|
|
||||||
|
**Interactivity:**
|
||||||
|
- Click on plot → jump to that time
|
||||||
|
- Hover on point → show values (alt, range, velocity)
|
||||||
|
- Hover on event marker → show label
|
||||||
|
- Zoom (mousewheel) — not yet implemented
|
||||||
|
|
||||||
|
### 3D Flight Animation
|
||||||
|
|
||||||
|
**Top-down view** showing:
|
||||||
|
- Vehicle model (small rocket symbol or sphere)
|
||||||
|
- Flight path (line from start to current position)
|
||||||
|
- Current altitude/range indicators
|
||||||
|
- Compass heading
|
||||||
|
|
||||||
|
**Implementation:** Canvas 2D or Three.js (current: Canvas)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Playback Controls
|
||||||
|
|
||||||
|
### Timeline Bar
|
||||||
|
|
||||||
|
Located below trajectory plot:
|
||||||
|
|
||||||
|
**Play/Pause Button:**
|
||||||
|
- Starts/stops animation
|
||||||
|
- Keyboard: Space bar
|
||||||
|
|
||||||
|
**Speed Selector:**
|
||||||
|
- 1× — real time
|
||||||
|
- 5× — 5× faster
|
||||||
|
- 10× — 10× faster
|
||||||
|
|
||||||
|
**Scrubber (Time Slider):**
|
||||||
|
- Drag to jump to any time
|
||||||
|
- Release to play from that point
|
||||||
|
- Shows current time in seconds
|
||||||
|
|
||||||
|
**Event Timeline:**
|
||||||
|
- Vertical lines at event times
|
||||||
|
- Hover to see event name
|
||||||
|
- Click to jump to event
|
||||||
|
|
||||||
|
### Keyboard Shortcuts
|
||||||
|
|
||||||
|
- **Space**: Play/pause
|
||||||
|
- **→**: Jump forward 10 seconds
|
||||||
|
- **←**: Jump backward 10 seconds
|
||||||
|
- **Home**: Jump to start
|
||||||
|
- **End**: Jump to end
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Advanced Features
|
||||||
|
|
||||||
|
### Thrust Curves (Future)
|
||||||
|
|
||||||
|
Instead of constant thrust, can import actual thrust vs. time:
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
type: 'thrustCurve',
|
||||||
|
data: [
|
||||||
|
{time: 0, thrust: 150000},
|
||||||
|
{time: 5, thrust: 145000},
|
||||||
|
{time: 60, thrust: 0}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Would enable:
|
||||||
|
- Throttling analysis (variable thrust)
|
||||||
|
- Solid rocket motor curves
|
||||||
|
- Multi-engine clustering
|
||||||
|
|
||||||
|
### Stage Separation
|
||||||
|
|
||||||
|
Currently single-stage only. Multi-stage would require:
|
||||||
|
- Stage definition (separate vehicle configs)
|
||||||
|
- Coast phase between stages
|
||||||
|
- Staging logic (when to ignite next stage)
|
||||||
|
- Updated mass calculations
|
||||||
|
|
||||||
|
### Wind Effects
|
||||||
|
|
||||||
|
Could add:
|
||||||
|
- Constant wind (direction, magnitude)
|
||||||
|
- Turbulence (atmospheric gusts)
|
||||||
|
- Wind shear (altitude-dependent)
|
||||||
|
- Affects trajectory & landing site
|
||||||
|
|
||||||
|
### Control Systems
|
||||||
|
|
||||||
|
Could add:
|
||||||
|
- Attitude feedback (pitch control gains)
|
||||||
|
- Stability margin (static/dynamic)
|
||||||
|
- Fin-based control
|
||||||
|
- Thrust vector control (TVC)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Trajectory looks wrong
|
||||||
|
- Check vehicle mass (should match design)
|
||||||
|
- Verify drag coefficient (0.25 ± 0.05 typical)
|
||||||
|
- Confirm reference area (cross-sectional)
|
||||||
|
- Ensure engine thrust profile is correct
|
||||||
|
|
||||||
|
### Apogee seems too low
|
||||||
|
- Verify engine thrust (check import)
|
||||||
|
- Check mass (try reducing payload)
|
||||||
|
- Ensure Isp is correct (affects delta-v)
|
||||||
|
- Try longer burn time or higher chamber pressure
|
||||||
|
|
||||||
|
### Simulation diverges (NaN values)
|
||||||
|
- Reduce timestep dt (smaller step size)
|
||||||
|
- Check for negative mass (propellant consumed)
|
||||||
|
- Verify atmosphere model (no singularities)
|
||||||
|
- Ensure velocity doesn't exceed sound barrier (should be OK)
|
||||||
|
|
||||||
|
### Playback animation slow
|
||||||
|
- Reduce speed (don't use 10×)
|
||||||
|
- Close other browser tabs
|
||||||
|
- Lower resolution (use simpler plot)
|
||||||
|
- Reduce state vector size (use larger dt)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- Beard, R. W., & McLain, T. W. (2012). Small unmanned aircraft: Theory and practice. Princeton University Press.
|
||||||
|
- Vallado, D. A., Crawford, P., Hujsa, R., & Kelso, T. S. (2006). Revisiting spacetrack report #3. AIAA/AAS Astrodynamics Specialist Conference.
|
||||||
|
- US Standard Atmosphere 1976. NASA TM-X-74335
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated**: 2025-02 | **Status**: Current (v1)
|
||||||
14
src/App.jsx
14
src/App.jsx
@@ -3,6 +3,7 @@ import Home from './pages/Home.jsx'
|
|||||||
import Solver from './pages/Solver.jsx'
|
import Solver from './pages/Solver.jsx'
|
||||||
import EnginePage from './pages/EnginePage.jsx'
|
import EnginePage from './pages/EnginePage.jsx'
|
||||||
import RocketPage from './pages/RocketPage.jsx'
|
import RocketPage from './pages/RocketPage.jsx'
|
||||||
|
import TrajectoryPage from './pages/TrajectoryPage.jsx'
|
||||||
import KnowledgebaseFuelsPage from './pages/KnowledgebaseFuelsPage.jsx'
|
import KnowledgebaseFuelsPage from './pages/KnowledgebaseFuelsPage.jsx'
|
||||||
import KnowledgebaseEquationsPage from './pages/KnowledgebaseEquationsPage.jsx'
|
import KnowledgebaseEquationsPage from './pages/KnowledgebaseEquationsPage.jsx'
|
||||||
import KnowledgebaseAblativesPage from './pages/KnowledgebaseAblativesPage.jsx'
|
import KnowledgebaseAblativesPage from './pages/KnowledgebaseAblativesPage.jsx'
|
||||||
@@ -71,6 +72,18 @@ export default function App() {
|
|||||||
>
|
>
|
||||||
Rocket
|
Rocket
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
<NavLink
|
||||||
|
to="/design/trajectory"
|
||||||
|
className={({ isActive }) =>
|
||||||
|
`block px-4 py-2 text-sm transition-colors ${
|
||||||
|
isActive
|
||||||
|
? 'bg-slate-700 text-white'
|
||||||
|
: 'text-slate-300 hover:bg-slate-700 hover:text-white'
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Trajectory
|
||||||
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -138,6 +151,7 @@ export default function App() {
|
|||||||
<Route path="/solver" element={<Solver />} />
|
<Route path="/solver" element={<Solver />} />
|
||||||
<Route path="/design/engine" element={<EnginePage />} />
|
<Route path="/design/engine" element={<EnginePage />} />
|
||||||
<Route path="/design/rocket" element={<RocketPage />} />
|
<Route path="/design/rocket" element={<RocketPage />} />
|
||||||
|
<Route path="/design/trajectory" element={<TrajectoryPage />} />
|
||||||
<Route path="/knowledgebase/fuels" element={<KnowledgebaseFuelsPage />} />
|
<Route path="/knowledgebase/fuels" element={<KnowledgebaseFuelsPage />} />
|
||||||
<Route path="/knowledgebase/equations" element={<KnowledgebaseEquationsPage />} />
|
<Route path="/knowledgebase/equations" element={<KnowledgebaseEquationsPage />} />
|
||||||
<Route path="/knowledgebase/ablatives" element={<KnowledgebaseAblativesPage />} />
|
<Route path="/knowledgebase/ablatives" element={<KnowledgebaseAblativesPage />} />
|
||||||
|
|||||||
@@ -51,7 +51,10 @@ function TankSection({ yCenter, L_cyl, R, color, opacity = 1 }) {
|
|||||||
[R],
|
[R],
|
||||||
)
|
)
|
||||||
|
|
||||||
const material = (
|
return (
|
||||||
|
<>
|
||||||
|
{/* Cylindrical body */}
|
||||||
|
<mesh geometry={cylGeo} position={[0, yCenter, 0]}>
|
||||||
<meshStandardMaterial
|
<meshStandardMaterial
|
||||||
color={color}
|
color={color}
|
||||||
metalness={0.45}
|
metalness={0.45}
|
||||||
@@ -61,21 +64,30 @@ function TankSection({ yCenter, L_cyl, R, color, opacity = 1 }) {
|
|||||||
opacity={opacity}
|
opacity={opacity}
|
||||||
depthWrite={opacity >= 1}
|
depthWrite={opacity >= 1}
|
||||||
/>
|
/>
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* Cylindrical body */}
|
|
||||||
<mesh geometry={cylGeo} position={[0, yCenter, 0]}>
|
|
||||||
{material}
|
|
||||||
</mesh>
|
</mesh>
|
||||||
{/* Top dome */}
|
{/* Top dome (hemisphere pointing up) */}
|
||||||
<mesh geometry={topDomeGeo} position={[0, yCenter + L_cyl / 2 + R / 2, 0]}>
|
<mesh geometry={topDomeGeo} position={[0, yCenter + L_cyl / 2, 0]}>
|
||||||
{material}
|
<meshStandardMaterial
|
||||||
|
color={color}
|
||||||
|
metalness={0.45}
|
||||||
|
roughness={0.5}
|
||||||
|
side={THREE.DoubleSide}
|
||||||
|
transparent={opacity < 1}
|
||||||
|
opacity={opacity}
|
||||||
|
depthWrite={opacity >= 1}
|
||||||
|
/>
|
||||||
</mesh>
|
</mesh>
|
||||||
{/* Bottom dome */}
|
{/* Bottom dome (hemisphere pointing down) */}
|
||||||
<mesh geometry={botDomeGeo} position={[0, yCenter - L_cyl / 2 - R / 2, 0]}>
|
<mesh geometry={botDomeGeo} position={[0, yCenter - L_cyl / 2, 0]}>
|
||||||
{material}
|
<meshStandardMaterial
|
||||||
|
color={color}
|
||||||
|
metalness={0.45}
|
||||||
|
roughness={0.5}
|
||||||
|
side={THREE.DoubleSide}
|
||||||
|
transparent={opacity < 1}
|
||||||
|
opacity={opacity}
|
||||||
|
depthWrite={opacity >= 1}
|
||||||
|
/>
|
||||||
</mesh>
|
</mesh>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@@ -129,7 +141,7 @@ function RocketShape({ geometry: geo }) {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
outerRadius: R,
|
outerRadius: R,
|
||||||
L_nose, L_payload, L_tank, L_tank_fuel, L_tank_ox, L_engine,
|
L_nose, L_payload, L_tank, L_tank_fuel, L_tank_ox, L_tank_fuel_cyl, L_tank_ox_cyl, L_engine,
|
||||||
totalLength, r_inner, arrangement, innerPropellant, noseProfilePoints,
|
totalLength, r_inner, arrangement, innerPropellant, noseProfilePoints,
|
||||||
} = geo
|
} = geo
|
||||||
|
|
||||||
|
|||||||
141
src/components/trajectory/TimelineBar.jsx
Normal file
141
src/components/trajectory/TimelineBar.jsx
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import { useState, useRef, useEffect } from 'react'
|
||||||
|
|
||||||
|
export function TimelineBar({
|
||||||
|
simulationResults,
|
||||||
|
currentTime,
|
||||||
|
onTimeChange,
|
||||||
|
isPlaying,
|
||||||
|
onPlayPause,
|
||||||
|
playbackSpeed,
|
||||||
|
onPlaybackSpeedChange,
|
||||||
|
}) {
|
||||||
|
const [showEventForm, setShowEventForm] = useState(false)
|
||||||
|
const [newEventTime, setNewEventTime] = useState('')
|
||||||
|
const [newEventLabel, setNewEventLabel] = useState('')
|
||||||
|
const [newEventType, setNewEventType] = useState('marker')
|
||||||
|
const timelineRef = useRef(null)
|
||||||
|
|
||||||
|
if (!simulationResults) {
|
||||||
|
return (
|
||||||
|
<div className="bg-slate-800 border-t border-slate-700 p-4 h-32">
|
||||||
|
<p className="text-slate-400 text-sm">Run simulation to see timeline</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { states, events, summary } = simulationResults
|
||||||
|
const maxTime = summary.t_max
|
||||||
|
|
||||||
|
// Handle timeline scrub
|
||||||
|
const handleTimelineClick = (e) => {
|
||||||
|
if (!timelineRef.current) return
|
||||||
|
const rect = timelineRef.current.getBoundingClientRect()
|
||||||
|
const percent = (e.clientX - rect.left) / rect.width
|
||||||
|
const t = Math.max(0, Math.min(maxTime, percent * maxTime))
|
||||||
|
onTimeChange(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event colors
|
||||||
|
const eventColors = {
|
||||||
|
engine: '#ef4444',
|
||||||
|
guidance: '#f59e0b',
|
||||||
|
marker: '#8b5cf6',
|
||||||
|
jettison: '#06b6d4',
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-slate-800 border-t border-slate-700">
|
||||||
|
{/* Playback controls */}
|
||||||
|
<div className="flex items-center gap-3 px-4 py-3 border-b border-slate-700">
|
||||||
|
<button
|
||||||
|
onClick={() => onTimeChange(0)}
|
||||||
|
className="px-2 py-1 bg-slate-700 hover:bg-slate-600 rounded text-xs text-slate-300"
|
||||||
|
title="Rewind"
|
||||||
|
>
|
||||||
|
◀◀
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={onPlayPause}
|
||||||
|
className={`px-3 py-1 rounded text-xs font-medium ${
|
||||||
|
isPlaying
|
||||||
|
? 'bg-red-600 hover:bg-red-500 text-white'
|
||||||
|
: 'bg-blue-600 hover:bg-blue-500 text-white'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isPlaying ? '⏸ Pause' : '▶ Play'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span className="text-xs text-slate-400">Speed:</span>
|
||||||
|
<select
|
||||||
|
value={playbackSpeed}
|
||||||
|
onChange={e => onPlaybackSpeedChange(parseFloat(e.target.value))}
|
||||||
|
className="px-2 py-0.5 bg-slate-700 border border-slate-600 rounded text-xs text-slate-300 focus:outline-none"
|
||||||
|
>
|
||||||
|
<option value={1}>1×</option>
|
||||||
|
<option value={5}>5×</option>
|
||||||
|
<option value={10}>10×</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className="text-sm text-slate-300 font-mono ml-auto">
|
||||||
|
t = {currentTime.toFixed(2)} s / {maxTime.toFixed(1)} s
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Timeline */}
|
||||||
|
<div className="relative px-4 py-4">
|
||||||
|
<div
|
||||||
|
ref={timelineRef}
|
||||||
|
onClick={handleTimelineClick}
|
||||||
|
className="relative h-12 bg-slate-900 border border-slate-700 rounded cursor-pointer hover:bg-slate-800 transition-colors"
|
||||||
|
>
|
||||||
|
{/* Event markers */}
|
||||||
|
{events.map(event => {
|
||||||
|
const percent = (event.t / maxTime) * 100
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={event.id}
|
||||||
|
style={{
|
||||||
|
left: `${percent}%`,
|
||||||
|
backgroundColor: event.color || eventColors[event.type] || '#64748b',
|
||||||
|
}}
|
||||||
|
className="absolute top-0 bottom-0 w-1 rounded-t hover:brightness-150"
|
||||||
|
title={`${event.label} @ t=${event.t.toFixed(2)}s`}
|
||||||
|
>
|
||||||
|
{/* Label above */}
|
||||||
|
<div className="absolute bottom-full mb-1 left-1/2 transform -translate-x-1/2 bg-slate-700 border border-slate-600 rounded px-2 py-1 text-xs text-white whitespace-nowrap opacity-0 hover:opacity-100 transition-opacity z-10">
|
||||||
|
{event.label}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* Playhead */}
|
||||||
|
<div
|
||||||
|
style={{ left: `${(currentTime / maxTime) * 100}%` }}
|
||||||
|
className="absolute top-0 bottom-0 w-0.5 bg-emerald-400 pointer-events-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Event list below */}
|
||||||
|
<div className="mt-3 text-xs space-y-1">
|
||||||
|
{events.slice(0, 5).map(event => (
|
||||||
|
<div key={event.id} className="flex items-center gap-2 text-slate-400">
|
||||||
|
<div
|
||||||
|
className="w-2 h-2 rounded-full"
|
||||||
|
style={{ backgroundColor: event.color || eventColors[event.type] || '#64748b' }}
|
||||||
|
/>
|
||||||
|
<span className="w-20 text-slate-500">{event.t.toFixed(1)}s</span>
|
||||||
|
<span>{event.label}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{events.length > 5 && (
|
||||||
|
<div className="text-slate-500">+{events.length - 5} more events</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
203
src/components/trajectory/TrajectoryPlot.jsx
Normal file
203
src/components/trajectory/TrajectoryPlot.jsx
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
import { useRef, useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
|
export function TrajectoryPlot({ simulationResults, currentState, onTimeClick }) {
|
||||||
|
const canvasRef = useRef(null)
|
||||||
|
const containerRef = useRef(null)
|
||||||
|
const [canvasSize, setCanvasSize] = useState({ width: 800, height: 500 })
|
||||||
|
|
||||||
|
const plotData = useMemo(() => {
|
||||||
|
if (!simulationResults || !simulationResults.states.length) return null
|
||||||
|
|
||||||
|
const states = simulationResults.states
|
||||||
|
const xs = states.map(s => s.x)
|
||||||
|
const ys = states.map(s => s.y)
|
||||||
|
|
||||||
|
const minX = Math.min(...xs)
|
||||||
|
const maxX = Math.max(...xs)
|
||||||
|
const minY = 0
|
||||||
|
const maxY = Math.max(...ys)
|
||||||
|
|
||||||
|
// 10% padding
|
||||||
|
const padX = (maxX - minX) * 0.1 || 1000
|
||||||
|
const padY = maxY * 0.1 || 1000
|
||||||
|
|
||||||
|
return {
|
||||||
|
states,
|
||||||
|
bounds: {
|
||||||
|
x: [minX - padX, maxX + padX],
|
||||||
|
y: [minY, maxY + padY],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}, [simulationResults])
|
||||||
|
|
||||||
|
// Handle canvas resize
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
if (containerRef.current) {
|
||||||
|
const rect = containerRef.current.getBoundingClientRect()
|
||||||
|
setCanvasSize({ width: rect.width || 800, height: rect.height || 500 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleResize()
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
return () => window.removeEventListener('resize', handleResize)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Handle canvas click for scrubbing
|
||||||
|
const handleCanvasClick = (e) => {
|
||||||
|
if (!canvasRef.current || !plotData) return
|
||||||
|
|
||||||
|
const canvas = canvasRef.current
|
||||||
|
const rect = canvas.getBoundingClientRect()
|
||||||
|
const clickX = e.clientX - rect.left
|
||||||
|
const clickY = e.clientY - rect.top
|
||||||
|
|
||||||
|
// Convert pixel to data coordinates
|
||||||
|
const { x: xBounds, y: yBounds } = plotData.bounds
|
||||||
|
const xRange = xBounds[1] - xBounds[0]
|
||||||
|
const yRange = yBounds[1] - yBounds[0]
|
||||||
|
|
||||||
|
const dataX = xBounds[0] + (clickX / canvas.width) * xRange
|
||||||
|
const dataY = yBounds[1] - (clickY / canvas.height) * yRange
|
||||||
|
|
||||||
|
// Find closest state by x position
|
||||||
|
let closestState = plotData.states[0]
|
||||||
|
let minDist = Infinity
|
||||||
|
for (const state of plotData.states) {
|
||||||
|
const dist = Math.abs(state.x - dataX)
|
||||||
|
if (dist < minDist) {
|
||||||
|
minDist = dist
|
||||||
|
closestState = state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onTimeClick?.(closestState.t)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!canvasRef.current || !plotData) return
|
||||||
|
|
||||||
|
const canvas = canvasRef.current
|
||||||
|
const ctx = canvas.getContext('2d')
|
||||||
|
|
||||||
|
// Clear
|
||||||
|
ctx.fillStyle = '#0f172a'
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||||||
|
|
||||||
|
const { x: xBounds, y: yBounds } = plotData.bounds
|
||||||
|
const xRange = xBounds[1] - xBounds[0]
|
||||||
|
const yRange = yBounds[1] - yBounds[0]
|
||||||
|
|
||||||
|
// Utility to convert data to canvas coords
|
||||||
|
const toCanvasX = (x) => ((x - xBounds[0]) / xRange) * canvas.width
|
||||||
|
const toCanvasY = (y) => canvas.height - ((y - yBounds[0]) / yRange) * canvas.height
|
||||||
|
|
||||||
|
// Draw grid
|
||||||
|
ctx.strokeStyle = '#1e293b'
|
||||||
|
ctx.lineWidth = 0.5
|
||||||
|
ctx.font = '12px monospace'
|
||||||
|
ctx.fillStyle = '#64748b'
|
||||||
|
|
||||||
|
// Vertical grid lines (downrange)
|
||||||
|
const xStep = (xRange / 1000) * 100 // ~10 lines
|
||||||
|
for (let x = Math.ceil(xBounds[0] / xStep) * xStep; x < xBounds[1]; x += xStep) {
|
||||||
|
const cx = toCanvasX(x)
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.moveTo(cx, 0)
|
||||||
|
ctx.lineTo(cx, canvas.height)
|
||||||
|
ctx.stroke()
|
||||||
|
if (x % (xStep * 2) === 0) {
|
||||||
|
ctx.fillText(`${(x / 1000).toFixed(0)} km`, cx + 5, canvas.height - 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontal grid lines (altitude)
|
||||||
|
const yStep = (yRange / 1000) * 100 // ~10 lines
|
||||||
|
for (let y = Math.ceil(yBounds[0] / yStep) * yStep; y < yBounds[1]; y += yStep) {
|
||||||
|
const cy = toCanvasY(y)
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.moveTo(0, cy)
|
||||||
|
ctx.lineTo(canvas.width, cy)
|
||||||
|
ctx.stroke()
|
||||||
|
if (y % (yStep * 2) === 0) {
|
||||||
|
ctx.fillText(`${(y / 1000).toFixed(0)} km`, 10, cy - 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw trajectory path
|
||||||
|
ctx.strokeStyle = '#3b82f6'
|
||||||
|
ctx.lineWidth = 2
|
||||||
|
ctx.beginPath()
|
||||||
|
|
||||||
|
for (let i = 0; i < plotData.states.length; i++) {
|
||||||
|
const s = plotData.states[i]
|
||||||
|
const cx = toCanvasX(s.x)
|
||||||
|
const cy = toCanvasY(s.y)
|
||||||
|
|
||||||
|
if (i === 0) {
|
||||||
|
ctx.moveTo(cx, cy)
|
||||||
|
} else {
|
||||||
|
ctx.lineTo(cx, cy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.stroke()
|
||||||
|
|
||||||
|
// Draw current position marker (if available)
|
||||||
|
if (currentState) {
|
||||||
|
const cx = toCanvasX(currentState.x)
|
||||||
|
const cy = toCanvasY(currentState.y)
|
||||||
|
|
||||||
|
// Outer ring
|
||||||
|
ctx.strokeStyle = '#10b981'
|
||||||
|
ctx.lineWidth = 2
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.arc(cx, cy, 8, 0, Math.PI * 2)
|
||||||
|
ctx.stroke()
|
||||||
|
|
||||||
|
// Inner circle
|
||||||
|
ctx.fillStyle = '#10b981'
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.arc(cx, cy, 4, 0, Math.PI * 2)
|
||||||
|
ctx.fill()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw axes
|
||||||
|
ctx.strokeStyle = '#475569'
|
||||||
|
ctx.lineWidth = 2
|
||||||
|
const origin_x = toCanvasX(0)
|
||||||
|
const origin_y = toCanvasY(0)
|
||||||
|
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.moveTo(origin_x, canvas.height)
|
||||||
|
ctx.lineTo(origin_x, 0)
|
||||||
|
ctx.stroke()
|
||||||
|
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.moveTo(0, origin_y)
|
||||||
|
ctx.lineTo(canvas.width, origin_y)
|
||||||
|
ctx.stroke()
|
||||||
|
|
||||||
|
// Labels
|
||||||
|
ctx.fillStyle = '#cbd5e1'
|
||||||
|
ctx.font = 'bold 14px sans-serif'
|
||||||
|
ctx.fillText('Downrange (m)', canvas.width - 200, canvas.height - 20)
|
||||||
|
ctx.save()
|
||||||
|
ctx.translate(20, 100)
|
||||||
|
ctx.rotate(-Math.PI / 2)
|
||||||
|
ctx.fillText('Altitude (m)', 0, 0)
|
||||||
|
ctx.restore()
|
||||||
|
}, [plotData, currentState])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={containerRef} className="w-full h-full">
|
||||||
|
<canvas
|
||||||
|
ref={canvasRef}
|
||||||
|
width={canvasSize.width}
|
||||||
|
height={canvasSize.height}
|
||||||
|
onClick={handleCanvasClick}
|
||||||
|
className="w-full h-full border border-slate-700 bg-slate-950 cursor-crosshair rounded block"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -195,7 +195,7 @@ export function calcFeedSystem(thermo, feedSystem, burnTime) {
|
|||||||
if (type === 'pressure_fed') {
|
if (type === 'pressure_fed') {
|
||||||
const p_tank = p0 * (feedFactor || 1.3)
|
const p_tank = p0 * (feedFactor || 1.3)
|
||||||
const R_press = pressurantR || 2077 // J/(kg·K) — helium
|
const R_press = pressurantR || 2077 // J/(kg·K) — helium
|
||||||
const T_press = pressurantT || 300 // K — ambient temperature
|
const T_press = pressurantT || 293 // K — standard conditions (20°C)
|
||||||
const m_press = (p_tank * V_prop) / (R_press * T_press)
|
const m_press = (p_tank * V_prop) / (R_press * T_press)
|
||||||
return { type, p_tank, V_fuel, V_ox, V_prop, m_press }
|
return { type, p_tank, V_fuel, V_ox, V_prop, m_press }
|
||||||
}
|
}
|
||||||
@@ -235,8 +235,8 @@ export function calcStructure(thermo, chamberGeom, nozzleGeom, structure) {
|
|||||||
const t_chamber = (p0 * rc) / allowableStress
|
const t_chamber = (p0 * rc) / allowableStress
|
||||||
const t_throat = (p0 * rt) / allowableStress
|
const t_throat = (p0 * rt) / allowableStress
|
||||||
|
|
||||||
// Exit nozzle: assume exit pressure is much lower, use 0.1 * p0 as estimate
|
// Exit nozzle: assume exit pressure ≈ 0.1 MPa (ambient for sea-level optimized nozzle)
|
||||||
const pe = p0 * 0.1
|
const pe = 0.1e6
|
||||||
const t_nozzle_exit = (pe * re) / allowableStress
|
const t_nozzle_exit = (pe * re) / allowableStress
|
||||||
|
|
||||||
// Mass estimation: thin cylindrical/frustum shells + injector plate
|
// Mass estimation: thin cylindrical/frustum shells + injector plate
|
||||||
|
|||||||
@@ -496,7 +496,7 @@ export const COMBINATIONS = [
|
|||||||
flameTemp: 3250,
|
flameTemp: 3250,
|
||||||
optimalOF: 6.0,
|
optimalOF: 6.0,
|
||||||
gamma: 1.21,
|
gamma: 1.21,
|
||||||
R: 934,
|
R: 760,
|
||||||
chamberPressureRef: '6.9 MPa',
|
chamberPressureRef: '6.9 MPa',
|
||||||
expansionRatioRef: 40,
|
expansionRatioRef: 40,
|
||||||
energeticCategory: 'Cryogenic Bipropellant',
|
energeticCategory: 'Cryogenic Bipropellant',
|
||||||
@@ -800,7 +800,7 @@ export const ABLATIVE_MATERIALS = [
|
|||||||
erosionRateRange: [0.05, 0.20],
|
erosionRateRange: [0.05, 0.20],
|
||||||
maxTemp: 3600,
|
maxTemp: 3600,
|
||||||
density: 1450,
|
density: 1450,
|
||||||
thermalConductivity: 8.0,
|
thermalConductivity: 3.5,
|
||||||
charYield: 0.58,
|
charYield: 0.58,
|
||||||
pressureExponent: 0.33,
|
pressureExponent: 0.33,
|
||||||
applications: ['nozzle throat (high heat flux)', 'combustion chamber'],
|
applications: ['nozzle throat (high heat flux)', 'combustion chamber'],
|
||||||
@@ -815,13 +815,13 @@ export const ABLATIVE_MATERIALS = [
|
|||||||
'EPDM is a flexible elastomeric ablator suitable for low-pressure engines and non-critical applications. It provides good insulation properties and flexibility, reducing structural loads. Inexpensive and easy to manufacture in various geometries.',
|
'EPDM is a flexible elastomeric ablator suitable for low-pressure engines and non-critical applications. It provides good insulation properties and flexibility, reducing structural loads. Inexpensive and easy to manufacture in various geometries.',
|
||||||
erosionRate: 0.50,
|
erosionRate: 0.50,
|
||||||
erosionRateRange: [0.30, 0.80],
|
erosionRateRange: [0.30, 0.80],
|
||||||
maxTemp: 2200,
|
maxTemp: 700,
|
||||||
density: 920,
|
density: 920,
|
||||||
thermalConductivity: 0.25,
|
thermalConductivity: 0.25,
|
||||||
charYield: 0.40,
|
charYield: 0.40,
|
||||||
pressureExponent: 0.50,
|
pressureExponent: 0.50,
|
||||||
applications: ['low-pressure chamber', 'insulation layer'],
|
applications: ['low-pressure chamber', 'insulation layer'],
|
||||||
notes: 'Low cost and simple manufacturing. Limited to low-pressure (<1 MPa) applications due to high erosion rates.',
|
notes: 'Low cost and simple manufacturing. Limited to low-pressure (<1 MPa) applications due to high erosion rates. Virgin material decomposes above ~700 K.',
|
||||||
refPressure: 0.5e6,
|
refPressure: 0.5e6,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -867,8 +867,8 @@ export const ABLATIVE_MATERIALS = [
|
|||||||
erosionRate: 0.06,
|
erosionRate: 0.06,
|
||||||
erosionRateRange: [0.03, 0.12],
|
erosionRateRange: [0.03, 0.12],
|
||||||
maxTemp: 3800,
|
maxTemp: 3800,
|
||||||
density: 1500,
|
density: 270,
|
||||||
thermalConductivity: 5.5,
|
thermalConductivity: 0.25,
|
||||||
charYield: 0.60,
|
charYield: 0.60,
|
||||||
pressureExponent: 0.30,
|
pressureExponent: 0.30,
|
||||||
applications: ['nozzle throat (ultra-high performance)', 'chamber (extreme conditions)'],
|
applications: ['nozzle throat (ultra-high performance)', 'chamber (extreme conditions)'],
|
||||||
@@ -884,5 +884,5 @@ export const STRUCTURAL_MATERIALS = [
|
|||||||
{ id: 'ss_304', name: 'Stainless Steel 304', yieldStrength: 215e6, density: 7900, youngModulus: 193e9 },
|
{ id: 'ss_304', name: 'Stainless Steel 304', yieldStrength: 215e6, density: 7900, youngModulus: 193e9 },
|
||||||
{ id: 'inconel_718', name: 'Inconel 718', yieldStrength: 1100e6, density: 8190, youngModulus: 200e9 },
|
{ id: 'inconel_718', name: 'Inconel 718', yieldStrength: 1100e6, density: 8190, youngModulus: 200e9 },
|
||||||
{ id: 'ti_6al_4v', name: 'Titanium Ti-6Al-4V', yieldStrength: 880e6, density: 4430, youngModulus: 114e9 },
|
{ id: 'ti_6al_4v', name: 'Titanium Ti-6Al-4V', yieldStrength: 880e6, density: 4430, youngModulus: 114e9 },
|
||||||
{ id: 'cfrp', name: 'CFRP (typical)', yieldStrength: 600e6, density: 1600, youngModulus: 70e9 },
|
{ id: 'cfrp', name: 'CFRP (typical)', yieldStrength: 450e6, density: 1600, youngModulus: 70e9 },
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export function calcRocketGeometry(engineData, rocketInputs) {
|
|||||||
// Tank pressure (Pa)
|
// Tank pressure (Pa)
|
||||||
const p_tank = feedSystem === 'pump_fed' ? 2e6 : p0 * 1.2
|
const p_tank = feedSystem === 'pump_fed' ? 2e6 : p0 * 1.2
|
||||||
|
|
||||||
// Hoop stress wall thickness (same for all tanks at same pressure and radius)
|
// Hoop stress wall thickness for outer tank at radius R
|
||||||
t_wall = (p_tank * R) / (material.yieldStrength / SF)
|
t_wall = (p_tank * R) / (material.yieldStrength / SF)
|
||||||
|
|
||||||
if (arrangement === 'coaxial') {
|
if (arrangement === 'coaxial') {
|
||||||
@@ -158,9 +158,13 @@ export function calcRocketGeometry(engineData, rocketInputs) {
|
|||||||
const V_eff_outer = V_outer * (1 + ullage / 100)
|
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))
|
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
|
// Inner annular tank (no domes): apply ullage and full volume in cylindrical section
|
||||||
|
const V_eff_inner = V_inner * (1 + ullage / 100)
|
||||||
const A_annulus = Math.PI * (R * R - r_inner * r_inner)
|
const A_annulus = Math.PI * (R * R - r_inner * r_inner)
|
||||||
const L_tank_cyl_inner = V_inner / A_annulus
|
const L_tank_cyl_inner = V_eff_inner / A_annulus
|
||||||
|
|
||||||
|
// Separate wall thickness for inner tank at inner radius
|
||||||
|
const t_wall_inner = (p_tank * r_inner) / (material.yieldStrength / SF)
|
||||||
|
|
||||||
// Total outer tank length
|
// Total outer tank length
|
||||||
const L_tank_outer = L_tank_cyl_outer + 2 * R
|
const L_tank_outer = L_tank_cyl_outer + 2 * R
|
||||||
@@ -179,7 +183,7 @@ export function calcRocketGeometry(engineData, rocketInputs) {
|
|||||||
const m_dome_outer = 4 * Math.PI * R * R * t_wall * 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_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_cyl_inner = 2 * Math.PI * r_inner * t_wall_inner * L_tank_cyl_inner * material.density
|
||||||
const m_tank_inner = m_cyl_inner // Inner annular tank, no domes
|
const m_tank_inner = m_cyl_inner // Inner annular tank, no domes
|
||||||
|
|
||||||
m_tank_total = m_tank_outer + m_tank_inner
|
m_tank_total = m_tank_outer + m_tank_inner
|
||||||
@@ -211,7 +215,9 @@ export function calcRocketGeometry(engineData, rocketInputs) {
|
|||||||
// ── Pressurant system (pressure-fed only) ─────────────────────────────
|
// ── Pressurant system (pressure-fed only) ─────────────────────────────
|
||||||
let m_pressurant = 0
|
let m_pressurant = 0
|
||||||
if (feedSystem === 'pressure_fed') {
|
if (feedSystem === 'pressure_fed') {
|
||||||
const m_He = (p_tank * V_prop) / (R_HE * T_PRESSURANT)
|
// Pressurant must fill both propellant and ullage space
|
||||||
|
const V_eff = V_prop * (1 + ullage / 100)
|
||||||
|
const m_He = (p_tank * V_eff) / (R_HE * T_PRESSURANT)
|
||||||
const m_bottle = m_He * 4 // Conservative bottle mass estimate
|
const m_bottle = m_He * 4 // Conservative bottle mass estimate
|
||||||
m_pressurant = m_He + m_bottle
|
m_pressurant = m_He + m_bottle
|
||||||
}
|
}
|
||||||
|
|||||||
346
src/engine/trajectoryCalcs.js
Normal file
346
src/engine/trajectoryCalcs.js
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
/**
|
||||||
|
* Trajectory simulation using 4th-order Runge-Kutta integration.
|
||||||
|
* Physics: 3-DOF point-mass with thrust, drag, gravity.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// US Standard Atmosphere (simplified piecewise linear in log-rho)
|
||||||
|
function atmosphereDensity(h) {
|
||||||
|
// h in meters
|
||||||
|
if (h < 0) h = 0
|
||||||
|
|
||||||
|
// Simplified US Standard Atmosphere
|
||||||
|
if (h <= 11000) {
|
||||||
|
// Troposphere: ρ = ρ0 * (1 - L*h/T0)^(-g0*M/(R*L))
|
||||||
|
// Approximate with exponential: ρ = ρ0 * exp(-h / H) where H ≈ 8500 m
|
||||||
|
const rho0 = 1.225 // kg/m³ at sea level
|
||||||
|
const H = 8500 // scale height in meters
|
||||||
|
return rho0 * Math.exp(-h / H)
|
||||||
|
} else if (h <= 20000) {
|
||||||
|
// Stratosphere (simplified)
|
||||||
|
const rho11k = 1.225 * Math.exp(-11000 / 8500)
|
||||||
|
return rho11k * Math.exp(-(h - 11000) / 6500)
|
||||||
|
} else {
|
||||||
|
// Higher altitude (even rarer)
|
||||||
|
const rho20k = 1.225 * Math.exp(-20000 / 8500) * Math.exp(-(9000) / 6500)
|
||||||
|
return rho20k * Math.exp(-(h - 20000) / 4500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gravity with altitude correction
|
||||||
|
function gravity(y) {
|
||||||
|
const g0 = 9.80665
|
||||||
|
const R_earth = 6.371e6 // meters
|
||||||
|
return g0 * Math.pow(R_earth / (R_earth + y), 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute pitch angle (gravity turn or from pitch program)
|
||||||
|
function computePitch(state, config, events, t) {
|
||||||
|
// Check if there's a pitch program event at or before time t
|
||||||
|
const pitchEvents = events.filter(e => e.type === 'guidance' && e.t <= t)
|
||||||
|
if (pitchEvents.length > 0) {
|
||||||
|
const latest = pitchEvents.reduce((a, b) => a.t > b.t ? a : b)
|
||||||
|
return latest.pitch_angle ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gravity turn: pitch follows velocity vector angle
|
||||||
|
const { y, vx, vy } = state
|
||||||
|
|
||||||
|
// Start vertical, then follow velocity vector
|
||||||
|
if (y < config.pitchStartAlt) {
|
||||||
|
return 90 // vertical
|
||||||
|
}
|
||||||
|
|
||||||
|
// Velocity angle from horizontal
|
||||||
|
const vMag = Math.hypot(vx, vy)
|
||||||
|
if (vMag < 0.1) return 90 // very slow, stay vertical
|
||||||
|
|
||||||
|
// pitch = 90 - elevation_angle, so pitch = arctan(vy/vx) in degrees
|
||||||
|
const elevAngle = Math.atan2(vy, vx) * 180 / Math.PI
|
||||||
|
return Math.max(0, elevAngle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply jettison events (mass drop)
|
||||||
|
function applyJettison(state, events, t) {
|
||||||
|
let m = state.m
|
||||||
|
const jettisonEvents = events.filter(e => e.type === 'jettison' && e.t <= t)
|
||||||
|
if (jettisonEvents.length > 0) {
|
||||||
|
const latest = jettisonEvents.reduce((a, b) => a.t > b.t ? a : b)
|
||||||
|
if (latest.mass_drop) {
|
||||||
|
m -= latest.mass_drop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derivatives for RK4
|
||||||
|
function derivatives(state, config, events, t) {
|
||||||
|
let { x, y, vx, vy, m } = state
|
||||||
|
|
||||||
|
// Apply jettison if applicable
|
||||||
|
m = applyJettison(state, events, t)
|
||||||
|
|
||||||
|
// Atmosphere
|
||||||
|
const rho = atmosphereDensity(y)
|
||||||
|
|
||||||
|
// Velocity magnitude and direction
|
||||||
|
const vMag = Math.hypot(vx, vy)
|
||||||
|
|
||||||
|
// Gravity
|
||||||
|
const g = gravity(y)
|
||||||
|
|
||||||
|
// Thrust and burn
|
||||||
|
let F = 0
|
||||||
|
let mdot = 0
|
||||||
|
|
||||||
|
if (m > config.m_dry && config.t_burn_remaining > 0) {
|
||||||
|
F = config.F
|
||||||
|
mdot = config.mdot
|
||||||
|
config.t_burn_remaining -= config.dt // decrement locally (will be overridden in main loop)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pitch angle
|
||||||
|
const pitch = computePitch(state, config, events, t)
|
||||||
|
const pitchRad = pitch * Math.PI / 180
|
||||||
|
|
||||||
|
// Thrust direction (along pitch angle from horizontal)
|
||||||
|
const Fx = F * Math.cos(pitchRad)
|
||||||
|
const Fy = F * Math.sin(pitchRad)
|
||||||
|
|
||||||
|
// Drag force
|
||||||
|
const Cd = config.Cd
|
||||||
|
const A_ref = config.A_ref
|
||||||
|
const F_drag = 0.5 * rho * vMag * vMag * Cd * A_ref
|
||||||
|
|
||||||
|
let Fdrag_x = 0
|
||||||
|
let Fdrag_y = 0
|
||||||
|
if (vMag > 0.01) {
|
||||||
|
Fdrag_x = -(F_drag * vx / vMag)
|
||||||
|
Fdrag_y = -(F_drag * vy / vMag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acceleration
|
||||||
|
const ax = (Fx + Fdrag_x) / m
|
||||||
|
const ay = (Fy + Fdrag_y) / m - g
|
||||||
|
|
||||||
|
return {
|
||||||
|
dx: vx,
|
||||||
|
dy: vy,
|
||||||
|
dvx: ax,
|
||||||
|
dvy: ay,
|
||||||
|
dm: -mdot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RK4 step
|
||||||
|
function rk4Step(state, config, events, t, dt) {
|
||||||
|
const k1 = derivatives(state, config, events, t)
|
||||||
|
|
||||||
|
const state2 = {
|
||||||
|
x: state.x + k1.dx * dt / 2,
|
||||||
|
y: state.y + k1.dy * dt / 2,
|
||||||
|
vx: state.vx + k1.dvx * dt / 2,
|
||||||
|
vy: state.vy + k1.dvy * dt / 2,
|
||||||
|
m: state.m + k1.dm * dt / 2,
|
||||||
|
}
|
||||||
|
const k2 = derivatives(state2, config, events, t + dt / 2)
|
||||||
|
|
||||||
|
const state3 = {
|
||||||
|
x: state.x + k2.dx * dt / 2,
|
||||||
|
y: state.y + k2.dy * dt / 2,
|
||||||
|
vx: state.vx + k2.dvx * dt / 2,
|
||||||
|
vy: state.vy + k2.dvy * dt / 2,
|
||||||
|
m: state.m + k2.dm * dt / 2,
|
||||||
|
}
|
||||||
|
const k3 = derivatives(state3, config, events, t + dt / 2)
|
||||||
|
|
||||||
|
const state4 = {
|
||||||
|
x: state.x + k3.dx * dt,
|
||||||
|
y: state.y + k3.dy * dt,
|
||||||
|
vx: state.vx + k3.dvx * dt,
|
||||||
|
vy: state.vy + k3.dvy * dt,
|
||||||
|
m: state.m + k3.dm * dt,
|
||||||
|
}
|
||||||
|
const k4 = derivatives(state4, config, events, t + dt)
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: state.x + (k1.dx + 2*k2.dx + 2*k3.dx + k4.dx) * dt / 6,
|
||||||
|
y: state.y + (k1.dy + 2*k2.dy + 2*k3.dy + k4.dy) * dt / 6,
|
||||||
|
vx: state.vx + (k1.dvx + 2*k2.dvx + 2*k3.dvx + k4.dvx) * dt / 6,
|
||||||
|
vy: state.vy + (k1.dvy + 2*k2.dvy + 2*k3.dvy + k4.dvy) * dt / 6,
|
||||||
|
m: Math.max(state.m + (k1.dm + 2*k2.dm + 2*k3.dm + k4.dm) * dt / 6, config.m_dry),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run trajectory simulation.
|
||||||
|
* Returns { states, events, summary }
|
||||||
|
*/
|
||||||
|
export function runSimulation(config, userEvents = []) {
|
||||||
|
const {
|
||||||
|
F, Isp, mdot, m_wet, m_dry, burnTime,
|
||||||
|
Cd, A_ref,
|
||||||
|
pitchStartAlt, launchAngle,
|
||||||
|
dt, t_max,
|
||||||
|
} = config
|
||||||
|
|
||||||
|
const g0 = 9.80665
|
||||||
|
|
||||||
|
// Initial state
|
||||||
|
let state = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
vx: 0,
|
||||||
|
vy: 0,
|
||||||
|
m: m_wet,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Launch angle (degrees) → velocity components
|
||||||
|
const launchRad = (launchAngle || 90) * Math.PI / 180
|
||||||
|
|
||||||
|
// Configuration for derivatives
|
||||||
|
const simConfig = {
|
||||||
|
F, mdot, m_dry, Cd, A_ref,
|
||||||
|
pitchStartAlt: pitchStartAlt || 100,
|
||||||
|
dt: dt || 0.05,
|
||||||
|
t_burn_remaining: burnTime || 30,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Results
|
||||||
|
const states = []
|
||||||
|
let t = 0
|
||||||
|
let meco_t = null
|
||||||
|
let apogee_t = null
|
||||||
|
let apogee_y = 0
|
||||||
|
let max_q = 0
|
||||||
|
let max_q_t = 0
|
||||||
|
let vy_last = 0
|
||||||
|
|
||||||
|
// Add initial state
|
||||||
|
states.push({
|
||||||
|
t: 0,
|
||||||
|
x: state.x,
|
||||||
|
y: state.y,
|
||||||
|
vx: state.vx,
|
||||||
|
vy: state.vy,
|
||||||
|
m: state.m,
|
||||||
|
phase: 'standby',
|
||||||
|
q: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Main RK4 loop
|
||||||
|
while (t < t_max && state.y >= 0) {
|
||||||
|
// Determine phase
|
||||||
|
let phase = 'coast'
|
||||||
|
if (state.m > m_dry + 0.1 && simConfig.t_burn_remaining > 0) {
|
||||||
|
phase = 'burn'
|
||||||
|
simConfig.t_burn_remaining -= dt
|
||||||
|
if (simConfig.t_burn_remaining <= 0) {
|
||||||
|
meco_t = t
|
||||||
|
simConfig.t_burn_remaining = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RK4 integration step
|
||||||
|
state = rk4Step(state, simConfig, userEvents, t, dt)
|
||||||
|
t += dt
|
||||||
|
|
||||||
|
// Dynamic pressure
|
||||||
|
const rho = atmosphereDensity(state.y)
|
||||||
|
const v_mag = Math.hypot(state.vx, state.vy)
|
||||||
|
const q = 0.5 * rho * v_mag * v_mag
|
||||||
|
|
||||||
|
if (q > max_q) {
|
||||||
|
max_q = q
|
||||||
|
max_q_t = t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for apogee (vy crosses zero)
|
||||||
|
if (vy_last > 0 && state.vy <= 0 && apogee_t === null) {
|
||||||
|
apogee_t = t
|
||||||
|
apogee_y = state.y
|
||||||
|
}
|
||||||
|
vy_last = state.vy
|
||||||
|
|
||||||
|
// Record state
|
||||||
|
states.push({
|
||||||
|
t: Math.round(t * 1000) / 1000, // round to 3 decimals
|
||||||
|
x: state.x,
|
||||||
|
y: Math.max(0, state.y),
|
||||||
|
vx: state.vx,
|
||||||
|
vy: state.vy,
|
||||||
|
m: state.m,
|
||||||
|
phase,
|
||||||
|
q,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Landing
|
||||||
|
const landing_t = t
|
||||||
|
const landing_y = 0
|
||||||
|
|
||||||
|
// Auto-detect events
|
||||||
|
const autoEvents = []
|
||||||
|
autoEvents.push({
|
||||||
|
id: `event_liftoff`,
|
||||||
|
t: 0.1,
|
||||||
|
label: 'Liftoff',
|
||||||
|
type: 'engine',
|
||||||
|
color: '#10b981',
|
||||||
|
})
|
||||||
|
|
||||||
|
if (meco_t !== null) {
|
||||||
|
autoEvents.push({
|
||||||
|
id: `event_meco`,
|
||||||
|
t: meco_t,
|
||||||
|
label: 'MECO',
|
||||||
|
type: 'engine',
|
||||||
|
color: '#ef4444',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max_q_t > 0) {
|
||||||
|
autoEvents.push({
|
||||||
|
id: `event_maxq`,
|
||||||
|
t: max_q_t,
|
||||||
|
label: 'Max Q',
|
||||||
|
type: 'guidance',
|
||||||
|
color: '#f59e0b',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apogee_t !== null) {
|
||||||
|
autoEvents.push({
|
||||||
|
id: `event_apogee`,
|
||||||
|
t: apogee_t,
|
||||||
|
label: 'Apogee',
|
||||||
|
type: 'marker',
|
||||||
|
color: '#3b82f6',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
autoEvents.push({
|
||||||
|
id: `event_landing`,
|
||||||
|
t: landing_t,
|
||||||
|
label: 'Landing',
|
||||||
|
type: 'marker',
|
||||||
|
color: '#8b5cf6',
|
||||||
|
})
|
||||||
|
|
||||||
|
// Merge user and auto events
|
||||||
|
const allEvents = [...userEvents, ...autoEvents].sort((a, b) => a.t - b.t)
|
||||||
|
|
||||||
|
return {
|
||||||
|
states,
|
||||||
|
events: allEvents,
|
||||||
|
summary: {
|
||||||
|
t_max: landing_t,
|
||||||
|
apogee: apogee_y,
|
||||||
|
apogee_t,
|
||||||
|
meco_t,
|
||||||
|
max_q,
|
||||||
|
max_q_t,
|
||||||
|
downrange: state.x,
|
||||||
|
final_velocity: Math.hypot(state.vx, state.vy),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
128
src/hooks/useTrajectory.js
Normal file
128
src/hooks/useTrajectory.js
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import { useState, useMemo, useCallback, useEffect } from 'react'
|
||||||
|
import { runSimulation } from '../engine/trajectoryCalcs.js'
|
||||||
|
|
||||||
|
export function useTrajectory() {
|
||||||
|
// Simulation parameters
|
||||||
|
const [config, setConfig] = useState({
|
||||||
|
F: 5000, // Newtons
|
||||||
|
Isp: 250, // seconds
|
||||||
|
mdot: 2, // kg/s
|
||||||
|
m_wet: 100, // kg
|
||||||
|
m_dry: 20, // kg
|
||||||
|
burnTime: 40, // seconds
|
||||||
|
Cd: 0.3,
|
||||||
|
A_ref: 0.05, // m²
|
||||||
|
pitchStartAlt: 100, // meters
|
||||||
|
launchAngle: 90, // degrees
|
||||||
|
dt: 0.05, // timestep (s)
|
||||||
|
t_max: 600, // max simulation time (s)
|
||||||
|
})
|
||||||
|
|
||||||
|
// User-defined events
|
||||||
|
const [userEvents, setUserEvents] = useState([])
|
||||||
|
|
||||||
|
// Simulation results
|
||||||
|
const [simulationResults, setSimulationResults] = useState(null)
|
||||||
|
const [error, setError] = useState(null)
|
||||||
|
|
||||||
|
// Playback state
|
||||||
|
const [isPlaying, setIsPlaying] = useState(false)
|
||||||
|
const [playbackSpeed, setPlaybackSpeed] = useState(1) // 1x, 5x, 10x
|
||||||
|
const [currentTime, setCurrentTime] = useState(0)
|
||||||
|
const [currentIndex, setCurrentIndex] = useState(0)
|
||||||
|
|
||||||
|
// Update a config value
|
||||||
|
const updateConfig = useCallback((key, value) => {
|
||||||
|
setConfig(prev => ({
|
||||||
|
...prev,
|
||||||
|
[key]: value,
|
||||||
|
}))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Run simulation
|
||||||
|
const runSim = useCallback(() => {
|
||||||
|
try {
|
||||||
|
setError(null)
|
||||||
|
const results = runSimulation(config, userEvents)
|
||||||
|
setSimulationResults(results)
|
||||||
|
setCurrentTime(0)
|
||||||
|
setCurrentIndex(0)
|
||||||
|
setIsPlaying(false)
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message)
|
||||||
|
}
|
||||||
|
}, [config, userEvents])
|
||||||
|
|
||||||
|
// Get current state (for playhead position)
|
||||||
|
const currentState = useMemo(() => {
|
||||||
|
if (!simulationResults || simulationResults.states.length === 0) return null
|
||||||
|
return simulationResults.states[currentIndex] || simulationResults.states[0]
|
||||||
|
}, [simulationResults, currentIndex])
|
||||||
|
|
||||||
|
// Playback loop (update time on interval)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isPlaying || !simulationResults) return
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setCurrentTime(prev => {
|
||||||
|
const maxTime = simulationResults.summary.t_max
|
||||||
|
const increment = 0.016 * playbackSpeed // ~60 fps * playback speed
|
||||||
|
let next = prev + increment
|
||||||
|
if (next >= maxTime) {
|
||||||
|
setIsPlaying(false)
|
||||||
|
return maxTime
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
}, 16) // ~60 fps
|
||||||
|
|
||||||
|
return () => clearInterval(interval)
|
||||||
|
}, [isPlaying, simulationResults, playbackSpeed])
|
||||||
|
|
||||||
|
// Update currentIndex based on currentTime
|
||||||
|
useEffect(() => {
|
||||||
|
if (!simulationResults) return
|
||||||
|
const states = simulationResults.states
|
||||||
|
let idx = 0
|
||||||
|
for (let i = 0; i < states.length; i++) {
|
||||||
|
if (states[i].t <= currentTime) {
|
||||||
|
idx = i
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setCurrentIndex(idx)
|
||||||
|
}, [currentTime, simulationResults])
|
||||||
|
|
||||||
|
// Add/remove user events
|
||||||
|
const addEvent = useCallback((event) => {
|
||||||
|
setUserEvents(prev => [...prev, event])
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const removeEvent = useCallback((eventId) => {
|
||||||
|
setUserEvents(prev => prev.filter(e => e.id !== eventId))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const updateEvent = useCallback((eventId, updates) => {
|
||||||
|
setUserEvents(prev => prev.map(e => e.id === eventId ? { ...e, ...updates } : e))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return {
|
||||||
|
config,
|
||||||
|
updateConfig,
|
||||||
|
userEvents,
|
||||||
|
addEvent,
|
||||||
|
removeEvent,
|
||||||
|
updateEvent,
|
||||||
|
simulationResults,
|
||||||
|
error,
|
||||||
|
runSim,
|
||||||
|
isPlaying,
|
||||||
|
setIsPlaying,
|
||||||
|
playbackSpeed,
|
||||||
|
setPlaybackSpeed,
|
||||||
|
currentTime,
|
||||||
|
setCurrentTime,
|
||||||
|
currentState,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ const SECTIONS = [
|
|||||||
'Solver',
|
'Solver',
|
||||||
'Engine Designer',
|
'Engine Designer',
|
||||||
'Rocket Designer',
|
'Rocket Designer',
|
||||||
|
'Trajectory Simulator',
|
||||||
'Knowledgebase',
|
'Knowledgebase',
|
||||||
'Variables Reference',
|
'Variables Reference',
|
||||||
]
|
]
|
||||||
@@ -234,6 +235,232 @@ export default function DocsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Trajectory Simulator */}
|
||||||
|
<section id="Trajectory Simulator" className="space-y-4">
|
||||||
|
<h2 className="text-3xl font-bold text-white">Trajectory Simulator</h2>
|
||||||
|
<p className="text-slate-300">
|
||||||
|
Simulate realistic rocket flight trajectories using numerical integration with atmospheric and aerodynamic physics.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="space-y-4 mt-4">
|
||||||
|
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
|
||||||
|
<h3 className="text-lg font-semibold text-white mb-3">Overview</h3>
|
||||||
|
<p className="text-slate-300 mb-3">
|
||||||
|
The Trajectory Simulator integrates engine design and rocket parameters to predict flight paths. It:
|
||||||
|
</p>
|
||||||
|
<ul className="space-y-2 text-slate-300 list-disc list-inside">
|
||||||
|
<li>Solves the 3-DOF equations of motion in 2D (altitude × downrange)</li>
|
||||||
|
<li>Includes thrust, atmospheric drag, and gravity (with altitude correction)</li>
|
||||||
|
<li>Uses real atmosphere model (US Standard Atmosphere)</li>
|
||||||
|
<li>Tracks vehicle mass during propellant burn</li>
|
||||||
|
<li>Detects key flight events (liftoff, MECO, apogee, landing)</li>
|
||||||
|
<li>Enables interactive playback with timeline scrubbing</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
|
||||||
|
<h3 className="text-lg font-semibold text-white mb-3">Physics Model</h3>
|
||||||
|
<p className="text-slate-300 mb-4">The simulator uses 4th-order Runge-Kutta (RK4) integration with the following state vector:</p>
|
||||||
|
<div className="bg-slate-950 rounded p-4 font-mono text-amber-300 text-sm mb-4">
|
||||||
|
state = [x, y, vx, vy, m]
|
||||||
|
</div>
|
||||||
|
<p className="text-slate-300 mb-3">where:</p>
|
||||||
|
<ul className="space-y-1 text-slate-300 text-sm">
|
||||||
|
<li><span className="font-mono text-amber-300">x</span> = downrange distance (m)</li>
|
||||||
|
<li><span className="font-mono text-amber-300">y</span> = altitude above sea level (m)</li>
|
||||||
|
<li><span className="font-mono text-amber-300">vx, vy</span> = velocity components (m/s)</li>
|
||||||
|
<li><span className="font-mono text-amber-300">m</span> = vehicle mass (kg)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
|
||||||
|
<h3 className="text-lg font-semibold text-white mb-3">Forces</h3>
|
||||||
|
<p className="text-slate-300 mb-3">Three forces act on the vehicle:</p>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<p className="text-slate-300 font-semibold mb-2">1. Thrust</p>
|
||||||
|
<div className="bg-slate-950 rounded p-3 font-mono text-amber-300 text-sm mb-2">
|
||||||
|
F<sub>thrust</sub> = F × (cos θ, sin θ)
|
||||||
|
</div>
|
||||||
|
<p className="text-slate-300 text-sm">
|
||||||
|
where <span className="font-mono text-amber-300">F</span> is engine thrust and <span className="font-mono text-amber-300">θ</span> is pitch angle (0° horizontal, 90° vertical).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<p className="text-slate-300 font-semibold mb-2">2. Drag</p>
|
||||||
|
<div className="bg-slate-950 rounded p-3 font-mono text-amber-300 text-sm mb-2">
|
||||||
|
F<sub>drag</sub> = -0.5 × ρ(y) × v² × C<sub>d</sub> × A<sub>ref</sub> × (v̂)
|
||||||
|
</div>
|
||||||
|
<p className="text-slate-300 text-sm">
|
||||||
|
Atmospheric density ρ varies with altitude; drag opposes the velocity vector.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-slate-300 font-semibold mb-2">3. Gravity (Altitude-Corrected)</p>
|
||||||
|
<div className="bg-slate-950 rounded p-3 font-mono text-amber-300 text-sm mb-2">
|
||||||
|
g(y) = g₀ × (R<sub>E</sub> / (R<sub>E</sub> + y))²
|
||||||
|
</div>
|
||||||
|
<p className="text-slate-300 text-sm">
|
||||||
|
where g₀ = 9.80665 m/s² and R<sub>E</sub> = 6,371 km (Earth radius).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
|
||||||
|
<h3 className="text-lg font-semibold text-white mb-3">Atmosphere Model</h3>
|
||||||
|
<p className="text-slate-300 mb-3">
|
||||||
|
Density vs. altitude follows a piecewise US Standard Atmosphere:
|
||||||
|
</p>
|
||||||
|
<ul className="space-y-2 text-slate-300 text-sm">
|
||||||
|
<li><strong>Troposphere (0–11 km):</strong> Scale height H = 8,500 m → ρ = ρ₀ × exp(−h/H)</li>
|
||||||
|
<li><strong>Stratosphere (11–20 km):</strong> H = 6,500 m</li>
|
||||||
|
<li><strong>Higher altitude:</strong> H = 4,500 m (simplified)</li>
|
||||||
|
</ul>
|
||||||
|
<p className="text-slate-300 text-sm mt-3">
|
||||||
|
Sea-level density ρ₀ = 1.225 kg/m³. This provides realistic dynamic pressure (Q) changes during ascent.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
|
||||||
|
<h3 className="text-lg font-semibold text-white mb-3">Pitch Program (Guidance)</h3>
|
||||||
|
<p className="text-slate-300 mb-3">
|
||||||
|
By default, the vehicle follows a gravity turn:
|
||||||
|
</p>
|
||||||
|
<ul className="space-y-2 text-slate-300 text-sm">
|
||||||
|
<li><strong>Initial phase:</strong> Vertical hold (90°) until reaching "Pitch Start Altitude"</li>
|
||||||
|
<li><strong>Gravity turn:</strong> Pitch angle equals the velocity vector angle (no additional steering)</li>
|
||||||
|
<li><strong>User events:</strong> Override pitch at specific times via guidance events (advanced)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
|
||||||
|
<h3 className="text-lg font-semibold text-white mb-3">Mass Depletion</h3>
|
||||||
|
<p className="text-slate-300 mb-3">
|
||||||
|
During engine burn:
|
||||||
|
</p>
|
||||||
|
<div className="bg-slate-950 rounded p-3 font-mono text-amber-300 text-sm mb-3">
|
||||||
|
dm/dt = −ṁ
|
||||||
|
</div>
|
||||||
|
<p className="text-slate-300 text-sm">
|
||||||
|
where ṁ is mass flow rate (mdot). The engine shuts off when m ≤ m_dry (dry mass) or burn duration elapsed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
|
||||||
|
<h3 className="text-lg font-semibold text-white mb-3">Integration Method: RK4</h3>
|
||||||
|
<p className="text-slate-300 mb-3">
|
||||||
|
4th-order Runge-Kutta (RK4) provides excellent accuracy with a fixed timestep:
|
||||||
|
</p>
|
||||||
|
<div className="bg-slate-950 rounded p-3 font-mono text-amber-300 text-sm text-xs mb-3 overflow-x-auto">
|
||||||
|
state(t+dt) = state(t) + (dt/6) × (k₁ + 2k₂ + 2k₃ + k₄)
|
||||||
|
</div>
|
||||||
|
<p className="text-slate-300 text-sm">
|
||||||
|
Default timestep: 0.05 s (produces ~12,000 steps for a typical 600 s flight).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
|
||||||
|
<h3 className="text-lg font-semibold text-white mb-3">Input Parameters</h3>
|
||||||
|
<p className="text-slate-300 mb-3">Configure the simulation with:</p>
|
||||||
|
<div className="space-y-3 text-slate-300 text-sm">
|
||||||
|
<div className="bg-slate-950 rounded p-3">
|
||||||
|
<p className="font-semibold text-white">Engine Parameters</p>
|
||||||
|
<ul className="mt-2 space-y-1">
|
||||||
|
<li>Thrust <span className="font-mono text-amber-300">F</span> (N)</li>
|
||||||
|
<li>Specific Impulse <span className="font-mono text-amber-300">Isp</span> (s) — reference only in this tool</li>
|
||||||
|
<li>Mass Flow Rate <span className="font-mono text-amber-300">ṁ</span> (kg/s)</li>
|
||||||
|
<li>Wet Mass <span className="font-mono text-amber-300">m_wet</span>, Dry Mass <span className="font-mono text-amber-300">m_dry</span> (kg)</li>
|
||||||
|
<li>Burn Time (s)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="bg-slate-950 rounded p-3">
|
||||||
|
<p className="font-semibold text-white">Aerodynamics</p>
|
||||||
|
<ul className="mt-2 space-y-1">
|
||||||
|
<li>Drag Coefficient <span className="font-mono text-amber-300">C<sub>d</sub></span> (default 0.3)</li>
|
||||||
|
<li>Reference Area <span className="font-mono text-amber-300">A_ref</span> (m²) — cross-sectional area</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="bg-slate-950 rounded p-3">
|
||||||
|
<p className="font-semibold text-white">Flight Profile</p>
|
||||||
|
<ul className="mt-2 space-y-1">
|
||||||
|
<li>Pitch Start Altitude (m) — when to begin gravity turn</li>
|
||||||
|
<li>Launch Angle (°) — typically 90° for vertical launch</li>
|
||||||
|
<li>Timestep <span className="font-mono text-amber-300">dt</span> (s) — RK4 step size</li>
|
||||||
|
<li>Max Simulation Time (s) — cutoff to prevent infinite loops</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
|
||||||
|
<h3 className="text-lg font-semibold text-white mb-3">Auto-Detected Events</h3>
|
||||||
|
<p className="text-slate-300 mb-3">
|
||||||
|
The simulator automatically identifies key mission milestones:
|
||||||
|
</p>
|
||||||
|
<ul className="space-y-2 text-slate-300 text-sm">
|
||||||
|
<li><strong>Liftoff:</strong> t ≈ 0.1 s</li>
|
||||||
|
<li><strong>MECO:</strong> Main Engine Cutoff, when m ≤ m_dry</li>
|
||||||
|
<li><strong>Max Q:</strong> Peak dynamic pressure (0.5 × ρ × v²)</li>
|
||||||
|
<li><strong>Apogee:</strong> Maximum altitude (where vy crosses zero)</li>
|
||||||
|
<li><strong>Landing:</strong> Final impact at y = 0</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
|
||||||
|
<h3 className="text-lg font-semibold text-white mb-3">Results & Visualization</h3>
|
||||||
|
<p className="text-slate-300 mb-3">
|
||||||
|
After simulation runs:
|
||||||
|
</p>
|
||||||
|
<ul className="space-y-2 text-slate-300 text-sm list-disc list-inside">
|
||||||
|
<li><strong>Altitude vs. Downrange Plot:</strong> 2D trajectory curve with auto-scaled axes</li>
|
||||||
|
<li><strong>Event Markers:</strong> Color-coded points (green=engine, red=MECO, amber=Max Q, blue=apogee, purple=landing)</li>
|
||||||
|
<li><strong>Timeline Scrubber:</strong> Interactive playback controls; click plot to seek</li>
|
||||||
|
<li><strong>Live Readout:</strong> Current altitude, velocity, mass, phase during playback</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
|
||||||
|
<h3 className="text-lg font-semibold text-white mb-3">Workflow: Engine to Trajectory</h3>
|
||||||
|
<ol className="space-y-2 text-slate-300 text-sm list-decimal list-inside">
|
||||||
|
<li>Design an engine in <strong>/design/engine</strong></li>
|
||||||
|
<li>Export engine JSON</li>
|
||||||
|
<li>Go to <strong>/design/trajectory</strong></li>
|
||||||
|
<li>Click "Import Engine JSON" and select the exported file</li>
|
||||||
|
<li>Parameters auto-populate (thrust, Isp, mass flow, burn time, nozzle area)</li>
|
||||||
|
<li>Adjust aerodynamics (Cd, A_ref) and flight profile as needed</li>
|
||||||
|
<li>Click "Run Simulation"</li>
|
||||||
|
<li>Use Play/Pause, speed controls, and timeline scrubbing to explore the flight</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
|
||||||
|
<h3 className="text-lg font-semibold text-white mb-3">Tips & Limitations</h3>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div>
|
||||||
|
<p className="text-slate-300 font-semibold mb-1">✓ Strengths</p>
|
||||||
|
<ul className="space-y-1 text-slate-300 text-sm list-disc list-inside">
|
||||||
|
<li>Fast RK4 integration (typical flight solves in <100 ms)</li>
|
||||||
|
<li>Realistic atmosphere and gravity models</li>
|
||||||
|
<li>Good accuracy for point-mass trajectory (±5% apogee typical)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-slate-300 font-semibold mb-1">⚠ Limitations</p>
|
||||||
|
<ul className="space-y-1 text-slate-300 text-sm list-disc list-inside">
|
||||||
|
<li><strong>Point-mass model:</strong> Ignores rotational dynamics, wind, spinning</li>
|
||||||
|
<li><strong>2D only:</strong> Assumes flat Earth in vertical plane (no coriolis, curvature)</li>
|
||||||
|
<li><strong>Constant Cd:</strong> Drag coefficient doesn't vary with Mach or angle of attack</li>
|
||||||
|
<li><strong>Simple pitch program:</strong> Gravity turn only; no attitude control or TVC</li>
|
||||||
|
<li><strong>Sea-level launch:</strong> Assumes pad at elevation 0</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
{/* Knowledgebase */}
|
{/* Knowledgebase */}
|
||||||
<section id="Knowledgebase" className="space-y-4">
|
<section id="Knowledgebase" className="space-y-4">
|
||||||
<h2 className="text-3xl font-bold text-white">Knowledgebase</h2>
|
<h2 className="text-3xl font-bold text-white">Knowledgebase</h2>
|
||||||
|
|||||||
375
src/pages/TrajectoryPage.jsx
Normal file
375
src/pages/TrajectoryPage.jsx
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
import { useRef, useState } from 'react'
|
||||||
|
import { useTrajectory } from '../hooks/useTrajectory.js'
|
||||||
|
import { parseEngineImport, downloadBlob } from '../engine/engineExportImport.js'
|
||||||
|
import { TrajectoryPlot } from '../components/trajectory/TrajectoryPlot.jsx'
|
||||||
|
import { TimelineBar } from '../components/trajectory/TimelineBar.jsx'
|
||||||
|
import ErrorBoundary from '../components/ErrorBoundary.jsx'
|
||||||
|
import { formatValue } from '../engine/format.js'
|
||||||
|
|
||||||
|
function ResultRow({ label, value, unit }) {
|
||||||
|
const display = value !== null && value !== undefined && isFinite(value)
|
||||||
|
? formatValue(value)
|
||||||
|
: '—'
|
||||||
|
return (
|
||||||
|
<div className="flex justify-between items-baseline gap-2 text-sm">
|
||||||
|
<span className="text-slate-400">{label}</span>
|
||||||
|
<div className="flex items-baseline gap-1">
|
||||||
|
<span className={`font-mono ${display === '—' ? 'text-slate-600' : 'text-green-400'}`}>
|
||||||
|
{display}
|
||||||
|
</span>
|
||||||
|
{unit && <span className="text-slate-500 text-xs shrink-0">{unit}</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function NumInput({ label, value, onChange, units, step = 1 }) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-1 mb-2">
|
||||||
|
<label className="text-xs text-slate-400">{label}</label>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={value ?? ''}
|
||||||
|
step={step}
|
||||||
|
onChange={e => onChange(parseFloat(e.target.value) || 0)}
|
||||||
|
className="flex-1 px-2 py-1 bg-slate-800 border border-slate-600 rounded text-sm text-slate-100
|
||||||
|
focus:border-blue-500 focus:outline-none"
|
||||||
|
/>
|
||||||
|
{units && <span className="text-xs text-slate-500 shrink-0">{units}</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectInput({ label, value, onChange, options }) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-1 mb-2">
|
||||||
|
<label className="text-xs text-slate-400">{label}</label>
|
||||||
|
<select
|
||||||
|
value={value}
|
||||||
|
onChange={e => onChange(e.target.value)}
|
||||||
|
className="w-full px-2 py-1 bg-slate-800 border border-slate-600 rounded text-sm text-slate-100
|
||||||
|
focus:border-blue-500 focus:outline-none"
|
||||||
|
>
|
||||||
|
{options.map(o => (
|
||||||
|
<option key={o.value} value={o.value}>{o.label}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ResultSection({ title, children }) {
|
||||||
|
return (
|
||||||
|
<div className="mb-4">
|
||||||
|
<h4 className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2 pb-1 border-b border-slate-800">
|
||||||
|
{title}
|
||||||
|
</h4>
|
||||||
|
<div className="space-y-1 text-sm">{children}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TrajectoryPage() {
|
||||||
|
const trajectory = useTrajectory()
|
||||||
|
const fileInputRef = useRef(null)
|
||||||
|
const [importError, setImportError] = useState(null)
|
||||||
|
|
||||||
|
// Import engine data from JSON
|
||||||
|
const handleImport = async (e) => {
|
||||||
|
const file = e.target.files?.[0]
|
||||||
|
if (!file) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
setImportError(null)
|
||||||
|
const text = await file.text()
|
||||||
|
const data = JSON.parse(text)
|
||||||
|
|
||||||
|
// Handle engine design export
|
||||||
|
if (data.type === 'engine_design') {
|
||||||
|
const engineData = parseEngineImport(data)
|
||||||
|
|
||||||
|
if (engineData.thermoResults) {
|
||||||
|
const { F, Isp, mdot } = engineData.thermoResults
|
||||||
|
const burnTime = data.burnTime || 30
|
||||||
|
|
||||||
|
// Calculate m_dry and m_wet from propellant mass and burn time
|
||||||
|
const m_prop = mdot * burnTime
|
||||||
|
const m_dry = data.m_dry || 20
|
||||||
|
const m_wet = m_dry + m_prop
|
||||||
|
|
||||||
|
trajectory.updateConfig('F', F || 5000)
|
||||||
|
trajectory.updateConfig('Isp', Isp || 250)
|
||||||
|
trajectory.updateConfig('mdot', mdot || 2)
|
||||||
|
trajectory.updateConfig('m_wet', m_wet)
|
||||||
|
trajectory.updateConfig('m_dry', m_dry)
|
||||||
|
trajectory.updateConfig('burnTime', burnTime)
|
||||||
|
|
||||||
|
// Estimate A_ref from nozzle exit area if available
|
||||||
|
if (data.nozzleGeometry?.A_e) {
|
||||||
|
trajectory.updateConfig('A_ref', data.nozzleGeometry.A_e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setImportError(err.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileInputRef.current) {
|
||||||
|
fileInputRef.current.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { config, simulationResults, error, currentState } = trajectory
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ErrorBoundary>
|
||||||
|
<div className="flex-1 flex flex-col overflow-hidden">
|
||||||
|
{/* Main content area: left panel + center plot */}
|
||||||
|
<div className="flex-1 flex overflow-hidden gap-0">
|
||||||
|
{/* Left panel */}
|
||||||
|
<div className="w-80 bg-slate-900 border-r border-slate-700 overflow-y-auto p-4">
|
||||||
|
<h2 className="text-lg font-bold text-white mb-4">Trajectory Simulation</h2>
|
||||||
|
|
||||||
|
{/* Import */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<button
|
||||||
|
onClick={() => fileInputRef.current?.click()}
|
||||||
|
className="w-full px-3 py-2 bg-blue-600 hover:bg-blue-500 rounded text-sm font-medium text-white transition-colors"
|
||||||
|
>
|
||||||
|
Import Engine JSON
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
type="file"
|
||||||
|
accept=".json"
|
||||||
|
onChange={handleImport}
|
||||||
|
className="hidden"
|
||||||
|
/>
|
||||||
|
{importError && (
|
||||||
|
<p className="mt-2 text-xs text-red-400">{importError}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Simulation parameters */}
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3 pb-2 border-b border-slate-700">
|
||||||
|
Engine Parameters
|
||||||
|
</h3>
|
||||||
|
<NumInput
|
||||||
|
label="Thrust (N)"
|
||||||
|
value={config.F}
|
||||||
|
onChange={v => trajectory.updateConfig('F', v)}
|
||||||
|
units="N"
|
||||||
|
step={100}
|
||||||
|
/>
|
||||||
|
<NumInput
|
||||||
|
label="Isp (s)"
|
||||||
|
value={config.Isp}
|
||||||
|
onChange={v => trajectory.updateConfig('Isp', v)}
|
||||||
|
units="s"
|
||||||
|
step={5}
|
||||||
|
/>
|
||||||
|
<NumInput
|
||||||
|
label="Mass Flow (kg/s)"
|
||||||
|
value={config.mdot}
|
||||||
|
onChange={v => trajectory.updateConfig('mdot', v)}
|
||||||
|
units="kg/s"
|
||||||
|
step={0.1}
|
||||||
|
/>
|
||||||
|
<NumInput
|
||||||
|
label="Wet Mass (kg)"
|
||||||
|
value={config.m_wet}
|
||||||
|
onChange={v => trajectory.updateConfig('m_wet', v)}
|
||||||
|
units="kg"
|
||||||
|
/>
|
||||||
|
<NumInput
|
||||||
|
label="Dry Mass (kg)"
|
||||||
|
value={config.m_dry}
|
||||||
|
onChange={v => trajectory.updateConfig('m_dry', v)}
|
||||||
|
units="kg"
|
||||||
|
/>
|
||||||
|
<NumInput
|
||||||
|
label="Burn Time (s)"
|
||||||
|
value={config.burnTime}
|
||||||
|
onChange={v => trajectory.updateConfig('burnTime', v)}
|
||||||
|
units="s"
|
||||||
|
step={1}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<h3 className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3 pb-2 border-b border-slate-700">
|
||||||
|
Aerodynamics
|
||||||
|
</h3>
|
||||||
|
<NumInput
|
||||||
|
label="Drag Coefficient"
|
||||||
|
value={config.Cd}
|
||||||
|
onChange={v => trajectory.updateConfig('Cd', v)}
|
||||||
|
units=""
|
||||||
|
step={0.01}
|
||||||
|
/>
|
||||||
|
<NumInput
|
||||||
|
label="Ref Area (m²)"
|
||||||
|
value={config.A_ref}
|
||||||
|
onChange={v => trajectory.updateConfig('A_ref', v)}
|
||||||
|
units="m²"
|
||||||
|
step={0.001}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<h3 className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3 pb-2 border-b border-slate-700">
|
||||||
|
Flight Profile
|
||||||
|
</h3>
|
||||||
|
<NumInput
|
||||||
|
label="Pitch Start Alt (m)"
|
||||||
|
value={config.pitchStartAlt}
|
||||||
|
onChange={v => trajectory.updateConfig('pitchStartAlt', v)}
|
||||||
|
units="m"
|
||||||
|
step={10}
|
||||||
|
/>
|
||||||
|
<NumInput
|
||||||
|
label="Launch Angle (°)"
|
||||||
|
value={config.launchAngle}
|
||||||
|
onChange={v => trajectory.updateConfig('launchAngle', v)}
|
||||||
|
units="°"
|
||||||
|
step={1}
|
||||||
|
/>
|
||||||
|
<NumInput
|
||||||
|
label="Timestep (s)"
|
||||||
|
value={config.dt}
|
||||||
|
onChange={v => trajectory.updateConfig('dt', v)}
|
||||||
|
units="s"
|
||||||
|
step={0.01}
|
||||||
|
/>
|
||||||
|
<NumInput
|
||||||
|
label="Max Time (s)"
|
||||||
|
value={config.t_max}
|
||||||
|
onChange={v => trajectory.updateConfig('t_max', v)}
|
||||||
|
units="s"
|
||||||
|
step={10}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Run button */}
|
||||||
|
<button
|
||||||
|
onClick={trajectory.runSim}
|
||||||
|
className="w-full mt-6 px-4 py-2 bg-emerald-600 hover:bg-emerald-500 rounded-lg text-sm font-semibold text-white transition-colors"
|
||||||
|
>
|
||||||
|
Run Simulation
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<p className="mt-2 text-xs text-red-400">{error}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Results */}
|
||||||
|
{simulationResults && (
|
||||||
|
<div className="mt-6 pt-6 border-t border-slate-700">
|
||||||
|
<ResultSection title="Mission Summary">
|
||||||
|
<ResultRow
|
||||||
|
label="Apogee"
|
||||||
|
value={simulationResults.summary.apogee}
|
||||||
|
unit="m"
|
||||||
|
/>
|
||||||
|
<ResultRow
|
||||||
|
label="Apogee Time"
|
||||||
|
value={simulationResults.summary.apogee_t}
|
||||||
|
unit="s"
|
||||||
|
/>
|
||||||
|
<ResultRow
|
||||||
|
label="Max Q"
|
||||||
|
value={simulationResults.summary.max_q}
|
||||||
|
unit="Pa"
|
||||||
|
/>
|
||||||
|
<ResultRow
|
||||||
|
label="Flight Time"
|
||||||
|
value={simulationResults.summary.t_max}
|
||||||
|
unit="s"
|
||||||
|
/>
|
||||||
|
<ResultRow
|
||||||
|
label="Downrange"
|
||||||
|
value={simulationResults.summary.downrange}
|
||||||
|
unit="m"
|
||||||
|
/>
|
||||||
|
<ResultRow
|
||||||
|
label="Final Velocity"
|
||||||
|
value={simulationResults.summary.final_velocity}
|
||||||
|
unit="m/s"
|
||||||
|
/>
|
||||||
|
</ResultSection>
|
||||||
|
|
||||||
|
{currentState && (
|
||||||
|
<ResultSection title="Current State">
|
||||||
|
<ResultRow
|
||||||
|
label="Time"
|
||||||
|
value={currentState.t}
|
||||||
|
unit="s"
|
||||||
|
/>
|
||||||
|
<ResultRow
|
||||||
|
label="Altitude"
|
||||||
|
value={currentState.y}
|
||||||
|
unit="m"
|
||||||
|
/>
|
||||||
|
<ResultRow
|
||||||
|
label="Downrange"
|
||||||
|
value={currentState.x}
|
||||||
|
unit="m"
|
||||||
|
/>
|
||||||
|
<ResultRow
|
||||||
|
label="Velocity"
|
||||||
|
value={Math.hypot(currentState.vx, currentState.vy)}
|
||||||
|
unit="m/s"
|
||||||
|
/>
|
||||||
|
<ResultRow
|
||||||
|
label="Mass"
|
||||||
|
value={currentState.m}
|
||||||
|
unit="kg"
|
||||||
|
/>
|
||||||
|
<ResultRow
|
||||||
|
label="Phase"
|
||||||
|
value={currentState.phase}
|
||||||
|
unit=""
|
||||||
|
/>
|
||||||
|
</ResultSection>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Center: plot */}
|
||||||
|
{simulationResults ? (
|
||||||
|
<div className="flex-1 bg-slate-950 p-4 overflow-hidden">
|
||||||
|
<TrajectoryPlot
|
||||||
|
simulationResults={simulationResults}
|
||||||
|
currentState={currentState}
|
||||||
|
onTimeClick={(t) => trajectory.setCurrentTime(t)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex-1 flex items-center justify-center bg-slate-950">
|
||||||
|
<p className="text-slate-500 text-center">
|
||||||
|
Import an engine and click "Run Simulation" to see trajectory
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Timeline at bottom */}
|
||||||
|
{simulationResults && (
|
||||||
|
<TimelineBar
|
||||||
|
simulationResults={simulationResults}
|
||||||
|
currentTime={trajectory.currentTime}
|
||||||
|
onTimeChange={trajectory.setCurrentTime}
|
||||||
|
isPlaying={trajectory.isPlaying}
|
||||||
|
onPlayPause={() => trajectory.setIsPlaying(!trajectory.isPlaying)}
|
||||||
|
playbackSpeed={trajectory.playbackSpeed}
|
||||||
|
onPlaybackSpeedChange={trajectory.setPlaybackSpeed}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ErrorBoundary>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user