use macroquad::prelude as mq; mod engine; use engine::*; mod camera; mod config; mod drone; mod helpers; mod rendering; mod simulation; mod graphics_util; use crate::drone::input::*; use crate::simulation::{SimMode, Simulation}; use crate::config::SimulationConfig; use helpers::list_files; use std::fs; const INPUTS_DIR: &str = "inputs/"; const CONFIGS_DIR: &str = "configurations/"; const RESULTS_DIR: &str = "results/"; fn window_conf() -> mq::Conf { mq::Conf { window_title: "RustDroneSim".to_owned(), window_resizable: true, platform: mq::miniquad::conf::Platform { ..Default::default() }, ..Default::default() } } use std::env; enum RunMode { Batch, Record { output: String }, } fn parse_cli() -> RunMode { let mut args = env::args().skip(1); match args.next().as_deref() { Some("record") => { let output = args.next().expect("record mode requires output file path"); RunMode::Record { output } } Some("batch") | None => RunMode::Batch, Some(other) => panic!("Unknown mode: {}", other), } } #[macroquad::main(window_conf)] async fn main() { match parse_cli() { RunMode::Batch => run_batch().await, RunMode::Record { output } => run_record(output).await, } } async fn run_batch() { fs::create_dir_all(RESULTS_DIR).unwrap(); let input_files = list_files(INPUTS_DIR); let config_files = list_files(CONFIGS_DIR); for input_path in input_files { let input_name = input_path.file_stem().unwrap().to_string_lossy(); let inputs = InputRecording::load_inputs_from_csv(input_path.to_str().unwrap()) .expect("Failed to load input recording"); for config_path in &config_files { let config_name = config_path.file_stem().unwrap().to_string_lossy(); let config: SimulationConfig = toml::from_str(&fs::read_to_string(config_path).unwrap()) .expect("Invalid config file"); println!( "Running simulation: input={} config={}", input_name, config_name ); /* World */ let mut world = World::new(config.tickrate); /* Drone */ let drone = drone::Drone::new( &mut world, Box::new(drone::pidcontroller::PIDController { target_rate: config.target_rate, proportional_multiplier: config.proportional(), integral_multiplier: config.integral(), diferential_multiplier: config.diferential(), ..Default::default() }), drone::MotorCharacteristics { max_thrust: config.max_thrust, max_torque: config.max_torque, ..Default::default() }, config.mass, ); let result_file = format!("{}/{}_{}.csv", RESULTS_DIR, input_name, config_name); let mut sim = Simulation::new( drone, world, false, SimMode::Playback(inputs.clone(), 0.0), Some(result_file), config.drone_tick_rate, ); sim.run().await.unwrap(); } } println!("All simulations completed."); } async fn run_record(output: String) { println!("Recording inputs to {}", output); let tickrate = 60000.0; let mut world = World::new(tickrate); let drone = drone::Drone::new( &mut world, Box::new(drone::pidcontroller::PIDController { ..Default::default() }), drone::MotorCharacteristics { max_thrust: 2.6, max_torque: 0.5, ..Default::default() }, 0.350, ); let mut sim = Simulation::new( drone, world, true, SimMode::Record(InputRecording::default(), output), None, 600, ); sim.run().await.unwrap(); }