Skip to main content

steel_core/physics/
physics_state.rs

1//! Entity physics state representation.
2
3use glam::DVec3;
4#[cfg(test)]
5use steel_registry::entity_type::EntityDimensions;
6use steel_utils::WorldAabb;
7
8use crate::behavior::BlockCollisionContext;
9
10/// Immutable entity movement input used by the collision resolver.
11///
12/// Steel keeps authoritative physical state on `EntityBase`; this type is a
13/// narrow snapshot of the fields vanilla `Entity.move` needs while resolving a
14/// single movement.
15#[derive(Debug, Clone, Copy)]
16pub(crate) struct EntityPhysicsState {
17    /// Current position (center of bounding box at feet level).
18    position: DVec3,
19
20    /// Entity's axis-aligned bounding box in world coordinates.
21    bounding_box: WorldAabb,
22
23    /// Maximum height the entity can step up automatically.
24    max_up_step: f32,
25
26    /// Whether the entity backs away from ledges for this movement.
27    backs_off_from_edge: bool,
28
29    /// Whether the entity is on the ground (affects step-up and jump mechanics).
30    on_ground: bool,
31
32    /// Remaining fall distance for fall damage calculation.
33    fall_distance: f64,
34
35    /// Whether vanilla collision context should treat this entity as descending.
36    descending: bool,
37
38    /// Whether vanilla lets this entity walk on powder snow.
39    can_walk_on_powder_snow: bool,
40
41    /// Whether vanilla collision context should treat this entity as a falling block.
42    is_falling_block: bool,
43}
44
45impl EntityPhysicsState {
46    /// Creates a new physics state from the current entity bounding box.
47    ///
48    /// Vanilla `Entity.move` resolves movement from the entity's actual
49    /// `boundingBox`, not a box reconstructed from dimensions.
50    #[must_use]
51    pub const fn new(position: DVec3, bounding_box: WorldAabb, max_up_step: f32) -> Self {
52        Self {
53            position,
54            bounding_box,
55            max_up_step,
56            backs_off_from_edge: false,
57            on_ground: false,
58            fall_distance: 0.0,
59            descending: false,
60            can_walk_on_powder_snow: false,
61            is_falling_block: false,
62        }
63    }
64
65    /// Creates a new physics state with custom dimensions.
66    #[cfg(test)]
67    #[must_use]
68    pub fn with_dimensions(
69        position: DVec3,
70        dimensions: EntityDimensions,
71        max_up_step: f32,
72    ) -> Self {
73        let bounding_box = Self::make_bounding_box(position, &dimensions);
74
75        Self::new(position, bounding_box, max_up_step)
76    }
77
78    /// Creates a bounding box from position and dimensions.
79    /// Box is centered on X/Z with Y at entity feet (vanilla behavior).
80    #[cfg(test)]
81    #[must_use]
82    fn make_bounding_box(position: DVec3, dimensions: &EntityDimensions) -> WorldAabb {
83        let half_width = f64::from(dimensions.width) / 2.0;
84        let height = f64::from(dimensions.height);
85
86        WorldAabb::entity_box(position.x, position.y, position.z, half_width, height)
87    }
88
89    /// Returns the current bottom-center position.
90    #[must_use]
91    pub const fn position(self) -> DVec3 {
92        self.position
93    }
94
95    /// Returns the current world-space bounding box.
96    #[must_use]
97    pub const fn bounding_box(self) -> WorldAabb {
98        self.bounding_box
99    }
100
101    /// Returns the maximum automatic step-up height.
102    #[must_use]
103    pub const fn max_up_step(self) -> f32 {
104        self.max_up_step
105    }
106
107    /// Returns whether sneak-edge prevention should apply.
108    #[must_use]
109    pub const fn backs_off_from_edge(self) -> bool {
110        self.backs_off_from_edge
111    }
112
113    /// Returns whether the entity was on ground before movement.
114    #[must_use]
115    pub const fn on_ground(self) -> bool {
116        self.on_ground
117    }
118
119    /// Returns the accumulated fall distance before movement.
120    #[must_use]
121    pub const fn fall_distance(self) -> f64 {
122        self.fall_distance
123    }
124
125    /// Returns the vanilla block collision context for this movement snapshot.
126    #[must_use]
127    pub const fn block_collision_context(self) -> BlockCollisionContext {
128        BlockCollisionContext::entity(self.position.y, self.descending)
129            .with_fall_distance(self.fall_distance)
130            .with_can_walk_on_powder_snow(self.can_walk_on_powder_snow)
131            .with_falling_block(self.is_falling_block)
132    }
133
134    /// Returns a copy with the pre-movement ground flag set.
135    #[must_use]
136    pub const fn with_on_ground(mut self, on_ground: bool) -> Self {
137        self.on_ground = on_ground;
138        self
139    }
140
141    /// Returns a copy with sneak-edge prevention enabled or disabled.
142    #[must_use]
143    pub const fn with_backs_off_from_edge(mut self, backs_off_from_edge: bool) -> Self {
144        self.backs_off_from_edge = backs_off_from_edge;
145        self
146    }
147
148    /// Returns a copy with the accumulated fall distance set.
149    #[must_use]
150    pub const fn with_fall_distance(mut self, fall_distance: f64) -> Self {
151        self.fall_distance = fall_distance;
152        self
153    }
154
155    /// Returns a copy with the collision-context descending flag set.
156    #[must_use]
157    pub const fn with_descending(mut self, descending: bool) -> Self {
158        self.descending = descending;
159        self
160    }
161
162    /// Returns a copy with powder-snow walkability set.
163    #[must_use]
164    pub const fn with_can_walk_on_powder_snow(mut self, can_walk_on_powder_snow: bool) -> Self {
165        self.can_walk_on_powder_snow = can_walk_on_powder_snow;
166        self
167    }
168
169    /// Returns a copy with falling-block collision context set.
170    #[must_use]
171    pub const fn with_falling_block(mut self, is_falling_block: bool) -> Self {
172        self.is_falling_block = is_falling_block;
173        self
174    }
175}