gizmo_physics_rigid/world/
mod.rs1use crate::{
2 components::{RigidBody, Velocity},
3 integrator::Integrator,
4 solver::ConstraintSolver,
5};
6use gizmo_physics_core::broadphase::SpatialHash;
7use gizmo_physics_core::{CollisionEvent, ContactManifold, TriggerEvent};
8use gizmo_physics_core::components::{Collider, Transform};
9use gizmo_physics_core::BodyHandle;
10
11use std::collections::HashMap;
12use std::path::PathBuf;
13
14mod construction;
15mod query;
16mod snapshot;
17mod step;
18#[cfg(test)]
19#[allow(clippy::field_reassign_with_default)] mod tests;
21
22#[derive(Debug)]
25#[non_exhaustive]
26pub enum SnapshotError {
27 Create {
29 path: PathBuf,
31 source: std::io::Error,
33 },
34 Serialize(serde_json::Error),
36}
37
38impl std::fmt::Display for SnapshotError {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 match self {
41 SnapshotError::Create { path, .. } => {
42 write!(f, "failed to create physics snapshot file '{}'", path.display())
43 }
44 SnapshotError::Serialize(_) => {
45 write!(f, "failed to serialize physics snapshot to JSON")
46 }
47 }
48 }
49}
50
51impl std::error::Error for SnapshotError {
52 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
53 match self {
54 SnapshotError::Create { source, .. } => Some(source),
55 SnapshotError::Serialize(source) => Some(source),
56 }
57 }
58}
59
60impl From<serde_json::Error> for SnapshotError {
61 fn from(e: serde_json::Error) -> Self {
62 SnapshotError::Serialize(e)
63 }
64}
65
66#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
67#[non_exhaustive]
68pub enum ZoneShape {
69 Box {
70 min: gizmo_math::Vec3,
71 max: gizmo_math::Vec3,
72 },
73 Sphere {
74 center: gizmo_math::Vec3,
75 radius: f32,
76 },
77}
78
79impl ZoneShape {
80 pub fn contains(&self, p: gizmo_math::Vec3) -> bool {
81 match self {
82 ZoneShape::Box { min, max } => {
83 p.x >= min.x
84 && p.x <= max.x
85 && p.y >= min.y
86 && p.y <= max.y
87 && p.z >= min.z
88 && p.z <= max.z
89 }
90 ZoneShape::Sphere { center, radius } => {
91 (p - *center).length_squared() <= radius * radius
92 }
93 }
94 }
95}
96
97#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
98#[non_exhaustive]
99pub struct GravityField {
100 pub shape: ZoneShape,
101 pub gravity: gizmo_math::Vec3,
102 pub falloff_radius: f32, pub priority: i32,
104}
105
106impl Default for GravityField {
107 fn default() -> Self {
108 Self {
109 shape: ZoneShape::Sphere {
110 center: gizmo_math::Vec3::ZERO,
111 radius: 1.0,
112 },
113 gravity: gizmo_math::Vec3::new(0.0, -9.81, 0.0),
114 falloff_radius: 0.0,
115 priority: 0,
116 }
117 }
118}
119
120#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
121pub struct FluidZone {
122 pub shape: ZoneShape,
123 pub density: f32, pub viscosity: f32, pub linear_drag: f32, pub quadratic_drag: f32, #[serde(default)]
130 pub fog_color: [f32; 3],
131 #[serde(default)]
133 pub fog_density: f32,
134}
135
136impl Default for FluidZone {
137 fn default() -> Self {
138 Self {
139 shape: ZoneShape::Sphere {
140 center: gizmo_math::Vec3::ZERO,
141 radius: 1.0,
142 },
143 density: 1000.0,
144 viscosity: 1.0,
145 linear_drag: 0.0,
146 quadratic_drag: 0.0,
147 fog_color: [0.02, 0.10, 0.14], fog_density: 0.08,
149 }
150 }
151}
152
153#[derive(Debug, Clone, Copy, PartialEq)]
156pub struct WaterSample {
157 pub surface_y: f32,
159 pub depth: f32,
161 pub density: f32,
163 pub fog_color: [f32; 3],
165 pub fog_density: f32,
167}
168
169impl PhysicsWorld {
170 pub fn water_at(&self, p: gizmo_math::Vec3) -> Option<WaterSample> {
173 let mut best: Option<WaterSample> = None;
174 for zone in &self.fluid_zones {
175 if !zone.shape.contains(p) {
176 continue;
177 }
178 let surface_y = match zone.shape {
179 ZoneShape::Box { max, .. } => max.y,
180 ZoneShape::Sphere { center, radius } => center.y + radius,
181 };
182 let sample = WaterSample {
183 surface_y,
184 depth: (surface_y - p.y).max(0.0),
185 density: zone.density,
186 fog_color: zone.fog_color,
187 fog_density: zone.fog_density,
188 };
189 if best.map_or(true, |b| surface_y > b.surface_y) {
190 best = Some(sample);
191 }
192 }
193 best
194 }
195
196 pub fn is_submerged(&self, p: gizmo_math::Vec3) -> bool {
198 self.fluid_zones.iter().any(|z| z.shape.contains(p))
199 }
200}
201
202const PHYSICS_HZ: f32 = 240.0;
204const FIXED_DT: f32 = 1.0 / PHYSICS_HZ;
205const MAX_SUBSTEPS: u32 = 64; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
209#[derive(Default)]
210#[non_exhaustive]
211pub enum Weather {
212 #[default]
213 Sunny,
214 Rain,
215 Snow,
216}
217
218
219#[derive(Debug, Clone)]
221pub struct PhysicsStateSnapshot {
222 pub transforms: Vec<Transform>,
223 pub velocities: Vec<Velocity>,
224}
225
226#[derive(serde::Serialize, serde::Deserialize)]
228pub struct PhysicsWorld {
229 pub weather: Weather,
230
231 #[serde(skip)]
232 pub integrator: Integrator,
233 #[serde(skip)]
234 pub solver: ConstraintSolver,
235 #[serde(skip)]
236 pub spatial_hash: SpatialHash,
237 #[serde(skip)]
238 pub collision_events: Vec<CollisionEvent>,
239 #[serde(skip)]
240 pub trigger_events: Vec<TriggerEvent>,
241 #[serde(skip)]
242 pub fracture_events: Vec<gizmo_physics_core::FractureEvent>,
243 #[serde(skip)]
244 pub fracture_cache: crate::fracture::PreFracturedCache,
245 #[serde(skip)]
246 pub joints: Vec<crate::joints::Joint>,
247 #[serde(skip)]
248 pub joint_solver: crate::joints::JointSolver,
249
250 pub gravity_fields: Vec<GravityField>,
251 pub fluid_zones: Vec<FluidZone>,
252
253 #[serde(skip)]
254 pub(crate) contact_cache: HashMap<(BodyHandle, BodyHandle), (bool, Option<ContactManifold>)>,
255
256 pub accumulator: f32,
257 pub render_alpha: f32,
258
259 #[serde(skip)]
260 pub metrics: crate::island::PhysicsMetrics,
261
262 pub entities: Vec<BodyHandle>,
264 pub rigid_bodies: Vec<RigidBody>,
265 pub transforms: Vec<Transform>,
266 pub velocities: Vec<Velocity>,
267 pub colliders: Vec<Collider>,
268 pub entity_index_map: HashMap<u32, usize>,
269
270 #[serde(skip)]
272 pub is_paused: bool,
273 #[serde(skip)]
274 pub step_once: bool,
275 #[serde(skip)]
276 pub rewind_requested: bool,
277 #[serde(skip)]
278 pub history: std::collections::VecDeque<PhysicsStateSnapshot>,
279 pub max_history_frames: usize,
280
281 #[serde(skip)]
282 pub watchlist: std::collections::HashSet<BodyHandle>,
283}
284
285impl Default for PhysicsWorld {
286 fn default() -> Self {
287 Self::new()
288 }
289}
290
291#[derive(Debug, Clone)]
300pub struct WorldSnapshot {
301 transforms: Vec<Transform>,
302 velocities: Vec<crate::components::Velocity>,
303 rigid_bodies: Vec<crate::components::RigidBody>,
304 contact_cache: HashMap<(BodyHandle, BodyHandle), (bool, Option<ContactManifold>)>,
305 accumulator: f32,
306 gravity_fields: Vec<GravityField>,
311 fluid_zones: Vec<FluidZone>,
312}