2025-12-14 22:04:04 +00:00
|
|
|
use macroquad::prelude as mq;
|
|
|
|
|
use nalgebra as na;
|
|
|
|
|
use rapier3d::prelude as rp;
|
|
|
|
|
use std::error::Error;
|
|
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
|
drone::{
|
|
|
|
|
controller::DroneController,
|
|
|
|
|
input::{InputRecording, JoystickInput},
|
|
|
|
|
Drone,
|
|
|
|
|
},
|
|
|
|
|
engine::World,
|
2026-02-06 13:59:27 +00:00
|
|
|
logger::CsvLogger,
|
2025-12-14 22:04:04 +00:00
|
|
|
rendering::Renderer,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub enum SimMode {
|
|
|
|
|
Record(InputRecording, String),
|
|
|
|
|
Playback(InputRecording, f32),
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-06 13:59:27 +00:00
|
|
|
enum StepOutcome {
|
|
|
|
|
Continue,
|
|
|
|
|
Exit,
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-14 22:04:04 +00:00
|
|
|
pub struct DataResultRecord {
|
|
|
|
|
pub time: f32,
|
|
|
|
|
pub current_angular_velocity: na::Vector3<f32>,
|
|
|
|
|
pub target_angular_velocity: na::Vector3<f32>,
|
|
|
|
|
pub applied_motor_offset: na::Vector3<f32>,
|
|
|
|
|
pub desired_motor_offset: na::Vector3<f32>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct Simulation {
|
|
|
|
|
pub drone: Drone,
|
|
|
|
|
pub world: World,
|
|
|
|
|
pub mode: SimMode,
|
2026-02-06 13:59:27 +00:00
|
|
|
|
|
|
|
|
logger: Option<CsvLogger>,
|
2025-12-14 22:04:04 +00:00
|
|
|
drone_tick_rate: u64,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Simulation {
|
|
|
|
|
pub fn new(
|
|
|
|
|
drone: Drone,
|
|
|
|
|
world: World,
|
|
|
|
|
mode: SimMode,
|
|
|
|
|
results_file: Option<String>,
|
|
|
|
|
drone_tick_rate: u64,
|
|
|
|
|
) -> Self {
|
2026-02-06 13:59:27 +00:00
|
|
|
let logger = match &results_file {
|
|
|
|
|
Some(path) => Some(CsvLogger::new(path).unwrap()),
|
|
|
|
|
None => None,
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-14 22:04:04 +00:00
|
|
|
let mut s = Self {
|
|
|
|
|
drone,
|
|
|
|
|
world,
|
|
|
|
|
mode,
|
2026-02-06 13:59:27 +00:00
|
|
|
logger,
|
2025-12-14 22:04:04 +00:00
|
|
|
drone_tick_rate,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
s.world.register_free_collider(
|
|
|
|
|
rp::ColliderBuilder::cuboid(30.0, 1.0, 30.0)
|
|
|
|
|
.translation(na::vector![0.0, -2.0, 0.0])
|
|
|
|
|
.restitution(0.5)
|
|
|
|
|
.sensor(true)
|
|
|
|
|
.build(),
|
|
|
|
|
None,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-04 17:54:41 +00:00
|
|
|
pub async fn run_and_render(&mut self) -> Result<(), Box<dyn Error>> {
|
|
|
|
|
let mut renderer: Renderer = Renderer::new(Box::new(crate::camera::AttachedCamera::new(
|
|
|
|
|
self.drone.rb_handle,
|
|
|
|
|
na::vector![1.0, 0.0, 0.0],
|
|
|
|
|
na::vector![0.5, 0.0, 0.0],
|
|
|
|
|
)));
|
|
|
|
|
|
|
|
|
|
renderer.light.set_location(
|
|
|
|
|
na::vector![70.0, 150.0, -90.0].into(),
|
|
|
|
|
na::vector![-0.4, -0.7, 0.6].into(),
|
|
|
|
|
);
|
|
|
|
|
renderer.update_light(&self.world);
|
|
|
|
|
|
2025-12-14 22:04:04 +00:00
|
|
|
loop {
|
2026-02-06 13:59:27 +00:00
|
|
|
match self.step()? {
|
|
|
|
|
StepOutcome::Continue => {
|
|
|
|
|
if self.world.tick
|
|
|
|
|
% ((self.world.integration_parameters.inv_dt() / 60.0) as u64)
|
|
|
|
|
== 0
|
|
|
|
|
{
|
|
|
|
|
renderer.update_camera(&self.world);
|
|
|
|
|
renderer.draw(&mut self.world);
|
|
|
|
|
mq::next_frame().await;
|
2025-12-14 22:04:04 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-06 13:59:27 +00:00
|
|
|
StepOutcome::Exit => {
|
|
|
|
|
self.shutdown()?;
|
|
|
|
|
return Ok(());
|
2025-12-14 22:04:04 +00:00
|
|
|
}
|
2026-02-04 17:54:41 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn run(&mut self) -> Result<(), Box<dyn Error>> {
|
|
|
|
|
loop {
|
2026-02-06 13:59:27 +00:00
|
|
|
match self.step()? {
|
|
|
|
|
StepOutcome::Continue => {}
|
|
|
|
|
StepOutcome::Exit => {
|
|
|
|
|
self.shutdown()?;
|
|
|
|
|
return Ok(());
|
2026-02-04 17:54:41 +00:00
|
|
|
}
|
2026-02-06 13:59:27 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-04 17:54:41 +00:00
|
|
|
|
2026-02-06 13:59:27 +00:00
|
|
|
fn step(&mut self) -> Result<StepOutcome, Box<dyn Error>> {
|
|
|
|
|
let current_input: JoystickInput;
|
|
|
|
|
let current_time = self.world.get_time() as f32;
|
|
|
|
|
|
|
|
|
|
match &mut self.mode {
|
|
|
|
|
SimMode::Record(recording, _) => {
|
|
|
|
|
current_input = recording.add_input_from_keyboard(current_time);
|
|
|
|
|
if mq::is_key_pressed(mq::KeyCode::Q) {
|
|
|
|
|
return Ok(StepOutcome::Exit);
|
2026-02-04 17:54:41 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-06 13:59:27 +00:00
|
|
|
SimMode::Playback(recording, start_time) => {
|
|
|
|
|
let playback_time = current_time - *start_time;
|
|
|
|
|
current_input = recording.get_input(playback_time);
|
2026-02-04 17:54:41 +00:00
|
|
|
|
2026-02-06 13:59:27 +00:00
|
|
|
if recording.ended(playback_time) {
|
|
|
|
|
println!("Playback ended.");
|
|
|
|
|
return Ok(StepOutcome::Exit);
|
2025-12-14 22:04:04 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-06 13:59:27 +00:00
|
|
|
}
|
2026-02-04 17:54:41 +00:00
|
|
|
|
2026-02-06 13:59:27 +00:00
|
|
|
// --- Physics ---
|
|
|
|
|
self.world.step();
|
|
|
|
|
if self.world.tick
|
|
|
|
|
% ((self.world.integration_parameters.inv_dt() / self.drone_tick_rate as f32) as u64)
|
|
|
|
|
== 0
|
|
|
|
|
{
|
2026-02-04 17:54:41 +00:00
|
|
|
if let Some(cont) = self
|
|
|
|
|
.drone
|
|
|
|
|
.controller
|
|
|
|
|
.as_mut_any()
|
2026-02-06 13:59:27 +00:00
|
|
|
.downcast_mut::<crate::drone::stacked::StackedController>()
|
2026-02-04 17:54:41 +00:00
|
|
|
{
|
2026-02-06 13:59:27 +00:00
|
|
|
cont.set_input(current_input);
|
2026-02-04 17:54:41 +00:00
|
|
|
}
|
2026-02-06 13:59:27 +00:00
|
|
|
self.drone.process_controller_tick(&mut self.world);
|
2025-12-14 22:04:04 +00:00
|
|
|
}
|
2026-02-06 13:59:27 +00:00
|
|
|
self.drone.process_tick(&mut self.world);
|
|
|
|
|
|
2026-02-06 17:11:04 +00:00
|
|
|
let target_angular_vel: na::Vector3<f32> = na::vector![
|
2026-02-06 13:59:27 +00:00
|
|
|
current_input.roll_input,
|
|
|
|
|
current_input.yaw_input,
|
|
|
|
|
current_input.pitch_input,
|
|
|
|
|
] * 3.14;
|
|
|
|
|
|
|
|
|
|
let applied_motor_diff: na::Vector3<f32> = na::vector![
|
|
|
|
|
(self.drone.current_throttles[1] + self.drone.current_throttles[2]
|
|
|
|
|
- self.drone.current_throttles[0]
|
|
|
|
|
- self.drone.current_throttles[3]),
|
|
|
|
|
(self.drone.current_throttles[0] + self.drone.current_throttles[2]
|
|
|
|
|
- self.drone.current_throttles[1]
|
|
|
|
|
- self.drone.current_throttles[3]),
|
|
|
|
|
(self.drone.current_throttles[2] + self.drone.current_throttles[3]
|
|
|
|
|
- self.drone.current_throttles[0]
|
|
|
|
|
- self.drone.current_throttles[1])
|
|
|
|
|
] / 2.0;
|
|
|
|
|
|
|
|
|
|
if let Some(logger) = &mut self.logger {
|
|
|
|
|
logger
|
|
|
|
|
.log(&crate::logger::SimLogRow::from_data(
|
|
|
|
|
self.world.get_time(),
|
|
|
|
|
target_angular_vel,
|
|
|
|
|
self.drone
|
|
|
|
|
.get_rot(&self.world)
|
|
|
|
|
.inverse()
|
|
|
|
|
.transform_vector(&self.drone.get_angvel(&self.world))
|
|
|
|
|
.into(),
|
|
|
|
|
applied_motor_diff,
|
2026-02-06 17:11:04 +00:00
|
|
|
na::vector![0.0, 0.0, 0.0],
|
2026-02-06 13:59:27 +00:00
|
|
|
))
|
|
|
|
|
.unwrap();
|
|
|
|
|
}
|
|
|
|
|
Ok(StepOutcome::Continue)
|
2025-12-14 22:04:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn shutdown(&mut self) -> Result<(), Box<dyn Error>> {
|
|
|
|
|
// Save input recording if needed
|
|
|
|
|
if let SimMode::Record(recording, dest) = &self.mode {
|
|
|
|
|
recording.save_inputs_to_csv(dest)?;
|
|
|
|
|
println!("Input recording saved to {}", dest);
|
|
|
|
|
}
|
2026-02-06 13:59:27 +00:00
|
|
|
if let Some(logger) = &mut self.logger {
|
|
|
|
|
logger.flush()?;
|
2025-12-14 22:04:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|