Skip to main content

box2d_rust/shape/
geometry_api.rs

1// Geometry access for the b2Shape_* API: typed getters, the b2Shape_Set*
2// geometry replacements (with proxy reset), parent chain lookup, and wind.
3// Split from api.rs to stay under the file-length limit.
4//
5// SPDX-FileCopyrightText: 2023 Erin Catto
6// SPDX-License-Identifier: MIT
7
8use super::api::reset_proxy;
9use super::{compute_shape_margin, get_shape_index};
10use crate::body::wake_body;
11use crate::collision::{Capsule, ChainSegment, Circle, Polygon, Segment, ShapeGeometry, ShapeType};
12use crate::core::NULL_INDEX;
13use crate::id::{ChainId, ShapeId};
14use crate::math_functions::{
15    abs_float, add, cross, cross_sv, distance_squared, dot, get_length_and_normalize, left_perp,
16    lerp, mul_add, mul_sub, mul_sv, neg, normalize, right_perp, rotate_vector, sub, Vec2,
17    VEC2_ZERO,
18};
19use crate::solver_set::{AWAKE_SET, FIRST_SLEEPING_SET};
20use crate::types::BodyType;
21use crate::world::World;
22
23/// Get a copy of the shape's circle. Asserts the type is correct.
24/// (b2Shape_GetCircle)
25pub fn shape_get_circle(world: &World, shape_id: ShapeId) -> Circle {
26    let shape_index = get_shape_index(world, shape_id);
27    match &world.shapes[shape_index as usize].geometry {
28        ShapeGeometry::Circle(circle) => *circle,
29        _ => unreachable!("shape is not a circle"),
30    }
31}
32
33/// Get a copy of the shape's line segment. Asserts the type is correct.
34/// (b2Shape_GetSegment)
35pub fn shape_get_segment(world: &World, shape_id: ShapeId) -> Segment {
36    let shape_index = get_shape_index(world, shape_id);
37    match &world.shapes[shape_index as usize].geometry {
38        ShapeGeometry::Segment(segment) => *segment,
39        _ => unreachable!("shape is not a segment"),
40    }
41}
42
43/// Get a copy of the shape's chain segment. These come from chain shapes.
44/// Asserts the type is correct. (b2Shape_GetChainSegment)
45pub fn shape_get_chain_segment(world: &World, shape_id: ShapeId) -> ChainSegment {
46    let shape_index = get_shape_index(world, shape_id);
47    match &world.shapes[shape_index as usize].geometry {
48        ShapeGeometry::ChainSegment(chain_segment) => *chain_segment,
49        _ => unreachable!("shape is not a chain segment"),
50    }
51}
52
53/// Get a copy of the shape's capsule. Asserts the type is correct.
54/// (b2Shape_GetCapsule)
55pub fn shape_get_capsule(world: &World, shape_id: ShapeId) -> Capsule {
56    let shape_index = get_shape_index(world, shape_id);
57    match &world.shapes[shape_index as usize].geometry {
58        ShapeGeometry::Capsule(capsule) => *capsule,
59        _ => unreachable!("shape is not a capsule"),
60    }
61}
62
63/// Get a copy of the shape's convex polygon. Asserts the type is correct.
64/// (b2Shape_GetPolygon)
65pub fn shape_get_polygon(world: &World, shape_id: ShapeId) -> Polygon {
66    let shape_index = get_shape_index(world, shape_id);
67    match &world.shapes[shape_index as usize].geometry {
68        ShapeGeometry::Polygon(polygon) => *polygon,
69        _ => unreachable!("shape is not a polygon"),
70    }
71}
72
73/// Change the geometry after a shape's geometry field is replaced: recompute
74/// the margin and refresh the proxy, waking attached bodies. Shared tail of
75/// the b2Shape_Set* functions.
76fn set_geometry(world: &mut World, shape_index: i32, geometry: ShapeGeometry) {
77    {
78        let shape = &mut world.shapes[shape_index as usize];
79        shape.geometry = geometry;
80        shape.aabb_margin = compute_shape_margin(shape);
81    }
82
83    // need to wake bodies so they can react to the shape change
84    let wake_bodies = true;
85    let destroy_proxy = true;
86    reset_proxy(world, shape_index, wake_bodies, destroy_proxy);
87}
88
89/// Allows you to change a shape to be a circle or update the current circle.
90/// (b2Shape_SetCircle)
91pub fn shape_set_circle(world: &mut World, shape_id: ShapeId, circle: &Circle) {
92    crate::recording::record_op(world, |rec, _| {
93        crate::recording::write_shape_geometry(
94            rec,
95            crate::recording::OP_SHAPE_SET_CIRCLE,
96            shape_id,
97            |buf| crate::recording::rec_w_circle(buf, *circle),
98        )
99    });
100    debug_assert!(!world.locked);
101    if world.locked {
102        return;
103    }
104
105    let shape_index = get_shape_index(world, shape_id);
106    set_geometry(world, shape_index, ShapeGeometry::Circle(*circle));
107}
108
109/// Allows you to change a shape to be a capsule or update the current capsule.
110/// (b2Shape_SetCapsule)
111pub fn shape_set_capsule(world: &mut World, shape_id: ShapeId, capsule: &Capsule) {
112    crate::recording::record_op(world, |rec, _| {
113        crate::recording::write_shape_geometry(
114            rec,
115            crate::recording::OP_SHAPE_SET_CAPSULE,
116            shape_id,
117            |buf| crate::recording::rec_w_capsule(buf, *capsule),
118        )
119    });
120    debug_assert!(!world.locked);
121    if world.locked {
122        return;
123    }
124
125    let length_sqr = distance_squared(capsule.center1, capsule.center2);
126    if length_sqr <= crate::constants::linear_slop() * crate::constants::linear_slop() {
127        return;
128    }
129
130    let shape_index = get_shape_index(world, shape_id);
131    set_geometry(world, shape_index, ShapeGeometry::Capsule(*capsule));
132}
133
134/// Allows you to change a shape to be a segment or update the current segment.
135/// (b2Shape_SetSegment)
136pub fn shape_set_segment(world: &mut World, shape_id: ShapeId, segment: &Segment) {
137    crate::recording::record_op(world, |rec, _| {
138        crate::recording::write_shape_geometry(
139            rec,
140            crate::recording::OP_SHAPE_SET_SEGMENT,
141            shape_id,
142            |buf| crate::recording::rec_w_segment(buf, *segment),
143        )
144    });
145    debug_assert!(!world.locked);
146    if world.locked {
147        return;
148    }
149
150    let shape_index = get_shape_index(world, shape_id);
151    set_geometry(world, shape_index, ShapeGeometry::Segment(*segment));
152}
153
154/// Allows you to change a shape to be a polygon or update the current polygon.
155/// (b2Shape_SetPolygon)
156pub fn shape_set_polygon(world: &mut World, shape_id: ShapeId, polygon: &Polygon) {
157    crate::recording::record_op(world, |rec, _| {
158        crate::recording::write_shape_geometry(
159            rec,
160            crate::recording::OP_SHAPE_SET_POLYGON,
161            shape_id,
162            |buf| crate::recording::rec_w_polygon(buf, polygon),
163        )
164    });
165    debug_assert!(!world.locked);
166    if world.locked {
167        return;
168    }
169
170    let shape_index = get_shape_index(world, shape_id);
171    set_geometry(world, shape_index, ShapeGeometry::Polygon(*polygon));
172}
173
174/// Allows you to change a shape to be a stand-alone chain segment or update
175/// the current one. Cannot be used on a segment owned by a chain shape.
176/// (b2Shape_SetChainSegment)
177pub fn shape_set_chain_segment(world: &mut World, shape_id: ShapeId, chain_segment: &ChainSegment) {
178    crate::recording::record_op(world, |rec, _| {
179        crate::recording::write_shape_geometry(
180            rec,
181            crate::recording::OP_SHAPE_SET_CHAIN_SEGMENT,
182            shape_id,
183            |buf| crate::recording::rec_w_chainseg(buf, *chain_segment),
184        )
185    });
186    debug_assert!(!world.locked);
187    if world.locked {
188        return;
189    }
190
191    let shape_index = get_shape_index(world, shape_id);
192
193    // Cannot modify a chain segment that has a parent chain shape
194    if let ShapeGeometry::ChainSegment(existing) = &world.shapes[shape_index as usize].geometry {
195        if existing.chain_id != NULL_INDEX {
196            debug_assert!(false, "cannot modify a chain segment owned by a chain");
197            return;
198        }
199    }
200
201    let length_sqr = distance_squared(chain_segment.segment.point1, chain_segment.segment.point2);
202    if length_sqr <= crate::constants::linear_slop() * crate::constants::linear_slop() {
203        return;
204    }
205
206    let mut local = *chain_segment;
207    local.chain_id = NULL_INDEX;
208    set_geometry(world, shape_index, ShapeGeometry::ChainSegment(local));
209}
210
211/// Get the parent chain id if the shape type is a chain segment, otherwise
212/// returns the null chain id. (b2Shape_GetParentChain)
213pub fn shape_get_parent_chain(world: &World, shape_id: ShapeId) -> ChainId {
214    let shape_index = get_shape_index(world, shape_id);
215    if let ShapeGeometry::ChainSegment(chain_segment) = &world.shapes[shape_index as usize].geometry
216    {
217        let chain_id = chain_segment.chain_id;
218        if chain_id != NULL_INDEX {
219            let chain = &world.chain_shapes[chain_id as usize];
220            return ChainId {
221                index1: chain_id + 1,
222                world0: world.world_id,
223                generation: chain.generation,
224            };
225        }
226    }
227
228    ChainId::default()
229}
230
231/// Apply wind to a shape, applying drag and lift forces to the owning body.
232/// (b2Shape_ApplyWind)
233///
234/// force = 0.5 * air_density * velocity^2 * area
235/// <https://en.wikipedia.org/wiki/Density_of_air>
236/// <https://en.wikipedia.org/wiki/Lift_(force)>
237pub fn shape_apply_wind(
238    world: &mut World,
239    shape_id: ShapeId,
240    wind: Vec2,
241    drag: f32,
242    lift: f32,
243    wake: bool,
244) {
245    crate::recording::record_op(world, |rec, _| {
246        crate::recording::write_shape_apply_wind(rec, shape_id, wind, drag, lift, wake)
247    });
248    let shape_index = get_shape_index(world, shape_id);
249
250    let shape_type = world.shapes[shape_index as usize].shape_type();
251    if shape_type != ShapeType::Circle
252        && shape_type != ShapeType::Capsule
253        && shape_type != ShapeType::Polygon
254    {
255        return;
256    }
257
258    let body_id = world.shapes[shape_index as usize].body_id;
259
260    if world.bodies[body_id as usize].type_ != BodyType::Dynamic {
261        return;
262    }
263
264    if world.bodies[body_id as usize].set_index >= FIRST_SLEEPING_SET && !wake {
265        return;
266    }
267
268    if world.bodies[body_id as usize].set_index != AWAKE_SET {
269        // Must wake for state to exist
270        wake_body(world, body_id);
271    }
272
273    debug_assert!(world.bodies[body_id as usize].set_index == AWAKE_SET);
274
275    let local_index = world.bodies[body_id as usize].local_index;
276    let (transform, local_center) = {
277        let sim = &world.solver_sets[AWAKE_SET as usize].body_sims[local_index as usize];
278        (sim.transform, sim.local_center)
279    };
280    let (linear_velocity, angular_velocity) = {
281        let state = &world.solver_sets[AWAKE_SET as usize].body_states[local_index as usize];
282        (state.linear_velocity, state.angular_velocity)
283    };
284    let shape = &world.shapes[shape_index as usize];
285
286    let length_units = crate::core::get_length_units_per_meter();
287    let volume_units = length_units * length_units * length_units;
288
289    // In 2D I'm assuming unit depth
290    let air_density = 1.2250 / volume_units;
291
292    let mut force = VEC2_ZERO;
293    let mut torque = 0.0f32;
294
295    match &shape.geometry {
296        ShapeGeometry::Circle(circle) => {
297            let radius = circle.radius;
298            let centroid = shape.local_centroid;
299            let lever = rotate_vector(transform.q, sub(centroid, local_center));
300            let shape_velocity = add(linear_velocity, cross_sv(angular_velocity, lever));
301            let relative_velocity = mul_sub(wind, drag, shape_velocity);
302            let mut speed = 0.0;
303            let direction = get_length_and_normalize(&mut speed, relative_velocity);
304            let projected_area = 2.0 * radius;
305            force = mul_sv(
306                0.5 * air_density * projected_area * speed * speed,
307                direction,
308            );
309            torque = cross(lever, force);
310        }
311
312        ShapeGeometry::Capsule(capsule) => {
313            let centroid = shape.local_centroid;
314            let lever = rotate_vector(transform.q, sub(centroid, local_center));
315            let shape_velocity = add(linear_velocity, cross_sv(angular_velocity, lever));
316            let relative_velocity = mul_sub(wind, drag, shape_velocity);
317            let mut speed = 0.0;
318            let direction = get_length_and_normalize(&mut speed, relative_velocity);
319
320            let mut d = sub(capsule.center2, capsule.center1);
321            d = rotate_vector(transform.q, d);
322
323            let radius = capsule.radius;
324            let projected_area = 2.0 * radius + abs_float(cross(d, direction));
325
326            // Normal that opposes the wind
327            let mut normal = left_perp(normalize(d));
328            if dot(normal, direction) > 0.0 {
329                normal = neg(normal);
330            }
331
332            // portion of wind that is perpendicular to surface
333            let lift_direction = cross_sv(cross(normal, direction), direction);
334
335            let force_magnitude = 0.5 * air_density * projected_area * speed * speed;
336            force = mul_sv(force_magnitude, mul_add(direction, lift, lift_direction));
337
338            let edge_lever = mul_add(lever, radius, normal);
339            torque = cross(edge_lever, force);
340        }
341
342        ShapeGeometry::Polygon(polygon) => {
343            let centroid = shape.local_centroid;
344            let lever = rotate_vector(transform.q, sub(centroid, local_center));
345            let shape_velocity = add(linear_velocity, cross_sv(angular_velocity, lever));
346            let relative_velocity = mul_sub(wind, drag, shape_velocity);
347            let mut speed = 0.0;
348            let direction = get_length_and_normalize(&mut speed, relative_velocity);
349
350            // polygon radius is ignored for simplicity
351            let count = polygon.count as usize;
352            let vertices = &polygon.vertices;
353
354            let mut v1 = vertices[count - 1];
355            for vertex in vertices[..count].iter() {
356                let v2 = *vertex;
357                let mut d = sub(v2, v1);
358                let edge_center = lerp(v1, v2, 0.5);
359                v1 = v2;
360
361                d = rotate_vector(transform.q, d);
362
363                let projected_area = cross(d, direction);
364                if projected_area <= 0.0 {
365                    // back facing
366                    continue;
367                }
368
369                let normal = right_perp(normalize(d));
370
371                // portion of wind that is perpendicular to surface
372                let lift_direction = cross_sv(cross(normal, direction), direction);
373
374                let force_magnitude = 0.5 * air_density * projected_area * speed * speed;
375                let f = mul_sv(force_magnitude, mul_add(direction, lift, lift_direction));
376
377                let edge_lever = rotate_vector(transform.q, sub(edge_center, local_center));
378
379                force = add(force, f);
380                torque += cross(edge_lever, f);
381            }
382        }
383
384        _ => {}
385    }
386
387    let sim = &mut world.solver_sets[AWAKE_SET as usize].body_sims[local_index as usize];
388    sim.force = add(sim.force, force);
389    sim.torque += torque;
390}