bevy_tnua_physics_integration_layer/lib.rs
1//! # Physics Integration Layer for bevy-tnua
2//!
3//! Crates that implement a physics layer integration for Tnua (like bevy-tnua-rapier or
4//! bevy-tnua-avian) should depend on this crate and not on the main bevy-tnua crate. This crate
5//! should update less often - only when there are changes in the integration layer (which is not
6//! supposed to change as much) or when Bevy itself updates.
7//!
8//! To integrate a Bevy physics engine with Tnua, one should create a plugin named
9//! `Tnua<physics-engine-name>Plugin`, which:
10//!
11//! * Configures [`TnuaSystemSet`] to not run when the physics engine is paused.
12//! * Add systems, to the [`TnuaPipelineStages::Sensors`] stage, that update:
13//! * [`TnuaRigidBodyTracker`](data_for_backends::TnuaRigidBodyTracker) with the objects current
14//! kinematic status (position, rotation, velocity, angular velocity) as well as the gravity
15//! currently applied to it.
16//! * [`TnuaProximitySensor`](data_for_backends::TnuaProximitySensor) with the _first_ tangible
17//! collider within range, and [`TnuaGhostSensor`](data_for_backends::TnuaGhostSensor) with
18//! _all_ the ghost colliders found before that tangible collider.
19//! * A tangible collider is a **non-ghost** collider that physically interacts with the
20//! character's collider.
21//! * A ghost collider is a collider marked with the
22//! [`TnuaGhostPlatform`](data_for_backends::TnuaGhostPlatform) component. It may or may not
23//! physically interact with the character's collider - as long as it has the component it is
24//! considered a ghost collider.
25//! * The sensor should ignore the owner entity's collider.
26//! * If the sensor has the
27//! [`TnuaSubservientSensor`](subservient_sensors::TnuaSubservientSensor) component, the
28//! "owner entity" is defined as the `owner_entity` field from that component and not the
29//! entity the sensor component is attached to.
30//! * The detection should be done with a ray cast, unless the sensor is configured to cast a
31//! shape instead. Such configuration is done with component, defined by the integration
32//! crate, that specifies the shape to cast in a way the integration crate can pass on to the
33//! physics engine. The name of that component should be
34//! `Tnua<physics-engine-name>SensorShape`.
35//! * The detection should skip entities marked with the
36//! [`TnuaNotPlatform`](data_for_backends::TnuaNotPlatform) component.
37//! * [`TnuaObstacleRadar`](data_for_backends::TnuaObstacleRadar) with all the entities within
38//! proximity (defined by `radius` and `height` fields on the radar component). The system must
39//! first call the
40//! [`pre_marking_update`](data_for_backends::TnuaObstacleRadar::pre_marking_update) method,
41//! and only then call [`mark_seen`](data_for_backends::TnuaObstacleRadar::mark_seen) on each
42//! entity still within range in the current frame.
43//!
44//! The integration crate may update all these components in one system or multiple systems as it
45//! sees fit.
46//!
47//! * Add a system, to the [`TnuaPipelineStages::Motors`] stage, that applies all the impulses and
48//! accelerations from [`TnuaMotor`](data_for_backends::TnuaMotor) components.
49//!
50//! Here, too, if it makes sense to split this work into multiple systems the integration crate
51//! may do so at its own discretion.
52//!
53//! * Ensure that [`TnuaSystemSet`] runs before the integration backend's systems.
54//!
55//! * Handle [`TnuaGravity`](data_for_backends::TnuaGravity) in whatever way the integration crate
56//! sees fit. This usually entails:
57//! * Feeding the gravity set in the `TnuaGravity` into the `TnuaRigidBodyTracker` instead of
58//! the regular global gravity.
59//! * Nullifying the regular global gravity. This can be done either by applying an opposing
60//! force or by applying an opposing force or by using some facility offered by the physics
61//! backend (e.g. both Rapier and Avian have a `GravityScale` component which can be used for
62//! that purpose)
63//! * Applying the gravity from `TnuaGravity` as a force.
64//!
65//! If the physics backend has its own way to do per-rigid-body gravity, the integration crate
66//! may prefer to sync `TnuaGravity` into that mechanism instead (and should support that
67//! mechanism as the input source for `TnuaRigidBodyTracker::gravity` when it is used, even
68//! if `TnuaGravity` is not used)
69//!
70//! * Define a type that implements [`TnuaSpatialExt`](spatial_ext::TnuaSpatialExt). That type will
71//! typically be a [`SystemParam`](bevy::ecs::system::SystemParam) that contains queries and
72//! resources for getting the required information from the physics backend.
73//!
74//! The integration backend's systems must run with the same timing as the physics backend. If the
75//! physics backend supports running in a different schedule, the integration plugin should also
76//! support it by adding a `::new()` method that accepts a schedule and registers all the systems
77//! there. It should also implement `Default` to make it run in the default schedule (usually
78//! `Update`)
79//!
80//! Note that a physics backend may run its systems under `PostUpdate` instead of `Update` (both
81//! Rapier and Avian do this by default). In this case, it's still okay for the integration backend
82//! to run under `Update`, because they use the same timing.
83//!
84//! If the integration crate needs the character entity to have more components from the physics
85//! engine crate, that one would not naturally add to it, it should define a bundle named
86//! `Tnua<physics-engine-name>IOBundle` that adds these components. One would naturally add a rigid
87//! body and a collider, so they should not go in that bundle, but if the crate needs things users
88//! rarely think about - for example, bevy_rapier's `ReadMassProperties` - then these components
89//! should go in that bundle.
90use bevy::prelude::*;
91
92pub mod data_for_backends;
93pub mod math;
94pub mod obstacle_radar;
95pub mod spatial_ext;
96pub mod subservient_sensors;
97
98/// Umbrella system set for [`TnuaPipelineStages`].
99///
100/// The physics backends' plugins are responsible for preventing this entire system set from
101/// running when the physics backend itself is paused.
102#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)]
103pub struct TnuaSystemSet;
104
105/// The various stages of the Tnua pipeline.
106#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)]
107pub enum TnuaPipelineStages {
108 /// Data is read from the physics backend.
109 Sensors,
110 /// Data is propagated through the subservient sensors.
111 SubservientSensors,
112 /// Tnua decieds how the entity should be manipulated.
113 Logic,
114 /// Forces are applied in the physics backend.
115 Motors,
116}