2025-12-14 22:04:04 +00:00
|
|
|
use macroquad::prelude as mq;
|
2026-03-11 12:50:50 +00:00
|
|
|
use nalgebra::{self as na};
|
2025-12-14 22:04:04 +00:00
|
|
|
use rapier3d::prelude as rp;
|
|
|
|
|
use std::error::Error;
|
|
|
|
|
|
|
|
|
|
use crate::{
|
2026-03-15 23:44:21 +00:00
|
|
|
controller::{
|
|
|
|
|
self,
|
|
|
|
|
input::{Input, InputRecording, ModeInput},
|
|
|
|
|
},
|
2026-03-11 12:50:50 +00:00
|
|
|
drone::Drone,
|
2025-12-14 22:04:04 +00:00
|
|
|
engine::World,
|
2026-03-02 15:39:21 +00:00
|
|
|
logger::{Logger, SimLogRow},
|
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 Simulation {
|
|
|
|
|
pub drone: Drone,
|
|
|
|
|
pub world: World,
|
|
|
|
|
pub mode: SimMode,
|
2026-02-06 13:59:27 +00:00
|
|
|
|
2026-03-02 15:39:21 +00:00
|
|
|
logger: Option<Logger>,
|
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 {
|
2026-03-02 15:39:21 +00:00
|
|
|
Some(path) => Some(Logger::new(path).unwrap()),
|
2026-02-06 13:59:27 +00:00
|
|
|
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>> {
|
2026-02-23 08:23:52 +00:00
|
|
|
let current_input: Input;
|
2026-02-06 13:59:27 +00:00
|
|
|
let current_time = self.world.get_time() as f32;
|
|
|
|
|
|
|
|
|
|
match &mut self.mode {
|
|
|
|
|
SimMode::Record(recording, _) => {
|
2026-03-15 23:44:21 +00:00
|
|
|
current_input = recording.add_input(
|
|
|
|
|
current_time,
|
|
|
|
|
Input {
|
|
|
|
|
joystick: controller::input_from_keyboard(),
|
|
|
|
|
mode: ModeInput::Acro,
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
2026-02-06 13:59:27 +00:00
|
|
|
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-23 08:23:52 +00:00
|
|
|
if recording.has_ended(playback_time) {
|
2026-02-06 13:59:27 +00:00
|
|
|
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-03-11 12:50:50 +00:00
|
|
|
.downcast_mut::<crate::controller::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-03-11 11:39:58 +00:00
|
|
|
/* Logging */
|
2026-02-06 13:59:27 +00:00
|
|
|
|
|
|
|
|
let applied_motor_diff: na::Vector3<f32> = na::vector![
|
2026-03-02 18:40:33 +00:00
|
|
|
(self.drone.current_throttles[0] + self.drone.current_throttles[3]
|
2026-02-06 13:59:27 +00:00
|
|
|
- self.drone.current_throttles[1]
|
2026-03-02 18:40:33 +00:00
|
|
|
- self.drone.current_throttles[2]),
|
2026-02-06 13:59:27 +00:00
|
|
|
(self.drone.current_throttles[2] + self.drone.current_throttles[3]
|
|
|
|
|
- self.drone.current_throttles[0]
|
2026-03-02 18:40:33 +00:00
|
|
|
- self.drone.current_throttles[1]),
|
|
|
|
|
(self.drone.current_throttles[0] + self.drone.current_throttles[2]
|
|
|
|
|
- self.drone.current_throttles[1]
|
|
|
|
|
- self.drone.current_throttles[3])
|
2026-02-06 13:59:27 +00:00
|
|
|
] / 2.0;
|
|
|
|
|
|
2026-03-11 11:39:58 +00:00
|
|
|
if let Some(cont) = self
|
|
|
|
|
.drone
|
|
|
|
|
.controller
|
|
|
|
|
.as_mut_any()
|
2026-03-11 12:50:50 +00:00
|
|
|
.downcast_mut::<crate::controller::stacked::StackedController>()
|
2026-03-11 11:39:58 +00:00
|
|
|
{
|
|
|
|
|
let target_mot = cont.rate_rt.last_output.unwrap_or_default().0;
|
|
|
|
|
|
|
|
|
|
let target_angular_vel = if current_input.mode != ModeInput::Acro {
|
|
|
|
|
cont.rotation_rt.last_output.unwrap_or_default().0
|
|
|
|
|
} else {
|
|
|
|
|
na::vector![
|
|
|
|
|
current_input.joystick.roll_input,
|
|
|
|
|
current_input.joystick.pitch_input,
|
|
|
|
|
current_input.joystick.yaw_input,
|
|
|
|
|
] * 3.14
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let target_rot = if current_input.mode != ModeInput::Rotation {
|
|
|
|
|
cont.linaccel_rt.last_output.unwrap_or_default().0 .0
|
|
|
|
|
} else {
|
|
|
|
|
na::vector![
|
|
|
|
|
current_input.rotation.roll,
|
|
|
|
|
current_input.rotation.pitch,
|
|
|
|
|
current_input.rotation.yaw,
|
|
|
|
|
] * 3.14
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let accel_target = na::vector![
|
|
|
|
|
current_input.acceleration.x,
|
|
|
|
|
current_input.acceleration.y,
|
|
|
|
|
current_input.acceleration.z
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let accel = self.drone.get_accel();
|
|
|
|
|
|
|
|
|
|
if let Some(logger) = &mut self.logger {
|
|
|
|
|
logger
|
|
|
|
|
.log(&SimLogRow {
|
|
|
|
|
time: self.world.get_time(),
|
|
|
|
|
linvel_current: (*self.drone.get_rb(&self.world).linvel()).into(),
|
|
|
|
|
accel_current: accel.into(),
|
|
|
|
|
accel_target: accel_target.into(),
|
|
|
|
|
angvel_target: target_angular_vel.into(),
|
|
|
|
|
angvel_current: self
|
|
|
|
|
.drone
|
|
|
|
|
.get_rot(&self.world)
|
|
|
|
|
.inverse()
|
|
|
|
|
.transform_vector(&self.drone.get_angvel(&self.world))
|
|
|
|
|
.into(),
|
|
|
|
|
rot_current: self.drone.get_rot(&self.world).scaled_axis().into(),
|
|
|
|
|
rot_target: na::UnitQuaternion::from_scaled_axis(target_rot)
|
|
|
|
|
.scaled_axis()
|
|
|
|
|
.into(),
|
|
|
|
|
mot_current: applied_motor_diff.into(),
|
|
|
|
|
mot_target: target_mot.into(),
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
}
|
2026-02-06 13:59:27 +00:00
|
|
|
}
|
2026-03-11 11:39:58 +00:00
|
|
|
|
2026-02-06 13:59:27 +00:00
|
|
|
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 {
|
2026-02-23 08:23:52 +00:00
|
|
|
recording.save_to_file(dest)?;
|
2025-12-14 22:04:04 +00:00
|
|
|
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(())
|
|
|
|
|
}
|
|
|
|
|
}
|