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