Skip to main content

box3d_rust/shape/
accessors.rs

1//! Remaining public shape getters/setters from shape.c: event flags, density,
2//! name, user data, AABB, type/body/world/sensor, ray cast, closest point.
3//!
4//! SPDX-FileCopyrightText: 2025 Erin Catto
5//! SPDX-License-Identifier: MIT
6
7use super::dispatch::{compute_shape_mass, make_shape_proxy};
8use super::lifecycle::get_shape;
9use super::query::ray_cast_shape;
10use super::shape_flags;
11use crate::body::{get_body_transform, get_body_transform_quick, make_body_id};
12use crate::constants::{NULL_NAME, SHAPE_NAME_LENGTH};
13use crate::contact::contact_flags;
14use crate::core::NULL_INDEX;
15use crate::distance::{shape_distance, DistanceInput, ShapeProxy, SimplexCache};
16use crate::events::ContactData;
17use crate::geometry::{MassData, RayCastInput, ShapeType};
18use crate::id::{BodyId, ContactId, ShapeId, WorldId};
19use crate::math_functions::{
20    inv_mul_transforms, is_valid_float, is_valid_position, is_valid_vec3, offset_pos,
21    to_relative_transform, transform_point, Aabb, Pos, Vec3, POS_ZERO, TRANSFORM_IDENTITY,
22    VEC3_ZERO,
23};
24use crate::types::WorldCastOutput;
25use crate::world::World;
26
27fn truncate_shape_name(name: &str) -> &str {
28    if name.len() > SHAPE_NAME_LENGTH {
29        let mut end = SHAPE_NAME_LENGTH;
30        while end > 0 && !name.is_char_boundary(end) {
31            end -= 1;
32        }
33        &name[..end]
34    } else {
35        name
36    }
37}
38
39fn set_shape_flag(world: &mut World, shape_id: ShapeId, bit: u8, flag: bool) {
40    debug_assert!(!world.locked);
41    if world.locked {
42        return;
43    }
44    let index = get_shape(world, shape_id);
45    let shape = &mut world.shapes[index as usize];
46    if flag {
47        shape.flags |= bit;
48    } else {
49        shape.flags &= !bit;
50    }
51}
52
53fn shape_has_flag(world: &World, shape_id: ShapeId, bit: u8) -> bool {
54    let index = get_shape(world, shape_id);
55    (world.shapes[index as usize].flags & bit) != 0
56}
57
58/// (b3Shape_EnableSensorEvents)
59pub fn shape_enable_sensor_events(world: &mut World, shape_id: ShapeId, flag: bool) {
60    crate::recording::with_recording(world, |rec| {
61        rec.write_shape_enable_sensor_events(shape_id, flag);
62    });
63    set_shape_flag(world, shape_id, shape_flags::ENABLE_SENSOR_EVENTS, flag);
64}
65
66/// (b3Shape_AreSensorEventsEnabled)
67pub fn shape_are_sensor_events_enabled(world: &World, shape_id: ShapeId) -> bool {
68    shape_has_flag(world, shape_id, shape_flags::ENABLE_SENSOR_EVENTS)
69}
70
71/// (b3Shape_EnableContactEvents)
72pub fn shape_enable_contact_events(world: &mut World, shape_id: ShapeId, flag: bool) {
73    crate::recording::with_recording(world, |rec| {
74        rec.write_shape_enable_contact_events(shape_id, flag);
75    });
76    set_shape_flag(world, shape_id, shape_flags::ENABLE_CONTACT_EVENTS, flag);
77}
78
79/// (b3Shape_AreContactEventsEnabled)
80pub fn shape_are_contact_events_enabled(world: &World, shape_id: ShapeId) -> bool {
81    shape_has_flag(world, shape_id, shape_flags::ENABLE_CONTACT_EVENTS)
82}
83
84/// (b3Shape_EnablePreSolveEvents)
85pub fn shape_enable_pre_solve_events(world: &mut World, shape_id: ShapeId, flag: bool) {
86    crate::recording::with_recording(world, |rec| {
87        rec.write_shape_enable_pre_solve_events(shape_id, flag);
88    });
89    set_shape_flag(world, shape_id, shape_flags::ENABLE_PRE_SOLVE_EVENTS, flag);
90}
91
92/// (b3Shape_ArePreSolveEventsEnabled)
93pub fn shape_are_pre_solve_events_enabled(world: &World, shape_id: ShapeId) -> bool {
94    shape_has_flag(world, shape_id, shape_flags::ENABLE_PRE_SOLVE_EVENTS)
95}
96
97/// (b3Shape_EnableHitEvents)
98pub fn shape_enable_hit_events(world: &mut World, shape_id: ShapeId, flag: bool) {
99    crate::recording::with_recording(world, |rec| {
100        rec.write_shape_enable_hit_events(shape_id, flag);
101    });
102    set_shape_flag(world, shape_id, shape_flags::ENABLE_HIT_EVENTS, flag);
103}
104
105/// (b3Shape_AreHitEventsEnabled)
106pub fn shape_are_hit_events_enabled(world: &World, shape_id: ShapeId) -> bool {
107    shape_has_flag(world, shape_id, shape_flags::ENABLE_HIT_EVENTS)
108}
109
110/// (b3Shape_SetUserData) — Rust stores `u64` instead of `void*`.
111pub fn shape_set_user_data(world: &mut World, shape_id: ShapeId, user_data: u64) {
112    let index = get_shape(world, shape_id);
113    world.shapes[index as usize].user_data = user_data;
114}
115
116/// (b3Shape_GetUserData)
117pub fn shape_get_user_data(world: &World, shape_id: ShapeId) -> u64 {
118    let index = get_shape(world, shape_id);
119    world.shapes[index as usize].user_data
120}
121
122/// Set the shape name (truncated to [`SHAPE_NAME_LENGTH`]). Uses the world name
123/// cache rather than C's fixed char buffer. (b3Shape_SetName)
124pub fn shape_set_name(world: &mut World, shape_id: ShapeId, name: &str) {
125    crate::recording::with_recording(world, |rec| {
126        rec.write_shape_set_name(shape_id, name);
127    });
128    let truncated = truncate_shape_name(name);
129    let name_id = world.names.add_name(truncated);
130    let index = get_shape(world, shape_id);
131    world.shapes[index as usize].name_id = name_id;
132}
133
134/// (b3Shape_GetName)
135pub fn shape_get_name(world: &World, shape_id: ShapeId) -> &str {
136    let index = get_shape(world, shape_id);
137    let name_id = world.shapes[index as usize].name_id;
138    if name_id == NULL_NAME {
139        return "";
140    }
141    world.names.find_name(name_id).unwrap_or("")
142}
143
144/// (b3Shape_IsSensor)
145pub fn shape_is_sensor(world: &World, shape_id: ShapeId) -> bool {
146    let index = get_shape(world, shape_id);
147    world.shapes[index as usize].sensor_index != NULL_INDEX
148}
149
150/// (b3Shape_GetType)
151pub fn shape_get_type(world: &World, shape_id: ShapeId) -> ShapeType {
152    let index = get_shape(world, shape_id);
153    world.shapes[index as usize].shape_type()
154}
155
156/// (b3Shape_GetBody)
157pub fn shape_get_body(world: &World, shape_id: ShapeId) -> BodyId {
158    let index = get_shape(world, shape_id);
159    let body_index = world.shapes[index as usize].body_id;
160    make_body_id(world, body_index)
161}
162
163/// (b3Shape_GetWorld)
164pub fn shape_get_world(world: &World, shape_id: ShapeId) -> WorldId {
165    WorldId {
166        index1: shape_id.world0.wrapping_add(1),
167        generation: world.generation,
168    }
169}
170
171/// (b3Shape_SetDensity)
172pub fn shape_set_density(
173    world: &mut World,
174    shape_id: ShapeId,
175    density: f32,
176    update_body_mass: bool,
177) {
178    crate::recording::with_recording(world, |rec| {
179        rec.write_shape_set_density(shape_id, density, update_body_mass);
180    });
181    debug_assert!(is_valid_float(density) && density >= 0.0);
182    debug_assert!(!world.locked);
183    if world.locked {
184        return;
185    }
186
187    let index = get_shape(world, shape_id);
188    if density == world.shapes[index as usize].density {
189        return;
190    }
191
192    world.shapes[index as usize].density = density;
193
194    if update_body_mass {
195        let body_index = world.shapes[index as usize].body_id;
196        crate::body::update_body_mass_data(world, body_index);
197    }
198}
199
200/// (b3Shape_GetDensity)
201pub fn shape_get_density(world: &World, shape_id: ShapeId) -> f32 {
202    let index = get_shape(world, shape_id);
203    world.shapes[index as usize].density
204}
205
206/// (b3Shape_GetAABB)
207pub fn shape_get_aabb(world: &World, shape_id: ShapeId) -> Aabb {
208    let index = get_shape(world, shape_id);
209    world.shapes[index as usize].aabb
210}
211
212/// World-space ray cast against a single shape. (b3Shape_RayCast)
213///
214/// Re-centers on `origin` so the cast runs in float precision far from the
215/// world origin, matching C.
216pub fn shape_ray_cast(
217    world: &World,
218    shape_id: ShapeId,
219    origin: Pos,
220    translation: Vec3,
221) -> WorldCastOutput {
222    debug_assert!(is_valid_position(origin));
223    debug_assert!(is_valid_vec3(translation));
224
225    let index = get_shape(world, shape_id);
226    let body_id = world.shapes[index as usize].body_id;
227    let transform = to_relative_transform(get_body_transform(world, body_id), origin);
228
229    let input = RayCastInput {
230        origin: VEC3_ZERO,
231        translation,
232        max_fraction: 1.0,
233    };
234
235    let local = ray_cast_shape(&world.shapes[index as usize], transform, &input);
236    WorldCastOutput {
237        normal: local.normal,
238        point: offset_pos(origin, local.point),
239        fraction: local.fraction,
240        iterations: local.iterations,
241        triangle_index: local.triangle_index,
242        child_index: local.child_index,
243        material_index: local.material_index,
244        hit: local.hit,
245    }
246}
247
248/// Closest point on a shape to a target in the query frame. (b3Shape_GetClosestPoint)
249///
250/// Low-level closest point is a documented float carve-out far from the origin.
251pub fn shape_get_closest_point(world: &World, shape_id: ShapeId, target: Vec3) -> Vec3 {
252    let index = get_shape(world, shape_id);
253    let body_id = world.shapes[index as usize].body_id;
254    let body = &world.bodies[body_id as usize];
255    let transform = to_relative_transform(get_body_transform_quick(world, body), POS_ZERO);
256
257    let mut proxy_b = ShapeProxy::default();
258    proxy_b.points[0] = target;
259    proxy_b.count = 1;
260    proxy_b.radius = 0.0;
261
262    let input = DistanceInput {
263        proxy_a: make_shape_proxy(&world.shapes[index as usize]),
264        proxy_b,
265        transform: inv_mul_transforms(transform, TRANSFORM_IDENTITY),
266        use_radii: true,
267    };
268
269    let mut cache = SimplexCache::default();
270    let output = shape_distance(&input, &mut cache, None);
271
272    // Witness point comes back in frame A; lift it back to the query frame.
273    transform_point(transform, output.point_a)
274}
275
276/// Conservative contact capacity for a non-sensor shape. (b3Shape_GetContactCapacity)
277pub fn shape_get_contact_capacity(world: &World, shape_id: ShapeId) -> i32 {
278    debug_assert!(!world.locked);
279    if world.locked {
280        return 0;
281    }
282
283    let shape_index = get_shape(world, shape_id);
284    let shape = &world.shapes[shape_index as usize];
285    if shape.sensor_index != NULL_INDEX {
286        return 0;
287    }
288
289    // Conservative and fast
290    world.bodies[shape.body_id as usize].contact_count
291}
292
293/// Touching contact data involving this shape. (b3Shape_GetContactData)
294pub fn shape_get_contact_data(
295    world: &World,
296    shape_id: ShapeId,
297    capacity: usize,
298) -> Vec<ContactData> {
299    debug_assert!(!world.locked);
300    if world.locked {
301        return Vec::new();
302    }
303
304    let shape_index = get_shape(world, shape_id);
305    let shape = &world.shapes[shape_index as usize];
306    if shape.sensor_index != NULL_INDEX {
307        return Vec::new();
308    }
309
310    let mut out = Vec::new();
311    let mut contact_key = world.bodies[shape.body_id as usize].head_contact_key;
312    while contact_key != NULL_INDEX && out.len() < capacity {
313        let contact_id = contact_key >> 1;
314        let edge_index = contact_key & 1;
315
316        let contact = &world.contacts[contact_id as usize];
317        contact_key = contact.edges[edge_index as usize].next_key;
318
319        // Does contact involve this shape and is it touching?
320        if (contact.shape_id_a == shape_index || contact.shape_id_b == shape_index)
321            && (contact.flags & contact_flags::TOUCHING) != 0
322        {
323            let shape_a = &world.shapes[contact.shape_id_a as usize];
324            let shape_b = &world.shapes[contact.shape_id_b as usize];
325
326            out.push(ContactData {
327                contact_id: ContactId {
328                    index1: contact.contact_id + 1,
329                    world0: shape_id.world0,
330                    padding: 0,
331                    generation: contact.generation,
332                },
333                shape_id_a: ShapeId {
334                    index1: shape_a.id + 1,
335                    world0: shape_id.world0,
336                    generation: shape_a.generation,
337                },
338                shape_id_b: ShapeId {
339                    index1: shape_b.id + 1,
340                    world0: shape_id.world0,
341                    generation: shape_b.generation,
342                },
343                manifolds: contact.manifolds.clone(),
344            });
345        }
346    }
347
348    debug_assert!(out.len() <= capacity);
349    out
350}
351
352/// Capacity required for [`shape_get_sensor_data`] / sensor overlaps.
353/// Returns 0 if the shape is not a sensor. (b3Shape_GetSensorCapacity)
354pub fn shape_get_sensor_capacity(world: &World, shape_id: ShapeId) -> i32 {
355    debug_assert!(!world.locked);
356    if world.locked {
357        return 0;
358    }
359
360    let shape_index = get_shape(world, shape_id);
361    let shape = &world.shapes[shape_index as usize];
362    if shape.sensor_index == NULL_INDEX {
363        return 0;
364    }
365
366    world.sensors[shape.sensor_index as usize].overlaps2.len() as i32
367}
368
369/// Overlapped shapes for a sensor. Overlaps may contain destroyed shapes —
370/// use [`super::shape_is_valid`] to confirm each. (b3Shape_GetSensorData;
371/// docs also call this GetSensorOverlaps)
372pub fn shape_get_sensor_data(world: &World, shape_id: ShapeId, capacity: usize) -> Vec<ShapeId> {
373    debug_assert!(!world.locked);
374    if world.locked {
375        return Vec::new();
376    }
377
378    let shape_index = get_shape(world, shape_id);
379    let shape = &world.shapes[shape_index as usize];
380    if shape.sensor_index == NULL_INDEX {
381        return Vec::new();
382    }
383
384    let sensor = &world.sensors[shape.sensor_index as usize];
385    let count = sensor.overlaps2.len().min(capacity);
386    sensor.overlaps2[..count]
387        .iter()
388        .map(|visitor| ShapeId {
389            index1: visitor.shape_id + 1,
390            world0: shape_id.world0,
391            generation: visitor.generation,
392        })
393        .collect()
394}
395
396/// Alias for [`shape_get_sensor_data`] matching the header doc name
397/// `b3Shape_GetSensorOverlaps`.
398pub fn shape_get_sensor_overlaps(
399    world: &World,
400    shape_id: ShapeId,
401    capacity: usize,
402) -> Vec<ShapeId> {
403    shape_get_sensor_data(world, shape_id, capacity)
404}
405
406/// Compute the mass data for a shape. (b3Shape_ComputeMassData)
407pub fn shape_compute_mass_data(world: &World, shape_id: ShapeId) -> MassData {
408    let shape_index = get_shape(world, shape_id);
409    compute_shape_mass(&world.shapes[shape_index as usize])
410}