1use crate::{
2 dynamics::{
3 joints::{
4 EntityConstraint,
5 joint_graph::{JointGraph, JointGraphChange},
6 },
7 solver::{
8 SolverDiagnostics,
9 constraint_graph::{
10 COLOR_OVERFLOW_INDEX, ConstraintGraph, ContactManifoldHandle, GraphColor,
11 },
12 contact::ContactConstraint,
13 islands::{BodyIslandNode, IslandId, PhysicsIslands, WakeIslands},
14 schedule::SubstepSolverSystems,
15 softness_parameters::{SoftnessCoefficients, SoftnessParameters},
16 solver_body::{
17 SolverBodies, SolverBodiesAccess, SolverBody, SolverBodyIndex, SolverBodyInertia,
18 },
19 },
20 },
21 prelude::*,
22};
23use bevy::{
24 ecs::{
25 entity_disabling::Disabled,
26 query::QueryData,
27 system::{SystemState, lifetimeless::Read},
28 },
29 prelude::*,
30};
31use core::cmp::Ordering;
32
33pub struct SolverPlugin {
79 length_unit: f32,
80}
81
82impl Default for SolverPlugin {
83 fn default() -> Self {
84 Self::new_with_length_unit(1.0)
85 }
86}
87
88impl SolverPlugin {
89 pub fn new_with_length_unit(unit: f32) -> Self {
94 Self { length_unit: unit }
95 }
96}
97
98impl Plugin for SolverPlugin {
99 fn build(&self, app: &mut App) {
100 app.init_resource::<SolverConfig>()
101 .init_resource::<ContactSoftnessCoefficients>()
102 .init_resource::<ContactConstraints>()
103 .init_resource::<ConstraintGraph>();
104
105 if app
106 .world()
107 .get_resource::<PhysicsLengthUnit>()
108 .is_none_or(|unit| unit.0 == 1.0)
109 {
110 app.insert_resource(PhysicsLengthUnit(self.length_unit));
111 }
112
113 let cached_state = CachedContactStatusChangeSystemState(SystemState::new(app.world_mut()));
115 app.insert_resource(cached_state);
116
117 let physics = app
119 .get_schedule_mut(PhysicsSchedule)
120 .expect("add PhysicsSchedule first");
121
122 physics.add_systems(update_contact_softness.before(PhysicsStepSystems::NarrowPhase));
123
124 physics.add_systems(apply_contact_status_changes.in_set(PhysicsStepSystems::First));
132 physics.add_systems(
133 (apply_contact_status_changes, apply_joint_graph_changes)
134 .chain()
135 .in_set(PhysicsStepSystems::Solver)
136 .before(SolverSystems::PrepareSolverBodies),
137 );
138
139 physics.add_systems(
141 prepare_contact_constraints.in_set(SolverSystems::PrepareContactConstraints),
142 );
143
144 physics.add_systems(solve_restitution.in_set(SolverSystems::Restitution));
146
147 physics.add_systems(store_contact_impulses.in_set(SolverSystems::StoreContactImpulses));
149
150 let substeps = app
152 .get_schedule_mut(SubstepSchedule)
153 .expect("add SubstepSchedule first");
154
155 substeps.add_systems(warm_start.in_set(SubstepSolverSystems::WarmStart));
159
160 substeps.add_systems(solve_contacts::<true>.in_set(SubstepSolverSystems::SolveConstraints));
162
163 substeps.add_systems(solve_contacts::<false>.in_set(SubstepSolverSystems::Relax));
166
167 substeps.add_systems(
169 (
170 joint_damping::<FixedJoint>,
171 joint_damping::<RevoluteJoint>,
172 #[cfg(feature = "3d")]
173 joint_damping::<SphericalJoint>,
174 joint_damping::<PrismaticJoint>,
175 joint_damping::<DistanceJoint>,
176 )
177 .chain()
178 .in_set(SubstepSolverSystems::Damping),
179 );
180 }
181
182 fn finish(&self, app: &mut App) {
183 app.register_physics_diagnostics::<SolverDiagnostics>();
185 }
186}
187
188pub fn apply_contact_status_changes(
190 mut changes: ResMut<ContactStatusChangeQueue>,
191 contact_graph: Res<ContactGraph>,
192 mut constraint_graph: ResMut<ConstraintGraph>,
193 mut islands: Option<ResMut<PhysicsIslands>>,
194 mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
195 mut commands: Commands,
196) {
197 if changes.is_empty() {
198 return;
199 }
200
201 let mut islands_to_wake: Vec<IslandId> = Vec::new();
202
203 for change in changes.drain(..) {
204 apply_contact_status_change(
205 change,
206 &contact_graph,
207 &mut constraint_graph,
208 islands.as_deref_mut(),
209 &mut body_islands,
210 &mut islands_to_wake,
211 );
212 }
213
214 if !islands_to_wake.is_empty() {
215 islands_to_wake.sort_unstable();
216 islands_to_wake.dedup();
217
218 commands.queue(WakeIslands(islands_to_wake));
220 }
221}
222
223pub fn apply_joint_graph_changes(
225 mut changes: MessageReader<JointGraphChange>,
226 joint_graph: Res<JointGraph>,
227 mut islands: Option<ResMut<PhysicsIslands>>,
228 mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
229 mut commands: Commands,
230) {
231 let Some(islands) = &mut islands else {
232 changes.clear();
234 return;
235 };
236
237 let mut islands_to_wake: Vec<IslandId> = Vec::new();
238
239 for &change in changes.read() {
240 match change {
241 JointGraphChange::Added(joint_id) => {
242 if joint_graph.get_by_id(joint_id).is_none() {
244 continue;
245 }
246
247 if let Some(island) = islands.add_joint(joint_id, &mut body_islands, &joint_graph)
249 && island.is_sleeping
250 {
251 islands_to_wake.push(island.id);
253 }
254 }
255 JointGraphChange::Removed(joint_id) => {
256 if islands.joint_node(joint_id).is_none() {
258 continue;
259 }
260
261 if let Some(island) = islands.remove_joint(joint_id, &mut body_islands)
263 && island.is_sleeping
264 {
265 islands_to_wake.push(island.id);
267 }
268 }
269 }
270 }
271
272 if !islands_to_wake.is_empty() {
273 islands_to_wake.sort_unstable();
274 islands_to_wake.dedup();
275
276 commands.queue(WakeIslands(islands_to_wake));
278 }
279}
280
281fn apply_contact_status_change(
285 change: ContactStatusChange,
286 contact_graph: &ContactGraph,
287 constraint_graph: &mut ConstraintGraph,
288 islands: Option<&mut PhysicsIslands>,
289 body_islands: &mut Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
290 islands_to_wake: &mut Vec<IslandId>,
291) {
292 match change {
293 ContactStatusChange::StartedGeneratingConstraints(contact) => {
294 let Some((_, contact_pair)) = contact_graph.get_by_id(contact) else {
295 return;
296 };
297
298 for _ in 0..contact_pair.manifolds.len() {
300 constraint_graph.push_manifold(contact_pair);
301 }
302
303 if let Some(islands) = islands {
305 let island = islands.add_contact(contact, body_islands, contact_graph);
306
307 if let Some(island) = island
308 && island.is_sleeping
309 {
310 islands_to_wake.push(island.id);
312 }
313 }
314 }
315 ContactStatusChange::StoppedGeneratingConstraints {
316 contact_id: contact,
317 body1,
318 body2,
319 } => {
320 constraint_graph.remove_contact(contact, body1, body2);
322
323 if let Some(islands) = islands
325 && islands.contact_node(contact).is_some()
326 {
327 let island = islands.remove_contact(contact, body_islands);
328 let island_id = island.id;
329
330 if island.is_sleeping {
332 islands_to_wake.push(island_id);
334 }
335
336 islands.remove_island_if_empty(island_id);
338 }
339 }
340 ContactStatusChange::ManifoldCountChanged {
341 contact,
342 body1,
343 body2,
344 delta,
345 } => {
346 if delta > 0 {
347 let Some((_, contact_pair)) = contact_graph.get_by_id(contact) else {
349 return;
350 };
351 for _ in 0..delta {
352 constraint_graph.push_manifold(contact_pair);
353 }
354 } else {
355 for _ in 0..delta.unsigned_abs() {
357 constraint_graph.pop_manifold(contact, body1, body2);
358 }
359 }
360 }
361 }
362}
363
364#[derive(Resource)]
366struct CachedContactStatusChangeSystemState(
367 SystemState<(
368 ResMut<'static, ContactStatusChangeQueue>,
369 Res<'static, ContactGraph>,
370 ResMut<'static, ConstraintGraph>,
371 Option<ResMut<'static, PhysicsIslands>>,
372 Query<
373 'static,
374 'static,
375 &'static mut BodyIslandNode,
376 Or<(With<Disabled>, Without<Disabled>)>,
377 >,
378 )>,
379);
380
381#[cfg_attr(
396 feature = "2d",
397 doc = "use avian2d::{dynamics::solver::FlushContactStatusChangeQueue, prelude::*};"
398)]
399#[cfg_attr(
400 feature = "3d",
401 doc = "use avian3d::{dynamics::solver::FlushContactStatusChangeQueue, prelude::*};"
402)]
403pub struct FlushContactStatusChangeQueue;
415
416impl Command for FlushContactStatusChangeQueue {
417 type Out = ();
418
419 fn apply(self, world: &mut World) {
420 world.try_resource_scope(
422 |world, mut state: Mut<CachedContactStatusChangeSystemState>| {
423 let mut islands_to_wake: Vec<IslandId> = Vec::new();
424
425 {
426 let (
427 mut changes,
428 contact_graph,
429 mut constraint_graph,
430 mut islands,
431 mut body_islands,
432 ) = state.0.get_mut(world).unwrap();
433
434 for change in changes.drain(..) {
435 apply_contact_status_change(
436 change,
437 &contact_graph,
438 &mut constraint_graph,
439 islands.as_deref_mut(),
440 &mut body_islands,
441 &mut islands_to_wake,
442 );
443 }
444 }
445
446 if !islands_to_wake.is_empty() {
447 islands_to_wake.sort_unstable();
448 islands_to_wake.dedup();
449
450 WakeIslands(islands_to_wake).apply(world);
452 }
453 },
454 );
455 }
456}
457
458#[derive(Resource, Clone, Debug, Deref, DerefMut, PartialEq, Reflect)]
499#[reflect(Resource)]
500pub struct PhysicsLengthUnit(pub f32);
501
502impl Default for PhysicsLengthUnit {
503 fn default() -> Self {
504 Self(1.0)
505 }
506}
507
508#[derive(Resource, Clone, Debug, PartialEq, Reflect)]
514#[reflect(Resource)]
515pub struct SolverConfig {
516 pub contact_damping_ratio: f32,
527
528 pub contact_frequency_factor: f32,
540
541 pub max_overlap_solve_speed: f32,
550
551 pub warm_start_coefficient: f32,
562
563 pub restitution_threshold: f32,
575
576 pub restitution_iterations: usize,
588}
589
590impl Default for SolverConfig {
591 fn default() -> Self {
592 Self {
593 contact_damping_ratio: 10.0,
594 contact_frequency_factor: 1.5,
595 max_overlap_solve_speed: 4.0,
596 warm_start_coefficient: 1.0,
597 restitution_threshold: 1.0,
598 restitution_iterations: 1,
599 }
600 }
601}
602
603#[derive(Resource, Clone, Copy, PartialEq, Reflect)]
608#[reflect(Resource)]
609pub struct ContactSoftnessCoefficients {
610 pub dynamic: SoftnessCoefficients,
612 pub non_dynamic: SoftnessCoefficients,
614}
615
616impl Default for ContactSoftnessCoefficients {
617 fn default() -> Self {
618 Self {
619 dynamic: SoftnessParameters::new(10.0, 30.0).compute_coefficients(1.0 / 60.0),
620 non_dynamic: SoftnessParameters::new(10.0, 60.0).compute_coefficients(1.0 / 60.0),
621 }
622 }
623}
624
625fn update_contact_softness(
626 mut coefficients: ResMut<ContactSoftnessCoefficients>,
627 solver_config: Res<SolverConfig>,
628 physics_time: Res<Time<Physics>>,
629 substep_time: Res<Time<Substeps>>,
630) {
631 if solver_config.is_changed() || physics_time.is_changed() || substep_time.is_changed() {
632 let dt = physics_time.delta_secs();
633 let h = substep_time.delta_secs();
634
635 let max_hz = 1.0 / (dt * 2.0);
638 let hz = solver_config.contact_frequency_factor * max_hz.min(0.25 / h);
639
640 coefficients.dynamic = SoftnessParameters::new(solver_config.contact_damping_ratio, hz)
641 .compute_coefficients(h);
642
643 coefficients.non_dynamic =
646 SoftnessParameters::new(solver_config.contact_damping_ratio, 2.0 * hz)
647 .compute_coefficients(h);
648 }
649}
650
651#[derive(Resource, Default, Deref, DerefMut)]
653pub struct ContactConstraints(pub Vec<ContactConstraint>);
654
655#[derive(QueryData)]
656pub(super) struct BodyQuery {
657 pub(super) rb: Read<RigidBody>,
658 pub(super) linear_velocity: Read<LinearVelocity>,
659 pub(super) index: Option<Read<SolverBodyIndex>>,
660}
661
662fn prepare_contact_constraints(
663 contact_graph: Res<ContactGraph>,
664 mut constraint_graph: ResMut<ConstraintGraph>,
665 mut diagnostics: ResMut<SolverDiagnostics>,
666 bodies: Query<BodyQuery, RigidBodyActiveFilter>,
667 solver_bodies: Res<SolverBodies>,
668 narrow_phase_config: Res<NarrowPhaseConfig>,
669 contact_softness: Res<ContactSoftnessCoefficients>,
670) {
671 let start = crate::utils::Instant::now();
672
673 for color in constraint_graph.colors.iter_mut() {
674 color.contact_constraints.clear();
678 }
679
680 let mut active_colors = constraint_graph
682 .colors
683 .iter_mut()
684 .filter(|color| !color.manifold_handles.is_empty())
685 .collect::<Vec<&mut GraphColor>>();
686
687 crate::utils::par_for_each(&mut active_colors, 2, |_i, color| {
689 for handle in color.manifold_handles.iter() {
690 let contact_pair = contact_graph
692 .get_by_id(handle.contact_id)
693 .unwrap_or_else(|| {
694 panic!("Contact pair not found in graph: {:?}", handle.contact_id)
695 })
696 .1;
697 let manifold_index = handle.manifold_index;
698 let manifold = &contact_pair.manifolds[manifold_index];
699
700 if !contact_pair.generates_constraints() {
701 continue;
702 }
703
704 let (Some(body1_entity), Some(body2_entity)) = (contact_pair.body1, contact_pair.body2)
705 else {
706 continue;
707 };
708
709 let Ok(body1) = bodies.get(body1_entity) else {
711 continue;
712 };
713 let Ok(body2) = bodies.get(body2_entity) else {
714 continue;
715 };
716
717 if !body1.rb.is_dynamic() && !body2.rb.is_dynamic() {
720 continue;
722 }
723
724 let index1 = body1.index.copied().unwrap_or(SolverBodyIndex::INVALID);
727 let index2 = body2.index.copied().unwrap_or(SolverBodyIndex::INVALID);
728 let inertia1 = solver_bodies
729 .get_inertia(index1)
730 .unwrap_or(&SolverBodyInertia::DUMMY);
731 let inertia2 = solver_bodies
732 .get_inertia(index2)
733 .unwrap_or(&SolverBodyInertia::DUMMY);
734
735 let constraint = ContactConstraint::generate(
736 index1,
737 index2,
738 inertia1,
739 inertia2,
740 body1.linear_velocity.0,
741 body2.linear_velocity.0,
742 contact_pair.contact_id,
743 manifold,
744 manifold_index,
745 narrow_phase_config.match_contacts,
746 &contact_softness,
747 );
748
749 if !constraint.points.is_empty() {
750 color.contact_constraints.push(constraint);
751 }
752 }
753 });
754
755 diagnostics.prepare_constraints += start.elapsed();
756 diagnostics.contact_constraint_count = constraint_graph
757 .colors
758 .iter()
759 .map(|color| color.contact_constraints.len())
760 .sum::<usize>() as u32;
761}
762
763fn warm_start(
767 mut solver_bodies: ResMut<SolverBodies>,
768 mut constraint_graph: ResMut<ConstraintGraph>,
769 solver_config: Res<SolverConfig>,
770 mut diagnostics: ResMut<SolverDiagnostics>,
771) {
772 let start = crate::utils::Instant::now();
773
774 let access = solver_bodies.access();
775
776 for constraint in constraint_graph.colors[COLOR_OVERFLOW_INDEX]
778 .contact_constraints
779 .iter_mut()
780 {
781 warm_start_internal(&access, constraint, solver_config.warm_start_coefficient);
782 }
783
784 for color in constraint_graph
786 .colors
787 .iter_mut()
788 .take(COLOR_OVERFLOW_INDEX)
789 .filter(|color| !color.contact_constraints.is_empty())
790 {
791 crate::utils::par_for_each(&mut color.contact_constraints, 64, |_i, constraint| {
792 warm_start_internal(&access, constraint, solver_config.warm_start_coefficient);
793 });
794 }
795
796 diagnostics.warm_start += start.elapsed();
797}
798
799fn warm_start_internal(
800 access: &SolverBodiesAccess,
801 constraint: &mut ContactConstraint,
802 warm_start_coefficient: f32,
803) {
804 debug_assert!(!constraint.points.is_empty());
805
806 let mut dummy_body1 = SolverBody::DUMMY;
807 let mut dummy_body2 = SolverBody::DUMMY;
808
809 let (mut body1, mut inertia1) = (&mut dummy_body1, &SolverBodyInertia::DUMMY);
810 let (mut body2, mut inertia2) = (&mut dummy_body2, &SolverBodyInertia::DUMMY);
811
812 let (b1, b2) =
818 unsafe { access.get_pair_unchecked_mut(constraint.body_index1, constraint.body_index2) };
819 if let Some((body, inertia)) = b1 {
820 body1 = body;
821 inertia1 = inertia;
822 }
823 if let Some((body, inertia)) = b2 {
824 body2 = body;
825 inertia2 = inertia;
826 }
827
828 match constraint.relative_dominance.cmp(&0) {
830 Ordering::Greater => inertia1 = &SolverBodyInertia::DUMMY,
831 Ordering::Less => inertia2 = &SolverBodyInertia::DUMMY,
832 _ => {}
833 }
834
835 constraint.warm_start(body1, body2, inertia1, inertia2, warm_start_coefficient);
836}
837
838#[allow(clippy::too_many_arguments)]
851#[allow(clippy::type_complexity)]
852fn solve_contacts<const USE_BIAS: bool>(
853 mut solver_bodies: ResMut<SolverBodies>,
854 mut constraint_graph: ResMut<ConstraintGraph>,
855 solver_config: Res<SolverConfig>,
856 length_unit: Res<PhysicsLengthUnit>,
857 time: Res<Time>,
858 mut diagnostics: ResMut<SolverDiagnostics>,
859) {
860 let start = crate::utils::Instant::now();
861
862 let delta_secs = time.delta_secs();
863 let max_overlap_solve_speed = solver_config.max_overlap_solve_speed * length_unit.0;
864
865 let access = solver_bodies.access();
866
867 for constraint in constraint_graph.colors[COLOR_OVERFLOW_INDEX]
869 .contact_constraints
870 .iter_mut()
871 {
872 solve_contacts_internal::<USE_BIAS>(
873 &access,
874 constraint,
875 max_overlap_solve_speed,
876 delta_secs,
877 );
878 }
879
880 for color in constraint_graph
882 .colors
883 .iter_mut()
884 .take(COLOR_OVERFLOW_INDEX)
885 .filter(|color| !color.contact_constraints.is_empty())
886 {
887 crate::utils::par_for_each(&mut color.contact_constraints, 64, |_i, constraint| {
888 solve_contacts_internal::<USE_BIAS>(
889 &access,
890 constraint,
891 max_overlap_solve_speed,
892 delta_secs,
893 );
894 });
895 }
896
897 if USE_BIAS {
898 diagnostics.solve_constraints += start.elapsed();
899 } else {
900 diagnostics.relax_velocities += start.elapsed();
901 }
902}
903
904fn solve_contacts_internal<const USE_BIAS: bool>(
905 access: &SolverBodiesAccess,
906 constraint: &mut ContactConstraint,
907 max_overlap_solve_speed: f32,
908 delta_secs: f32,
909) {
910 let mut dummy_body1 = SolverBody::DUMMY;
911 let mut dummy_body2 = SolverBody::DUMMY;
912
913 let (mut body1, mut inertia1) = (&mut dummy_body1, &SolverBodyInertia::DUMMY);
914 let (mut body2, mut inertia2) = (&mut dummy_body2, &SolverBodyInertia::DUMMY);
915
916 let (b1, b2) =
922 unsafe { access.get_pair_unchecked_mut(constraint.body_index1, constraint.body_index2) };
923 if let Some((body, inertia)) = b1 {
924 body1 = body;
925 inertia1 = inertia;
926 }
927 if let Some((body, inertia)) = b2 {
928 body2 = body;
929 inertia2 = inertia;
930 }
931
932 match constraint.relative_dominance.cmp(&0) {
934 Ordering::Greater => inertia1 = &SolverBodyInertia::DUMMY,
935 Ordering::Less => inertia2 = &SolverBodyInertia::DUMMY,
936 _ => {}
937 }
938
939 constraint.solve::<USE_BIAS>(
940 body1,
941 body2,
942 inertia1,
943 inertia2,
944 delta_secs,
945 max_overlap_solve_speed,
946 );
947}
948
949#[allow(clippy::too_many_arguments)]
957#[allow(clippy::type_complexity)]
958fn solve_restitution(
959 mut solver_bodies: ResMut<SolverBodies>,
960 mut constraint_graph: ResMut<ConstraintGraph>,
961 solver_config: Res<SolverConfig>,
962 length_unit: Res<PhysicsLengthUnit>,
963 mut diagnostics: ResMut<SolverDiagnostics>,
964) {
965 let start = crate::utils::Instant::now();
966
967 let threshold = solver_config.restitution_threshold * length_unit.0;
969
970 let access = solver_bodies.access();
971
972 for constraint in constraint_graph.colors[COLOR_OVERFLOW_INDEX]
974 .contact_constraints
975 .iter_mut()
976 {
977 solve_restitution_internal(
978 &access,
979 constraint,
980 threshold,
981 solver_config.restitution_iterations,
982 );
983 }
984
985 for color in constraint_graph
987 .colors
988 .iter_mut()
989 .take(COLOR_OVERFLOW_INDEX)
990 .filter(|color| !color.contact_constraints.is_empty())
991 {
992 crate::utils::par_for_each(&mut color.contact_constraints, 64, |_i, constraint| {
993 solve_restitution_internal(
994 &access,
995 constraint,
996 threshold,
997 solver_config.restitution_iterations,
998 );
999 });
1000 }
1001
1002 diagnostics.apply_restitution += start.elapsed();
1003}
1004
1005fn solve_restitution_internal(
1006 access: &SolverBodiesAccess,
1007 constraint: &mut ContactConstraint,
1008 threshold: f32,
1009 iterations: usize,
1010) {
1011 let restitution = constraint.restitution;
1012
1013 if restitution == 0.0 {
1014 return;
1015 }
1016
1017 let mut dummy_body1 = SolverBody::DUMMY;
1018 let mut dummy_body2 = SolverBody::DUMMY;
1019
1020 let (mut body1, mut inertia1) = (&mut dummy_body1, &SolverBodyInertia::DUMMY);
1021 let (mut body2, mut inertia2) = (&mut dummy_body2, &SolverBodyInertia::DUMMY);
1022
1023 let (b1, b2) =
1029 unsafe { access.get_pair_unchecked_mut(constraint.body_index1, constraint.body_index2) };
1030 if let Some((body, inertia)) = b1 {
1031 body1 = body;
1032 inertia1 = inertia;
1033 }
1034 if let Some((body, inertia)) = b2 {
1035 body2 = body;
1036 inertia2 = inertia;
1037 }
1038
1039 match constraint.relative_dominance.cmp(&0) {
1041 Ordering::Greater => inertia1 = &SolverBodyInertia::DUMMY,
1042 Ordering::Less => inertia2 = &SolverBodyInertia::DUMMY,
1043 _ => {}
1044 }
1045
1046 let point_count = constraint.points.len();
1049 let iterations = if point_count > 1 { iterations } else { 1 };
1050
1051 for _ in 0..iterations {
1052 constraint.apply_restitution(body1, body2, inertia1, inertia2, threshold);
1053 }
1054}
1055
1056fn store_contact_impulses(
1059 mut contact_graph: ResMut<ContactGraph>,
1060 mut constraint_graph: ResMut<ConstraintGraph>,
1061 mut diagnostics: ResMut<SolverDiagnostics>,
1062) {
1063 let start = crate::utils::Instant::now();
1064
1065 for color in constraint_graph.colors.iter_mut() {
1066 for constraint in &mut color.contact_constraints {
1067 let Some(manifold) = contact_graph.get_manifold_mut(ContactManifoldHandle {
1068 contact_id: constraint.contact_id,
1069 manifold_index: constraint.manifold_index,
1070 }) else {
1071 unreachable!(
1072 "Contact manifold {:?} for contact ID {:?} not found in contact graph.",
1073 constraint.contact_id, constraint.manifold_index
1074 );
1075 };
1076
1077 for (contact, constraint_point) in
1078 manifold.points.iter_mut().zip(constraint.points.iter())
1079 {
1080 contact.warm_start_normal_impulse = constraint_point.normal_part.impulse;
1081 contact.warm_start_tangent_impulse = constraint_point
1082 .tangent_part
1083 .as_ref()
1084 .map_or(default(), |part| part.impulse);
1085 contact.normal_impulse = constraint_point.normal_part.total_impulse;
1086 }
1087 }
1088 }
1089
1090 diagnostics.store_impulses += start.elapsed();
1091}
1092
1093#[allow(clippy::type_complexity)]
1095pub fn joint_damping<T: Component + EntityConstraint<2>>(
1096 mut solver_bodies: ResMut<SolverBodies>,
1097 index_query: Query<&SolverBodyIndex>,
1098 joints: Query<(&T, &JointDamping)>,
1099 time: Res<Time>,
1100) {
1101 let delta_secs = time.delta_secs();
1102
1103 let access = solver_bodies.access();
1104
1105 let mut dummy_body1 = SolverBody::DUMMY;
1106 let mut dummy_body2 = SolverBody::DUMMY;
1107
1108 for (joint, damping) in &joints {
1109 let entities = joint.entities();
1110
1111 let index1 = index_query
1114 .get(entities[0])
1115 .copied()
1116 .unwrap_or(SolverBodyIndex::INVALID);
1117 let index2 = index_query
1118 .get(entities[1])
1119 .copied()
1120 .unwrap_or(SolverBodyIndex::INVALID);
1121
1122 if index1 == index2 {
1123 continue;
1124 }
1125
1126 let (mut body1, mut inertia1) = (&mut dummy_body1, &SolverBodyInertia::DUMMY);
1127 let (mut body2, mut inertia2) = (&mut dummy_body2, &SolverBodyInertia::DUMMY);
1128
1129 let (b1, b2) = unsafe { access.get_pair_unchecked_mut(index1, index2) };
1133 if let Some((body, inertia)) = b1 {
1135 if !body.flags.is_kinematic() {
1136 inertia1 = inertia;
1137 }
1138 body1 = body;
1139 }
1140 if let Some((body, inertia)) = b2 {
1141 if !body.flags.is_kinematic() {
1142 inertia2 = inertia;
1143 }
1144 body2 = body;
1145 }
1146
1147 let delta_omega = (body2.angular_velocity - body1.angular_velocity)
1148 * (damping.angular * delta_secs).min(1.0);
1149
1150 let i1 = inertia1.effective_inv_angular_inertia();
1151 let i2 = inertia2.effective_inv_angular_inertia();
1152
1153 #[cfg(feature = "2d")]
1154 let angular_impulse = (i1 + i2).recip_or_zero() * delta_omega;
1155 #[cfg(feature = "3d")]
1156 let angular_impulse = inverse_on_free_axes(i1 + i2) * delta_omega;
1157 body1.angular_velocity += i1 * angular_impulse;
1158 body2.angular_velocity -= i2 * angular_impulse;
1159
1160 let delta_v = (body2.linear_velocity - body1.linear_velocity)
1161 * (damping.linear * delta_secs).min(1.0);
1162
1163 let w1 = inertia1.effective_inv_mass();
1164 let w2 = inertia2.effective_inv_mass();
1165
1166 let p = delta_v * (w1 + w2).recip_or_zero();
1167
1168 body1.linear_velocity += p * w1;
1169 body2.linear_velocity -= p * w2;
1170 }
1171}
1172
1173#[cfg(feature = "3d")]
1175fn inverse_on_free_axes(inv_inertia: SymmetricTensor) -> SymmetricTensor {
1176 let locked = SymmetricTensor::from_diagonal(Vec3::select(
1177 inv_inertia.diagonal().cmpeq(Vec3::ZERO),
1178 Vec3::ONE,
1179 Vec3::ZERO,
1180 ));
1181 (inv_inertia + locked).inverse_or_zero() - locked
1182}