peng_quad/lib.rs
1//! # Peng - A Minimal Quadrotor Autonomy Framework
2//!
3//! A high-performance quadrotor autonomy framework written in Rust that provides
4//! real-time dynamics simulation, trajectory planning, and control with modern
5//! visualization capabilities.
6//!
7//! # Features
8//!
9//! ## Real-time Simulation
10//! - High-fidelity quadrotor dynamics with configurable parameters
11//! - IMU and depth sensor simulation
12//! - Optional RK4 integration for accurate dynamics
13//!
14//! ## Advanced Control
15//! - PID control for position and attitude with tunable gains
16//! - Integral windup prevention
17//! - Support for different control frequencies
18//!
19//! ## Rich Trajectory Planning
20//! - Minimum jerk line trajectory planner
21//! - Lissajous curve planner
22//! - Circular trajectory planner
23//! - Obstacle avoidance planner
24//! - Waypoint navigation planner
25//! - Landing planner
26//!
27//! ## Visualization & Debug
28//! - Real-time 3D visualization via rerun.io
29//! - Depth map rendering
30//! - State telemetry logging
31//! - Configurable logging frequencies
32//!
33//! ## Performance
34//! - Memory-safe and Efficient Rust implementation
35//! - Multi-threaded depth rendering
36//!
37//! ## Example
38//! ```
39//! use nalgebra::Vector3;
40//! use peng_quad::{Quadrotor, SimulationError};
41//! let (time_step, mass, gravity, drag_coefficient) = (0.01, 1.3, 9.81, 0.01);
42//! let inertia_matrix = [0.0347563, 0.0, 0.0, 0.0, 0.0458929, 0.0, 0.0, 0.0, 0.0977];
43//! let quadrotor = Quadrotor::new(time_step, mass, gravity, drag_coefficient, inertia_matrix);
44//! ```
45use rand::{RngExt, SeedableRng};
46use rayon::prelude::*;
47pub mod config;
48use nalgebra::{
49 DMatrix, DVector, Matrix3, Quaternion, Rotation3, SMatrix, UnitQuaternion, Vector3,
50};
51use osqp::{CscMatrix, Problem, Settings};
52use rand_chacha::ChaCha8Rng;
53use rand_distr::{Distribution, Normal};
54use std::f32::consts::PI;
55use std::ops::AddAssign;
56#[derive(thiserror::Error, Debug)]
57/// Represents errors that can occur during simulation
58/// # Example
59/// ```
60/// use peng_quad::SimulationError;
61/// let error = SimulationError::NalgebraError("Matrix inversion failed".to_string());
62/// ```
63pub enum SimulationError {
64 /// Error related to Rerun visualization
65 #[error("Rerun error: {0}")]
66 RerunError(#[from] rerun::RecordingStreamError),
67 /// Error related to Rerun spawn process
68 #[error("Rerun spawn error: {0}")]
69 RerunSpawnError(#[from] rerun::SpawnError),
70 /// Error related to Rerun logger setup
71 #[error("Rerun SetLogger error: {0}")]
72 SetLoggerError(#[from] rerun::external::log::SetLoggerError),
73 /// Error related to linear algebra operations
74 #[error("Nalgebra error: {0}")]
75 NalgebraError(String),
76 /// Error related to the OSQP solver
77 #[error("OSQP error: {0}")]
78 OSQPError(String),
79 /// Error related to normal distribution calculations
80 #[error("Normal error: {0}")]
81 NormalError(#[from] rand_distr::NormalError),
82 /// Other general errors
83 #[error("Other error: {0}")]
84 OtherError(String),
85}
86/// Represents a quadrotor with its physical properties and state
87/// # Example
88/// ```
89/// use nalgebra::Vector3;
90/// use peng_quad::Quadrotor;
91/// let (time_step, mass, gravity, drag_coefficient) = (0.01, 1.3, 9.81, 0.01);
92/// let inertia_matrix = [0.0347563, 0.0, 0.0, 0.0, 0.0458929, 0.0, 0.0, 0.0, 0.0977];
93/// let quadrotor = Quadrotor::new(time_step, mass, gravity, drag_coefficient, inertia_matrix);
94/// ```
95pub struct Quadrotor {
96 /// Current position of the quadrotor in 3D space
97 pub position: Vector3<f32>,
98 /// Current velocity of the quadrotor
99 pub velocity: Vector3<f32>,
100 /// Current acceleration of the quadrotor
101 pub acceleration: Vector3<f32>,
102 /// Current orientation of the quadrotor
103 pub orientation: UnitQuaternion<f32>,
104 /// Current angular velocity of the quadrotor
105 pub angular_velocity: Vector3<f32>,
106 /// Mass of the quadrotor in kg
107 pub mass: f32,
108 /// Gravitational acceleration in m/s^2
109 pub gravity: f32,
110 /// Simulation time step in seconds
111 pub time_step: f32,
112 /// Drag coefficient
113 pub drag_coefficient: f32,
114 /// Inertia matrix of the quadrotor
115 pub inertia_matrix: Matrix3<f32>,
116 /// Inverse of the inertia matrix
117 pub inertia_matrix_inv: Matrix3<f32>,
118}
119/// Implementation of the Quadrotor struct
120impl Quadrotor {
121 /// Creates a new Quadrotor with default parameters
122 /// # Arguments
123 /// * `time_step` - The simulation time step in seconds
124 /// * `mass` - The mass of the quadrotor in kg
125 /// * `gravity` - The gravitational acceleration in m/s^2
126 /// * `drag_coefficient` - The drag coefficient
127 /// * `inertia_matrix` - The inertia matrix of the quadrotor
128 /// # Returns
129 /// * A new Quadrotor instance
130 /// # Errors
131 /// * Returns a SimulationError if the inertia matrix cannot be inverted
132 /// # Example
133 /// ```
134 /// use nalgebra::Vector3;
135 /// use peng_quad::Quadrotor;
136 ///
137 /// let (time_step, mass, gravity, drag_coefficient) = (0.01, 1.3, 9.81, 0.01);
138 /// let inertia_matrix = [0.0347563, 0.0, 0.0, 0.0, 0.0458929, 0.0, 0.0, 0.0, 0.0977];
139 /// let quadrotor = Quadrotor::new(time_step, mass, gravity, drag_coefficient, inertia_matrix);
140 /// ```
141 pub fn new(
142 time_step: f32,
143 mass: f32,
144 gravity: f32,
145 drag_coefficient: f32,
146 inertia_matrix: [f32; 9],
147 ) -> Result<Self, SimulationError> {
148 let inertia_matrix = Matrix3::from_row_slice(&inertia_matrix);
149 let inertia_matrix_inv =
150 inertia_matrix
151 .try_inverse()
152 .ok_or(SimulationError::NalgebraError(
153 "Failed to invert inertia matrix".to_string(),
154 ))?;
155 Ok(Self {
156 position: Vector3::zeros(),
157 velocity: Vector3::zeros(),
158 acceleration: Vector3::zeros(),
159 orientation: UnitQuaternion::identity(),
160 angular_velocity: Vector3::zeros(),
161 mass,
162 gravity,
163 time_step,
164 drag_coefficient,
165 inertia_matrix,
166 inertia_matrix_inv,
167 })
168 }
169 /// Updates the quadrotor's dynamics with control inputs
170 /// # Arguments
171 /// * `control_thrust` - The total thrust force applied to the quadrotor
172 /// * `control_torque` - The 3D torque vector applied to the quadrotor
173 /// # Example
174 /// ```
175 /// use nalgebra::Vector3;
176 /// use peng_quad::Quadrotor;
177 ///
178 /// let (time_step, mass, gravity, drag_coefficient) = (0.01, 1.3, 9.81, 0.01);
179 /// let inertia_matrix = [0.0347563, 0.0, 0.0, 0.0, 0.0458929, 0.0, 0.0, 0.0, 0.0977];
180 /// let mut quadrotor = Quadrotor::new(time_step, mass, gravity, drag_coefficient, inertia_matrix).unwrap();
181 /// let control_thrust = mass * gravity;
182 /// let control_torque = Vector3::new(0.0, 0.0, 0.0);
183 /// quadrotor.update_dynamics_with_controls_euler(control_thrust, &control_torque);
184 /// ```
185 pub fn update_dynamics_with_controls_euler(
186 &mut self,
187 control_thrust: f32,
188 control_torque: &Vector3<f32>,
189 ) {
190 let gravity_force = Vector3::new(0.0, 0.0, -self.mass * self.gravity);
191 let drag_force = -self.drag_coefficient * self.velocity.norm() * self.velocity;
192 let thrust_world = self.orientation * Vector3::new(0.0, 0.0, control_thrust);
193 self.acceleration = (thrust_world + gravity_force + drag_force) / self.mass;
194 self.velocity += self.acceleration * self.time_step;
195 self.position += self.velocity * self.time_step;
196 let inertia_angular_velocity = self.inertia_matrix * self.angular_velocity;
197 let gyroscopic_torque = self.angular_velocity.cross(&inertia_angular_velocity);
198 let angular_acceleration = self.inertia_matrix_inv * (control_torque - gyroscopic_torque);
199 self.angular_velocity += angular_acceleration * self.time_step;
200 self.orientation *=
201 UnitQuaternion::from_scaled_axis(self.angular_velocity * self.time_step);
202 }
203 /// Updates the quadrotor's dynamics with control inputs using the Runge-Kutta 4th order method
204 /// # Arguments
205 /// * `control_thrust` - The total thrust force applied to the quadrotor
206 /// * `control_torque` - The 3D torque vector applied to the quadrotor
207 /// # Example
208 /// ```
209 /// use nalgebra::Vector3;
210 /// use peng_quad::Quadrotor;
211 /// let (time_step, mass, gravity, drag_coefficient) = (0.01, 1.3, 9.81, 0.01);
212 /// let inertia_matrix = [0.0347563, 0.0, 0.0, 0.0, 0.0458929, 0.0, 0.0, 0.0, 0.0977];
213 /// let mut quadrotor = Quadrotor::new(time_step, mass, gravity, drag_coefficient, inertia_matrix).unwrap();
214 /// let control_thrust = mass * gravity;
215 /// let control_torque = Vector3::new(0.0, 0.0, 0.0);
216 /// quadrotor.update_dynamics_with_controls_rk4(control_thrust, &control_torque);
217 /// ```
218 pub fn update_dynamics_with_controls_rk4(
219 &mut self,
220 control_thrust: f32,
221 control_torque: &Vector3<f32>,
222 ) {
223 let h = self.time_step;
224 let state = self.get_state();
225
226 let k1 = self.state_derivative(&state, control_thrust, control_torque);
227 let mut temp_state = [0.0; 13];
228 for i in 0..13 {
229 temp_state[i] = state[i] + 0.5 * h * k1[i];
230 }
231 let k2 = self.state_derivative(&temp_state, control_thrust, control_torque);
232
233 for i in 0..13 {
234 temp_state[i] = state[i] + 0.5 * h * k2[i];
235 }
236 let k3 = self.state_derivative(&temp_state, control_thrust, control_torque);
237
238 for i in 0..13 {
239 temp_state[i] = state[i] + h * k3[i];
240 }
241 let k4 = self.state_derivative(&temp_state, control_thrust, control_torque);
242
243 let mut new_state = [0.0; 13];
244 for i in 0..13 {
245 new_state[i] = state[i] + (h / 6.0) * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]);
246 }
247
248 let mut q = Quaternion::new(new_state[9], new_state[6], new_state[7], new_state[8]);
249 q = q.normalize();
250 new_state[6..10].copy_from_slice(q.coords.as_slice());
251
252 self.set_state(&new_state);
253 }
254 /// Returns the state derivative of the quadrotor
255 /// # Arguments
256 /// * `state` - The state of the quadrotor
257 /// # Example
258 /// ```
259 /// use nalgebra::Vector3;
260 /// use peng_quad::Quadrotor;
261 /// use nalgebra::UnitQuaternion;
262 /// let (time_step, mass, gravity, drag_coefficient) = (0.01, 1.3, 9.81, 0.01);
263 /// let inertia_matrix = [0.0347563, 0.0, 0.0, 0.0, 0.0458929, 0.0, 0.0, 0.0, 0.0977];
264 /// let quadrotor = Quadrotor::new(time_step, mass, gravity, drag_coefficient, inertia_matrix).unwrap();
265 /// let state = quadrotor.get_state();
266 /// ```
267 pub fn get_state(&self) -> [f32; 13] {
268 let mut state = [0.0; 13];
269 state[0..3].copy_from_slice(self.position.as_slice());
270 state[3..6].copy_from_slice(self.velocity.as_slice());
271 state[6..10].copy_from_slice(self.orientation.coords.as_slice());
272 state[10..13].copy_from_slice(self.angular_velocity.as_slice());
273 state
274 }
275 /// Sets the state of the quadrotor
276 /// # Arguments
277 /// * `state` - The state of the quadrotor
278 /// # Example
279 /// ```
280 /// use nalgebra::Vector3;
281 /// use peng_quad::Quadrotor;
282 /// use nalgebra::UnitQuaternion;
283 /// let (time_step, mass, gravity, drag_coefficient) = (0.01, 1.3, 9.81, 0.01);
284 /// let inertia_matrix = [0.0347563, 0.0, 0.0, 0.0, 0.0458929, 0.0, 0.0, 0.0, 0.0977];
285 /// let mut quadrotor = Quadrotor::new(time_step, mass, gravity, drag_coefficient, inertia_matrix).unwrap();
286 /// let state = [
287 /// 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
288 /// ];
289 /// quadrotor.set_state(&state);
290 /// ```
291 pub fn set_state(&mut self, state: &[f32; 13]) {
292 self.position = Vector3::from_column_slice(&state[0..3]);
293 self.velocity = Vector3::from_column_slice(&state[3..6]);
294 self.orientation = UnitQuaternion::from_quaternion(Quaternion::new(
295 state[9], state[6], state[7], state[8],
296 ));
297 self.angular_velocity = Vector3::from_column_slice(&state[10..13]);
298 }
299 /// Calculates the derivative of the state of the quadrotor
300 /// # Arguments
301 /// * `state` - The current state of the quadrotor
302 /// * `control_thrust` - The thrust applied to the quadrotor
303 /// * `control_torque` - The torque applied to the quadrotor
304 /// # Returns
305 /// The derivative of the state
306 /// # Example
307 /// ```
308 /// use nalgebra::Vector3;
309 /// use peng_quad::Quadrotor;
310 /// use nalgebra::UnitQuaternion;
311 /// let (time_step, mass, gravity, drag_coefficient) = (0.01, 1.3, 9.81, 0.01);
312 /// let inertia_matrix = [0.0347563, 0.0, 0.0, 0.0, 0.0458929, 0.0, 0.0, 0.0, 0.0977];
313 /// let mut quadrotor = Quadrotor::new(time_step, mass, gravity, drag_coefficient, inertia_matrix).unwrap();
314 /// let state = [
315 /// 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
316 /// ];
317 /// let control_thrust = 0.0;
318 /// let control_torque = Vector3::new(0.0, 0.0, 0.0);
319 /// let derivative = quadrotor.state_derivative(&state, control_thrust, &control_torque);
320 /// ```
321 pub fn state_derivative(
322 &mut self,
323 state: &[f32],
324 control_thrust: f32,
325 control_torque: &Vector3<f32>,
326 ) -> [f32; 13] {
327 let velocity = Vector3::from_column_slice(&state[3..6]);
328 let orientation = UnitQuaternion::from_quaternion(Quaternion::new(
329 state[9], state[6], state[7], state[8],
330 ));
331 // Quaternion deriviative
332 let omega_quat = Quaternion::new(0.0, state[10], state[11], state[12]);
333 let q_dot = orientation.into_inner() * omega_quat * 0.5;
334
335 let angular_velocity = Vector3::from_column_slice(&state[10..13]);
336
337 let gravity_force = Vector3::new(0.0, 0.0, -self.mass * self.gravity);
338 let drag_force = -self.drag_coefficient * velocity.norm() * velocity;
339 let thrust_world = orientation * Vector3::new(0.0, 0.0, control_thrust);
340 self.acceleration = (thrust_world + gravity_force + drag_force) / self.mass;
341
342 let inertia_angular_velocity = self.inertia_matrix * angular_velocity;
343 let gyroscopic_torque = angular_velocity.cross(&inertia_angular_velocity);
344 let angular_acceleration = self.inertia_matrix_inv * (control_torque - gyroscopic_torque);
345
346 let mut derivative = [0.0; 13];
347 derivative[0..3].copy_from_slice(velocity.as_slice());
348 derivative[3..6].copy_from_slice(self.acceleration.as_slice());
349 derivative[6..10].copy_from_slice(q_dot.coords.as_slice());
350 derivative[10..13].copy_from_slice(angular_acceleration.as_slice());
351 derivative
352 }
353 /// Simulates IMU readings
354 /// # Returns
355 /// * A tuple containing the true acceleration and angular velocity of the quadrotor
356 /// # Errors
357 /// * Returns a SimulationError if the IMU readings cannot be calculated
358 /// # Example
359 /// ```
360 /// use nalgebra::Vector3;
361 /// use peng_quad::Quadrotor;
362 ///
363 /// let (time_step, mass, gravity, drag_coefficient) = (0.01, 1.3, 9.81, 0.01);
364 /// let inertia_matrix = [0.0347563, 0.0, 0.0, 0.0, 0.0458929, 0.0, 0.0, 0.0, 0.0977];
365 /// let quadrotor = Quadrotor::new(time_step, mass, gravity, drag_coefficient, inertia_matrix).unwrap();
366 /// let (true_acceleration, true_angular_velocity) = quadrotor.read_imu().unwrap();
367 /// ```
368 pub fn read_imu(&self) -> Result<(Vector3<f32>, Vector3<f32>), SimulationError> {
369 Ok((self.acceleration, self.angular_velocity))
370 }
371}
372/// Represents an Inertial Measurement Unit (IMU) with bias and noise characteristics
373/// # Example
374/// ```
375/// use nalgebra::Vector3;
376/// use peng_quad::Imu;
377/// let accel_noise_std = 0.0003;
378/// let gyro_noise_std = 0.02;
379/// let accel_bias_std = 0.0001;
380/// let gyro_bias_std = 0.001;
381/// let imu = Imu::new(accel_noise_std, gyro_noise_std, accel_bias_std, gyro_bias_std);
382/// ```
383pub struct Imu {
384 /// Accelerometer bias
385 pub accel_bias: Vector3<f32>,
386 /// Gyroscope bias
387 pub gyro_bias: Vector3<f32>,
388 /// Standard deviation of accelerometer noise
389 pub accel_noise_std: f32,
390 /// Standard deviation of gyroscope noise
391 pub gyro_noise_std: f32,
392 /// Standard deviation of accelerometer bias drift
393 pub accel_bias_std: f32,
394 /// Standard deviation of gyroscope bias drift
395 pub gyro_bias_std: f32,
396 /// Accelerometer noise distribution
397 accel_noise: Normal<f32>,
398 /// Gyroscope noise distribution
399 gyro_noise: Normal<f32>,
400 /// Accelerometer bias drift distribution
401 accel_bias_drift: Normal<f32>,
402 /// Gyroscope bias drift distribution
403 gyro_bias_drift: Normal<f32>,
404 /// Random number generator
405 rng: ChaCha8Rng,
406}
407/// Implements the IMU
408impl Imu {
409 /// Creates a new IMU with default parameters
410 /// # Arguments
411 /// * `accel_noise_std` - Standard deviation of accelerometer noise
412 /// * `gyro_noise_std` - Standard deviation of gyroscope noise
413 /// * `accel_bias_std` - Standard deviation of accelerometer bias drift
414 /// * `gyro_bias_std` - Standard deviation of gyroscope bias drift
415 /// # Returns
416 /// * A new Imu instance
417 /// # Example
418 /// ```
419 /// use peng_quad::Imu;
420 ///
421 /// let imu = Imu::new(0.01, 0.01, 0.01, 0.01);
422 /// ```
423 pub fn new(
424 accel_noise_std: f32,
425 gyro_noise_std: f32,
426 accel_bias_std: f32,
427 gyro_bias_std: f32,
428 ) -> Result<Self, SimulationError> {
429 Ok(Self {
430 accel_bias: Vector3::zeros(),
431 gyro_bias: Vector3::zeros(),
432 accel_noise_std,
433 gyro_noise_std,
434 accel_bias_std,
435 gyro_bias_std,
436 accel_noise: Normal::new(0.0, accel_noise_std)?,
437 gyro_noise: Normal::new(0.0, gyro_noise_std)?,
438 accel_bias_drift: Normal::new(0.0, accel_bias_std)?,
439 gyro_bias_drift: Normal::new(0.0, gyro_bias_std)?,
440 rng: ChaCha8Rng::from_rng(&mut rand::rng()),
441 })
442 }
443 /// Updates the IMU biases over time
444 /// # Arguments
445 /// * `dt` - Time step for the update
446 /// # Errors
447 /// * Returns a SimulationError if the bias drift cannot be calculated
448 /// # Example
449 /// ```
450 /// use peng_quad::Imu;
451 ///
452 /// let mut imu = Imu::new(0.01, 0.01, 0.01, 0.01).unwrap();
453 /// imu.update(0.01).unwrap();
454 /// ```
455 pub fn update(&mut self, dt: f32) -> Result<(), SimulationError> {
456 let dt_sqrt = fast_sqrt(dt);
457 let accel_drift = self.accel_bias_drift.sample(&mut self.rng) * dt_sqrt;
458 let gyro_drift = self.gyro_bias_drift.sample(&mut self.rng) * dt_sqrt;
459 self.accel_bias += Vector3::from_iterator((0..3).map(|_| accel_drift));
460 self.gyro_bias += Vector3::from_iterator((0..3).map(|_| gyro_drift));
461 Ok(())
462 }
463 /// Simulates IMU readings with added bias and noise
464 ///
465 /// The added bias and noise are based on normal distributions
466 /// # Arguments
467 /// * `true_acceleration` - The true acceleration vector
468 /// * `true_angular_velocity` - The true angular velocity vector
469 /// # Returns
470 /// * A tuple containing the measured acceleration and angular velocity
471 /// # Errors
472 /// * Returns a SimulationError if the IMU readings cannot be calculated
473 /// # Example
474 /// ```
475 /// use nalgebra::Vector3;
476 /// use peng_quad::Imu;
477 ///
478 /// let mut imu = Imu::new(0.01, 0.01, 0.01, 0.01).unwrap();
479 /// let true_acceleration = Vector3::new(0.0, 0.0, 9.81);
480 /// let true_angular_velocity = Vector3::new(0.0, 0.0, 0.0);
481 /// let (measured_acceleration, measured_ang_velocity) = imu.read(true_acceleration, true_angular_velocity).unwrap();
482 /// ```
483 pub fn read(
484 &mut self,
485 true_acceleration: Vector3<f32>,
486 true_angular_velocity: Vector3<f32>,
487 ) -> Result<(Vector3<f32>, Vector3<f32>), SimulationError> {
488 let accel_noise_sample =
489 Vector3::from_iterator((0..3).map(|_| self.accel_noise.sample(&mut self.rng)));
490 let gyro_noise_sample =
491 Vector3::from_iterator((0..3).map(|_| self.gyro_noise.sample(&mut self.rng)));
492 let measured_acceleration = true_acceleration + self.accel_bias + accel_noise_sample;
493 let measured_ang_velocity = true_angular_velocity + self.gyro_bias + gyro_noise_sample;
494 Ok((measured_acceleration, measured_ang_velocity))
495 }
496}
497/// PID controller for quadrotor position and attitude control
498///
499/// The kpid_pos and kpid_att gains are following the format of
500/// proportional, derivative and integral gains
501/// # Example
502/// ```
503/// use nalgebra::Vector3;
504/// use peng_quad::PIDController;
505/// let kpid_pos = [
506/// [1.0, 1.0, 1.0],
507/// [0.1, 0.1, 0.1],
508/// [0.01, 0.01, 0.01],
509/// ];
510/// let kpid_att = [
511/// [1.0, 1.0, 1.0],
512/// [0.1, 0.1, 0.1],
513/// [0.01, 0.01, 0.01],
514/// ];
515/// let max_integral_pos = [1.0, 1.0, 1.0];
516/// let max_integral_att = [1.0, 1.0, 1.0];
517/// let mass = 1.0;
518/// let gravity = 9.81;
519/// let pid_controller = PIDController::new(kpid_pos, kpid_att, max_integral_pos, max_integral_att, mass, gravity);
520/// ```
521pub struct PIDController {
522 /// PID gain for position control including proportional, derivative, and integral gains
523 pub kpid_pos: [Vector3<f32>; 3],
524 /// PID gain for attitude control including proportional, derivative, and integral gains
525 pub kpid_att: [Vector3<f32>; 3],
526 /// Accumulated integral error for position
527 pub integral_pos_error: Vector3<f32>,
528 /// Accumulated integral error for attitude
529 pub integral_att_error: Vector3<f32>,
530 /// Maximum allowed integral error for position
531 pub max_integral_pos: Vector3<f32>,
532 /// Maximum allowed integral error for attitude
533 pub max_integral_att: Vector3<f32>,
534 /// Mass of the quadrotor
535 pub mass: f32,
536 /// Gravity constant
537 pub gravity: f32,
538}
539/// Implementation of PIDController
540impl PIDController {
541 /// Creates a new PIDController with default gains
542 /// gains are in the order of proportional, derivative, and integral
543 /// # Arguments
544 /// * `_kpid_pos` - PID gains for position control
545 /// * `_kpid_att` - PID gains for attitude control
546 /// * `_max_integral_pos` - Maximum allowed integral error for position
547 /// * `_max_integral_att` - Maximum allowed integral error for attitude
548 /// # Returns
549 /// * A new PIDController instance
550 /// # Example
551 /// ```
552 /// use nalgebra::Vector3;
553 /// use peng_quad::PIDController;
554 /// let kpid_pos = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
555 /// let kpid_att = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
556 /// let max_integral_pos = [1.0, 1.0, 1.0];
557 /// let max_integral_att = [1.0, 1.0, 1.0];
558 /// let mass = 1.0;
559 /// let gravity = 9.81;
560 /// let pid = PIDController::new(kpid_pos, kpid_att, max_integral_pos, max_integral_att, mass, gravity);
561 /// ```
562 pub fn new(
563 _kpid_pos: [[f32; 3]; 3],
564 _kpid_att: [[f32; 3]; 3],
565 _max_integral_pos: [f32; 3],
566 _max_integral_att: [f32; 3],
567 _mass: f32,
568 _gravity: f32,
569 ) -> Self {
570 Self {
571 kpid_pos: _kpid_pos.map(Vector3::from),
572 kpid_att: _kpid_att.map(Vector3::from),
573 integral_pos_error: Vector3::zeros(),
574 integral_att_error: Vector3::zeros(),
575 max_integral_pos: Vector3::from(_max_integral_pos),
576 max_integral_att: Vector3::from(_max_integral_att),
577 mass: _mass,
578 gravity: _gravity,
579 }
580 }
581 /// Computes attitude control torques
582 /// # Arguments
583 /// * `desired_orientation` - The desired orientation quaternion
584 /// * `current_orientation` - The current orientation quaternion
585 /// * `current_angular_velocity` - The current angular velocity
586 /// * `dt` - Time step
587 /// # Returns
588 /// * The computed attitude control torques
589 /// # Example
590 /// ```
591 /// use nalgebra::{UnitQuaternion, Vector3};
592 /// use peng_quad::PIDController;
593 ///
594 /// let kpid_pos = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
595 /// let kpid_att = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
596 /// let max_integral_pos = [1.0, 1.0, 1.0];
597 /// let max_integral_att = [1.0, 1.0, 1.0];
598 /// let mass = 1.0;
599 /// let gravity = 9.81;
600 /// let mut pid = PIDController::new(kpid_pos, kpid_att, max_integral_pos, max_integral_att, mass, gravity);
601 /// let desired_orientation = UnitQuaternion::identity();
602 /// let current_orientation = UnitQuaternion::identity();
603 /// let current_angular_velocity = Vector3::zeros();
604 /// let dt = 0.01;
605 /// let control_torques = pid.compute_attitude_control(&desired_orientation, ¤t_orientation, ¤t_angular_velocity, dt);
606 /// ```
607 pub fn compute_attitude_control(
608 &mut self,
609 desired_orientation: &UnitQuaternion<f32>,
610 current_orientation: &UnitQuaternion<f32>,
611 current_angular_velocity: &Vector3<f32>,
612 dt: f32,
613 ) -> Vector3<f32> {
614 let error_orientation = current_orientation.inverse() * desired_orientation;
615 let (roll_error, pitch_error, yaw_error) = error_orientation.euler_angles();
616 let error_angles = Vector3::new(roll_error, pitch_error, yaw_error);
617 self.integral_att_error += error_angles * dt;
618 self.integral_att_error = self
619 .integral_att_error
620 .zip_map(&self.max_integral_att, |int, max| int.clamp(-max, max));
621 let error_angular_velocity = -current_angular_velocity; // TODO: Add desired angular velocity
622 self.kpid_att[0].component_mul(&error_angles)
623 + self.kpid_att[1].component_mul(&error_angular_velocity)
624 + self.kpid_att[2].component_mul(&self.integral_att_error)
625 }
626 /// Computes position control thrust and desired orientation
627 /// # Arguments
628 /// * `desired_position` - The desired position
629 /// * `desired_velocity` - The desired velocity
630 /// * `desired_yaw` - The desired yaw angle
631 /// * `current_position` - The current position
632 /// * `current_velocity` - The current velocity
633 /// * `dt` - Time step
634 /// * `mass` - Mass of the quadrotor
635 /// * `gravity` - Gravitational acceleration
636 /// # Returns
637 /// * A tuple containing the computed thrust and desired orientation quaternion
638 /// # Example
639 /// ```
640 /// use nalgebra::{UnitQuaternion, Vector3};
641 /// use peng_quad::PIDController;
642 ///
643 /// let kpid_pos = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
644 /// let kpid_att = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
645 /// let max_integral_pos = [1.0, 1.0, 1.0];
646 /// let max_integral_att = [1.0, 1.0, 1.0];
647 /// let mass = 1.0;
648 /// let gravity = 9.81;
649 /// let mut pid = PIDController::new(kpid_pos, kpid_att, max_integral_pos, max_integral_att, mass, gravity);
650 /// let desired_position = Vector3::new(0.0, 0.0, 1.0);
651 /// let desired_velocity = Vector3::zeros();
652 /// let desired_yaw = 0.0;
653 /// let current_position = Vector3::zeros();
654 /// let current_velocity = Vector3::zeros();
655 /// let dt = 0.01;
656 /// let (thrust, desired_orientation) = pid.compute_position_control(&desired_position, &desired_velocity, desired_yaw, ¤t_position, ¤t_velocity, dt);
657 /// ```
658 pub fn compute_position_control(
659 &mut self,
660 desired_position: &Vector3<f32>,
661 desired_velocity: &Vector3<f32>,
662 desired_yaw: f32,
663 current_position: &Vector3<f32>,
664 current_velocity: &Vector3<f32>,
665 dt: f32,
666 ) -> (f32, UnitQuaternion<f32>) {
667 let error_position = desired_position - current_position;
668 let error_velocity = desired_velocity - current_velocity;
669 self.integral_pos_error += error_position * dt;
670 self.integral_pos_error = self
671 .integral_pos_error
672 .zip_map(&self.max_integral_pos, |int, max| int.clamp(-max, max));
673 let acceleration = self.kpid_pos[0].component_mul(&error_position)
674 + self.kpid_pos[1].component_mul(&error_velocity)
675 + self.kpid_pos[2].component_mul(&self.integral_pos_error);
676 let gravity_compensation = Vector3::new(0.0, 0.0, self.gravity);
677 let total_acceleration = acceleration + gravity_compensation;
678 let total_acc_norm = total_acceleration.norm();
679 let thrust = self.mass * total_acc_norm;
680 let desired_orientation = if total_acc_norm > 1e-6 {
681 let z_body = total_acceleration / total_acc_norm;
682 let yaw_rotation = UnitQuaternion::from_euler_angles(0.0, 0.0, desired_yaw);
683 let x_body_horizontal = yaw_rotation * Vector3::new(1.0, 0.0, 0.0);
684 let y_body = z_body.cross(&x_body_horizontal).normalize();
685 let x_body = y_body.cross(&z_body);
686 UnitQuaternion::from_rotation_matrix(&Rotation3::from_matrix_unchecked(
687 Matrix3::from_columns(&[x_body, y_body, z_body]),
688 ))
689 } else {
690 UnitQuaternion::from_euler_angles(0.0, 0.0, desired_yaw)
691 };
692 (thrust, desired_orientation)
693 }
694}
695/// Enum representing different types of trajectory planners
696/// # Example
697/// ```
698/// use peng_quad::PlannerType;
699/// use peng_quad::HoverPlanner;
700/// let hover_planner : PlannerType = PlannerType::Hover(HoverPlanner{
701/// target_position: nalgebra::Vector3::new(0.0, 0.0, 1.0),
702/// target_yaw: 0.0,
703/// });
704/// ```
705pub enum PlannerType {
706 /// Hover planner
707 Hover(HoverPlanner),
708 /// Minimum jerk line planner
709 MinimumJerkLine(MinimumJerkLinePlanner),
710 /// Minimum jerk circle planner
711 Lissajous(LissajousPlanner),
712 /// Minimum jerk circle planner
713 Circle(CirclePlanner),
714 /// Minimum jerk landing planner
715 Landing(LandingPlanner),
716 /// Obstacle avoidance planner
717 ObstacleAvoidance(ObstacleAvoidancePlanner),
718 /// Minimum snap waypoint planner
719 MinimumSnapWaypoint(MinimumSnapWaypointPlanner),
720 /// Quadratic Polynomial based waypoint planner
721 QPpolyTraj(QPpolyTrajPlanner),
722}
723/// Implementation of the planner type
724impl PlannerType {
725 /// Plans the trajectory based on the current planner type
726 /// # Arguments
727 /// * `current_position` - The current position of the quadrotor
728 /// * `current_velocity` - The current velocity of the quadrotor
729 /// * `time` - The current simulation time
730 /// # Returns
731 /// * A tuple containing the desired position, velocity, and yaw angle
732 /// # Example
733 /// ```
734 /// use nalgebra::Vector3;
735 /// use peng_quad::PlannerType;
736 /// use peng_quad::HoverPlanner;
737 /// let hover_planner = HoverPlanner {
738 /// target_position: Vector3::new(0.0, 0.0, 1.0),
739 /// target_yaw: 0.0
740 /// };
741 /// let hover_planner_type = PlannerType::Hover(hover_planner);
742 /// let (desired_position, desired_velocity, desired_yaw) = hover_planner_type.plan(Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 0.0), 0.0);
743 /// ```
744 pub fn plan(
745 &self,
746 current_position: Vector3<f32>,
747 current_velocity: Vector3<f32>,
748 time: f32,
749 ) -> (Vector3<f32>, Vector3<f32>, f32) {
750 match self {
751 PlannerType::Hover(p) => p.plan(current_position, current_velocity, time),
752 PlannerType::MinimumJerkLine(p) => p.plan(current_position, current_velocity, time),
753 PlannerType::Lissajous(p) => p.plan(current_position, current_velocity, time),
754 PlannerType::Circle(p) => p.plan(current_position, current_velocity, time),
755 PlannerType::Landing(p) => p.plan(current_position, current_velocity, time),
756 PlannerType::ObstacleAvoidance(p) => p.plan(current_position, current_velocity, time),
757 PlannerType::MinimumSnapWaypoint(p) => p.plan(current_position, current_velocity, time),
758 PlannerType::QPpolyTraj(p) => p.plan(current_position, current_velocity, time),
759 }
760 }
761 /// Checks if the current trajectory is finished
762 /// # Arguments
763 /// * `current_position` - The current position of the quadrotor
764 /// * `time` - The current simulation time
765 /// # Returns
766 /// * `true` if the trajectory is finished, `false` otherwise
767 /// # Example
768 /// ```
769 /// use nalgebra::Vector3;
770 /// use peng_quad::PlannerType;
771 /// use peng_quad::HoverPlanner;
772 /// use peng_quad::Planner;
773 /// let hover_planner = HoverPlanner{
774 /// target_position: Vector3::new(0.0, 0.0, 1.0),
775 /// target_yaw: 0.0,
776 /// };
777 /// let is_finished = hover_planner.is_finished(Vector3::new(0.0, 0.0, 0.0), 0.0);
778 /// ```
779 pub fn is_finished(
780 &self,
781 current_position: Vector3<f32>,
782 time: f32,
783 ) -> Result<bool, SimulationError> {
784 match self {
785 PlannerType::Hover(p) => p.is_finished(current_position, time),
786 PlannerType::MinimumJerkLine(p) => p.is_finished(current_position, time),
787 PlannerType::Lissajous(p) => p.is_finished(current_position, time),
788 PlannerType::Circle(p) => p.is_finished(current_position, time),
789 PlannerType::Landing(p) => p.is_finished(current_position, time),
790 PlannerType::ObstacleAvoidance(p) => p.is_finished(current_position, time),
791 PlannerType::MinimumSnapWaypoint(p) => p.is_finished(current_position, time),
792 PlannerType::QPpolyTraj(p) => p.is_finished(current_position, time),
793 }
794 }
795}
796/// Trait defining the interface for trajectory planners
797/// # Example
798/// ```
799/// use nalgebra::Vector3;
800/// use peng_quad::{Planner, SimulationError};
801/// struct TestPlanner;
802/// impl Planner for TestPlanner {
803/// fn plan(
804/// &self,
805/// current_position: Vector3<f32>,
806/// current_velocity: Vector3<f32>,
807/// time: f32,
808/// ) -> (Vector3<f32>, Vector3<f32>, f32) {
809/// (Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 0.0), 0.0)
810/// }
811/// fn is_finished(
812/// &self,
813/// current_position: Vector3<f32>,
814/// time: f32,
815/// ) -> Result<bool, SimulationError> {
816/// Ok(true)
817/// }
818/// }
819/// ```
820pub trait Planner {
821 /// Plans the trajectory based on the current state and time
822 /// # Arguments
823 /// * `current_position` - The current position of the quadrotor
824 /// * `current_velocity` - The current velocity of the quadrotor
825 /// * `time` - The current simulation time
826 /// # Returns
827 /// * A tuple containing the desired position, velocity, and yaw angle
828 /// # Example
829 /// ```
830 /// use nalgebra::Vector3;
831 /// use peng_quad::{Planner, SimulationError};
832 /// struct TestPlanner;
833 /// impl Planner for TestPlanner {
834 /// fn plan(
835 /// &self,
836 /// current_position: Vector3<f32>,
837 /// current_velocity: Vector3<f32>,
838 /// time: f32,
839 /// ) -> (Vector3<f32>, Vector3<f32>, f32) {
840 /// (Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 0.0), 0.0)
841 /// }
842 /// fn is_finished(
843 /// &self,
844 /// current_position: Vector3<f32>,
845 /// time: f32,
846 /// ) -> Result<bool, SimulationError> {
847 /// Ok(true)
848 /// }
849 /// }
850 /// ```
851 fn plan(
852 &self,
853 current_position: Vector3<f32>,
854 current_velocity: Vector3<f32>,
855 time: f32,
856 ) -> (Vector3<f32>, Vector3<f32>, f32);
857 /// Checks if the current trajectory is finished
858 /// # Arguments
859 /// * `current_position` - The current position of the quadrotor
860 /// * `time` - The current simulation time
861 /// # Returns
862 /// * `true` if the trajectory is finished, `false` otherwise
863 /// # Example
864 /// ```
865 /// use nalgebra::Vector3;
866 /// use peng_quad::{Planner, SimulationError};
867 /// struct TestPlanner;
868 /// impl Planner for TestPlanner {
869 /// fn plan(
870 /// &self,
871 /// current_position: Vector3<f32>,
872 /// current_velocity: Vector3<f32>,
873 /// time: f32,
874 /// ) -> (Vector3<f32>, Vector3<f32>, f32) {
875 /// (Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 0.0), 0.0)
876 /// }
877 /// fn is_finished(
878 /// &self,
879 /// current_position: Vector3<f32>,
880 /// time: f32,
881 /// ) -> Result<bool, SimulationError> {
882 /// Ok(true)
883 /// }
884 /// }
885 /// ```
886 fn is_finished(
887 &self,
888 current_position: Vector3<f32>,
889 time: f32,
890 ) -> Result<bool, SimulationError>;
891}
892/// Planner for hovering at a fixed position
893/// # Example
894/// ```
895/// use nalgebra::Vector3;
896/// use peng_quad::HoverPlanner;
897/// let hover_planner = HoverPlanner {
898/// target_position: Vector3::new(0.0, 0.0, 0.0),
899/// target_yaw: 0.0,
900/// };
901/// ```
902pub struct HoverPlanner {
903 /// Target position for hovering
904 pub target_position: Vector3<f32>,
905 /// Target yaw angle for hovering
906 pub target_yaw: f32,
907}
908/// Implementation of the `Planner` trait for the `HoverPlanner`
909impl Planner for HoverPlanner {
910 fn plan(
911 &self,
912 _current_position: Vector3<f32>,
913 _current_velocity: Vector3<f32>,
914 _time: f32,
915 ) -> (Vector3<f32>, Vector3<f32>, f32) {
916 (self.target_position, Vector3::zeros(), self.target_yaw)
917 }
918
919 fn is_finished(
920 &self,
921 _current_position: Vector3<f32>,
922 _time: f32,
923 ) -> Result<bool, SimulationError> {
924 Ok(false) // Hover planner never "finished"
925 }
926}
927/// Planner for minimum jerk trajectories along a straight line
928/// # Example
929/// ```
930/// use nalgebra::Vector3;
931/// use peng_quad::MinimumJerkLinePlanner;
932/// let minimum_jerk_line_planner = MinimumJerkLinePlanner {
933/// start_position: Vector3::new(0.0, 0.0, 0.0),
934/// end_position: Vector3::new(1.0, 1.0, 1.0),
935/// start_yaw: 0.0,
936/// end_yaw: 0.0,
937/// start_time: 0.0,
938/// duration: 1.0,
939/// };
940/// ```
941pub struct MinimumJerkLinePlanner {
942 /// Starting position of the trajectory
943 pub start_position: Vector3<f32>,
944 /// Ending position of the trajectory
945 pub end_position: Vector3<f32>,
946 /// Starting yaw angle
947 pub start_yaw: f32,
948 /// Ending yaw angle
949 pub end_yaw: f32,
950 /// Start time of the trajectory
951 pub start_time: f32,
952 /// Duration of the trajectory
953 pub duration: f32,
954}
955/// Implementation of the planner trait for minimum jerk line planner
956impl Planner for MinimumJerkLinePlanner {
957 fn plan(
958 &self,
959 _current_position: Vector3<f32>,
960 _current_velocity: Vector3<f32>,
961 time: f32,
962 ) -> (Vector3<f32>, Vector3<f32>, f32) {
963 let t = ((time - self.start_time) / self.duration).clamp(0.0, 1.0);
964 let t2 = t * t;
965 let t3 = t2 * t;
966 let t4 = t3 * t;
967 let s = 10.0 * t2 - 15.0 * t3 + 6.0 * t4;
968 let s_dot = (30.0 * t.powi(2) - 60.0 * t.powi(3) + 30.0 * t.powi(4)) / self.duration;
969 let position = self.start_position + (self.end_position - self.start_position) * s;
970 let velocity = (self.end_position - self.start_position) * s_dot;
971 let yaw = self.start_yaw + (self.end_yaw - self.start_yaw) * s;
972 (position, velocity, yaw)
973 }
974
975 fn is_finished(
976 &self,
977 _current_position: Vector3<f32>,
978 _time: f32,
979 ) -> Result<bool, SimulationError> {
980 Ok((_current_position - self.end_position).norm() < 0.01
981 && _time >= self.start_time + self.duration)
982 }
983}
984/// Planner for Lissajous curve trajectories
985/// # Example
986/// ```
987/// use nalgebra::Vector3;
988/// use peng_quad::LissajousPlanner;
989/// let lissajous_planner = LissajousPlanner {
990/// start_position: Vector3::new(0.0, 0.0, 0.0),
991/// center: Vector3::new(1.0, 1.0, 1.0),
992/// amplitude: Vector3::new(1.0, 1.0, 1.0),
993/// frequency: Vector3::new(1.0, 1.0, 1.0),
994/// phase: Vector3::new(0.0, 0.0, 0.0),
995/// start_time: 0.0,
996/// duration: 1.0,
997/// start_yaw: 0.0,
998/// end_yaw: 0.0,
999/// ramp_time: 0.1,
1000/// };
1001/// ```
1002pub struct LissajousPlanner {
1003 /// Starting position of the trajectory
1004 pub start_position: Vector3<f32>,
1005 /// Center of the Lissajous curve
1006 pub center: Vector3<f32>,
1007 /// Amplitude of the Lissajous curve
1008 pub amplitude: Vector3<f32>,
1009 /// Frequency of the Lissajous curve
1010 pub frequency: Vector3<f32>,
1011 /// Phase of the Lissajous curve
1012 pub phase: Vector3<f32>,
1013 /// Start time of the trajectory
1014 pub start_time: f32,
1015 /// Duration of the trajectory
1016 pub duration: f32,
1017 /// Starting yaw angle
1018 pub start_yaw: f32,
1019 /// Ending yaw angle
1020 pub end_yaw: f32,
1021 /// Ramp-up time for smooth transitions
1022 pub ramp_time: f32,
1023}
1024/// Implementation of the planner trait for Lissajous curve trajectories
1025impl Planner for LissajousPlanner {
1026 fn plan(
1027 &self,
1028 _current_position: Vector3<f32>,
1029 _current_velocity: Vector3<f32>,
1030 time: f32,
1031 ) -> (Vector3<f32>, Vector3<f32>, f32) {
1032 let t = ((time - self.start_time) / self.duration).clamp(0.0, 1.0);
1033 let smooth_start = if t < self.ramp_time / self.duration {
1034 let t_ramp = t / (self.ramp_time / self.duration);
1035 t_ramp * t_ramp * (3.0 - 2.0 * t_ramp)
1036 } else {
1037 1.0
1038 };
1039 let velocity_ramp = if t < self.ramp_time / self.duration {
1040 smooth_start
1041 } else if t > 1.0 - self.ramp_time / self.duration {
1042 let t_down = (1.0 - t) / (self.ramp_time / self.duration);
1043 t_down * t_down * (3.0 - 2.0 * t_down)
1044 } else {
1045 1.0
1046 };
1047 let ang_pos = self.frequency * t * 2.0 * PI + self.phase;
1048 let lissajous = self.amplitude.component_mul(&ang_pos.map(f32::sin));
1049 let position =
1050 self.start_position + smooth_start * ((self.center + lissajous) - self.start_position);
1051 let mut velocity = Vector3::new(
1052 self.amplitude.x * self.frequency.x * 2.0 * PI * ang_pos.x.cos(),
1053 self.amplitude.y * self.frequency.y * 2.0 * PI * ang_pos.y.cos(),
1054 self.amplitude.z * self.frequency.z * 2.0 * PI * ang_pos.z.cos(),
1055 ) * velocity_ramp
1056 / self.duration;
1057 if t < self.ramp_time / self.duration {
1058 let transition_velocity = (self.center - self.start_position)
1059 * (2.0 * t / self.ramp_time - 2.0 * t * t / (self.ramp_time * self.ramp_time))
1060 / self.duration;
1061 velocity += transition_velocity;
1062 }
1063 let yaw = self.start_yaw + (self.end_yaw - self.start_yaw) * t;
1064 (position, velocity, yaw)
1065 }
1066
1067 fn is_finished(
1068 &self,
1069 _current_position: Vector3<f32>,
1070 time: f32,
1071 ) -> Result<bool, SimulationError> {
1072 Ok(time >= self.start_time + self.duration)
1073 }
1074}
1075/// Planner for circular trajectories
1076/// # Example
1077/// ```
1078/// use nalgebra::Vector3;
1079/// use peng_quad::CirclePlanner;
1080/// let circle_planner = CirclePlanner {
1081/// center: Vector3::new(1.0, 1.0, 1.0),
1082/// radius: 1.0,
1083/// angular_velocity: 1.0,
1084/// start_position: Vector3::new(0.0, 0.0, 0.0),
1085/// start_time: 0.0,
1086/// duration: 1.0,
1087/// start_yaw: 0.0,
1088/// end_yaw: 0.0,
1089/// ramp_time: 0.1,
1090/// };
1091/// ```
1092pub struct CirclePlanner {
1093 /// Center of the circular trajectory
1094 pub center: Vector3<f32>,
1095 /// Radius of the circular trajectory
1096 pub radius: f32,
1097 /// Angular velocity of the circular motion
1098 pub angular_velocity: f32,
1099 /// Starting position of the trajectory
1100 pub start_position: Vector3<f32>,
1101 /// Start time of the trajectory
1102 pub start_time: f32,
1103 /// Duration of the trajectory
1104 pub duration: f32,
1105 /// Starting yaw angle
1106 pub start_yaw: f32,
1107 /// Ending yaw angle
1108 pub end_yaw: f32,
1109 /// Ramp-up time for smooth transitions
1110 pub ramp_time: f32,
1111}
1112/// Implementation of the Planner trait for CirclePlanner
1113impl Planner for CirclePlanner {
1114 fn plan(
1115 &self,
1116 _current_position: Vector3<f32>,
1117 _current_velocity: Vector3<f32>,
1118 time: f32,
1119 ) -> (Vector3<f32>, Vector3<f32>, f32) {
1120 let t = (time - self.start_time) / self.duration;
1121 let t = t.clamp(0.0, 1.0);
1122 let smooth_start = if t < self.ramp_time / self.duration {
1123 let t_ramp = t / (self.ramp_time / self.duration);
1124 t_ramp * t_ramp * (3.0 - 2.0 * t_ramp)
1125 } else {
1126 1.0
1127 };
1128 let velocity_ramp = if t < self.ramp_time / self.duration {
1129 smooth_start
1130 } else if t > 1.0 - self.ramp_time / self.duration {
1131 let t_down = (1.0 - t) / (self.ramp_time / self.duration);
1132 t_down * t_down * (3.0 - 2.0 * t_down)
1133 } else {
1134 1.0
1135 };
1136 let angle = self.angular_velocity * t * self.duration;
1137 let circle_offset = Vector3::new(self.radius * angle.cos(), self.radius * angle.sin(), 0.0);
1138 let position = self.start_position
1139 + smooth_start * ((self.center + circle_offset) - self.start_position);
1140 let tangential_velocity = Vector3::new(
1141 -self.radius * self.angular_velocity * angle.sin(),
1142 self.radius * self.angular_velocity * angle.cos(),
1143 0.0,
1144 );
1145 let velocity = tangential_velocity * velocity_ramp;
1146 let yaw = self.start_yaw + (self.end_yaw - self.start_yaw) * t;
1147 (position, velocity, yaw)
1148 }
1149
1150 fn is_finished(
1151 &self,
1152 _current_position: Vector3<f32>,
1153 time: f32,
1154 ) -> Result<bool, SimulationError> {
1155 Ok(time >= self.start_time + self.duration)
1156 }
1157}
1158/// Planner for landing maneuvers
1159/// # Example
1160/// ```
1161/// use nalgebra::Vector3;
1162/// use peng_quad::LandingPlanner;
1163/// let landing_planner = LandingPlanner {
1164/// start_position: Vector3::new(0.0, 0.0, 1.0),
1165/// start_time: 0.0,
1166/// duration: 1.0,
1167/// start_yaw: 0.0,
1168/// };
1169/// ```
1170pub struct LandingPlanner {
1171 /// Starting position of the landing maneuver
1172 pub start_position: Vector3<f32>,
1173 /// Start time of the landing maneuver
1174 pub start_time: f32,
1175 /// Duration of the landing maneuver
1176 pub duration: f32,
1177 /// Starting yaw angle
1178 pub start_yaw: f32,
1179}
1180/// Implementation of the Planner trait for 'LandingPlanner'
1181impl Planner for LandingPlanner {
1182 fn plan(
1183 &self,
1184 _current_position: Vector3<f32>,
1185 _current_velocity: Vector3<f32>,
1186 time: f32,
1187 ) -> (Vector3<f32>, Vector3<f32>, f32) {
1188 let t = ((time - self.start_time) / self.duration).clamp(0.0, 1.0);
1189 let target_z = self.start_position.z * (1.0 - t);
1190 let target_position = Vector3::new(self.start_position.x, self.start_position.y, target_z);
1191 let target_velocity = Vector3::new(0.0, 0.0, -self.start_position.z / self.duration);
1192 (target_position, target_velocity, self.start_yaw)
1193 }
1194
1195 fn is_finished(
1196 &self,
1197 current_position: Vector3<f32>,
1198 time: f32,
1199 ) -> Result<bool, SimulationError> {
1200 Ok(current_position.z < 0.05 || time >= self.start_time + self.duration)
1201 }
1202}
1203/// Manages different trajectory planners and switches between them
1204/// # Example
1205/// ```
1206/// use nalgebra::Vector3;
1207/// use peng_quad::PlannerManager;
1208/// let initial_position = Vector3::new(0.0, 0.0, 1.0);
1209/// let initial_yaw = 0.0;
1210/// let planner_manager = PlannerManager::new(initial_position, initial_yaw);
1211/// ```
1212pub struct PlannerManager {
1213 /// The current planner
1214 pub current_planner: PlannerType,
1215}
1216/// Implementation of the PlannerManager
1217impl PlannerManager {
1218 /// Creates a new PlannerManager with an initial hover planner
1219 /// # Arguments
1220 /// * `initial_position` - The initial position for hovering
1221 /// * `initial_yaw` - The initial yaw angle for hovering
1222 /// # Returns
1223 /// * A new PlannerManager instance
1224 /// # Example
1225 /// ```
1226 /// use nalgebra::Vector3;
1227 /// use peng_quad::PlannerManager;
1228 /// let initial_position = Vector3::new(0.0, 0.0, 1.0);
1229 /// let initial_yaw = 0.0;
1230 /// let planner_manager = PlannerManager::new(initial_position, initial_yaw);
1231 /// ```
1232 pub fn new(initial_position: Vector3<f32>, initial_yaw: f32) -> Self {
1233 let hover_planner = HoverPlanner {
1234 target_position: initial_position,
1235 target_yaw: initial_yaw,
1236 };
1237 Self {
1238 current_planner: PlannerType::Hover(hover_planner),
1239 }
1240 }
1241 /// Sets a new planner
1242 /// # Arguments
1243 /// * `new_planner` - The new planner to be set
1244 /// # Example
1245 /// ```
1246 /// use nalgebra::Vector3;
1247 /// use peng_quad::{PlannerManager, CirclePlanner, PlannerType};
1248 /// let initial_position = Vector3::new(0.0, 0.0, 1.0);
1249 /// let initial_yaw = 0.0;
1250 /// let mut planner_manager = PlannerManager::new(initial_position, initial_yaw);
1251 /// let new_planner = CirclePlanner {
1252 /// center: Vector3::new(0.0, 0.0, 1.0),
1253 /// radius: 1.0,
1254 /// angular_velocity: 1.0,
1255 /// start_yaw: 0.0,
1256 /// end_yaw: 0.0,
1257 /// start_time: 0.0,
1258 /// duration: 10.0,
1259 /// ramp_time: 1.0,
1260 /// start_position: Vector3::new(0.0, 0.0, 1.0),
1261 /// };
1262 /// planner_manager.set_planner(PlannerType::Circle(new_planner));
1263 /// ```
1264 pub fn set_planner(&mut self, new_planner: PlannerType) {
1265 self.current_planner = new_planner;
1266 }
1267 /// Updates the current planner and returns the desired position, velocity, and yaw
1268 /// # Arguments
1269 /// * `current_position` - The current position of the quadrotor
1270 /// * `current_orientation` - The current orientation of the quadrotor
1271 /// * `current_velocity` - The current velocity of the quadrotor
1272 /// * `time` - The current simulation time
1273 /// # Returns
1274 /// * A tuple containing the desired position, velocity, and yaw angle
1275 /// # Errors
1276 /// * Returns a SimulationError if the current planner is not finished
1277 /// # Example
1278 /// ```
1279 /// use nalgebra::{Vector3, UnitQuaternion};
1280 /// use peng_quad::{PlannerManager, SimulationError};
1281 /// let initial_position = Vector3::new(0.0, 0.0, 1.0);
1282 /// let initial_yaw = 0.0;
1283 /// let mut planner_manager = PlannerManager::new(initial_position, initial_yaw);
1284 /// let current_position = Vector3::new(0.0, 0.0, 1.0);
1285 /// let current_orientation = UnitQuaternion::from_euler_angles(0.0, 0.0, 0.0);
1286 /// let current_velocity = Vector3::new(0.0, 0.0, 0.0);
1287 /// let obstacles = vec![];
1288 /// let time = 0.0;
1289 /// let result = planner_manager.update(current_position, current_orientation, current_velocity, time, &obstacles);
1290 /// match result {
1291 /// Ok((target_position, target_velocity, target_yaw)) => {
1292 /// println!("Target Position: {:?}", target_position);
1293 /// println!("Target Velocity: {:?}", target_velocity);
1294 /// println!("Target Yaw: {:?}", target_yaw);
1295 /// }
1296 /// Err(SimulationError) => {
1297 /// log::error!("Error: Planner is not finished");
1298 /// }
1299 /// }
1300 /// ```
1301 pub fn update(
1302 &mut self,
1303 current_position: Vector3<f32>,
1304 current_orientation: UnitQuaternion<f32>,
1305 current_velocity: Vector3<f32>,
1306 time: f32,
1307 obstacles: &[Obstacle],
1308 ) -> Result<(Vector3<f32>, Vector3<f32>, f32), SimulationError> {
1309 if self.current_planner.is_finished(current_position, time)? {
1310 log::info!("Time: {time:.2} s,\tSwitch Hover");
1311 self.current_planner = PlannerType::Hover(HoverPlanner {
1312 target_position: current_position,
1313 target_yaw: current_orientation.euler_angles().2,
1314 });
1315 }
1316 // Update obstacles for ObstacleAvoidancePlanner if needed
1317 if let PlannerType::ObstacleAvoidance(ref mut planner) = self.current_planner {
1318 planner.obstacles = obstacles.to_owned();
1319 }
1320 Ok(self
1321 .current_planner
1322 .plan(current_position, current_velocity, time))
1323 }
1324}
1325/// Obstacle avoidance planner that uses a potential field approach to avoid obstacles
1326///
1327/// The planner calculates a repulsive force for each obstacle and an attractive force towards the goal
1328/// The resulting force is then used to calculate the desired position and velocity
1329/// # Example
1330/// ```
1331/// use nalgebra::Vector3;
1332/// use peng_quad::{ObstacleAvoidancePlanner, Obstacle};
1333/// let planner = ObstacleAvoidancePlanner {
1334/// target_position: Vector3::new(0.0, 0.0, 1.0),
1335/// start_time: 0.0,
1336/// duration: 10.0,
1337/// start_yaw: 0.0,
1338/// end_yaw: 0.0,
1339/// obstacles: vec![Obstacle {
1340/// position: Vector3::new(1.0, 0.0, 1.0),
1341/// velocity: Vector3::new(0.0, 0.0, 0.0),
1342/// radius: 0.5,
1343/// }],
1344/// k_att: 1.0,
1345/// k_rep: 1.0,
1346/// k_vortex: 1.0,
1347/// d0: 1.0,
1348/// d_target: 1.0,
1349/// max_speed: 1.0,
1350/// };
1351/// ```
1352pub struct ObstacleAvoidancePlanner {
1353 /// Target position of the planner
1354 pub target_position: Vector3<f32>,
1355 /// Start time of the planner
1356 pub start_time: f32,
1357 /// Duration of the planner
1358 pub duration: f32,
1359 /// Starting yaw angle
1360 pub start_yaw: f32,
1361 /// Ending yaw angle
1362 pub end_yaw: f32,
1363 /// List of obstacles
1364 pub obstacles: Vec<Obstacle>,
1365 /// Attractive force gain
1366 pub k_att: f32,
1367 /// Repulsive force gain
1368 pub k_rep: f32,
1369 /// Vortex force gain
1370 pub k_vortex: f32,
1371 /// Influence distance of obstacles
1372 pub d0: f32,
1373 /// Influence distance of target
1374 pub d_target: f32,
1375 /// Maximum speed of the quadrotor
1376 pub max_speed: f32,
1377}
1378/// Implementation of the Planner trait for ObstacleAvoidancePlanner
1379impl Planner for ObstacleAvoidancePlanner {
1380 fn plan(
1381 &self,
1382 current_position: Vector3<f32>,
1383 current_velocity: Vector3<f32>,
1384 time: f32,
1385 ) -> (Vector3<f32>, Vector3<f32>, f32) {
1386 let t = ((time - self.start_time) / self.duration).clamp(0.0, 1.0);
1387 let distance_to_target = (self.target_position - current_position).norm();
1388 let f_att = self.k_att
1389 * self.smooth_attractive_force(distance_to_target)
1390 * (self.target_position - current_position).normalize();
1391 // Repulsive force from obstacles
1392 let mut f_rep = Vector3::zeros();
1393 let mut f_vortex = Vector3::zeros();
1394 for obstacle in &self.obstacles {
1395 let diff = current_position - obstacle.position;
1396 let distance = diff.norm();
1397 if distance < self.d0 {
1398 f_rep += self.k_rep
1399 * (1.0 / distance - 1.0 / self.d0)
1400 * (1.0 / distance.powi(2))
1401 * diff.normalize();
1402 f_vortex +=
1403 self.k_vortex * current_velocity.cross(&diff).normalize() / distance.powi(2);
1404 }
1405 }
1406 let f_total = f_att + f_rep + f_vortex;
1407 let desired_velocity = f_total.normalize() * self.max_speed.min(f_total.norm());
1408 let desired_position = current_position + desired_velocity * self.duration * (1.0 - t);
1409 let desired_yaw = self.start_yaw + (self.end_yaw - self.start_yaw) * t;
1410 (desired_position, desired_velocity, desired_yaw)
1411 }
1412
1413 fn is_finished(
1414 &self,
1415 current_position: Vector3<f32>,
1416 time: f32,
1417 ) -> Result<bool, SimulationError> {
1418 Ok((current_position - self.target_position).norm() < 0.1
1419 && time >= self.start_time + self.duration)
1420 }
1421}
1422/// Implementation of the ObstacleAvoidancePlanner
1423impl ObstacleAvoidancePlanner {
1424 /// A smooth attractive force function that transitions from linear to exponential decay
1425 /// When the distance to the target is less than the target distance, the force is linear
1426 /// When the distance is greater, the force decays exponentially
1427 /// # Arguments
1428 /// * `distance` - The distance to the target
1429 /// # Returns
1430 /// * The attractive force
1431 /// # Example
1432 /// ```
1433 /// use peng_quad::ObstacleAvoidancePlanner;
1434 /// let planner = ObstacleAvoidancePlanner {
1435 /// target_position: nalgebra::Vector3::new(0.0, 0.0, 1.0),
1436 /// start_time: 0.0,
1437 /// duration: 10.0,
1438 /// start_yaw: 0.0,
1439 /// end_yaw: 0.0,
1440 /// obstacles: vec![],
1441 /// k_att: 1.0,
1442 /// k_rep: 1.0,
1443 /// k_vortex: 1.0,
1444 /// d0: 1.0,
1445 /// d_target: 1.0,
1446 /// max_speed: 1.0,
1447 /// };
1448 /// let distance = 1.0;
1449 /// let force = planner.smooth_attractive_force(distance);
1450 /// ```
1451 #[inline]
1452 pub fn smooth_attractive_force(&self, distance: f32) -> f32 {
1453 if distance <= self.d_target {
1454 distance
1455 } else {
1456 self.d_target + (distance - self.d_target).tanh()
1457 }
1458 }
1459}
1460/// Waypoint planner that generates a minimum snap trajectory between waypoints
1461/// # Example
1462/// ```
1463/// use peng_quad::MinimumSnapWaypointPlanner;
1464/// use nalgebra::Vector3;
1465/// let planner = MinimumSnapWaypointPlanner::new(
1466/// vec![Vector3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 0.0)],
1467/// vec![0.0, 0.0],
1468/// vec![1.0],
1469/// 0.0,
1470/// );
1471/// ```
1472pub struct MinimumSnapWaypointPlanner {
1473 /// List of waypoints
1474 pub waypoints: Vec<Vector3<f32>>,
1475 /// List of yaw angles
1476 pub yaws: Vec<f32>,
1477 /// List of segment times to reach each waypoint
1478 pub times: Vec<f32>,
1479 /// Coefficients for the x, y, and z components of the trajectory
1480 pub coefficients: Vec<Vec<Vector3<f32>>>,
1481 /// Coefficients for the yaw component of the trajectory
1482 pub yaw_coefficients: Vec<Vec<f32>>,
1483 /// Start time of the trajectory
1484 pub start_time: f32,
1485}
1486/// Implementation of the MinimumSnapWaypointPlanner
1487impl MinimumSnapWaypointPlanner {
1488 /// Generate a new minimum snap waypoint planner
1489 /// # Arguments
1490 /// * `waypoints` - List of waypoints
1491 /// * `yaws` - List of yaw angles
1492 /// * `segment_times` - List of segment times to reach each waypoint
1493 /// * `start_time` - Start time of the trajectory
1494 /// # Returns
1495 /// * A new minimum snap waypoint planner
1496 /// # Errors
1497 /// * Returns an error if the number of waypoints, yaws, and segment times do not match
1498 /// # Example
1499 /// ```
1500 /// use peng_quad::MinimumSnapWaypointPlanner;
1501 /// use nalgebra::Vector3;
1502 /// let waypoints = vec![Vector3::zeros(), Vector3::new(1.0, 0.0, 0.0)];
1503 /// let yaws = vec![0.0, 0.0];
1504 /// let segment_times = vec![1.0];
1505 /// let start_time = 0.0;
1506 /// let planner = MinimumSnapWaypointPlanner::new(waypoints, yaws, segment_times, start_time);
1507 /// ```
1508 pub fn new(
1509 waypoints: Vec<Vector3<f32>>,
1510 yaws: Vec<f32>,
1511 segment_times: Vec<f32>,
1512 start_time: f32,
1513 ) -> Result<Self, SimulationError> {
1514 if waypoints.len() < 2 {
1515 return Err(SimulationError::OtherError(
1516 "At least two waypoints are required".to_string(),
1517 ));
1518 }
1519 if waypoints.len() != segment_times.len() + 1 || waypoints.len() != yaws.len() {
1520 return Err(SimulationError::OtherError("Number of segment times must be one less than number of waypoints, and yaws must match waypoints".to_string()));
1521 }
1522 let mut planner = Self {
1523 waypoints,
1524 yaws,
1525 times: segment_times,
1526 coefficients: Vec::new(),
1527 yaw_coefficients: Vec::new(),
1528 start_time,
1529 };
1530 planner.compute_minimum_snap_trajectories()?;
1531 planner.compute_minimum_acceleration_yaw_trajectories()?;
1532 Ok(planner)
1533 }
1534 /// Compute the coefficients for the minimum snap trajectory, calculated for each segment between waypoints
1535 /// # Errors
1536 /// * Returns an error if the nalgebra solver fails to solve the linear system
1537 /// # Example
1538 /// ```
1539 /// use peng_quad::MinimumSnapWaypointPlanner;
1540 /// use nalgebra::Vector3;
1541 /// let waypoints = vec![Vector3::zeros(), Vector3::new(1.0, 0.0, 0.0)];
1542 /// let yaws = vec![0.0, 0.0];
1543 /// let segment_times = vec![1.0];
1544 /// let start_time = 0.0;
1545 /// let mut planner = MinimumSnapWaypointPlanner::new(waypoints, yaws, segment_times, start_time).unwrap();
1546 /// planner.compute_minimum_snap_trajectories();
1547 /// ```
1548 pub fn compute_minimum_snap_trajectories(&mut self) -> Result<(), SimulationError> {
1549 let n = self.waypoints.len() - 1;
1550 for i in 0..n {
1551 let duration = self.times[i];
1552 let (start, end) = (self.waypoints[i], self.waypoints[i + 1]);
1553 let mut a = SMatrix::<f32, 8, 8>::zeros();
1554 let mut b = SMatrix::<f32, 8, 3>::zeros();
1555 a.fixed_view_mut::<4, 4>(0, 0).fill_with_identity();
1556 b.fixed_view_mut::<1, 3>(0, 0).copy_from(&start.transpose());
1557 b.fixed_view_mut::<1, 3>(4, 0).copy_from(&end.transpose());
1558 // End point constraints
1559 for j in 0..8 {
1560 a[(4, j)] = duration.powi(j as i32);
1561 if j > 0 {
1562 a[(5, j)] = j as f32 * duration.powi(j as i32 - 1);
1563 }
1564 if j > 1 {
1565 a[(6, j)] = j as f32 * (j - 1) as f32 * duration.powi(j as i32 - 2);
1566 }
1567 if j > 2 {
1568 a[(7, j)] =
1569 j as f32 * (j - 1) as f32 * (j - 2) as f32 * duration.powi(j as i32 - 3);
1570 }
1571 }
1572 let coeffs = a.lu().solve(&b).ok_or(SimulationError::NalgebraError(
1573 "Failed to solve for coefficients in MinimumSnapWaypointPlanner".to_string(),
1574 ))?;
1575 self.coefficients.push(
1576 (0..8)
1577 .map(|j| Vector3::new(coeffs[(j, 0)], coeffs[(j, 1)], coeffs[(j, 2)]))
1578 .collect(),
1579 );
1580 }
1581 Ok(())
1582 }
1583 /// Compute the coefficients for yaw trajectories
1584 /// The yaw trajectory is a cubic polynomial and interpolated between waypoints
1585 /// # Errors
1586 /// * Returns an error if nalgebra fails to solve for the coefficients
1587 /// # Example
1588 /// ```
1589 /// use peng_quad::MinimumSnapWaypointPlanner;
1590 /// use nalgebra::Vector3;
1591 /// let waypoints = vec![Vector3::zeros(), Vector3::new(1.0, 0.0, 0.0)];
1592 /// let yaws = vec![0.0, 0.0];
1593 /// let segment_times = vec![1.0];
1594 /// let start_time = 0.0;
1595 /// let mut planner = MinimumSnapWaypointPlanner::new(waypoints, yaws, segment_times, start_time).unwrap();
1596 /// planner.compute_minimum_snap_trajectories();
1597 /// planner.compute_minimum_acceleration_yaw_trajectories();
1598 /// ```
1599 pub fn compute_minimum_acceleration_yaw_trajectories(&mut self) -> Result<(), SimulationError> {
1600 let n = self.yaws.len() - 1; // Number of segments
1601 for i in 0..n {
1602 let (duration, start_yaw, end_yaw) = (self.times[i], self.yaws[i], self.yaws[i + 1]);
1603 let mut a = SMatrix::<f32, 4, 4>::zeros();
1604 let mut b = SMatrix::<f32, 4, 1>::zeros();
1605 (a[(0, 0)], a[(1, 1)]) = (1.0, 1.0);
1606 (b[0], b[2]) = (start_yaw, end_yaw);
1607 for j in 0..4 {
1608 a[(2, j)] = duration.powi(j as i32);
1609 if j > 0 {
1610 a[(3, j)] = j as f32 * duration.powi(j as i32 - 1);
1611 }
1612 }
1613 let yaw_coeffs = a.lu().solve(&b).ok_or(SimulationError::NalgebraError(
1614 "Failed to solve for yaw coefficients in MinimumSnapWaypointPlanner".to_string(),
1615 ))?;
1616 self.yaw_coefficients.push(yaw_coeffs.as_slice().to_vec());
1617 }
1618 Ok(())
1619 }
1620 /// Evaluate the trajectory at a given time, returns the position, velocity, yaw, and yaw rate at the given time
1621 /// # Arguments
1622 /// * `t` - The time to evaluate the trajectory at
1623 /// * `coeffs` - The coefficients for the position trajectory
1624 /// * `yaw_coeffs` - The coefficients for the yaw trajectory
1625 /// # Returns
1626 /// * `position` - The position at the given time (meters)
1627 /// * `velocity` - The velocity at the given time (meters / second)
1628 /// * `yaw` - The yaw at the given time (radians)
1629 /// * `yaw_rate` - The yaw rate at the given time (radians / second)
1630 /// # Example
1631 /// ```
1632 /// use nalgebra::Vector3;
1633 /// use peng_quad::MinimumSnapWaypointPlanner;
1634 /// let waypoints = vec![Vector3::zeros(), Vector3::new(1.0, 0.0, 0.0)];
1635 /// let yaws = vec![0.0, 0.0];
1636 /// let segment_times = vec![1.0];
1637 /// let start_time = 0.0;
1638 /// let mut planner = MinimumSnapWaypointPlanner::new(waypoints, yaws, segment_times, start_time).unwrap();
1639 /// planner.compute_minimum_snap_trajectories();
1640 /// planner.compute_minimum_acceleration_yaw_trajectories();
1641 /// let (position, velocity, yaw, yaw_rate) = planner.evaluate_polynomial(0.5, &planner.coefficients[0], &planner.yaw_coefficients[0]);
1642 /// ```
1643 pub fn evaluate_polynomial(
1644 &self,
1645 t: f32,
1646 coeffs: &[Vector3<f32>],
1647 yaw_coeffs: &[f32],
1648 ) -> (Vector3<f32>, Vector3<f32>, f32, f32) {
1649 let mut position = Vector3::zeros();
1650 let mut velocity = Vector3::zeros();
1651 let mut yaw = 0.0;
1652 let mut yaw_rate = 0.0;
1653 for (i, coeff) in coeffs.iter().enumerate() {
1654 let ti = t.powi(i as i32);
1655 position += coeff * ti;
1656 if i > 0 {
1657 velocity += coeff * (i as f32) * t.powi(i as i32 - 1);
1658 }
1659 }
1660 for (i, &coeff) in yaw_coeffs.iter().enumerate() {
1661 let ti = t.powi(i as i32);
1662 yaw += coeff * ti;
1663 if i > 0 {
1664 yaw_rate += coeff * (i as f32) * t.powi(i as i32 - 1);
1665 }
1666 }
1667 (position, velocity, yaw, yaw_rate)
1668 }
1669}
1670/// Implement the `Planner` trait for `MinimumSnapWaypointPlanner`
1671impl Planner for MinimumSnapWaypointPlanner {
1672 fn plan(
1673 &self,
1674 _current_position: Vector3<f32>,
1675 _current_velocity: Vector3<f32>,
1676 time: f32,
1677 ) -> (Vector3<f32>, Vector3<f32>, f32) {
1678 let relative_time = time - self.start_time;
1679 // Find the current segment
1680 let mut segment_start_time = 0.0;
1681 let mut current_segment = 0;
1682 for (i, &segment_duration) in self.times.iter().enumerate() {
1683 if relative_time < segment_start_time + segment_duration {
1684 current_segment = i;
1685 break;
1686 }
1687 segment_start_time += segment_duration;
1688 }
1689 // Evaluate the polynomial for the current segment
1690 let segment_time = relative_time - segment_start_time;
1691 let (position, velocity, yaw, _yaw_rate) = self.evaluate_polynomial(
1692 segment_time,
1693 &self.coefficients[current_segment],
1694 &self.yaw_coefficients[current_segment],
1695 );
1696 (position, velocity, yaw)
1697 }
1698
1699 fn is_finished(
1700 &self,
1701 current_position: Vector3<f32>,
1702 time: f32,
1703 ) -> Result<bool, SimulationError> {
1704 let last_waypoint = self.waypoints.last().ok_or(SimulationError::OtherError(
1705 "No waypoints available".to_string(),
1706 ))?;
1707 Ok(time >= self.start_time + self.times.iter().sum::<f32>()
1708 && (current_position - last_waypoint).norm() < 0.1)
1709 }
1710}
1711
1712/// Waypoint planner that generates a quadratic polynomial trajectory between waypoints
1713/// # Example
1714/// ```
1715/// use peng_quad::QPpolyTrajPlanner;
1716/// use nalgebra::Vector3;
1717/// let waypoints: Vec<Vec<f32>> = vec![vec![0.0,0.0,1.0,0.0], vec![1.0,0.0,1.0,0.0]];
1718/// let segment_times = vec![6.0];
1719/// let min_deriv = 3;
1720/// let polyorder = 9;
1721/// let smooth_upto = 4;
1722/// let max_velocity = 4.0;
1723/// let max_acceleration = 3.0;
1724/// let dt = 0.1;
1725/// let start_time = 0.0;
1726/// let mut qp_planner = QPpolyTrajPlanner::new(waypoints,segment_times,polyorder, min_deriv, smooth_upto,max_velocity,max_acceleration, start_time, dt);
1727/// ```
1728pub struct QPpolyTrajPlanner {
1729 /// Matrix of coefficients for each segment and each dimension, organized as nrows: polyorder*segment_times.len(), ncols: 4 (for x, y, z, yaw)
1730 pub coeff: DMatrix<f64>,
1731 /// Order of the polynomial to be used in computing trajectory
1732 pub polyorder: usize,
1733 /// Minimize which derivative in the QP problem (1->Velocity, 2->Acceleration, 3->Snap, 4->Jerk. Please note that derivative value greater than 4 is not supported)
1734 pub min_deriv: usize,
1735 /// Ensure continuity upto which derivative. NOTE: This MUST be <= polynomial_order (1->Velocity, 2->Acceleration, 3->Snap, 4->Jerk. Please note that derivative value greater than 4 is not supported)
1736 pub smooth_upto: usize,
1737 /// Vector of time values for each segment, which tells the planner how much time each segment should take to complete. Expressed in seconds.
1738 pub segment_times: Vec<f32>,
1739 /// Waypoints for each segment. Note that there should be segment_times.len() + 1 values for waypoints, with the position of the quadrotor being the very first waypoint.
1740 pub waypoints: Vec<Vec<f32>>,
1741 /// Maximum velocity constraint. Set to 0.0 to disregard inequality constraints. Please set reasonable values for this as it influences solver convergence or failure.
1742 pub max_velocity: f32,
1743 /// Maximum acceleration constraint. Set to 0.0 to disregard inequality constraints. Please set reasonable values for this as it influences solver convergence or failure.
1744 pub max_acceleration: f32,
1745 /// Time at which the simulation starts. This value has no bearing to QPpolyTraj itself, but is used during simulation since we use relative time internally when computing values.
1746 pub start_time: f32,
1747 /// Step time used while generating inequality constraints. Has no bearing if max_velocity or max_acceleration is set to 0.0. Please set reasonable value for this as it has a huge impact on OSQP solve time.
1748 pub dt: f32,
1749}
1750
1751impl QPpolyTrajPlanner {
1752 /// Generate a new QPpolyTraj planner
1753 /// # Arguments
1754 /// * `waypoints` - The waypoints for the trajectory
1755 /// * `segment_times` - The times for each segment of the trajectory
1756 /// * `polyorder` - The order of the polynomial
1757 /// * `min_deriv` - The minimum derivative to be considered
1758 /// * `smooth_upto` - The maximum derivative to be considered
1759 /// * `max_velocity` - The maximum velocity
1760 /// * `max_acceleration` - The maximum acceleration
1761 /// * `start_time` - The start time of the trajectory
1762 /// * `dt` - The time step for the trajectory
1763 /// # Errors
1764 /// * Returns an error if the specified waypoints, segment times, smooth_upto or polyorder is invalid
1765 /// # Examples
1766 /// ```
1767 /// use peng_quad::QPpolyTrajPlanner;
1768 /// use nalgebra::{DMatrix, DVector, Vector3};
1769 /// let waypoints: Vec<Vec<f32>> = vec![vec![0.0, 0.0, 0.0, 0.0], vec![1.0, 1.0, 1.5, 1.5707963267948966], vec![-1.0, 1.0, 1.75, 3.141592653589793], vec![0.0, -1.0, 1.0, -1.5707963267948966], vec![0.0, 0.0, 0.5, 0.0]];
1770 /// let segment_times = vec![5.0, 5.0, 5.0, 5.0];
1771 /// let polyorder = 9;
1772 /// let min_deriv = 3;
1773 /// let smooth_upto = 4;
1774 /// let max_velocity = 4.0;
1775 /// let max_acceleration = 2.0;
1776 /// let start_time = 0.0;
1777 /// let dt = 0.1;
1778 /// let mut qp_planner = QPpolyTrajPlanner::new(waypoints,segment_times,polyorder, min_deriv, smooth_upto,max_velocity,max_acceleration, start_time, dt).unwrap();
1779 /// ```
1780 #[allow(clippy::too_many_arguments)]
1781 pub fn new(
1782 waypoints: Vec<Vec<f32>>,
1783 segment_times: Vec<f32>,
1784 polyorder: usize,
1785 min_deriv: usize,
1786 smooth_upto: usize,
1787 max_velocity: f32,
1788 max_acceleration: f32,
1789 start_time: f32,
1790 dt: f32,
1791 ) -> Result<Self, SimulationError> {
1792 if waypoints.len() < 2 {
1793 return Err(SimulationError::OtherError(
1794 "At least two waypoints are required".to_string(),
1795 ));
1796 }
1797 if waypoints.len() != segment_times.len() + 1 || waypoints[0].len() != 4 {
1798 return Err(SimulationError::OtherError("Number of segment times must be one less than number of waypoints, and waypoints must contain x, y, z, yaw values".to_string()));
1799 }
1800 if smooth_upto > polyorder {
1801 return Err(SimulationError::OtherError(format!(
1802 "smooth_upto ({smooth_upto}) cannot be greater than polynomial order ({polyorder})"
1803 )));
1804 }
1805 let mut planner = Self {
1806 coeff: DMatrix::zeros(segment_times.len() * polyorder, 4),
1807 polyorder,
1808 min_deriv,
1809 smooth_upto,
1810 segment_times,
1811 waypoints,
1812 max_velocity,
1813 max_acceleration,
1814 start_time,
1815 dt,
1816 };
1817 planner.compute_coefficients()?;
1818 Ok(planner)
1819 }
1820
1821 /// Compute the position, velocity, yaw, and yaw_rate for a given time t in given segment
1822 /// # Arguments
1823 /// * `t` - The time at which to evaluate the polynomial
1824 /// * `segment` - The segment index for which to evaluate the polynomial
1825 /// # Returns
1826 /// * A tuple containing the position, velocity, yaw, and yaw_rate at time `t` in segment `segment`
1827 pub fn evaluate_polynomial(
1828 &self,
1829 t: f32,
1830 segment: usize,
1831 ) -> (Vector3<f32>, Vector3<f32>, f32, f32) {
1832 let mut position = Vector3::zeros();
1833 let mut velocity = Vector3::zeros();
1834
1835 let pos_basis = self.basis(t, 0);
1836 let vel_basis = self.basis(t, 1);
1837
1838 let row_offset = self.polyorder * segment;
1839
1840 let coeff_segment = &self.coeff.view((row_offset, 0), (self.polyorder, 4));
1841
1842 let pose_4d = pos_basis.transpose() * coeff_segment;
1843 let vel_4d = vel_basis.transpose() * coeff_segment;
1844
1845 //Convert to f32. This is bad for performance but currently no other way as OSQP requires f64 and Peng utilizes f32.
1846 let pose_4d_f32 = pose_4d.map(|x| x as f32);
1847 let vel_4d_f32 = vel_4d.map(|x| x as f32);
1848
1849 // Extract the necessary values
1850 for i in 0..3 {
1851 position[i] = pose_4d_f32[(0, i)];
1852 velocity[i] = vel_4d_f32[(0, i)];
1853 }
1854 let yaw = pose_4d_f32[(0, 3)];
1855 let yaw_rate = vel_4d_f32[(0, 3)];
1856
1857 (position, velocity, yaw, yaw_rate)
1858 }
1859
1860 /// Solve the Quadratic Programming problem and compute the coefficients for the whole trajectory.
1861 /// The coefficients will be arranged as (poly_order*num_segments, num_dims)
1862 /// # Errors
1863 /// * `SimulationError::OSQPError` - Failed to set up or solve OSQP problem
1864 fn compute_coefficients(&mut self) -> Result<(), SimulationError> {
1865 let settings: Settings = Settings::default()
1866 .max_iter(10000000)
1867 .eps_abs(1e-6)
1868 .eps_rel(5e-6)
1869 .verbose(false);
1870
1871 let (p, q, a, l, u) = self.prepare_qp_problem();
1872
1873 // Create an OSQP problem
1874 let mut problem = Problem::new(p, q.as_slice(), a, l.as_slice(), u.as_slice(), &settings)
1875 .map_err(|_| {
1876 SimulationError::OSQPError("Failed to set up OSQP problem".to_string())
1877 })?;
1878
1879 // Solve problem
1880 let result = problem.solve();
1881
1882 // Obtain the solution coefficients
1883 let coeff_vector = result
1884 .x()
1885 .map(|solution| DVector::from_iterator(solution.len(), solution.iter().cloned()))
1886 .ok_or(SimulationError::OSQPError(
1887 "Unable to solve OSQP problem".to_string(),
1888 ))?;
1889
1890 self.coeff = DMatrix::from_column_slice(
1891 self.segment_times.len() * self.polyorder,
1892 4,
1893 coeff_vector.as_slice(),
1894 );
1895
1896 Ok(())
1897 }
1898
1899 /// Combine the objective function, the equality and inequality constraints into a format for OSQP
1900 /// # Returns
1901 /// * A tuple containing the Q matrix, the linear term vector, the A matrix, the lower bound vector, and the upper bound vector
1902 fn prepare_qp_problem(
1903 &self,
1904 ) -> (
1905 CscMatrix<'_>,
1906 DVector<f64>,
1907 CscMatrix<'_>,
1908 DVector<f64>,
1909 DVector<f64>,
1910 ) {
1911 // 1. Generate the Q Matrix for x, y, z, yaw. Since the objective function is the same for all, we simply clone the first matrix instead of recomputing for each dim.
1912 let num_dims = 4;
1913 let num_vars_per_dim = self.polyorder * self.segment_times.len();
1914 let num_vars = num_vars_per_dim * num_dims;
1915
1916 // Stack Q matrices into a block-diagonal matrix (joint optimization)
1917 let q_x = self.generate_obj_function();
1918 let mut q = DMatrix::zeros(num_vars, num_vars);
1919
1920 for dim in 0..num_dims {
1921 let row_offset = dim * num_vars_per_dim;
1922 let col_offset = dim * num_vars_per_dim;
1923 q.view_mut((row_offset, col_offset), (q_x.nrows(), q_x.ncols()))
1924 .copy_from(&q_x);
1925 }
1926
1927 // 2. Generate the Equality constraints for x, y, z, yaw
1928
1929 // Construct a block diagonal A_eq matrix
1930 let num_constraints_per_block = (2 + self.smooth_upto) * (self.segment_times.len() + 1) - 2;
1931 let mut a_eq = DMatrix::zeros(num_constraints_per_block * num_dims, num_vars);
1932 let mut b_eq = DVector::zeros(num_constraints_per_block * num_dims);
1933
1934 for dim in 0..num_dims {
1935 let row_offset = dim * num_constraints_per_block;
1936 let col_offset = dim * num_vars_per_dim;
1937 let (small_a_eq, small_b_eq) = self.generate_equality_constraints(dim);
1938 a_eq.view_mut(
1939 (row_offset, col_offset),
1940 (num_constraints_per_block, num_vars_per_dim),
1941 )
1942 .copy_from(&small_a_eq);
1943 b_eq.rows_mut(row_offset, num_constraints_per_block)
1944 .copy_from(&small_b_eq);
1945 }
1946
1947 // 3. Generate the Inequality constraints, similar to how we generate the Q Matrix.
1948 // If no inequality constraints, create empty matrices
1949 let mut a_ineq: DMatrix<f64>;
1950 let mut d: DVector<f64>;
1951 let mut f: DVector<f64>;
1952 if self.max_acceleration > 0.0 && self.max_velocity > 0.0 {
1953 let (a_ineq_x, d_x, f_x) = self.generate_inequality_constraints();
1954 a_ineq = DMatrix::zeros(a_ineq_x.nrows() * num_dims, num_vars);
1955 d = DVector::zeros(d_x.len() * num_dims);
1956 f = DVector::zeros(f_x.len() * num_dims);
1957
1958 for dim in 0..num_dims {
1959 let row_offset = dim * a_ineq_x.nrows();
1960 let col_offset = dim * num_vars_per_dim;
1961 a_ineq
1962 .view_mut(
1963 (row_offset, col_offset),
1964 (a_ineq_x.nrows(), a_ineq_x.ncols()),
1965 )
1966 .copy_from(&a_ineq_x);
1967 d.rows_mut(row_offset, d_x.len()).copy_from(&d_x);
1968 f.rows_mut(row_offset, f_x.len()).copy_from(&f_x);
1969 }
1970 } else {
1971 // If no inequality constraints, create empty matrices
1972 a_ineq = DMatrix::zeros(0, num_vars);
1973 d = DVector::zeros(0);
1974 f = DVector::zeros(0);
1975 }
1976
1977 // 4. Combine equality and optionally, inequality constraints
1978 let total_constraints = a_eq.nrows() + a_ineq.nrows();
1979 let mut a = DMatrix::zeros(total_constraints, num_vars);
1980 a.view_mut((0, 0), (a_eq.nrows(), a_eq.ncols()))
1981 .copy_from(&a_eq);
1982
1983 let mut l = DVector::zeros(total_constraints);
1984 let mut u = DVector::zeros(total_constraints);
1985
1986 // Lower and upper bounds
1987 l.rows_mut(0, b_eq.len()).copy_from(&b_eq);
1988 u.rows_mut(0, b_eq.len()).copy_from(&b_eq); // Equality constraints: Enforcing l = u = b_eq is the same as A_eq*x = b_eq
1989
1990 if a_ineq.nrows() > 0 {
1991 a.view_mut((a_eq.nrows(), 0), (a_ineq.nrows(), a_ineq.ncols()))
1992 .copy_from(&a_ineq);
1993 l.rows_mut(b_eq.len(), d.len()).copy_from(&d);
1994 u.rows_mut(b_eq.len(), f.len()).copy_from(&f);
1995 }
1996
1997 // 5. Convert matrices to CSC format for osqp.rs
1998 let q_csc = self.convert_dense_to_sparse(&q).into_upper_tri();
1999 let a_csc = self.convert_dense_to_sparse(&a);
2000
2001 (q_csc, DVector::zeros(num_vars), a_csc, l, u)
2002 }
2003
2004 /// Generate the Q Cost Matrix and A Matrix in the required Csc sparse matrix form for osqp by converting the dense DMatrix.
2005 /// # Arguments
2006 /// * `a` - The dense matrix to be converted.
2007 /// # Returns
2008 /// * The sparse matrix in CSC format.
2009 fn convert_dense_to_sparse(&self, a: &DMatrix<f64>) -> CscMatrix<'_> {
2010 let (rows, cols) = a.shape();
2011
2012 let column_major_iter: Vec<f64> = a
2013 .column_iter()
2014 .flat_map(|col| col.iter().copied().collect::<Vec<f64>>())
2015 .collect();
2016
2017 CscMatrix::from_column_iter(rows, cols, column_major_iter)
2018 }
2019
2020 /// Generate the entire objective function for the entire trajectory for a single dimension
2021 /// # Returns
2022 /// * The objective function matrix.
2023 fn generate_obj_function(&self) -> DMatrix<f64> {
2024 let num_segments = self.segment_times.len();
2025 let coeff_num = self.polyorder * num_segments;
2026
2027 let mut q_total: DMatrix<f64> = DMatrix::zeros(coeff_num, coeff_num);
2028
2029 for (i, &segment_time) in self.segment_times.iter().enumerate() {
2030 let q_segment = self.generate_q(self.min_deriv, segment_time);
2031 q_total
2032 .view_mut(
2033 (i * self.polyorder, i * self.polyorder),
2034 (self.polyorder, self.polyorder),
2035 )
2036 .copy_from(&q_segment);
2037 }
2038 q_total
2039 }
2040
2041 /// Generate the Q matrix for a single segment of a single dimension
2042 /// # Arguments
2043 /// * `min_deriv` - The minimum derivative order to consider.
2044 /// * `time` - The time at which to evaluate the basis functions.
2045 /// # Returns
2046 /// * The Q matrix for the segment.
2047 fn generate_q(&self, min_deriv: usize, time: f32) -> DMatrix<f64> {
2048 let mut q: DMatrix<f64> = DMatrix::zeros(self.polyorder, self.polyorder);
2049 let poly = self.basis(time, min_deriv);
2050
2051 for i in 0..self.polyorder {
2052 for j in 0..self.polyorder {
2053 let power_order = ((i + j) as f64 - (2 * min_deriv) as f64 + 1.0).max(1.0); // Avoid division by zero
2054 q[(i, j)] = poly[i] * poly[j] * time as f64 / power_order;
2055 }
2056 }
2057 q
2058 }
2059
2060 /// Generate the equality matrix for a single dimension
2061 /// # Arguments
2062 /// * `dimension` - The dimension for which to generate the equality matrix.
2063 /// # Returns
2064 /// * The equality matrix and the vector of constraints.
2065 fn generate_equality_constraints(&self, dimension: usize) -> (DMatrix<f64>, DVector<f64>) {
2066 let num_segments = self.segment_times.len();
2067 let num_coeff = self.polyorder * num_segments;
2068
2069 // 1 position constraint + 4 derivative constraints (upto snap) for each beginning and ending waypoint = 5*2 +
2070 // 2 position constraints + 4 derivative constraints (upto snap) for each intermediate waypoint = 6*num__intermediate_waypoints. This is basically 6*(num_segments+1)-2.
2071 let num_constraints = (2 + self.smooth_upto) * (num_segments + 1) - 2;
2072
2073 // Initialize the equality matrix a and the vector b. a*x = b, where x is the vector of coefficients we want to compute.
2074
2075 let mut a: DMatrix<f64> = DMatrix::zeros(num_constraints, num_coeff);
2076 let mut b: DVector<f64> = DVector::zeros(num_constraints);
2077
2078 let mut row_index: usize = 0;
2079
2080 for i in 0..=num_segments {
2081 let col_offset = i * self.polyorder;
2082
2083 match i {
2084 // First part of the first waypoint of the trajectory. NOTE: The first position is assumed to be the position of the robot. Handles position[0] + derivatives = 0
2085 // This results in 5 constraints (rows) added here.
2086 0 => {
2087 let row = self.basis(0.0, 0);
2088 a.row_mut(row_index)
2089 .columns_mut(col_offset, self.polyorder)
2090 .copy_from(&row.transpose());
2091 b[row_index] = self.waypoints[i][dimension] as f64;
2092 row_index += 1;
2093
2094 //All derivatives from velocity up until snap must be 0
2095 for j in 1..=self.smooth_upto {
2096 let row = self.basis(0.0, j);
2097 a.row_mut(row_index)
2098 .columns_mut(col_offset, self.polyorder)
2099 .copy_from(&row.transpose());
2100 b[row_index] = 0.0;
2101 row_index += 1;
2102 }
2103 }
2104 //Last part of the last waypoint of the trajectory. Handles position[n-1] + derivatives = 0. This results in 5 constraints (rows) added here.
2105 n if n == num_segments => {
2106 let time = self.segment_times[num_segments - 1];
2107 let row = self.basis(time, 0);
2108 a.row_mut(row_index)
2109 .columns_mut(col_offset - self.polyorder, self.polyorder)
2110 .copy_from(&row.transpose());
2111 b[row_index] = self.waypoints[i][dimension] as f64;
2112 row_index += 1;
2113
2114 //All derivatives from velocity up until snap must be 0
2115 for j in 1..=self.smooth_upto {
2116 let row = self.basis(time, j);
2117 a.row_mut(row_index)
2118 .columns_mut(col_offset - self.polyorder, self.polyorder)
2119 .copy_from(&row.transpose());
2120 b[row_index] = 0.0;
2121 row_index += 1;
2122 }
2123 }
2124 // Intermediate segments. Handles prev_segment_position[i-1] = waypoint[i] = curr_segment_position[i] + derivative[i-1] = derivative[i]
2125 // This results in 6 constraints (rows) added here.
2126 _ => {
2127 {
2128 // Position constraint. position at segment i-1 must be equal to position at i which is equal to waypoint[i]
2129 let segment_prev = self.basis(self.segment_times[i - 1], 0);
2130 let segment_next = self.basis(0.0, 0);
2131 let prev_col_offset = (i - 1) * self.polyorder;
2132
2133 // Offset the col index to i-1 to assign it to the previous segment's coefficients
2134 a.row_mut(row_index)
2135 .columns_mut(prev_col_offset, self.polyorder)
2136 .copy_from(&segment_prev.transpose());
2137 b[row_index] = self.waypoints[i][dimension] as f64;
2138 row_index += 1;
2139 // Offset the col index to i to assign it to this segment's coefficients
2140 a.row_mut(row_index)
2141 .columns_mut(col_offset, self.polyorder)
2142 .copy_from(&segment_next.transpose());
2143 b[row_index] = self.waypoints[i][dimension] as f64;
2144 row_index += 1;
2145
2146 // To ensure velocity upto snap continuity, we equate the derivatives of the previous segment with the derivatives of the next segment.
2147 // Since we don't care what the value of the derivatives are, we just formulate the constraint such that deriv[prev_segment] - deriv[next_segment] = 0.
2148 // We do this by placing the derivatives of the prev segment at those times in the same row, along with the negative of next segment's derivatives.
2149
2150 for j in 1..=self.smooth_upto {
2151 let segment_prev = self.basis(self.segment_times[i - 1], j);
2152 let segment_next = self.basis(0.0, j);
2153
2154 a.row_mut(row_index)
2155 .columns_mut(prev_col_offset, self.polyorder)
2156 .copy_from(&segment_prev.transpose());
2157 a.row_mut(row_index)
2158 .columns_mut(col_offset, self.polyorder)
2159 .add_assign(-segment_next.transpose());
2160
2161 b[row_index] = 0.0;
2162
2163 row_index += 1;
2164 }
2165 }
2166 }
2167 }
2168 }
2169
2170 (a, b)
2171 }
2172 /// Compute the number of constraints
2173 /// # Returns
2174 /// * The total number of constraints.
2175 fn compute_num_constraints(&self) -> usize {
2176 // Skip if no inequality constraints
2177 if self.max_velocity == 0.0 || self.max_acceleration == 0.0 {
2178 return 0;
2179 }
2180 // For each segment, compute number of timesteps
2181 let total_constraints: usize = self
2182 .segment_times
2183 .iter()
2184 .map(|&segment_time| {
2185 // Calculate number of steps, excluding the end point
2186 let num_timesteps = (segment_time / self.dt).floor() as usize;
2187 // 2 constraints (velocity and acceleration) per timestep
2188 num_timesteps * 2
2189 })
2190 .sum();
2191 total_constraints
2192 }
2193
2194 /// Generate inequality constrain for a single dimension. This works by sampling the derivatives using the basis vector across time steps and
2195 /// enforcing velocity and acceleration constraints for each of those basis vectors.
2196 /// # Returns
2197 /// A tuple containing the inequality matrix `c`, the lower bound vector `d`, and the upper bound vector `f`.
2198 fn generate_inequality_constraints(&self) -> (DMatrix<f64>, DVector<f64>, DVector<f64>) {
2199 let num_segments = self.segment_times.len();
2200 let coeff_num = self.polyorder * num_segments;
2201
2202 let num_constraints = self.compute_num_constraints();
2203
2204 // Initialize the inequality matrix c, along with the vectors d and f, representing the inequality d <= c*x <= f, where x is the vector of coefficients.
2205
2206 let mut c: DMatrix<f64> = DMatrix::zeros(num_constraints, coeff_num);
2207 let mut d = DVector::from_element(num_constraints, 0.0); // We fill this up to -max_velocity or -max_acceleration.
2208 let mut f = DVector::from_element(num_constraints, 0.0); // We fill this up.
2209
2210 let mut row_index = 0;
2211
2212 // Iterate over each segment, then each time step in one segment, compute its basis vector for velocity and acceleration, and set the maximum.
2213 for (i, &segment_time) in self.segment_times.iter().enumerate() {
2214 // Col index for each segment
2215 let col_offset = i * self.polyorder;
2216
2217 let mut t = self.dt; // To avoid placing equality and inequality constraints at in the beginning.
2218 //Iterate over each time step in one segment
2219 while t < segment_time {
2220 let row_velocity = self.basis(t, 1);
2221
2222 c.row_mut(row_index)
2223 .columns_mut(col_offset, self.polyorder)
2224 .copy_from(&row_velocity.transpose());
2225 f[row_index] = self.max_velocity as f64;
2226 d[row_index] = -self.max_velocity as f64;
2227 row_index += 1;
2228
2229 let row_acceleration = self.basis(t, 2);
2230
2231 c.row_mut(row_index)
2232 .columns_mut(col_offset, self.polyorder)
2233 .copy_from(&row_acceleration.transpose());
2234 f[row_index] = self.max_acceleration as f64;
2235 d[row_index] = -self.max_acceleration as f64;
2236 row_index += 1;
2237 t += self.dt; // Update time step to compute basis vector at the next step.
2238 }
2239 }
2240 (c, d, f)
2241 }
2242
2243 /// Generate the basis vector of the given derivative order at the given time.
2244 /// # Arguments
2245 /// * `time` - The time at which to evaluate the basis vector.
2246 /// * `derivative` - The derivative order of the basis vector.
2247 /// # Returns
2248 /// * The basis vector of the given derivative order at the given time.
2249 fn basis(&self, time: f32, derivative: usize) -> DVector<f64> {
2250 assert!(derivative <= 4, "Derivative order must be less than 4");
2251 let time_f64 = time as f64;
2252 let t: Vec<f64> = (0..self.polyorder)
2253 .map(|i| time_f64.powi(i as i32))
2254 .collect();
2255
2256 let mut b = DVector::from_element(self.polyorder, 0.0);
2257
2258 for i in 0..self.polyorder {
2259 let power = i as isize - derivative as isize;
2260 let mut coeff = 1.0;
2261
2262 if power < 0 {
2263 b[i] = 0.0;
2264 } else {
2265 for j in (i - derivative + 1..=i).rev() {
2266 coeff *= j as f64;
2267 }
2268 b[i] = coeff * t[power as usize];
2269 }
2270 }
2271 b
2272 }
2273}
2274
2275/// Implement the `Planner` trait for `QPpolyTrajPlanner`
2276impl Planner for QPpolyTrajPlanner {
2277 fn plan(
2278 &self,
2279 _current_position: Vector3<f32>,
2280 _current_velocity: Vector3<f32>,
2281 time: f32,
2282 ) -> (Vector3<f32>, Vector3<f32>, f32) {
2283 let relative_time = time - self.start_time;
2284 // Find the current segment
2285 let mut segment_start_time = 0.0;
2286 let mut current_segment = 0;
2287 for (i, &segment_duration) in self.segment_times.iter().enumerate() {
2288 if relative_time < segment_start_time + segment_duration {
2289 current_segment = i;
2290 break;
2291 }
2292 segment_start_time += segment_duration;
2293 }
2294 // Evaluate the polynomial for the current segment
2295 let segment_time = relative_time - segment_start_time;
2296 let (position, velocity, yaw, _yaw_rate) =
2297 self.evaluate_polynomial(segment_time, current_segment);
2298 (position, velocity, yaw)
2299 }
2300
2301 fn is_finished(
2302 &self,
2303 current_position: Vector3<f32>,
2304 time: f32,
2305 ) -> Result<bool, SimulationError> {
2306 let last_waypoint = self.waypoints.last().ok_or(SimulationError::OtherError(
2307 "No waypoints available".to_string(),
2308 ))?;
2309
2310 let total_time: f32 = self.segment_times.iter().sum();
2311
2312 let last_position = Vector3::new(last_waypoint[0], last_waypoint[1], last_waypoint[2]);
2313
2314 Ok(time >= self.start_time + total_time && (current_position - last_position).norm() < 0.1)
2315 }
2316}
2317/// Represents a step in the planner schedule.
2318/// # Example
2319/// ```
2320/// use peng_quad::PlannerStepConfig;
2321/// let step = PlannerStepConfig {
2322/// step: 0,
2323/// planner_type: "MinimumJerkLocalPlanner".to_string(),
2324/// params: serde_yaml::Value::Null,
2325/// };
2326/// ```
2327pub struct PlannerStepConfig {
2328 /// The simulation step at which this planner should be activated (in ms unit).
2329 pub step: usize,
2330 /// The type of planner to use for this step.
2331 pub planner_type: String,
2332 /// Additional parameters for the planner, stored as a YAML value.
2333 pub params: serde_yaml::Value,
2334}
2335/// Updates the planner based on the current simulation step and configuration
2336/// # Arguments
2337/// * `planner_manager` - The PlannerManager instance to update
2338/// * `step` - The current simulation step in ms unit
2339/// * `time` - The current simulation time
2340/// * `simulation_frequency' - The simulation frequency in Hz
2341/// * `quad` - The Quadrotor instance
2342/// * `obstacles` - The current obstacles in the simulation
2343/// * `planner_config` - The planner configuration
2344/// # Errors
2345/// * If the planner could not be created
2346/// # Example
2347/// ```
2348/// use peng_quad::{PlannerManager, Quadrotor, Obstacle, PlannerStepConfig, update_planner};
2349/// use nalgebra::Vector3;
2350/// let simulation_frequency = 1000;
2351/// let initial_position = Vector3::new(0.0, 0.0, 0.0);
2352/// let initial_yaw = 0.0;
2353/// let mut planner_manager = PlannerManager::new(initial_position, initial_yaw);
2354/// let step = 0;
2355/// let time = 0.0;
2356/// let (time_step, mass, gravity, drag_coefficient) = (0.01, 1.3, 9.81, 0.01);
2357/// let inertia_matrix = [0.0347563, 0.0, 0.0, 0.0, 0.0458929, 0.0, 0.0, 0.0, 0.0977];
2358/// let quadrotor = Quadrotor::new(time_step, mass, gravity, drag_coefficient, inertia_matrix).unwrap();
2359/// let obstacles = vec![Obstacle::new(Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 0.0), 1.0)];
2360/// let planner_config = vec![PlannerStepConfig {
2361/// step: 0,
2362/// planner_type: "MinimumJerkLine".to_string(),
2363/// params:
2364/// serde_yaml::from_str(r#"
2365/// end_position: [0.0, 0.0, 1.0]
2366/// end_yaw: 0.0
2367/// duration: 2.0
2368/// "#).unwrap(),
2369/// }];
2370/// update_planner(&mut planner_manager, step, time, simulation_frequency, &quadrotor, &obstacles, &planner_config).unwrap();
2371/// ```
2372pub fn update_planner(
2373 planner_manager: &mut PlannerManager,
2374 step: usize,
2375 time: f32,
2376 simulation_frequency: usize,
2377 quad: &Quadrotor,
2378 obstacles: &[Obstacle],
2379 planner_config: &[PlannerStepConfig],
2380) -> Result<(), SimulationError> {
2381 if let Some(planner_step) = planner_config
2382 .iter()
2383 .find(|s| s.step * simulation_frequency == step * 1000)
2384 {
2385 log::info!("Time: {:.2} s,\tSwitch {}", time, planner_step.planner_type);
2386 planner_manager.set_planner(create_planner(planner_step, quad, time, obstacles)?);
2387 }
2388 Ok(())
2389}
2390/// Creates a planner based on the configuration
2391/// # Arguments
2392/// * `step` - The configuration for the planner step in ms unit
2393/// * `quad` - The Quadrotor instance
2394/// * `time` - The current simulation time
2395/// * `obstacles` - The current obstacles in the simulation
2396/// # Returns
2397/// * `PlannerType` - The created planner
2398/// # Errors
2399/// * If the planner type is not recognized
2400/// # Example
2401/// ```
2402/// use peng_quad::{PlannerType, Quadrotor, Obstacle, PlannerStepConfig, create_planner};
2403/// use nalgebra::Vector3;
2404/// let step = PlannerStepConfig {
2405/// step: 0,
2406/// planner_type: "MinimumJerkLine".to_string(),
2407/// params:
2408/// serde_yaml::from_str(r#"
2409/// end_position: [0.0, 0.0, 1.0]
2410/// end_yaw: 0.0
2411/// duration: 2.0
2412/// "#).unwrap(),
2413/// };
2414/// let time = 0.0;
2415/// let (time_step, mass, gravity, drag_coefficient) = (0.01, 1.3, 9.81, 0.01);
2416/// let inertia_matrix = [0.0347563, 0.0, 0.0, 0.0, 0.0458929, 0.0, 0.0, 0.0, 0.0977];
2417/// let quadrotor = Quadrotor::new(time_step, mass, gravity, drag_coefficient, inertia_matrix).unwrap();
2418/// let obstacles = vec![Obstacle::new(Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 0.0), 1.0)];
2419/// let planner = create_planner(&step, &quadrotor, time, &obstacles).unwrap();
2420/// match planner {
2421/// PlannerType::MinimumJerkLine(_) => log::info!("Created MinimumJerkLine planner"),
2422/// _ => log::info!("Created another planner"),
2423/// }
2424/// ```
2425pub fn create_planner(
2426 step: &PlannerStepConfig,
2427 quad: &Quadrotor,
2428 time: f32,
2429 obstacles: &[Obstacle],
2430) -> Result<PlannerType, SimulationError> {
2431 let params = &step.params;
2432 match step.planner_type.as_str() {
2433 "MinimumJerkLine" => Ok(PlannerType::MinimumJerkLine(MinimumJerkLinePlanner {
2434 start_position: quad.position,
2435 end_position: parse_vector3(params, "end_position")?,
2436 start_yaw: quad.orientation.euler_angles().2,
2437 end_yaw: parse_f32(params, "end_yaw")?,
2438 start_time: time,
2439 duration: parse_f32(params, "duration")?,
2440 })),
2441 "Lissajous" => Ok(PlannerType::Lissajous(LissajousPlanner {
2442 start_position: quad.position,
2443 center: parse_vector3(params, "center")?,
2444 amplitude: parse_vector3(params, "amplitude")?,
2445 frequency: parse_vector3(params, "frequency")?,
2446 phase: parse_vector3(params, "phase")?,
2447 start_time: time,
2448 duration: parse_f32(params, "duration")?,
2449 start_yaw: quad.orientation.euler_angles().2,
2450 end_yaw: parse_f32(params, "end_yaw")?,
2451 ramp_time: parse_f32(params, "ramp_time")?,
2452 })),
2453 "Circle" => Ok(PlannerType::Circle(CirclePlanner {
2454 center: parse_vector3(params, "center")?,
2455 radius: parse_f32(params, "radius")?,
2456 angular_velocity: parse_f32(params, "angular_velocity")?,
2457 start_position: quad.position,
2458 start_time: time,
2459 duration: parse_f32(params, "duration")?,
2460 start_yaw: quad.orientation.euler_angles().2,
2461 end_yaw: quad.orientation.euler_angles().2,
2462 ramp_time: parse_f32(params, "ramp_time")?,
2463 })),
2464 "ObstacleAvoidance" => Ok(PlannerType::ObstacleAvoidance(ObstacleAvoidancePlanner {
2465 target_position: parse_vector3(params, "target_position")?,
2466 start_time: time,
2467 duration: parse_f32(params, "duration")?,
2468 start_yaw: quad.orientation.euler_angles().2,
2469 end_yaw: parse_f32(params, "end_yaw")?,
2470 obstacles: obstacles.to_owned(),
2471 k_att: parse_f32(params, "k_att")?,
2472 k_rep: parse_f32(params, "k_rep")?,
2473 k_vortex: parse_f32(params, "k_vortex")?,
2474 d0: parse_f32(params, "d0")?,
2475 d_target: parse_f32(params, "d_target")?,
2476 max_speed: parse_f32(params, "max_speed")?,
2477 })),
2478 "MinimumSnapWaypoint" => {
2479 let mut waypoints = vec![quad.position];
2480 waypoints.extend(
2481 params["waypoints"]
2482 .as_sequence()
2483 .ok_or_else(|| SimulationError::OtherError("Invalid waypoints".to_string()))?
2484 .iter()
2485 .map(|w| {
2486 w.as_sequence()
2487 .and_then(|coords| {
2488 Some(Vector3::new(
2489 coords[0].as_f64()? as f32,
2490 coords[1].as_f64()? as f32,
2491 coords[2].as_f64()? as f32,
2492 ))
2493 })
2494 .ok_or(SimulationError::OtherError("Invalid waypoint".to_string()))
2495 })
2496 .collect::<Result<Vec<Vector3<f32>>, SimulationError>>()?,
2497 );
2498 let mut yaws = vec![quad.orientation.euler_angles().2];
2499 yaws.extend(
2500 params["yaws"]
2501 .as_sequence()
2502 .ok_or(SimulationError::OtherError("Invalid yaws".to_string()))?
2503 .iter()
2504 .map(|y| {
2505 y.as_f64()
2506 .map(|v| v as f32)
2507 .ok_or(SimulationError::OtherError("Invalid yaw".to_string()))
2508 })
2509 .collect::<Result<Vec<f32>, SimulationError>>()?,
2510 );
2511 let segment_times = params["segment_times"]
2512 .as_sequence()
2513 .ok_or_else(|| SimulationError::OtherError("Invalid segment_times".to_string()))?
2514 .iter()
2515 .map(|t| {
2516 t.as_f64().map(|v| v as f32).ok_or_else(|| {
2517 SimulationError::OtherError("Invalid segment time".to_string())
2518 })
2519 })
2520 .collect::<Result<Vec<f32>, SimulationError>>()?;
2521 MinimumSnapWaypointPlanner::new(waypoints, yaws, segment_times, time)
2522 .map(PlannerType::MinimumSnapWaypoint)
2523 }
2524 "QPpolyTraj" => {
2525 let mut waypoints = vec![vec![
2526 quad.position.x,
2527 quad.position.y,
2528 quad.position.z,
2529 quad.orientation.euler_angles().2,
2530 ]];
2531 waypoints.extend(
2532 params["waypoints"]
2533 .as_sequence()
2534 .ok_or_else(|| SimulationError::OtherError("Invalid waypoints".to_string()))?
2535 .iter()
2536 .map(|w| {
2537 w.as_sequence()
2538 .and_then(|coords| {
2539 Some(vec![
2540 coords[0].as_f64()? as f32,
2541 coords[1].as_f64()? as f32,
2542 coords[2].as_f64()? as f32,
2543 coords[3].as_f64()? as f32,
2544 ])
2545 })
2546 .ok_or(SimulationError::OtherError("Invalid waypoint".to_string()))
2547 })
2548 .collect::<Result<Vec<Vec<f32>>, SimulationError>>()?,
2549 );
2550
2551 let segment_times = params["segment_times"]
2552 .as_sequence()
2553 .ok_or_else(|| SimulationError::OtherError("Invalid segment_times".to_string()))?
2554 .iter()
2555 .map(|t| {
2556 t.as_f64().map(|v| v as f32).ok_or_else(|| {
2557 SimulationError::OtherError("Invalid segment time".to_string())
2558 })
2559 })
2560 .collect::<Result<Vec<f32>, SimulationError>>()?;
2561 QPpolyTrajPlanner::new(
2562 waypoints,
2563 segment_times,
2564 parse_usize(params, "polynomial_order")?,
2565 parse_usize(params, "minimize_derivative")?,
2566 parse_usize(params, "smooth_upto")?,
2567 parse_f32(params, "max_velocity")?,
2568 parse_f32(params, "max_acceleration")?,
2569 time,
2570 parse_f32(params, "dt")?,
2571 )
2572 .map(PlannerType::QPpolyTraj)
2573 }
2574 "Landing" => Ok(PlannerType::Landing(LandingPlanner {
2575 start_position: quad.position,
2576 start_time: time,
2577 duration: parse_f32(params, "duration")?,
2578 start_yaw: quad.orientation.euler_angles().2,
2579 })),
2580 _ => Err(SimulationError::OtherError(format!(
2581 "Unknown planner type: {}",
2582 step.planner_type
2583 ))),
2584 }
2585}
2586/// Helper function to parse Vector3 from YAML
2587/// # Arguments
2588/// * `value` - YAML value
2589/// * `key` - key to parse
2590/// # Returns
2591/// * `Vector3<f32>` - parsed vector
2592/// # Errors
2593/// * `SimulationError` - if the value is not a valid vector
2594/// # Example
2595/// ```
2596/// use nalgebra::Vector3;
2597/// use peng_quad::{parse_vector3, SimulationError};
2598/// let value = serde_yaml::from_str("test: [1.0, 2.0, 3.0]").unwrap();
2599/// assert_eq!(parse_vector3(&value, "test").unwrap(), Vector3::new(1.0, 2.0, 3.0));
2600/// ```
2601pub fn parse_vector3(
2602 value: &serde_yaml::Value,
2603 key: &str,
2604) -> Result<Vector3<f32>, SimulationError> {
2605 value[key]
2606 .as_sequence()
2607 .and_then(|seq| {
2608 if seq.len() == 3 {
2609 Some(Vector3::new(
2610 seq[0].as_f64()? as f32,
2611 seq[1].as_f64()? as f32,
2612 seq[2].as_f64()? as f32,
2613 ))
2614 } else {
2615 None
2616 }
2617 })
2618 .ok_or_else(|| SimulationError::OtherError(format!("Invalid {key} vector")))
2619}
2620/// Helper function to parse f32 from YAML
2621/// # Arguments
2622/// * `value` - YAML value
2623/// * `key` - key to parse
2624/// # Returns
2625/// * `f32` - parsed value
2626/// # Errors
2627/// * `SimulationError` - if the value is not a valid f32
2628/// # Example
2629/// ```
2630/// use peng_quad::{parse_f32, SimulationError};
2631/// let value = serde_yaml::from_str("key: 1.0").unwrap();
2632/// let result = parse_f32(&value, "key").unwrap();
2633/// assert_eq!(result, 1.0);
2634/// ```
2635pub fn parse_f32(value: &serde_yaml::Value, key: &str) -> Result<f32, SimulationError> {
2636 value[key]
2637 .as_f64()
2638 .map(|v| v as f32)
2639 .ok_or_else(|| SimulationError::OtherError(format!("Invalid {key}")))
2640}
2641
2642/// Helper function to parse unsigned integer from YAML
2643/// # Arguments
2644/// * `value` - YAML value
2645/// * `key` - key to parse
2646/// # Returns
2647/// * `usize` - parsed value
2648/// # Errors
2649/// * `SimulationError` - if the value is not a valid unsigned integer
2650/// # Example
2651/// ```
2652/// use peng_quad::{parse_usize, SimulationError};
2653/// let value = serde_yaml::from_str("key: 1").unwrap();
2654/// let result = parse_usize(&value, "key").unwrap();
2655/// assert_eq!(result, 1);
2656/// ```
2657pub fn parse_usize(value: &serde_yaml::Value, key: &str) -> Result<usize, SimulationError> {
2658 value[key]
2659 .as_i64()
2660 .and_then(|v| if v >= 0 { Some(v as usize) } else { None }) // Ensure non-negative
2661 .ok_or_else(|| SimulationError::OtherError(format!("Invalid {key}")))
2662}
2663/// Represents an obstacle in the simulation
2664/// # Example
2665/// ```
2666/// use peng_quad::Obstacle;
2667/// use nalgebra::Vector3;
2668/// let obstacle = Obstacle::new(Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 0.0), 1.0);
2669/// ```
2670#[derive(Clone)]
2671pub struct Obstacle {
2672 /// The position of the obstacle
2673 pub position: Vector3<f32>,
2674 /// The velocity of the obstacle
2675 pub velocity: Vector3<f32>,
2676 /// The radius of the obstacle
2677 pub radius: f32,
2678}
2679/// Implementation of the Obstacle
2680impl Obstacle {
2681 /// Creates a new obstacle with the given position, velocity, and radius
2682 /// # Arguments
2683 /// * `position` - The position of the obstacle
2684 /// * `velocity` - The velocity of the obstacle
2685 /// * `radius` - The radius of the obstacle
2686 /// # Returns
2687 /// * The new obstacle instance
2688 /// # Example
2689 /// ```
2690 /// use peng_quad::Obstacle;
2691 /// use nalgebra::Vector3;
2692 /// let obstacle = Obstacle::new(Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 0.0), 1.0);
2693 /// ```
2694 pub fn new(position: Vector3<f32>, velocity: Vector3<f32>, radius: f32) -> Self {
2695 Self {
2696 position,
2697 velocity,
2698 radius,
2699 }
2700 }
2701}
2702/// Represents a maze in the simulation
2703/// # Example
2704/// ```
2705/// use peng_quad::{Maze, Obstacle};
2706/// use rand_chacha::ChaCha8Rng;
2707/// use rand::SeedableRng;
2708/// use nalgebra::Vector3;
2709/// let maze = Maze {
2710/// lower_bounds: [0.0, 0.0, 0.0],
2711/// upper_bounds: [1.0, 1.0, 1.0],
2712/// obstacles: vec![Obstacle::new(Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 0.0), 1.0)],
2713/// obstacles_velocity_bounds: [0.0, 0.0, 0.0],
2714/// obstacles_radius_bounds: [0.0, 0.0],
2715/// rng: ChaCha8Rng::from_rng(&mut rand::rng()),
2716/// };
2717/// ```
2718pub struct Maze {
2719 /// The lower bounds of the maze in the x, y, and z directions
2720 pub lower_bounds: [f32; 3],
2721 /// The upper bounds of the maze in the x, y, and z directions
2722 pub upper_bounds: [f32; 3],
2723 /// The obstacles in the maze
2724 pub obstacles: Vec<Obstacle>,
2725 /// The bounds of the obstacles' velocity
2726 pub obstacles_velocity_bounds: [f32; 3],
2727 /// The bounds of the obstacles' radius
2728 pub obstacles_radius_bounds: [f32; 2],
2729 /// Rng for generating random numbers
2730 pub rng: ChaCha8Rng,
2731}
2732/// Implementation of the maze
2733impl Maze {
2734 /// Creates a new maze with the given bounds and number of obstacles
2735 /// # Arguments
2736 /// * `lower_bounds` - The lower bounds of the maze
2737 /// * `upper_bounds` - The upper bounds of the maze
2738 /// * `num_obstacles` - The number of obstacles in the maze
2739 /// # Returns
2740 /// * The new maze instance
2741 /// # Example
2742 /// ```
2743 /// use peng_quad::Maze;
2744 /// let maze = Maze::new([-1.0, -1.0, -1.0], [1.0, 1.0, 1.0], 5, [0.1, 0.1, 0.1], [0.1, 0.5]);
2745 /// ```
2746 pub fn new(
2747 lower_bounds: [f32; 3],
2748 upper_bounds: [f32; 3],
2749 num_obstacles: usize,
2750 obstacles_velocity_bounds: [f32; 3],
2751 obstacles_radius_bounds: [f32; 2],
2752 ) -> Self {
2753 let mut maze = Maze {
2754 lower_bounds,
2755 upper_bounds,
2756 obstacles: Vec::new(),
2757 obstacles_velocity_bounds,
2758 obstacles_radius_bounds,
2759 rng: ChaCha8Rng::from_rng(&mut rand::rng()),
2760 };
2761 maze.generate_obstacles(num_obstacles);
2762 maze
2763 }
2764 /// Generates the obstacles in the maze
2765 /// # Arguments
2766 /// * `num_obstacles` - The number of obstacles to generate
2767 /// # Example
2768 /// ```
2769 /// use peng_quad::Maze;
2770 /// let mut maze = Maze::new([-1.0, -1.0, -1.0], [1.0, 1.0, 1.0], 5, [0.1, 0.1, 0.1], [0.1, 0.5]);
2771 /// maze.generate_obstacles(5);
2772 /// ```
2773 pub fn generate_obstacles(&mut self, num_obstacles: usize) {
2774 self.obstacles = (0..num_obstacles)
2775 .map(|_| {
2776 let position = Vector3::new(
2777 self.rng
2778 .random_range(self.lower_bounds[0]..self.upper_bounds[0]),
2779 self.rng
2780 .random_range(self.lower_bounds[1]..self.upper_bounds[1]),
2781 self.rng
2782 .random_range(self.lower_bounds[2]..self.upper_bounds[2]),
2783 );
2784 let v_bounds = self.obstacles_velocity_bounds;
2785 let r_bounds = self.obstacles_radius_bounds;
2786 let velocity = Vector3::new(
2787 self.rng.random_range(-v_bounds[0]..v_bounds[0]),
2788 self.rng.random_range(-v_bounds[1]..v_bounds[1]),
2789 self.rng.random_range(-v_bounds[2]..v_bounds[2]),
2790 );
2791 let radius = self.rng.random_range(r_bounds[0]..r_bounds[1]);
2792 Obstacle::new(position, velocity, radius)
2793 })
2794 .collect();
2795 }
2796 /// Updates the obstacles in the maze, if an obstacle hits a boundary, it bounces off
2797 /// # Arguments
2798 /// * `dt` - The time step
2799 /// # Example
2800 /// ```
2801 /// use peng_quad::Maze;
2802 /// let mut maze = Maze::new([-1.0, -1.0, -1.0], [1.0, 1.0, 1.0], 5, [0.1, 0.1, 0.1], [0.1, 0.5]);
2803 /// maze.update_obstacles(0.1);
2804 /// ```
2805 pub fn update_obstacles(&mut self, dt: f32) {
2806 self.obstacles.iter_mut().for_each(|obstacle| {
2807 obstacle.position += obstacle.velocity * dt;
2808 for i in 0..3 {
2809 if obstacle.position[i] - obstacle.radius < self.lower_bounds[i]
2810 || obstacle.position[i] + obstacle.radius > self.upper_bounds[i]
2811 {
2812 obstacle.velocity[i] *= -1.0;
2813 }
2814 }
2815 });
2816 }
2817}
2818/// Represents a camera in the simulation which is used to render the depth of the scene
2819/// # Example
2820/// ```
2821/// use peng_quad::Camera;
2822/// let camera = Camera::new((800, 600), 60.0, 0.1, 100.0);
2823/// ```
2824pub struct Camera {
2825 /// The resolution of the camera
2826 pub resolution: (usize, usize),
2827 /// The vertical field of view of the camera
2828 pub fov_vertical: f32,
2829 /// The horizontal field of view of the camera
2830 pub fov_horizontal: f32,
2831 /// The vertical focal length of the camera
2832 pub vertical_focal_length: f32,
2833 /// The horizontal focal length of the camera
2834 pub horizontal_focal_length: f32,
2835 /// The near clipping plane of the camera
2836 pub near: f32,
2837 /// The far clipping plane of the camera
2838 pub far: f32,
2839 /// The aspect ratio of the camera
2840 pub aspect_ratio: f32,
2841 /// The ray directions of each pixel in the camera
2842 pub ray_directions: Vec<Vector3<f32>>,
2843 /// Depth buffer
2844 pub depth_buffer: Vec<f32>,
2845}
2846/// Implementation of the camera
2847impl Camera {
2848 /// Creates a new camera with the given resolution, field of view, near and far clipping planes
2849 /// # Arguments
2850 /// * `resolution` - The resolution of the camera
2851 /// * `fov_vertical` - The vertical field of view of the camera
2852 /// * `near` - The near clipping plane of the camera
2853 /// * `far` - The far clipping plane of the camera
2854 /// # Returns
2855 /// * The new camera instance
2856 /// # Example
2857 /// ```
2858 /// use peng_quad::Camera;
2859 /// let camera = Camera::new((800, 600), 1.0, 5.0, 120.0);
2860 /// ```
2861 pub fn new(resolution: (usize, usize), fov_vertical: f32, near: f32, far: f32) -> Self {
2862 let (width, height) = resolution;
2863 let (aspect_ratio, tan_half_fov) =
2864 (width as f32 / height as f32, (fov_vertical / 2.0).tan());
2865 let mut ray_directions = Vec::with_capacity(width * height);
2866 for y in 0..height {
2867 for x in 0..width {
2868 let x_ndc = (2.0 * x as f32 / width as f32 - 1.0) * aspect_ratio * tan_half_fov;
2869 let y_ndc = (1.0 - 2.0 * y as f32 / height as f32) * tan_half_fov;
2870 ray_directions.push(Vector3::new(1.0, x_ndc, y_ndc).normalize());
2871 }
2872 }
2873 let fov_horizontal =
2874 (width as f32 / height as f32 * (fov_vertical / 2.0).tan()).atan() * 2.0;
2875 let horizontal_focal_length = (width as f32 / 2.0) / ((fov_horizontal / 2.0).tan());
2876 let vertical_focal_length = (height as f32 / 2.0) / ((fov_vertical / 2.0).tan());
2877 let depth_buffer = vec![0.0; width * height];
2878
2879 Self {
2880 resolution,
2881 fov_vertical,
2882 fov_horizontal,
2883 vertical_focal_length,
2884 horizontal_focal_length,
2885 near,
2886 far,
2887 aspect_ratio,
2888 ray_directions,
2889 depth_buffer,
2890 }
2891 }
2892
2893 /// Renders the depth of the scene from the perspective of the quadrotor
2894 ///
2895 /// When the depth value is out of the near and far clipping planes, it is set to infinity
2896 /// When the resolution is larger than 32x24, multi-threading can accelerate the rendering
2897 /// # Arguments
2898 /// * `quad_position` - The position of the quadrotor
2899 /// * `quad_orientation` - The orientation of the quadrotor
2900 /// * `maze` - The maze in the scene
2901 /// * `use_multi_threading` - Whether to use multi-threading to render the depth
2902 /// # Errors
2903 /// * If the depth buffer is not large enough to store the depth values
2904 /// # Example
2905 /// ```
2906 /// use peng_quad::{Camera, Maze};
2907 /// use nalgebra::{Vector3, UnitQuaternion};
2908 /// let mut camera = Camera::new((800, 600), 60.0, 0.1, 100.0);
2909 /// let quad_position = Vector3::new(0.0, 0.0, 0.0);
2910 /// let quad_orientation = UnitQuaternion::identity();
2911 /// let mut maze = Maze::new([-1.0, -1.0, -1.0], [1.0, 1.0, 1.0], 5, [0.1, 0.1, 0.1], [0.1, 0.5]);
2912 /// let use_multi_threading = true;
2913 /// camera.render_depth(&quad_position, &quad_orientation, &maze, use_multi_threading);
2914 /// let use_multi_threading = false;
2915 /// camera.render_depth(&quad_position, &quad_orientation, &maze, use_multi_threading);
2916 /// ```
2917 pub fn render_depth(
2918 &mut self,
2919 quad_position: &Vector3<f32>,
2920 quad_orientation: &UnitQuaternion<f32>,
2921 maze: &Maze,
2922 use_multi_threading: bool,
2923 ) -> Result<(), SimulationError> {
2924 let (width, height) = self.resolution;
2925 let total_pixels = width * height;
2926 let rotation_camera_to_world = quad_orientation.to_rotation_matrix().matrix()
2927 * Matrix3::new(1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0);
2928 let rotation_world_to_camera = rotation_camera_to_world.transpose();
2929
2930 const CHUNK_SIZE: usize = 64;
2931 if use_multi_threading {
2932 self.depth_buffer
2933 .par_chunks_mut(CHUNK_SIZE)
2934 .enumerate()
2935 .try_for_each(|(chunk_idx, chunk)| {
2936 let start_idx = chunk_idx * CHUNK_SIZE;
2937 for (i, depth) in chunk.iter_mut().enumerate() {
2938 let ray_idx = start_idx + i;
2939 if ray_idx >= total_pixels {
2940 break;
2941 }
2942 *depth = ray_cast(
2943 quad_position,
2944 &rotation_world_to_camera,
2945 &(rotation_camera_to_world * self.ray_directions[ray_idx]),
2946 maze,
2947 self.near,
2948 self.far,
2949 )?;
2950 }
2951 Ok::<(), SimulationError>(())
2952 })?;
2953 } else {
2954 for i in 0..total_pixels {
2955 self.depth_buffer[i] = ray_cast(
2956 quad_position,
2957 &rotation_world_to_camera,
2958 &(rotation_camera_to_world * self.ray_directions[i]),
2959 maze,
2960 self.near,
2961 self.far,
2962 )?;
2963 }
2964 }
2965 Ok(())
2966 }
2967}
2968
2969/// Casts a ray from the camera origin in the given direction
2970/// # Arguments
2971/// * `origin` - The origin of the ray
2972/// * `rotation_world_to_camera` - The rotation matrix from world to camera coordinates
2973/// * `direction` - The direction of the ray
2974/// * `maze` - The maze in the scene
2975/// * `near` - The minimum distance to consider
2976/// * `far` - The maximum distance to consider
2977/// # Returns
2978/// * The distance to the closest obstacle hit by the ray
2979/// # Errors
2980/// * If the ray does not hit any obstacles
2981/// # Example
2982/// ```
2983/// use peng_quad::{ray_cast, Maze};
2984/// use nalgebra::{Vector3, Matrix3};
2985/// let origin = Vector3::new(0.0, 0.0, 0.0);
2986/// let rotation_world_to_camera = Matrix3::identity();
2987/// let direction = Vector3::new(0.0, 0.0, 1.0);
2988/// let maze = Maze::new([-1.0, -1.0, -1.0], [1.0, 1.0, 1.0], 5, [0.1, 0.1, 0.1], [0.1, 0.5]);
2989/// let near = 0.1;
2990/// let far = 100.0;
2991/// let distance = ray_cast(&origin, &rotation_world_to_camera, &direction, &maze, near, far);
2992/// ```
2993pub fn ray_cast(
2994 origin: &Vector3<f32>,
2995 rotation_world_to_camera: &Matrix3<f32>,
2996 direction: &Vector3<f32>,
2997 maze: &Maze,
2998 near: f32,
2999 far: f32,
3000) -> Result<f32, SimulationError> {
3001 let mut closest_hit = far;
3002 // Inline tube intersection
3003 for axis in 0..3 {
3004 let t_near = if direction[axis] > 0.0 {
3005 (maze.upper_bounds[axis] - origin[axis]) / direction[axis]
3006 } else {
3007 (maze.lower_bounds[axis] - origin[axis]) / direction[axis]
3008 };
3009 if t_near > near && t_near < closest_hit {
3010 let intersection_point = origin + direction * t_near;
3011 let mut valid = true;
3012 for i in 0..3 {
3013 if i != axis
3014 && (intersection_point[i] < maze.lower_bounds[i]
3015 || intersection_point[i] > maze.upper_bounds[i])
3016 {
3017 valid = false;
3018 break;
3019 }
3020 }
3021 if valid {
3022 closest_hit = t_near;
3023 }
3024 }
3025 }
3026 // Early exit if we've hit a wall closer than any possible obstacle
3027 if closest_hit <= near {
3028 return Ok(f32::INFINITY);
3029 }
3030 // Inline sphere intersection
3031 for obstacle in &maze.obstacles {
3032 let oc = origin - obstacle.position;
3033 let b = oc.dot(direction);
3034 let c = oc.dot(&oc) - obstacle.radius * obstacle.radius;
3035 let discriminant = b * b - c;
3036 if discriminant >= 0.0 {
3037 // let t = -b - discriminant.sqrt();
3038 let t = -b - fast_sqrt(discriminant);
3039 if t > near && t < closest_hit {
3040 closest_hit = t;
3041 }
3042 }
3043 }
3044 if closest_hit < far {
3045 Ok((rotation_world_to_camera * direction * closest_hit).x)
3046 } else {
3047 Ok(f32::INFINITY)
3048 }
3049}
3050/// Logs simulation data to the rerun recording stream
3051/// # Arguments
3052/// * `rec` - The rerun::RecordingStream instance
3053/// * `quad` - The Quadrotor instance
3054/// * `desired_position` - The desired position vector
3055/// * `measured_accel` - The measured acceleration vector
3056/// * `measured_gyro` - The measured angular velocity vector
3057/// # Errors
3058/// * If the data cannot be logged to the recording stream
3059/// # Example
3060/// ```no_run
3061/// use peng_quad::{Quadrotor, log_data};
3062/// use nalgebra::Vector3;
3063/// let rec = rerun::RecordingStreamBuilder::new("peng").spawn().unwrap();
3064/// let (time_step, mass, gravity, drag_coefficient) = (0.01, 1.3, 9.81, 0.01);
3065/// let inertia_matrix = [0.0347563, 0.0, 0.0, 0.0, 0.0458929, 0.0, 0.0, 0.0, 0.0977];
3066/// let quad = Quadrotor::new(time_step, mass, gravity, drag_coefficient, inertia_matrix).unwrap();
3067/// let desired_position = Vector3::new(0.0, 0.0, 0.0);
3068/// let desired_velocity = Vector3::new(0.0, 0.0, 0.0);
3069/// let measured_accel = Vector3::new(0.0, 0.0, 0.0);
3070/// let measured_gyro = Vector3::new(0.0, 0.0, 0.0);
3071/// log_data(&rec, &quad, &desired_position, &desired_velocity, &measured_accel, &measured_gyro).unwrap();
3072/// ```
3073pub fn log_data(
3074 rec: &rerun::RecordingStream,
3075 quad: &Quadrotor,
3076 desired_position: &Vector3<f32>,
3077 desired_velocity: &Vector3<f32>,
3078 measured_accel: &Vector3<f32>,
3079 measured_gyro: &Vector3<f32>,
3080) -> Result<(), SimulationError> {
3081 rec.log(
3082 "world/quad/desired_position",
3083 &rerun::Points3D::new([(desired_position.x, desired_position.y, desired_position.z)])
3084 .with_radii([0.1])
3085 .with_colors([rerun::Color::from_rgb(255, 255, 255)]),
3086 )?;
3087 rec.log(
3088 "world/quad/base_link",
3089 &rerun::Transform3D::from_translation_rotation(
3090 rerun::Vec3D::new(quad.position.x, quad.position.y, quad.position.z),
3091 rerun::Quaternion::from_xyzw([
3092 quad.orientation.i,
3093 quad.orientation.j,
3094 quad.orientation.k,
3095 quad.orientation.w,
3096 ]),
3097 ),
3098 )?;
3099 rec.log("world/quad/base_link", &rerun::TransformAxes3D::new(0.7))?;
3100 let (quad_roll, quad_pitch, quad_yaw) = quad.orientation.euler_angles();
3101 let quad_euler_angles: Vector3<f32> = Vector3::new(quad_roll, quad_pitch, quad_yaw);
3102 for (pre, vec) in [
3103 ("position", &quad.position),
3104 ("velocity", &quad.velocity),
3105 ("accel", measured_accel),
3106 ("orientation", &quad_euler_angles),
3107 ("gyro", measured_gyro),
3108 ("desired_position", desired_position),
3109 ("desired_velocity", desired_velocity),
3110 ] {
3111 for (i, a) in ["x", "y", "z"].iter().enumerate() {
3112 rec.log(format!("{pre}/{a}"), &rerun::Scalars::single(vec[i] as f64))?;
3113 }
3114 }
3115 Ok(())
3116}
3117/// Log the maze tube to the rerun recording stream
3118/// # Arguments
3119/// * `rec` - The rerun::RecordingStream instance
3120/// * `maze` - The maze instance
3121/// # Errors
3122/// * If the data cannot be logged to the recording stream
3123/// # Example
3124/// ```no_run
3125/// use peng_quad::{Maze, log_maze_tube};
3126/// use rerun::RecordingStreamBuilder;
3127/// let rec = rerun::RecordingStreamBuilder::new("peng").spawn().unwrap();
3128/// let mut maze = Maze::new([-1.0, -1.0, -1.0], [1.0, 1.0, 1.0], 5, [0.1, 0.1, 0.1], [0.1, 0.5]);
3129/// log_maze_tube(&rec, &maze).unwrap();
3130/// ```
3131pub fn log_maze_tube(rec: &rerun::RecordingStream, maze: &Maze) -> Result<(), SimulationError> {
3132 let (lower_bounds, upper_bounds) = (maze.lower_bounds, maze.upper_bounds);
3133 let center_position = rerun::external::glam::Vec3::new(
3134 (lower_bounds[0] + upper_bounds[0]) / 2.0,
3135 (lower_bounds[1] + upper_bounds[1]) / 2.0,
3136 (lower_bounds[2] + upper_bounds[2]) / 2.0,
3137 );
3138 let half_sizes = rerun::external::glam::Vec3::new(
3139 (upper_bounds[0] - lower_bounds[0]) / 2.0,
3140 (upper_bounds[1] - lower_bounds[1]) / 2.0,
3141 (upper_bounds[2] - lower_bounds[2]) / 2.0,
3142 );
3143 rec.log(
3144 "world/maze/tube",
3145 &rerun::Boxes3D::from_centers_and_half_sizes([center_position], [half_sizes])
3146 .with_colors([rerun::Color::from_rgb(128, 128, 255)]),
3147 )?;
3148 Ok(())
3149}
3150/// Log the maze obstacles to the rerun recording stream
3151/// # Arguments
3152/// * `rec` - The rerun::RecordingStream instance
3153/// * `maze` - The maze instance
3154/// # Errors
3155/// * If the data cannot be logged to the recording stream
3156/// # Example
3157/// ```no_run
3158/// use peng_quad::{Maze, log_maze_obstacles};
3159/// let rec = rerun::RecordingStreamBuilder::new("peng").spawn().unwrap();
3160/// let mut maze = Maze::new([-1.0, -1.0, -1.0], [1.0, 1.0, 1.0], 5, [0.1, 0.1, 0.1], [0.1, 0.5]);
3161/// log_maze_obstacles(&rec, &maze).unwrap();
3162/// ```
3163pub fn log_maze_obstacles(
3164 rec: &rerun::RecordingStream,
3165 maze: &Maze,
3166) -> Result<(), SimulationError> {
3167 let (positions, radii): (Vec<(f32, f32, f32)>, Vec<f32>) = maze
3168 .obstacles
3169 .iter()
3170 .map(|obstacle| {
3171 let pos = obstacle.position;
3172 ((pos.x, pos.y, pos.z), obstacle.radius)
3173 })
3174 .unzip();
3175 rec.log(
3176 "world/maze/obstacles",
3177 &rerun::Points3D::new(positions)
3178 .with_radii(radii)
3179 .with_colors([rerun::Color::from_rgb(255, 128, 128)]),
3180 )?;
3181 Ok(())
3182}
3183/// A struct to hold trajectory data
3184/// # Example
3185/// ```
3186/// use peng_quad::Trajectory;
3187/// let initial_point = nalgebra::Vector3::new(0.0, 0.0, 0.0);
3188/// let mut trajectory = Trajectory::new(initial_point);
3189/// ```
3190pub struct Trajectory {
3191 /// A vector of 3D points
3192 pub points: Vec<Vector3<f32>>,
3193 /// The last point that was logged
3194 pub last_logged_point: Vector3<f32>,
3195 /// The minimum distance between points to log
3196 pub min_distance_threadhold: f32,
3197}
3198/// Implement the Trajectory struct
3199impl Trajectory {
3200 /// Create a new Trajectory instance
3201 /// # Arguments
3202 /// * `initial_point` - The initial point to add to the trajectory
3203 /// # Returns
3204 /// * A new Trajectory instance
3205 /// # Example
3206 /// ```
3207 /// use peng_quad::Trajectory;
3208 /// let initial_point = nalgebra::Vector3::new(0.0, 0.0, 0.0);
3209 /// let mut trajectory = Trajectory::new(initial_point);
3210 /// ```
3211 pub fn new(initial_point: Vector3<f32>) -> Self {
3212 Self {
3213 points: vec![initial_point],
3214 last_logged_point: initial_point,
3215 min_distance_threadhold: 0.05,
3216 }
3217 }
3218 /// Add a point to the trajectory if it is further than the minimum distance threshold
3219 /// # Arguments
3220 /// * `point` - The point to add
3221 /// # Returns
3222 /// * `true` if the point was added, `false` otherwise
3223 /// # Example
3224 /// ```
3225 /// use peng_quad::Trajectory;
3226 /// let mut trajectory = Trajectory::new(nalgebra::Vector3::new(0.0, 0.0, 0.0));
3227 /// let point = nalgebra::Vector3::new(1.0, 0.0, 0.0);
3228 /// assert_eq!(trajectory.add_point(point), true);
3229 /// assert_eq!(trajectory.add_point(point), false);
3230 /// ```
3231 pub fn add_point(&mut self, point: Vector3<f32>) -> bool {
3232 if (point - self.last_logged_point).norm() > self.min_distance_threadhold {
3233 self.points.push(point);
3234 self.last_logged_point = point;
3235 true
3236 } else {
3237 false
3238 }
3239 }
3240}
3241/// log trajectory data to the rerun recording stream
3242/// # Arguments
3243/// * `rec` - The rerun::RecordingStream instance
3244/// * `trajectory` - The Trajectory instance
3245/// # Errors
3246/// * If the data cannot be logged to the recording stream
3247/// # Example
3248/// ```no_run
3249/// use peng_quad::{Trajectory, log_trajectory};
3250/// use nalgebra::Vector3;
3251/// let rec = rerun::RecordingStreamBuilder::new("peng").spawn().unwrap();
3252/// let mut trajectory = Trajectory::new(nalgebra::Vector3::new(0.0, 0.0, 0.0));
3253/// trajectory.add_point(nalgebra::Vector3::new(1.0, 0.0, 0.0));
3254/// log_trajectory(&rec, &trajectory).unwrap();
3255/// ```
3256pub fn log_trajectory(
3257 rec: &rerun::RecordingStream,
3258 trajectory: &Trajectory,
3259) -> Result<(), SimulationError> {
3260 let path = trajectory
3261 .points
3262 .iter()
3263 .map(|p| (p.x, p.y, p.z))
3264 .collect::<Vec<(f32, f32, f32)>>();
3265 rec.log(
3266 "world/quad/path",
3267 &rerun::LineStrips3D::new([path]).with_colors([rerun::Color::from_rgb(0, 255, 255)]),
3268 )?;
3269 Ok(())
3270}
3271/// Log depth image data to the rerun recording stream
3272///
3273/// When the depth value is `f32::INFINITY`, the pixel is considered invalid and logged as black
3274/// When the resolution is larger than 32x24, multi-threading can accelerate the rendering
3275/// # Arguments
3276/// * `rec` - The rerun::RecordingStream instance
3277/// * `cam` - The Camera instance
3278/// * `use_multi_threading` - Whether to use multithreading to log the depth image
3279/// # Errors
3280/// * If the data cannot be logged to the recording stream
3281/// # Example
3282/// ```no_run
3283/// use peng_quad::{log_depth_image, Camera};
3284/// let rec = rerun::RecordingStreamBuilder::new("peng").spawn().unwrap();
3285/// let camera = Camera::new((640, 480), 0.1, 100.0, 60.0);
3286/// let use_multi_threading = false;
3287/// log_depth_image(&rec, &camera, use_multi_threading).unwrap();
3288/// let use_multi_threading = true;
3289/// log_depth_image(&rec, &camera, use_multi_threading).unwrap();
3290/// ```
3291pub fn log_depth_image(
3292 rec: &rerun::RecordingStream,
3293 cam: &Camera,
3294 use_multi_threading: bool,
3295) -> Result<(), SimulationError> {
3296 let (width, height) = (cam.resolution.0, cam.resolution.1);
3297 let (min_depth, max_depth) = (cam.near, cam.far);
3298 let depth_image = &cam.depth_buffer;
3299 let mut image: rerun::external::ndarray::Array<u8, _> =
3300 rerun::external::ndarray::Array::zeros((height, width, 3));
3301 let depth_range = max_depth - min_depth;
3302 let scale_factor = 255.0 / depth_range;
3303 if use_multi_threading {
3304 const CHUNK_SIZE: usize = 32;
3305 image
3306 .as_slice_mut()
3307 .expect("Failed to get mutable slice of image")
3308 .par_chunks_exact_mut(CHUNK_SIZE * 3)
3309 .enumerate()
3310 .for_each(|(chunk_index, chunk)| {
3311 let start_index = chunk_index * 32;
3312 for (i, pixel) in chunk.chunks_exact_mut(3).enumerate() {
3313 let idx = start_index + i;
3314 let depth = depth_image[idx];
3315 let color = if depth.is_finite() {
3316 let normalized_depth =
3317 ((depth - min_depth) * scale_factor).clamp(0.0, 255.0);
3318 color_map_fn(normalized_depth)
3319 } else {
3320 (0, 0, 0)
3321 };
3322 (pixel[0], pixel[1], pixel[2]) = color;
3323 }
3324 });
3325 } else {
3326 for (index, pixel) in image
3327 .as_slice_mut()
3328 .expect("Failed to get mutable slice of image")
3329 .chunks_exact_mut(3)
3330 .enumerate()
3331 {
3332 let depth = depth_image[index];
3333 let color = if depth.is_finite() {
3334 let normalized_depth = ((depth - min_depth) * scale_factor).clamp(0.0, 255.0);
3335 color_map_fn(normalized_depth)
3336 } else {
3337 (0, 0, 0)
3338 };
3339 (pixel[0], pixel[1], pixel[2]) = color;
3340 }
3341 }
3342 let rerun_image = rerun::Image::from_color_model_and_tensor(rerun::ColorModel::RGB, image)
3343 .map_err(|e| SimulationError::OtherError(format!("Failed to create rerun image: {e}")))?;
3344 rec.log("world/quad/cam/depth", &rerun_image)?;
3345 Ok(())
3346}
3347/// creates pinhole camera
3348/// # Arguments
3349/// * `rec` - The rerun::RecordingStream instance
3350/// * `cam` - The camera object
3351/// * `cam_position` - The position vector of the camera (aligns with the quad)
3352/// * `cam_orientation` - The orientation quaternion of quad
3353/// * `cam_transform` - The transform matrix between quad and camera alignment
3354/// # Errors
3355/// * If the data cannot be logged to the recording stream
3356/// # Example
3357/// ```no_run
3358/// use peng_quad::{log_pinhole_depth, Camera};
3359/// use nalgebra::{Vector3, UnitQuaternion};
3360/// let rec = rerun::RecordingStreamBuilder::new("peng").spawn().unwrap();
3361/// let depth_image = vec![ 0.0f32 ; 640 * 480];
3362/// let cam_position = Vector3::new(0.0,0.0,0.0);
3363/// let cam_orientation = UnitQuaternion::identity();
3364/// let cam_transform = [0.0, 0.0, 1.0, -1.0, 0.0, 0.0, 0.0, -1.0, 0.0];
3365/// let camera = Camera::new((800, 600), 60.0, 0.1, 100.0);
3366/// log_pinhole_depth(&rec, &camera, cam_position, cam_orientation, cam_transform).unwrap();
3367/// ```
3368pub fn log_pinhole_depth(
3369 rec: &rerun::RecordingStream,
3370 cam: &Camera,
3371 cam_position: Vector3<f32>,
3372 cam_orientation: UnitQuaternion<f32>,
3373 cam_transform: [f32; 9],
3374) -> Result<(), SimulationError> {
3375 let depth_image = &cam.depth_buffer;
3376 let (width, height) = cam.resolution;
3377 let pinhole_camera = rerun::Pinhole::from_focal_length_and_resolution(
3378 (cam.horizontal_focal_length, cam.vertical_focal_length),
3379 (width as f32, height as f32),
3380 )
3381 .with_camera_xyz(rerun::components::ViewCoordinates::RDF)
3382 .with_resolution((width as f32, height as f32))
3383 .with_principal_point((width as f32 / 2.0, height as f32 / 2.0));
3384 let rotated_camera_orientation = UnitQuaternion::from_rotation_matrix(
3385 &(cam_orientation.to_rotation_matrix()
3386 * Rotation3::from_matrix_unchecked(Matrix3::from_row_slice(&cam_transform))),
3387 );
3388 let cam_transform = rerun::Transform3D::from_translation_rotation(
3389 rerun::Vec3D::new(cam_position.x, cam_position.y, cam_position.z),
3390 rerun::Quaternion::from_xyzw([
3391 rotated_camera_orientation.i,
3392 rotated_camera_orientation.j,
3393 rotated_camera_orientation.k,
3394 rotated_camera_orientation.w,
3395 ]),
3396 );
3397 rec.log("world/quad/cam", &cam_transform)?;
3398 rec.log("world/quad/cam", &pinhole_camera)?;
3399 let depth_image_rerun =
3400 rerun::external::ndarray::Array::from_shape_vec((height, width), depth_image.to_vec())
3401 .unwrap();
3402 rec.log(
3403 "world/quad/cam/rerun_depth",
3404 &rerun::DepthImage::try_from(depth_image_rerun)
3405 .unwrap()
3406 .with_meter(1.0),
3407 )?;
3408
3409 Ok(())
3410}
3411/// turbo color map function
3412/// # Arguments
3413/// * `gray` - The gray value in the range [0, 255]
3414/// # Returns
3415/// * The RGB color value in the range [0, 255]
3416/// # Example
3417/// ```
3418/// use peng_quad::color_map_fn;
3419/// let color = color_map_fn(128.0);
3420/// ```
3421#[inline]
3422pub fn color_map_fn(gray: f32) -> (u8, u8, u8) {
3423 let x = gray / 255.0;
3424 let r = (34.61
3425 + x * (1172.33 - x * (10793.56 - x * (33300.12 - x * (38394.49 - x * 14825.05)))))
3426 .clamp(0.0, 255.0) as u8;
3427 let g = (23.31 + x * (557.33 + x * (1225.33 - x * (3574.96 - x * (1073.77 + x * 707.56)))))
3428 .clamp(0.0, 255.0) as u8;
3429 let b = (27.2 + x * (3211.1 - x * (15327.97 - x * (27814.0 - x * (22569.18 - x * 6838.66)))))
3430 .clamp(0.0, 255.0) as u8;
3431 (r, g, b)
3432}
3433
3434/// Fast square root function
3435/// # Arguments
3436/// * `x` - The input value
3437/// # Returns
3438/// * The square root of the input value
3439#[inline(always)]
3440fn fast_sqrt(x: f32) -> f32 {
3441 let i = x.to_bits();
3442 let i = 0x1fbd1df5 + (i >> 1);
3443 f32::from_bits(i)
3444}