-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathmod.rs
More file actions
279 lines (258 loc) · 9.48 KB
/
mod.rs
File metadata and controls
279 lines (258 loc) · 9.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
//! Rapier-backed 2D physics backend.
//!
//! This module provides a minimal wrapper around `rapier2d` to support the
//! higher-level `lambda-rs` physics APIs without exposing vendor types outside
//! of the platform layer.
use std::{
collections::{
HashMap,
HashSet,
},
error::Error,
fmt,
};
use rapier2d::prelude::*;
mod colliders;
mod helpers;
mod queries;
mod rigid_bodies;
mod simulation;
#[cfg(test)]
mod tests;
/// The rigid body integration mode.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RigidBodyType2D {
/// A body that does not move under simulation.
Static,
/// A body affected by gravity and forces.
Dynamic,
/// A body integrated only by user-provided motion.
Kinematic,
}
/// Backend errors for 2D rigid body operations.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RigidBody2DBackendError {
/// The referenced rigid body was not found.
BodyNotFound,
/// The provided position is invalid.
InvalidPosition { x: f32, y: f32 },
/// The provided rotation is invalid.
InvalidRotation { radians: f32 },
/// The provided linear velocity is invalid.
InvalidVelocity { x: f32, y: f32 },
/// The provided force is invalid.
InvalidForce { x: f32, y: f32 },
/// The provided impulse is invalid.
InvalidImpulse { x: f32, y: f32 },
/// The provided dynamic mass is invalid.
InvalidMassKg { mass_kg: f32 },
/// The requested operation is unsupported for the body type.
UnsupportedOperation { body_type: RigidBodyType2D },
}
impl fmt::Display for RigidBody2DBackendError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BodyNotFound => {
return write!(formatter, "rigid body not found");
}
Self::InvalidPosition { x, y } => {
return write!(formatter, "invalid position: ({x}, {y})");
}
Self::InvalidRotation { radians } => {
return write!(formatter, "invalid rotation: {radians}");
}
Self::InvalidVelocity { x, y } => {
return write!(formatter, "invalid velocity: ({x}, {y})");
}
Self::InvalidForce { x, y } => {
return write!(formatter, "invalid force: ({x}, {y})");
}
Self::InvalidImpulse { x, y } => {
return write!(formatter, "invalid impulse: ({x}, {y})");
}
Self::InvalidMassKg { mass_kg } => {
return write!(formatter, "invalid mass_kg: {mass_kg}");
}
Self::UnsupportedOperation { body_type } => {
return write!(
formatter,
"unsupported operation for body_type: {body_type:?}"
);
}
}
}
}
impl Error for RigidBody2DBackendError {}
/// Backend errors for 2D collider operations.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Collider2DBackendError {
/// The referenced rigid body was not found.
BodyNotFound,
/// The provided polygon could not be represented as a convex hull.
InvalidPolygonDegenerate,
}
impl fmt::Display for Collider2DBackendError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BodyNotFound => {
return write!(formatter, "rigid body not found");
}
Self::InvalidPolygonDegenerate => {
return write!(formatter, "invalid polygon: degenerate");
}
}
}
}
impl Error for Collider2DBackendError {}
/// Backend-agnostic data describing the nearest 2D raycast hit.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RaycastHit2DBackend {
/// The hit rigid body's slot index.
pub body_slot_index: u32,
/// The hit rigid body's slot generation.
pub body_slot_generation: u32,
/// The world-space hit point.
pub point: [f32; 2],
/// The world-space unit hit normal.
pub normal: [f32; 2],
/// The non-negative hit distance in meters.
pub distance: f32,
}
/// Indicates whether a backend collision pair started or ended contact.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CollisionEventKind2DBackend {
/// The body pair started touching during the current backend step.
Started,
/// The body pair stopped touching during the current backend step.
Ended,
}
/// Backend-agnostic data describing one 2D collision event.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CollisionEvent2DBackend {
/// The transition kind for the body pair.
pub kind: CollisionEventKind2DBackend,
/// The first rigid body's slot index.
pub body_a_slot_index: u32,
/// The first rigid body's slot generation.
pub body_a_slot_generation: u32,
/// The second rigid body's slot index.
pub body_b_slot_index: u32,
/// The second rigid body's slot generation.
pub body_b_slot_generation: u32,
/// The representative world-space contact point, when available.
pub contact_point: Option<[f32; 2]>,
/// The representative world-space contact normal, when available.
pub normal: Option<[f32; 2]>,
/// The representative penetration depth, when available.
pub penetration: Option<f32>,
}
/// The fallback mass applied to dynamic bodies before density colliders exist.
const DYNAMIC_BODY_FALLBACK_MASS_KG: f32 = 1.0;
/// Stores per-body state that `lambda-rs` tracks alongside Rapier.
///
/// This slot exists because `lambda-rs` defines integration semantics that are
/// stricter than the vendor backend:
/// - Forces are accumulated and cleared explicitly by the public API.
/// - Impulses update velocity immediately.
///
/// # Invariants
/// - `rapier_handle` MUST reference a body in `PhysicsBackend2D::bodies`.
/// - `explicit_dynamic_mass_kg` MUST be `Some` only for dynamic bodies.
/// - `generation` MUST be non-zero and is used to validate handles.
#[derive(Debug, Clone, Copy)]
struct RigidBodySlot2D {
/// The rigid body's integration mode.
body_type: RigidBodyType2D,
/// The handle to the Rapier rigid body stored in the `RigidBodySet`.
rapier_handle: RigidBodyHandle,
/// Accumulated forces applied by the public API, in Newtons.
force_accumulator: [f32; 2],
/// The explicitly configured body mass in kilograms, if set.
///
/// When this value is `Some`, collider density MUST NOT affect body mass
/// properties. The backend enforces this by creating attached colliders with
/// zero density and using the configured value as the body's additional mass.
explicit_dynamic_mass_kg: Option<f32>,
/// Tracks whether the body has at least one positive-density collider.
///
/// This flag supports the spec requirement that bodies with no positive
/// density colliders default to `1.0` kg, while bodies with at least one
/// positive-density collider compute mass from collider density alone.
has_positive_density_colliders: bool,
/// A monotonically increasing counter used to validate stale handles.
generation: u32,
}
/// Stores per-collider state that `lambda-rs` tracks alongside Rapier.
///
/// # Invariants
/// - `rapier_handle` MUST reference a collider in `PhysicsBackend2D::colliders`.
/// - `generation` MUST be non-zero and is used to validate stale handles.
#[derive(Debug, Clone, Copy)]
struct ColliderSlot2D {
/// The handle to the Rapier collider stored in the `ColliderSet`.
rapier_handle: ColliderHandle,
/// The parent rigid body slot index that owns this collider.
parent_slot_index: u32,
/// The parent rigid body slot generation that owns this collider.
parent_slot_generation: u32,
/// A monotonically increasing counter used to validate stale handles.
generation: u32,
}
/// Describes how collider attachment should affect dynamic-body mass semantics.
///
/// This helper isolates `lambda-rs` mass rules from the Rapier attachment flow
/// so body creation and collider attachment share one backend policy source.
#[derive(Debug, Clone, Copy, PartialEq)]
struct ColliderAttachmentMassPlan2D {
/// The density value that MUST be passed to the Rapier collider builder.
rapier_density: f32,
/// Whether attaching this collider transitions the body to density-driven
/// mass computation.
should_mark_has_positive_density_colliders: bool,
/// Whether the initial fallback mass MUST be removed before insertion.
should_remove_fallback_mass: bool,
}
/// A normalized body-pair key used for backend collision tracking.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct BodyPairKey2D {
/// The first body slot index.
body_a_slot_index: u32,
/// The first body slot generation.
body_a_slot_generation: u32,
/// The second body slot index.
body_b_slot_index: u32,
/// The second body slot generation.
body_b_slot_generation: u32,
}
/// The representative contact selected for a body pair during one step.
#[derive(Debug, Clone, Copy, PartialEq)]
struct BodyPairContact2D {
/// The representative world-space contact point.
point: [f32; 2],
/// The representative world-space normal from body A toward body B.
normal: [f32; 2],
/// The non-negative penetration depth.
penetration: f32,
}
/// A 2D physics backend powered by `rapier2d`.
///
/// This type is an internal implementation detail used by `lambda-rs`.
pub struct PhysicsBackend2D {
gravity: Vector,
integration_parameters: IntegrationParameters,
islands: IslandManager,
broad_phase: BroadPhaseBvh,
narrow_phase: NarrowPhase,
bodies: RigidBodySet,
colliders: ColliderSet,
impulse_joints: ImpulseJointSet,
multibody_joints: MultibodyJointSet,
ccd_solver: CCDSolver,
pipeline: PhysicsPipeline,
rigid_body_slots_2d: Vec<RigidBodySlot2D>,
collider_slots_2d: Vec<ColliderSlot2D>,
collider_parent_slots_2d: HashMap<ColliderHandle, (u32, u32)>,
active_body_pairs_2d: HashSet<BodyPairKey2D>,
active_body_pair_order_2d: Vec<BodyPairKey2D>,
queued_collision_events_2d: Vec<CollisionEvent2DBackend>,
}