Skip to main content

anput_physics/
lib.rs

1pub mod collisions;
2pub mod components;
3pub mod constraints;
4pub mod density_fields;
5pub mod queries;
6pub mod solvers;
7pub mod utils;
8
9pub mod third_party {
10    pub use vek;
11}
12
13use crate::{
14    collisions::{
15        CollisionProfile, ContactDetection, ContactsCache, DensityFieldSpatialExtractor,
16        RepulsiveCollisionCallbacks, RepulsiveCollisionSolver, collect_contacts,
17        dispatch_contact_events,
18    },
19    components::{
20        AngularVelocity, BodyDensityFieldRelation, BodyMaterial, BodyParentRelation,
21        BodyParticleRelation, ExternalForces, LinearVelocity, Mass, ParticleConstraintRelation,
22        PhysicsBody, PhysicsParticle, Position,
23    },
24    constraints::distance::solve_distance_constraint,
25    density_fields::DensityFieldBox,
26    queries::shape::ShapeOverlapQuery,
27    solvers::{
28        apply_external_forces, apply_gravity, cache_current_as_previous_state, dampening_solver,
29        integrate_velocities, recalculate_velocities,
30    },
31};
32use anput::{scheduler::GraphSchedulerPlugin, view::TypedWorldView, world::Relation};
33use serde::{Deserialize, Serialize};
34use vek::Vec3;
35
36pub type Scalar = f32;
37pub use std::f32 as scalar;
38
39pub type PhysicsAccessBundleColumns = (
40    PhysicsBody,
41    PhysicsParticle,
42    BodyMaterial,
43    Mass,
44    Position,
45    LinearVelocity,
46    AngularVelocity,
47    ExternalForces,
48    CollisionProfile,
49    DensityFieldBox,
50    ContactDetection,
51    Relation<BodyParentRelation>,
52    Relation<BodyParticleRelation>,
53    Relation<BodyDensityFieldRelation>,
54    Relation<ParticleConstraintRelation>,
55);
56
57pub type PhysicsAccessView = TypedWorldView<PhysicsAccessBundleColumns>;
58
59#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
60pub struct PhysicsSimulation {
61    pub delta_time: Scalar,
62    pub gravity: Vec3<Scalar>,
63}
64
65impl Default for PhysicsSimulation {
66    fn default() -> Self {
67        Self {
68            delta_time: 1.0 / 20.0,
69            gravity: Default::default(),
70        }
71    }
72}
73
74impl PhysicsSimulation {
75    pub fn inverse_delta_time(&self) -> Scalar {
76        if self.delta_time.abs() > Scalar::EPSILON {
77            1.0 / self.delta_time
78        } else {
79            0.0
80        }
81    }
82}
83
84pub struct PhysicsPlugin<const LOCKING: bool> {
85    simulation: PhysicsSimulation,
86    shape_overlap_query: ShapeOverlapQuery,
87    install_repulsive_collision: bool,
88    install_apply_gravity: bool,
89    install_apply_external_forces: bool,
90    install_integrate_velocities: bool,
91    install_collect_contacts: bool,
92    install_dispatch_contact_events: bool,
93    repulsive_collision_callbacks: RepulsiveCollisionCallbacks,
94    install_dampening_solver: bool,
95    install_distance_constraints_solver: bool,
96}
97
98impl<const LOCKING: bool> Default for PhysicsPlugin<LOCKING> {
99    fn default() -> Self {
100        Self {
101            simulation: Default::default(),
102            shape_overlap_query: Default::default(),
103            install_repulsive_collision: true,
104            install_apply_gravity: true,
105            install_apply_external_forces: true,
106            install_integrate_velocities: true,
107            install_collect_contacts: true,
108            install_dispatch_contact_events: true,
109            repulsive_collision_callbacks: Default::default(),
110            install_dampening_solver: true,
111            install_distance_constraints_solver: true,
112        }
113    }
114}
115
116impl<const LOCKING: bool> PhysicsPlugin<LOCKING> {
117    pub fn barebones() -> Self {
118        Self {
119            simulation: PhysicsSimulation::default(),
120            shape_overlap_query: Default::default(),
121            install_repulsive_collision: false,
122            install_apply_gravity: false,
123            install_apply_external_forces: false,
124            install_integrate_velocities: false,
125            install_collect_contacts: false,
126            install_dispatch_contact_events: false,
127            repulsive_collision_callbacks: Default::default(),
128            install_dampening_solver: false,
129            install_distance_constraints_solver: false,
130        }
131    }
132
133    pub fn simulation(mut self, simulation: PhysicsSimulation) -> Self {
134        self.simulation = simulation;
135        self
136    }
137
138    pub fn shape_overlap_query(mut self, query: ShapeOverlapQuery) -> Self {
139        self.shape_overlap_query = query;
140        self
141    }
142
143    pub fn install_repulsive_collision(mut self, install: bool) -> Self {
144        self.install_repulsive_collision = install;
145        self
146    }
147
148    pub fn install_apply_gravity(mut self, install: bool) -> Self {
149        self.install_apply_gravity = install;
150        self
151    }
152
153    pub fn install_apply_external_forces(mut self, install: bool) -> Self {
154        self.install_apply_external_forces = install;
155        self
156    }
157
158    pub fn install_integrate_velocities(mut self, install: bool) -> Self {
159        self.install_integrate_velocities = install;
160        self
161    }
162
163    pub fn install_collect_contacts(mut self, install: bool) -> Self {
164        self.install_collect_contacts = install;
165        self
166    }
167
168    pub fn repulsive_collision_callbacks(mut self, callbacks: RepulsiveCollisionCallbacks) -> Self {
169        self.repulsive_collision_callbacks = callbacks;
170        self
171    }
172
173    pub fn install_dampening_solver(mut self, install: bool) -> Self {
174        self.install_dampening_solver = install;
175        self
176    }
177
178    pub fn install_distance_constraints_solver(mut self, install: bool) -> Self {
179        self.install_distance_constraints_solver = install;
180        self
181    }
182
183    pub fn make(self) -> GraphSchedulerPlugin<LOCKING> {
184        let Self {
185            simulation,
186            shape_overlap_query,
187            install_repulsive_collision,
188            install_apply_gravity,
189            install_apply_external_forces,
190            install_integrate_velocities,
191            install_collect_contacts,
192            install_dispatch_contact_events,
193            repulsive_collision_callbacks,
194            install_dampening_solver,
195            install_distance_constraints_solver,
196        } = self;
197
198        GraphSchedulerPlugin::<LOCKING>::default()
199            .name("physics_simulation")
200            .resource(simulation)
201            .resource(ContactsCache::default())
202            .plugin_setup(|plugin| {
203                plugin
204                    .name("pre_simulation")
205                    .maybe_setup(|plugin| {
206                        if install_apply_gravity {
207                            Some(plugin.system_setup(apply_gravity::<LOCKING>, |system| {
208                                system.name("apply_gravity")
209                            }))
210                        } else {
211                            None
212                        }
213                    })
214                    .maybe_setup(|plugin| {
215                        if install_apply_external_forces {
216                            Some(
217                                plugin.system_setup(apply_external_forces::<LOCKING>, |system| {
218                                    system.name("apply_external_forces")
219                                }),
220                            )
221                        } else {
222                            None
223                        }
224                    })
225                    .maybe_setup(|plugin| {
226                        if install_integrate_velocities {
227                            Some(
228                                plugin.system_setup(integrate_velocities::<LOCKING>, |system| {
229                                    system.name("integrate_velocities")
230                                }),
231                            )
232                        } else {
233                            None
234                        }
235                    })
236                    .plugin(
237                        anput_spatial::make_plugin::<LOCKING, DensityFieldSpatialExtractor>()
238                            .name("extract_spatial_info"),
239                    )
240                    .maybe_setup(|plugin| {
241                        if install_collect_contacts {
242                            Some(plugin.system_setup(collect_contacts::<LOCKING>, |system| {
243                                system.name("collect_contacts").local(shape_overlap_query)
244                            }))
245                        } else {
246                            None
247                        }
248                    })
249                    .maybe_setup(|plugin| {
250                        if install_dispatch_contact_events {
251                            Some(
252                                plugin.system_setup(dispatch_contact_events::<LOCKING>, |system| {
253                                    system.name("dispatch_contact_events")
254                                }),
255                            )
256                        } else {
257                            None
258                        }
259                    })
260                    .maybe_setup(|plugin| {
261                        if install_repulsive_collision {
262                            Some(plugin.system_setup(
263                                RepulsiveCollisionSolver::<LOCKING>,
264                                |system| {
265                                    system
266                                        .name("RepulsiveCollisionSolver")
267                                        .local(repulsive_collision_callbacks)
268                                },
269                            ))
270                        } else {
271                            None
272                        }
273                    })
274            })
275            .plugin_setup(|plugin| {
276                plugin
277                    .name("pre_solvers")
278                    .maybe_setup(|plugin| {
279                        if install_dampening_solver {
280                            Some(plugin.system_setup(dampening_solver::<LOCKING>, |system| {
281                                system.name("dampening_solver")
282                            }))
283                        } else {
284                            None
285                        }
286                    })
287                    .system_setup(cache_current_as_previous_state::<LOCKING>, |system| {
288                        system.name("cache_current_as_previous_state")
289                    })
290            })
291            .plugin_setup(|plugin| {
292                plugin.name("solvers").maybe_setup(|plugin| {
293                    if install_distance_constraints_solver {
294                        Some(
295                            plugin.system_setup(solve_distance_constraint::<LOCKING>, |system| {
296                                system.name("solve_distance_constraint")
297                            }),
298                        )
299                    } else {
300                        None
301                    }
302                })
303            })
304            .plugin_setup(|plugin| {
305                plugin
306                    .name("post_solvers")
307                    .system_setup(recalculate_velocities::<LOCKING>, |system| {
308                        system.name("recalculate_velocities")
309                    })
310            })
311            .plugin_setup(|plugin| plugin.name("post_simulation"))
312    }
313}