Skip to main content

box3d_rust/shape/
geometry_set.rs

1//! Geometry get/set for sphere, capsule, and hull. (shape.c Set*/Get*)
2//!
3//! Changing geometry destroys contacts and rebuilds the broad-phase proxy.
4//!
5//! SPDX-FileCopyrightText: 2025 Erin Catto
6//! SPDX-License-Identifier: MIT
7
8use super::lifecycle::{
9    compute_shape_margin, destroy_shape_allocation_for_shape_change, get_shape,
10};
11use super::mutators::reset_proxy;
12use super::ShapeGeometry;
13use crate::geometry::{Capsule, Sphere};
14use crate::hull::{is_valid_hull, HullData};
15use crate::id::ShapeId;
16use crate::math_functions::{is_valid_vec3, safe_scale, Vec3};
17use crate::mesh::{is_valid_mesh, MeshData};
18use crate::world::World;
19use std::rc::Rc;
20
21/// (b3Shape_GetSphere)
22pub fn shape_get_sphere(world: &World, shape_id: ShapeId) -> Sphere {
23    let index = get_shape(world, shape_id);
24    match &world.shapes[index as usize].geometry {
25        ShapeGeometry::Sphere(sphere) => *sphere,
26        _ => {
27            debug_assert!(false, "shape is not a sphere");
28            Sphere::default()
29        }
30    }
31}
32
33/// (b3Shape_GetCapsule)
34pub fn shape_get_capsule(world: &World, shape_id: ShapeId) -> Capsule {
35    let index = get_shape(world, shape_id);
36    match &world.shapes[index as usize].geometry {
37        ShapeGeometry::Capsule(capsule) => *capsule,
38        _ => {
39            debug_assert!(false, "shape is not a capsule");
40            Capsule::default()
41        }
42    }
43}
44
45/// (b3Shape_SetSphere)
46pub fn shape_set_sphere(world: &mut World, shape_id: ShapeId, sphere: &Sphere) {
47    crate::recording::with_recording(world, |rec| {
48        rec.write_shape_set_sphere(shape_id, *sphere);
49    });
50    debug_assert!(!world.locked);
51    if world.locked {
52        return;
53    }
54
55    world.locked = true;
56
57    let index = get_shape(world, shape_id);
58    destroy_shape_allocation_for_shape_change(world, index);
59
60    {
61        let shape = &mut world.shapes[index as usize];
62        shape.geometry = ShapeGeometry::Sphere(*sphere);
63        shape.aabb_margin = compute_shape_margin(shape);
64    }
65
66    // Need to wake bodies so they can react to the shape change.
67    let wake_bodies = true;
68    let destroy_proxy = true;
69    reset_proxy(world, index, wake_bodies, destroy_proxy);
70
71    world.locked = false;
72}
73
74/// (b3Shape_SetCapsule)
75pub fn shape_set_capsule(world: &mut World, shape_id: ShapeId, capsule: &Capsule) {
76    crate::recording::with_recording(world, |rec| {
77        rec.write_shape_set_capsule(shape_id, *capsule);
78    });
79    debug_assert!(!world.locked);
80    if world.locked {
81        return;
82    }
83
84    world.locked = true;
85
86    let index = get_shape(world, shape_id);
87    destroy_shape_allocation_for_shape_change(world, index);
88
89    {
90        let shape = &mut world.shapes[index as usize];
91        shape.geometry = ShapeGeometry::Capsule(*capsule);
92        shape.aabb_margin = compute_shape_margin(shape);
93    }
94
95    let wake_bodies = true;
96    let destroy_proxy = true;
97    reset_proxy(world, index, wake_bodies, destroy_proxy);
98
99    world.locked = false;
100}
101
102/// (b3Shape_SetMesh)
103///
104/// Retypes the shape to a mesh, swapping in `mesh` (an owned clone, as
105/// [`create_mesh_shape`](crate::shape::create_mesh_shape) does — C keeps a
106/// borrowed pointer) at the sanitized `scale`, then destroys the shape's
107/// contacts and rebuilds the broad-phase proxy. Like C, this does no mass
108/// update: mesh shapes carry no mass, and the C source only calls `b3ResetProxy`.
109pub fn shape_set_mesh(world: &mut World, shape_id: ShapeId, mesh: &MeshData, scale: Vec3) {
110    debug_assert!(is_valid_vec3(scale));
111    debug_assert!(is_valid_mesh(Some(mesh)));
112    debug_assert!(mesh.hash != 0);
113
114    debug_assert!(!world.locked);
115    if world.locked {
116        return;
117    }
118
119    world.locked = true;
120
121    let index = get_shape(world, shape_id);
122    destroy_shape_allocation_for_shape_change(world, index);
123
124    {
125        let shape = &mut world.shapes[index as usize];
126        shape.geometry = ShapeGeometry::Mesh {
127            data: mesh.clone(),
128            scale: safe_scale(scale),
129        };
130        shape.aabb_margin = compute_shape_margin(shape);
131    }
132
133    // Need to wake bodies so they can react to the shape change.
134    let wake_bodies = true;
135    let destroy_proxy = true;
136    reset_proxy(world, index, wake_bodies, destroy_proxy);
137
138    world.locked = false;
139}
140
141/// (b3Shape_SetHull)
142///
143/// Acquires the new hull before releasing the old so `hull` may safely alias
144/// the shape's current shared data.
145pub fn shape_set_hull(world: &mut World, shape_id: ShapeId, hull: &HullData) {
146    debug_assert!(is_valid_hull(hull));
147    debug_assert!(hull.hash != 0);
148
149    debug_assert!(!world.locked);
150    if world.locked {
151        return;
152    }
153
154    world.locked = true;
155
156    let index = get_shape(world, shape_id);
157
158    // Acquire the new hull before releasing the old so the input may safely
159    // alias the shape's current shared data.
160    let data = world.hull_database.add(hull);
161
162    // Same shared hull — avoid destroying contacts and recreating the proxy.
163    if let ShapeGeometry::Hull(existing) = &world.shapes[index as usize].geometry {
164        if Rc::ptr_eq(existing, &data) {
165            // Drop the extra Rc from add(); do not release from the database.
166            drop(data);
167            world.locked = false;
168            return;
169        }
170    }
171
172    destroy_shape_allocation_for_shape_change(world, index);
173
174    {
175        let shape = &mut world.shapes[index as usize];
176        shape.geometry = ShapeGeometry::Hull(data);
177        shape.aabb_margin = compute_shape_margin(shape);
178    }
179
180    let wake_bodies = true;
181    let destroy_proxy = true;
182    reset_proxy(world, index, wake_bodies, destroy_proxy);
183
184    world.locked = false;
185}