Skip to main content

gizmo_physics_rigid/world/
mod.rs

1use 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)] // testlerde Default sonrası alan atama okunabilirlik için
20mod tests;
21
22/// Errors that can occur while writing a physics-world diagnostic snapshot
23/// via [`PhysicsWorld::trigger_snapshot`].
24#[derive(Debug)]
25#[non_exhaustive]
26pub enum SnapshotError {
27    /// The snapshot file could not be created on disk.
28    Create {
29        /// Path the snapshot was being written to.
30        path: PathBuf,
31        /// Underlying I/O error.
32        source: std::io::Error,
33    },
34    /// The world state could not be serialized to JSON.
35    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, // If > 0, gravity drops off
103    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,        // kg/m^3
124    pub viscosity: f32,      // dynamic viscosity for Stokes drag
125    pub linear_drag: f32,    // fallback linear drag
126    pub quadratic_drag: f32, // fallback quadratic drag
127    /// Kamera bu hacimdeyken uygulanan su-altı sis rengi (lineer RGB). Böylece her su hacmi
128    /// kendi su-altı görünümünü tanımlar (sığ turkuaz vs derin lacivert). Serde-eksik → [0;3].
129    #[serde(default)]
130    pub fog_color: [f32; 3],
131    /// Su-altı sis yoğunluğu (Beer-Lambert; büyük = daha çabuk görüş kapanması). Serde-eksik → 0.
132    #[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], // deniz mavisi-yeşili
148            fog_density: 0.08,
149        }
150    }
151}
152
153/// Bir noktadaki su örneği: onu içeren fluid zone'un yüzey yüksekliği, derinlik ve yoğunluk.
154/// Yüzme karakter kontrolcüsü ve kamera-su-altı tespiti bu ortak sorguyu kullanır.
155#[derive(Debug, Clone, Copy, PartialEq)]
156pub struct WaterSample {
157    /// Su yüzeyinin dünya-Y'si (zone üstü).
158    pub surface_y: f32,
159    /// Noktanın yüzeyin ne kadar altında olduğu (m, ≥0).
160    pub depth: f32,
161    /// Sıvı yoğunluğu (kg/m³).
162    pub density: f32,
163    /// Bu hacmin su-altı sis rengi (kamera batıksa post-process için).
164    pub fog_color: [f32; 3],
165    /// Bu hacmin su-altı sis yoğunluğu.
166    pub fog_density: f32,
167}
168
169impl PhysicsWorld {
170    /// `p` herhangi bir fluid zone içindeyse su örneği döner (birden çok zone çakışırsa en ÜST
171    /// yüzeyli seçilir). Zone dışıysa `None`. Yüzme + kamera-batık tespiti için ortak sorgu.
172    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    /// `p` bir su hacminin içinde mi (batık mı)?
197    pub fn is_submerged(&self, p: gizmo_math::Vec3) -> bool {
198        self.fluid_zones.iter().any(|z| z.shape.contains(p))
199    }
200}
201
202/// Sabit iç fizik frekansı (Hz) - 240Hz (Sub-stepping ile mükemmel çarpışma tespiti)
203const PHYSICS_HZ: f32 = 240.0;
204const FIXED_DT: f32 = 1.0 / PHYSICS_HZ;
205/// Sub-step başına maksimum adım sayısı — spiral'i önler
206const MAX_SUBSTEPS: u32 = 64; // Increased from 8 to support larger DTs without losing simulation time
207
208#[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/// A compact snapshot of the physics state for rewinding
220#[derive(Debug, Clone)]
221pub struct PhysicsStateSnapshot {
222    pub transforms: Vec<Transform>,
223    pub velocities: Vec<Velocity>,
224}
225
226/// Main physics world that manages all physics simulation
227#[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    // SoA (Structure of Arrays) Memory Layout
263    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    // Timeline and Debugging
271    #[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/// Rollback/replay için TAM simülasyon durumu anlık görüntüsü (Faz 3 netcode).
292///
293/// `PhysicsStateSnapshot`'tan (yalnız transform+velocity, 1-kare rewind için) FARKLI:
294/// deterministik RE-SİMÜLASYON için gereken İÇ DURUMU da taşır — `rigid_bodies` (uyku
295/// durumu + sayaçlar), **`contact_cache` (warm-start impuls'ları)** ve substep
296/// `accumulator`. Bunlar olmadan restore sonrası çözücü farklı warm-start'la yakınsar →
297/// rollback re-simülasyonu kesintisiz simülasyondan SAPAR. (entities/colliders/
298/// entity_index_map rollback penceresinde DEĞİŞMEZ varsayılır — ekleme/silme yok.)
299#[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    // Force-field state also feeds `velocity_integration_step`, so it MUST be part of
307    // the rollback snapshot: these are public mutable `Vec`s that gameplay can add to /
308    // clear at runtime, and if one changes inside a rollback window a restore that left
309    // them untouched would resimulate under the wrong forces and diverge.
310    gravity_fields: Vec<GravityField>,
311    fluid_zones: Vec<FluidZone>,
312}