Skip to main content

box3d_rust/shape/
mutators.rs

1//! Shape filter and material public API from shape.c.
2//!
3//! SPDX-FileCopyrightText: 2025 Erin Catto
4//! SPDX-License-Identifier: MIT
5
6use super::dispatch::update_shape_aabbs;
7use super::lifecycle::get_shape;
8use crate::body::get_body_transform_quick;
9use crate::broad_phase::proxy_type;
10use crate::core::NULL_INDEX;
11use crate::geometry::{ShapeType, SurfaceMaterial};
12use crate::id::ShapeId;
13use crate::math_functions::{is_valid_float, is_valid_vec3};
14use crate::types::Filter;
15use crate::world::World;
16
17/// Destroy contacts on a shape and refresh its broad-phase proxy.
18/// (static b3ResetProxy)
19pub(crate) fn reset_proxy(
20    world: &mut World,
21    shape_index: i32,
22    wake_bodies: bool,
23    destroy_proxy: bool,
24) {
25    let body_id = world.shapes[shape_index as usize].body_id;
26    let shape_id = world.shapes[shape_index as usize].id;
27
28    // Destroy all contacts associated with this shape.
29    let mut contact_key = world.bodies[body_id as usize].head_contact_key;
30    while contact_key != NULL_INDEX {
31        let contact_id = contact_key >> 1;
32        let edge_index = contact_key & 1;
33        let next_key = world.contacts[contact_id as usize].edges[edge_index as usize].next_key;
34        let shape_id_a = world.contacts[contact_id as usize].shape_id_a;
35        let shape_id_b = world.contacts[contact_id as usize].shape_id_b;
36        contact_key = next_key;
37
38        if shape_id_a == shape_id || shape_id_b == shape_id {
39            crate::contact::destroy_contact(world, contact_id, wake_bodies);
40        }
41    }
42
43    let transform = get_body_transform_quick(world, &world.bodies[body_id as usize]);
44    let proxy_key = world.shapes[shape_index as usize].proxy_key;
45
46    if proxy_key != NULL_INDEX {
47        let proxy_type_ = proxy_type(proxy_key);
48        update_shape_aabbs(
49            &mut world.shapes[shape_index as usize],
50            transform,
51            proxy_type_,
52        );
53
54        if destroy_proxy {
55            let fat_aabb = world.shapes[shape_index as usize].fat_aabb;
56            let category_bits = world.shapes[shape_index as usize].filter.category_bits;
57            world.broad_phase.destroy_proxy(proxy_key);
58
59            let force_pair_creation = true;
60            world.shapes[shape_index as usize].proxy_key = world.broad_phase.create_proxy(
61                proxy_type_,
62                fat_aabb,
63                category_bits,
64                shape_id,
65                force_pair_creation,
66            );
67        } else {
68            let fat_aabb = world.shapes[shape_index as usize].fat_aabb;
69            world.broad_phase.move_proxy(proxy_key, fat_aabb);
70        }
71    } else {
72        let proxy_type_ = world.bodies[body_id as usize].type_;
73        update_shape_aabbs(
74            &mut world.shapes[shape_index as usize],
75            transform,
76            proxy_type_,
77        );
78    }
79
80    world.validate_solver_sets();
81}
82
83/// (b3Shape_SetFriction)
84pub fn shape_set_friction(world: &mut World, shape_id: ShapeId, friction: f32) {
85    crate::recording::with_recording(world, |rec| {
86        rec.write_shape_set_friction(shape_id, friction);
87    });
88    debug_assert!(is_valid_float(friction) && friction >= 0.0);
89    let index = get_shape(world, shape_id);
90    let shape = &mut world.shapes[index as usize];
91    debug_assert!(shape.shape_type() != ShapeType::Compound);
92    shape.shape_materials_mut()[0].friction = friction;
93}
94
95/// (b3Shape_GetFriction)
96pub fn shape_get_friction(world: &World, shape_id: ShapeId) -> f32 {
97    let index = get_shape(world, shape_id);
98    world.shapes[index as usize].shape_materials()[0].friction
99}
100
101/// (b3Shape_SetRestitution)
102pub fn shape_set_restitution(world: &mut World, shape_id: ShapeId, restitution: f32) {
103    crate::recording::with_recording(world, |rec| {
104        rec.write_shape_set_restitution(shape_id, restitution);
105    });
106    debug_assert!(is_valid_float(restitution) && restitution >= 0.0);
107    let index = get_shape(world, shape_id);
108    let shape = &mut world.shapes[index as usize];
109    debug_assert!(shape.shape_type() != ShapeType::Compound);
110    shape.shape_materials_mut()[0].restitution = restitution;
111}
112
113/// (b3Shape_GetRestitution)
114pub fn shape_get_restitution(world: &World, shape_id: ShapeId) -> f32 {
115    let index = get_shape(world, shape_id);
116    world.shapes[index as usize].shape_materials()[0].restitution
117}
118
119/// (b3Shape_SetSurfaceMaterial)
120pub fn shape_set_surface_material(
121    world: &mut World,
122    shape_id: ShapeId,
123    surface_material: SurfaceMaterial,
124) {
125    crate::recording::with_recording(world, |rec| {
126        rec.write_shape_set_surface_material(shape_id, surface_material);
127    });
128    debug_assert!(is_valid_float(surface_material.friction) && surface_material.friction >= 0.0);
129    debug_assert!(
130        is_valid_float(surface_material.restitution) && surface_material.restitution >= 0.0
131    );
132    debug_assert!(
133        is_valid_float(surface_material.rolling_resistance)
134            && surface_material.rolling_resistance >= 0.0
135    );
136    debug_assert!(is_valid_vec3(surface_material.tangent_velocity));
137
138    let index = get_shape(world, shape_id);
139    let shape = &mut world.shapes[index as usize];
140    debug_assert!(shape.shape_type() != ShapeType::Compound);
141    shape.shape_materials_mut()[0] = surface_material;
142}
143
144/// (b3Shape_GetSurfaceMaterial)
145pub fn shape_get_surface_material(world: &World, shape_id: ShapeId) -> SurfaceMaterial {
146    let index = get_shape(world, shape_id);
147    world.shapes[index as usize].shape_materials()[0]
148}
149
150/// (b3Shape_GetMeshMaterialCount)
151pub fn shape_get_mesh_material_count(world: &World, shape_id: ShapeId) -> i32 {
152    let index = get_shape(world, shape_id);
153    world.shapes[index as usize].material_count()
154}
155
156/// (b3Shape_SetMeshMaterial)
157pub fn shape_set_mesh_material(
158    world: &mut World,
159    shape_id: ShapeId,
160    surface_material: SurfaceMaterial,
161    material_index: i32,
162) {
163    debug_assert!(is_valid_float(surface_material.friction) && surface_material.friction >= 0.0);
164    debug_assert!(
165        is_valid_float(surface_material.restitution) && surface_material.restitution >= 0.0
166    );
167    debug_assert!(
168        is_valid_float(surface_material.rolling_resistance)
169            && surface_material.rolling_resistance >= 0.0
170    );
171    debug_assert!(is_valid_vec3(surface_material.tangent_velocity));
172
173    let index = get_shape(world, shape_id);
174    let shape = &mut world.shapes[index as usize];
175    debug_assert!(0 <= material_index && material_index < shape.material_count());
176    debug_assert!(shape.shape_type() != ShapeType::Compound);
177    shape.shape_materials_mut()[material_index as usize] = surface_material;
178}
179
180/// (b3Shape_GetMeshSurfaceMaterial)
181pub fn shape_get_mesh_surface_material(
182    world: &World,
183    shape_id: ShapeId,
184    material_index: i32,
185) -> SurfaceMaterial {
186    let index = get_shape(world, shape_id);
187    let shape = &world.shapes[index as usize];
188    debug_assert!(0 <= material_index && material_index < shape.material_count());
189    shape.shape_materials()[material_index as usize]
190}
191
192/// (b3Shape_GetFilter)
193pub fn shape_get_filter(world: &World, shape_id: ShapeId) -> Filter {
194    let index = get_shape(world, shape_id);
195    world.shapes[index as usize].filter
196}
197
198/// (b3Shape_SetFilter)
199///
200/// When `invoke_contacts` is true, contacts on the shape are destroyed and the
201/// broad-phase proxy is refreshed so pairs are recomputed on the next step.
202pub fn shape_set_filter(
203    world: &mut World,
204    shape_id: ShapeId,
205    filter: Filter,
206    invoke_contacts: bool,
207) {
208    crate::recording::with_recording(world, |rec| {
209        rec.write_shape_set_filter(shape_id, filter, invoke_contacts);
210    });
211    debug_assert!(!world.locked);
212    if world.locked {
213        return;
214    }
215
216    let index = get_shape(world, shape_id);
217    let shape = &mut world.shapes[index as usize];
218    if filter.mask_bits == shape.filter.mask_bits
219        && filter.category_bits == shape.filter.category_bits
220        && filter.group_index == shape.filter.group_index
221    {
222        return;
223    }
224
225    shape.filter = filter;
226
227    if invoke_contacts {
228        world.locked = true;
229        let wake_bodies = true;
230        // C compares after assignment (`filter.categoryBits == shape->filter.categoryBits`),
231        // so destroy_proxy is always true here. Match that exactly.
232        let destroy_proxy =
233            filter.category_bits == world.shapes[index as usize].filter.category_bits;
234        reset_proxy(world, index, wake_bodies, destroy_proxy);
235        world.locked = false;
236    }
237
238    // Note: this does not immediately update sensor overlaps. Sensor overlaps
239    // are updated the next time step (same as C).
240}