#!/usr/bin/env rust-script
use clap::{Parser, Subcommand, ValueEnum};
use serde::{Serialize, Deserialize};
use std::error::Error;
#[derive(Parser)]
#[command(name = "ballistics")]
#[command(author = "Ballistics Engine Team")]
#[command(version = "0.1.0")]
#[command(about = "High-performance ballistics trajectory calculator", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Trajectory {
#[arg(short = 'v', long)]
velocity: f64,
#[arg(short = 'a', long, default_value = "0.0")]
angle: f64,
#[arg(short = 'b', long, default_value = "0.5")]
bc: f64,
#[arg(short = 'm', long, default_value = "0.01")]
mass: f64,
#[arg(short = 'd', long, default_value = "0.00762")]
diameter: f64,
#[arg(long, default_value = "10.0")]
max_time: f64,
#[arg(long, default_value = "0.01")]
time_step: f64,
#[arg(short = 'o', long, default_value = "table")]
output: OutputFormat,
},
Info,
}
#[derive(Debug, Clone, Copy, ValueEnum)]
enum OutputFormat {
Json,
Csv,
Table,
}
#[derive(Debug, Serialize, Deserialize)]
struct TrajectoryPoint {
time: f64,
x: f64,
y: f64,
velocity: f64,
energy: f64,
}
#[derive(Debug, Serialize, Deserialize)]
struct TrajectoryResult {
max_range: f64,
max_height: f64,
time_of_flight: f64,
impact_velocity: f64,
impact_energy: f64,
trajectory: Vec<TrajectoryPoint>,
}
fn main() -> Result<(), Box<dyn Error>> {
let cli = Cli::parse();
match cli.command {
Commands::Trajectory {
velocity, angle, bc, mass, diameter,
max_time, time_step, output
} => {
let result = calculate_trajectory(
velocity, angle, bc, mass, diameter,
max_time, time_step
)?;
display_results(result, output)?;
},
Commands::Info => {
println!("ββββββββββββββββββββββββββββββββββββββββββ");
println!("β BALLISTICS ENGINE v0.1.0 β");
println!("β βββββββββββββββββββββββββββββββββββββββββ£");
println!("β A high-performance ballistics β");
println!("β trajectory calculation engine. β");
println!("β βββββββββββββββββββββββββββββββββββββββββ£");
println!("β Features: β");
println!("β β’ 6DOF trajectory integration β");
println!("β β’ Atmospheric modeling β");
println!("β β’ Drag coefficient interpolation β");
println!("β β’ Multiple output formats β");
println!("ββββββββββββββββββββββββββββββββββββββββββ");
}
}
Ok(())
}
fn calculate_trajectory(
velocity: f64,
angle_deg: f64,
bc: f64,
mass: f64,
diameter: f64,
max_time: f64,
time_step: f64,
) -> Result<TrajectoryResult, Box<dyn Error>> {
let angle_rad = angle_deg.to_radians();
let mut time = 0.0;
let mut x = 0.0;
let mut y = 0.0;
let mut vx = velocity * angle_rad.cos();
let mut vy = velocity * angle_rad.sin();
let mut trajectory = Vec::new();
let mut max_height = 0.0;
let air_density = 1.225; let g = 9.80665;
let area = std::f64::consts::PI * (diameter / 2.0).powi(2);
while time <= max_time && y >= 0.0 {
let v = (vx * vx + vy * vy).sqrt();
let energy = 0.5 * mass * v * v;
trajectory.push(TrajectoryPoint {
time,
x,
y,
velocity: v,
energy,
});
if y > max_height {
max_height = y;
}
let cd = 0.5 / bc;
let drag = 0.5 * air_density * cd * area * v;
let ax = -drag * vx / mass;
let ay = -drag * vy / mass - g;
vx += ax * time_step;
vy += ay * time_step;
x += vx * time_step;
y += vy * time_step;
time += time_step;
}
let last = trajectory.last().unwrap();
Ok(TrajectoryResult {
max_range: last.x,
max_height,
time_of_flight: last.time,
impact_velocity: last.velocity,
impact_energy: last.energy,
trajectory,
})
}
fn display_results(result: TrajectoryResult, format: OutputFormat) -> Result<(), Box<dyn Error>> {
match format {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&result)?);
},
OutputFormat::Csv => {
println!("time,x,y,velocity,energy");
for p in &result.trajectory {
println!("{:.3},{:.2},{:.2},{:.2},{:.2}",
p.time, p.x, p.y, p.velocity, p.energy);
}
},
OutputFormat::Table => {
println!("ββββββββββββββββββββββββββββββββββββββββββ");
println!("β TRAJECTORY RESULTS β");
println!("β βββββββββββββββββββββββββββββββββββββββββ£");
println!("β Max Range: {:>8.2} m β", result.max_range);
println!("β Max Height: {:>8.2} m β", result.max_height);
println!("β Time of Flight: {:>8.3} s β", result.time_of_flight);
println!("β Impact Velocity: {:>8.2} m/s β", result.impact_velocity);
println!("β Impact Energy: {:>8.2} J β", result.impact_energy);
println!("ββββββββββββββββββββββββββββββββββββββββββ");
println!("\nTrajectory Points (every {:.1}s):", result.time_of_flight / 10.0);
println!("ββββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ");
println!("β Time (s) β X (m) β Y (m) β Vel(m/s) β");
println!("ββββββββββββΌβββββββββββΌβββββββββββΌβββββββββββ€");
let step = result.trajectory.len() / 10;
for (i, p) in result.trajectory.iter().enumerate() {
if i % step.max(1) == 0 || i == result.trajectory.len() - 1 {
println!("β {:>8.3} β {:>8.2} β {:>8.2} β {:>8.2} β",
p.time, p.x, p.y, p.velocity);
}
}
println!("ββββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ");
},
}
Ok(())
}