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}