use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum BroadphaseKind {
SpatialHash,
AabbTree,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum SolverKind {
SequentialImpulse,
Xpbd,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WorldConfig {
pub timestep: f64,
pub gravity: [f64; 3],
pub velocity_iterations: u32,
pub position_iterations: u32,
pub deterministic: bool,
pub step: u64,
#[serde(default = "default_slop")]
pub position_slop: f64,
#[serde(default = "default_correction")]
pub position_correction: f64,
#[serde(default = "default_max_velocity")]
pub max_velocity: f64,
#[serde(default = "default_sub_steps")]
pub sub_steps: u32,
#[serde(default = "default_constraint_frequency")]
pub constraint_frequency: f64,
#[serde(default = "default_constraint_damping_ratio")]
pub constraint_damping_ratio: f64,
#[serde(default = "default_broadphase")]
pub broadphase: BroadphaseKind,
#[serde(default = "default_solver")]
pub solver: SolverKind,
}
fn default_slop() -> f64 {
0.01
}
fn default_correction() -> f64 {
0.2
}
fn default_max_velocity() -> f64 {
100.0
}
fn default_sub_steps() -> u32 {
1
}
fn default_constraint_frequency() -> f64 {
30.0
}
fn default_constraint_damping_ratio() -> f64 {
1.0
}
fn default_broadphase() -> BroadphaseKind {
BroadphaseKind::SpatialHash
}
fn default_solver() -> SolverKind {
SolverKind::SequentialImpulse
}
impl Default for WorldConfig {
fn default() -> Self {
Self {
timestep: 1.0 / 60.0,
gravity: [0.0, -9.81, 0.0],
velocity_iterations: 4,
position_iterations: 1,
deterministic: true,
step: 0,
position_slop: default_slop(),
position_correction: default_correction(),
max_velocity: default_max_velocity(),
sub_steps: default_sub_steps(),
constraint_frequency: default_constraint_frequency(),
constraint_damping_ratio: default_constraint_damping_ratio(),
broadphase: default_broadphase(),
solver: default_solver(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config() {
let config = WorldConfig::default();
assert_eq!(config.timestep, 1.0 / 60.0);
assert_eq!(config.gravity, [0.0, -9.81, 0.0]);
assert!(config.deterministic);
assert_eq!(config.velocity_iterations, 4);
assert_eq!(config.position_iterations, 1);
assert_eq!(config.step, 0);
}
#[test]
fn config_serde_roundtrip() {
let config = WorldConfig::default();
let json = serde_json::to_string(&config).unwrap();
let back: WorldConfig = serde_json::from_str(&json).unwrap();
assert_eq!(config, back);
}
#[test]
fn custom_config_serde() {
let config = WorldConfig {
timestep: 1.0 / 120.0,
gravity: [0.0, -10.0, 0.0],
velocity_iterations: 8,
position_iterations: 3,
deterministic: false,
step: 0,
position_slop: 0.02,
position_correction: 0.3,
max_velocity: 100.0,
sub_steps: 1,
constraint_frequency: 30.0,
constraint_damping_ratio: 1.0,
broadphase: BroadphaseKind::SpatialHash,
solver: SolverKind::SequentialImpulse,
};
let json = serde_json::to_string(&config).unwrap();
let back: WorldConfig = serde_json::from_str(&json).unwrap();
assert_eq!(config, back);
}
#[test]
fn solver_kind_serde() {
let config = WorldConfig {
solver: SolverKind::Xpbd,
..Default::default()
};
let json = serde_json::to_string(&config).unwrap();
let back: WorldConfig = serde_json::from_str(&json).unwrap();
assert_eq!(config.solver, back.solver);
assert_eq!(back.solver, SolverKind::Xpbd);
}
#[test]
fn solver_kind_default_is_sequential_impulse() {
let config = WorldConfig::default();
assert_eq!(config.solver, SolverKind::SequentialImpulse);
}
#[test]
fn zero_gravity_config() {
let config = WorldConfig {
gravity: [0.0, 0.0, 0.0],
..Default::default()
};
assert_eq!(config.gravity, [0.0, 0.0, 0.0]);
}
}