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
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
//! Object layers, broad-phase layers and the table that says which object layers collide.
//!
//! Every body lives in one object layer. Each object layer maps to one broad-phase layer, the
//! coarse tree the broad phase keeps its bodies in. Two bodies can only collide when their
//! object layers are enabled as a pair. See Jolt's documentation on collision detection:
//! <https://jrouwe.github.io/JoltPhysicsDocs/5.6.0/index.html#collision-detection>.
use oxijolt_sys::*;
use crate::owned::{JoltObject, Owned};
use crate::WorldError;
/// The object layer of a body: which other bodies it can collide with.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ObjectLayer(u32);
impl ObjectLayer {
/// Layer 0 in [`CollisionLayers::default`]: static geometry.
pub const NON_MOVING: Self = Self(0);
/// Layer 1 in [`CollisionLayers::default`]: everything that moves.
pub const MOVING: Self = Self(1);
/// Wraps a raw layer index. It is only valid in a world whose [`CollisionLayers`] has more
/// object layers than `value`.
pub const fn new(value: u32) -> Self {
Self(value)
}
/// The raw layer index.
pub const fn get(self) -> u32 {
self.0
}
}
/// A broad-phase layer: one of the trees the broad phase sorts bodies into.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BroadPhaseLayer(u8);
impl BroadPhaseLayer {
/// Broad-phase layer 0 in [`CollisionLayers::default`]: static geometry.
pub const NON_MOVING: Self = Self(0);
/// Broad-phase layer 1 in [`CollisionLayers::default`]: everything that moves.
pub const MOVING: Self = Self(1);
/// Wraps a raw broad-phase layer index.
pub const fn new(value: u8) -> Self {
Self(value)
}
/// The raw broad-phase layer index.
pub const fn get(self) -> u8 {
self.0
}
}
/// The collision layer setup of a world: object layers, the broad-phase layer of each, and the
/// pairs of object layers that collide.
///
/// Object layers are numbered in the order [`add_object_layer`](Self::add_object_layer) creates
/// them, and each one is mapped to a broad-phase layer when it is created.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CollisionLayers {
broad_phase_layer_count: u8,
broad_phase_of: Vec<BroadPhaseLayer>,
pairs: Vec<(ObjectLayer, ObjectLayer)>,
}
impl CollisionLayers {
/// Largest number of object layers a world accepts: 65535.
pub const MAX_OBJECT_LAYERS: u32 = u16::MAX as u32;
/// Creates a setup with `broad_phase_layer_count` broad-phase layers and no object layers.
pub fn new(broad_phase_layer_count: u8) -> Self {
Self {
broad_phase_layer_count,
broad_phase_of: Vec::new(),
pairs: Vec::new(),
}
}
/// Adds the next object layer, kept in `broad_phase`, and returns it.
pub fn add_object_layer(&mut self, broad_phase: BroadPhaseLayer) -> ObjectLayer {
let layer = ObjectLayer(self.broad_phase_of.len() as u32);
self.broad_phase_of.push(broad_phase);
layer
}
/// Lets bodies in layer `a` collide with bodies in layer `b`. The relation is symmetric.
pub fn enable_collision(&mut self, a: ObjectLayer, b: ObjectLayer) -> &mut Self {
self.pairs.push((a, b));
self
}
/// Number of object layers added so far.
pub fn object_layer_count(&self) -> u32 {
self.broad_phase_of.len() as u32
}
/// Number of broad-phase layers.
pub fn broad_phase_layer_count(&self) -> u8 {
self.broad_phase_layer_count
}
/// Checks everything Jolt's layer tables index with: an unmapped object layer or an
/// out-of-range index reads past the tables in a release build.
pub(crate) fn validate(&self) -> Result<(), WorldError> {
if self.broad_phase_of.is_empty() {
return Err(WorldError::InvalidLayers("there are no object layers"));
}
if self.broad_phase_of.len() > Self::MAX_OBJECT_LAYERS as usize {
return Err(WorldError::InvalidLayers("more than 65535 object layers"));
}
// 0xff is Jolt's invalid broad-phase layer, which a u8 count of at most 255 excludes.
if self.broad_phase_layer_count == 0 {
return Err(WorldError::InvalidLayers("there are no broad-phase layers"));
}
if self
.broad_phase_of
.iter()
.any(|layer| layer.0 >= self.broad_phase_layer_count)
{
return Err(WorldError::InvalidLayers(
"an object layer maps to a broad-phase layer that does not exist",
));
}
let count = self.object_layer_count();
if self.pairs.iter().any(|(a, b)| a.0 >= count || b.0 >= count) {
return Err(WorldError::InvalidLayers(
"a collision pair names an object layer that does not exist",
));
}
Ok(())
}
/// Builds Jolt's table-based layer interface and filters from this setup.
///
/// # Safety
/// `JPH_Init` has returned true and [`validate`](Self::validate) returned `Ok`.
pub(crate) unsafe fn create_tables(&self) -> Result<LayerTables, WorldError> {
let object_layers = self.object_layer_count();
let broad_phase_layers = u32::from(self.broad_phase_layer_count);
// SAFETY: Jolt is initialised (caller contract); the table takes only a count. The
// handle takes over the returned object.
let pair_filter =
unsafe { Owned::from_raw(JPH_ObjectLayerPairFilterTable_Create(object_layers)) }
.ok_or(WorldError::AllocationFailed("object layer pair filter"))?;
for &(a, b) in &self.pairs {
// SAFETY: `pair_filter` is live, and `validate` checked both layers are below the
// count the table was created with.
unsafe {
JPH_ObjectLayerPairFilterTable_EnableCollision(pair_filter.as_ptr(), a.0, b.0)
};
}
// SAFETY: Jolt is initialised (caller contract); the table takes only counts. The handle
// takes over the returned object.
let broad_phase = unsafe {
Owned::from_raw(JPH_BroadPhaseLayerInterfaceTable_Create(
object_layers,
broad_phase_layers,
))
}
.ok_or(WorldError::AllocationFailed("broad-phase layer interface"))?;
for (object_layer, broad_phase_layer) in (0..).zip(&self.broad_phase_of) {
// SAFETY: `broad_phase` is live; both indices are within the counts it was created
// with (`validate`).
unsafe {
JPH_BroadPhaseLayerInterfaceTable_MapObjectToBroadPhaseLayer(
broad_phase.as_ptr(),
object_layer,
broad_phase_layer.0,
)
};
}
// SAFETY: both tables are live and fully mapped with the counts passed here. Jolt reads
// them in the constructor and keeps no reference to either
// (`ObjectVsBroadPhaseLayerFilterTable.h`). The handle takes over the returned object.
let object_vs_broad_phase = unsafe {
Owned::from_raw(JPH_ObjectVsBroadPhaseLayerFilterTable_Create(
broad_phase.as_ptr(),
broad_phase_layers,
pair_filter.as_ptr(),
object_layers,
))
}
.ok_or(WorldError::AllocationFailed(
"object vs broad-phase layer filter",
))?;
Ok(LayerTables {
broad_phase,
pair_filter,
object_vs_broad_phase,
})
}
}
impl Default for CollisionLayers {
/// The setup of Jolt's HelloWorld sample: [`ObjectLayer::NON_MOVING`] in
/// [`BroadPhaseLayer::NON_MOVING`] and [`ObjectLayer::MOVING`] in
/// [`BroadPhaseLayer::MOVING`]; moving bodies collide with both layers, static ones only
/// with moving ones.
fn default() -> Self {
let mut layers = Self::new(2);
let non_moving = layers.add_object_layer(BroadPhaseLayer::NON_MOVING);
let moving = layers.add_object_layer(BroadPhaseLayer::MOVING);
layers
.enable_collision(moving, non_moving)
.enable_collision(moving, moving);
layers
}
}
/// The three layer objects a physics system needs. Owned here until
/// `JPH_PhysicsSystem_Create` succeeds with them; from then on the system owns them and
/// `JPH_PhysicsSystem_Destroy` deletes them, so the caller calls
/// [`forget`](Self::forget).
pub(crate) struct LayerTables {
broad_phase: Owned<JPH_BroadPhaseLayerInterface>,
pair_filter: Owned<JPH_ObjectLayerPairFilter>,
object_vs_broad_phase: Owned<JPH_ObjectVsBroadPhaseLayerFilter>,
}
impl LayerTables {
/// The pointers for `JPH_PhysicsSystemSettings`, still owned by `self`, as
/// `(broad_phase, pair_filter, object_vs_broad_phase)`.
pub(crate) fn as_raw(
&self,
) -> (
*mut JPH_BroadPhaseLayerInterface,
*mut JPH_ObjectLayerPairFilter,
*mut JPH_ObjectVsBroadPhaseLayerFilter,
) {
(
self.broad_phase.as_ptr(),
self.pair_filter.as_ptr(),
self.object_vs_broad_phase.as_ptr(),
)
}
/// Gives up ownership without destroying anything. Call only after
/// `JPH_PhysicsSystem_Create` returned a system built from these tables;
/// `JPH_PhysicsSystem_Destroy` deletes them later.
pub(crate) fn forget(self) {
let Self {
broad_phase,
pair_filter,
object_vs_broad_phase,
} = self;
// The system already holds these pointers.
broad_phase.into_raw();
pair_filter.into_raw();
object_vs_broad_phase.into_raw();
}
}
/// A layer pair table, owned by `LayerTables` until a physics system takes it over.
impl JoltObject for JPH_ObjectLayerPairFilter {
unsafe fn destroy(ptr: *mut Self) {
// SAFETY: the owner owns the filter (trait contract) and no physics system references
// it: a system that takes it over gets it through `LayerTables::forget`, which never
// destroys.
unsafe { JPH_ObjectLayerPairFilter_Destroy(ptr) };
}
}
/// A broad-phase layer table, owned by `LayerTables` until a physics system takes it over.
impl JoltObject for JPH_BroadPhaseLayerInterface {
unsafe fn destroy(ptr: *mut Self) {
// SAFETY: as for `JPH_ObjectLayerPairFilter`.
unsafe { JPH_BroadPhaseLayerInterface_Destroy(ptr) };
}
}
/// An object vs broad-phase layer table, owned by `LayerTables` until a physics system takes
/// it over.
impl JoltObject for JPH_ObjectVsBroadPhaseLayerFilter {
unsafe fn destroy(ptr: *mut Self) {
// SAFETY: as for `JPH_ObjectLayerPairFilter`.
unsafe { JPH_ObjectVsBroadPhaseLayerFilter_Destroy(ptr) };
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_maps_and_pairs_like_jolts_hello_world() {
let layers = CollisionLayers::default();
assert_eq!(layers.object_layer_count(), 2);
assert_eq!(layers.broad_phase_layer_count(), 2);
assert_eq!(
layers.broad_phase_of,
[BroadPhaseLayer::NON_MOVING, BroadPhaseLayer::MOVING]
);
assert_eq!(
layers.pairs,
[
(ObjectLayer::MOVING, ObjectLayer::NON_MOVING),
(ObjectLayer::MOVING, ObjectLayer::MOVING)
]
);
assert_eq!(layers.validate(), Ok(()));
}
#[test]
fn validate_rejects_inconsistent_tables() {
let invalid = |layers: &CollisionLayers| {
matches!(layers.validate(), Err(WorldError::InvalidLayers(_)))
};
assert!(invalid(&CollisionLayers::new(1)));
let mut no_broad_phase = CollisionLayers::new(0);
no_broad_phase.add_object_layer(BroadPhaseLayer::new(0));
assert!(invalid(&no_broad_phase));
let mut unknown_broad_phase = CollisionLayers::new(2);
unknown_broad_phase.add_object_layer(BroadPhaseLayer::new(2));
assert!(invalid(&unknown_broad_phase));
let mut unknown_pair = CollisionLayers::new(1);
let only = unknown_pair.add_object_layer(BroadPhaseLayer::new(0));
unknown_pair.enable_collision(only, ObjectLayer::new(1));
assert!(invalid(&unknown_pair));
}
/// Structures, terrain, items, actors and features: items collide with every layer,
/// nothing else collides.
fn five_layers() -> CollisionLayers {
let mut layers = CollisionLayers::new(2);
let structures = layers.add_object_layer(BroadPhaseLayer::NON_MOVING);
let terrain = layers.add_object_layer(BroadPhaseLayer::NON_MOVING);
let items = layers.add_object_layer(BroadPhaseLayer::MOVING);
let actors = layers.add_object_layer(BroadPhaseLayer::MOVING);
let features = layers.add_object_layer(BroadPhaseLayer::NON_MOVING);
for other in [structures, terrain, items, actors, features] {
layers.enable_collision(items, other);
}
layers
}
#[test]
fn five_layer_tables_are_built_and_released() {
let layers = five_layers();
assert_eq!(layers.object_layer_count(), 5);
assert_eq!(layers.validate(), Ok(()));
assert!(crate::world::ensure_initialized());
for _ in 0..100 {
// SAFETY: Jolt is initialised and `validate` passed just above.
let tables = unsafe { layers.create_tables() }.unwrap();
drop(tables);
}
}
}