Skip to main content

box2d_rust/shape/
api.rs

1// The b2Shape_* public API from shape.c: accessors, geometry get/set with
2// proxy reset, event flags, contact/sensor introspection, queries, and wind.
3//
4// b2Shape_GetWorld is omitted: there is no world registry in the Rust port.
5//
6// SPDX-FileCopyrightText: 2023 Erin Catto
7// SPDX-License-Identifier: MIT
8
9use super::{
10    compute_shape_mass, get_shape_index, make_shape_distance_proxy, ray_cast_shape,
11    update_shape_aabbs,
12};
13use crate::body::{
14    get_body_transform, get_body_transform_quick, make_body_id, update_body_mass_data,
15};
16use crate::collision::{MassData, RayCastInput, ShapeGeometry, ShapeType, WorldCastOutput};
17use crate::core::NULL_INDEX;
18use crate::distance::{make_proxy, shape_distance, DistanceInput, SimplexCache};
19use crate::events::ContactData;
20use crate::geometry::{point_in_capsule, point_in_circle, point_in_polygon};
21use crate::id::{BodyId, ContactId, ShapeId};
22use crate::math_functions::{
23    inv_mul_world_transforms, inv_transform_world_point, is_valid_float, is_valid_position,
24    is_valid_vec2, offset_pos, to_relative_transform, transform_world_point, Aabb, Pos, Vec2,
25    ROT_IDENTITY, VEC2_ZERO,
26};
27use crate::solver_set::AWAKE_SET;
28use crate::types::{Filter, SurfaceMaterial};
29use crate::world::World;
30
31/// Shape id validity. (b2Shape_IsValid — the world-registry check collapses
32/// to the index/generation check in the registry-less port)
33pub fn shape_is_valid(world: &World, id: ShapeId) -> bool {
34    let shape_id = id.index1 - 1;
35    if shape_id < 0 || world.shapes.len() as i32 <= shape_id {
36        return false;
37    }
38
39    let shape = &world.shapes[shape_id as usize];
40    if shape.id == NULL_INDEX {
41        // shape is free
42        return false;
43    }
44
45    debug_assert!(shape.id == shape_id);
46
47    id.generation == shape.generation
48}
49
50/// Get the id of the body that a shape is attached to. (b2Shape_GetBody)
51pub fn shape_get_body(world: &World, shape_id: ShapeId) -> BodyId {
52    let shape_index = get_shape_index(world, shape_id);
53    make_body_id(world, world.shapes[shape_index as usize].body_id)
54}
55
56/// Set the user data for a shape. (b2Shape_SetUserData)
57pub fn shape_set_user_data(world: &mut World, shape_id: ShapeId, user_data: u64) {
58    let shape_index = get_shape_index(world, shape_id);
59    world.shapes[shape_index as usize].user_data = user_data;
60}
61
62/// Get the user data for a shape. (b2Shape_GetUserData)
63pub fn shape_get_user_data(world: &World, shape_id: ShapeId) -> u64 {
64    let shape_index = get_shape_index(world, shape_id);
65    world.shapes[shape_index as usize].user_data
66}
67
68/// Returns true if the shape is a sensor. (b2Shape_IsSensor)
69pub fn shape_is_sensor(world: &World, shape_id: ShapeId) -> bool {
70    let shape_index = get_shape_index(world, shape_id);
71    world.shapes[shape_index as usize].sensor_index != NULL_INDEX
72}
73
74/// Test a point for overlap with a shape. (b2Shape_TestPoint)
75pub fn shape_test_point(world: &mut World, shape_id: ShapeId, point: Pos) -> bool {
76    let shape_index = get_shape_index(world, shape_id);
77    let shape = &world.shapes[shape_index as usize];
78
79    let transform = get_body_transform(world, shape.body_id);
80    let local_point = inv_transform_world_point(transform, point);
81
82    let result = match &shape.geometry {
83        ShapeGeometry::Capsule(capsule) => point_in_capsule(capsule, local_point),
84        ShapeGeometry::Circle(circle) => point_in_circle(circle, local_point),
85        ShapeGeometry::Polygon(polygon) => point_in_polygon(polygon, local_point),
86        _ => false,
87    };
88
89    crate::recording::record_query_result(
90        world,
91        crate::recording::OP_SHAPE_TEST_POINT,
92        |buf| {
93            crate::recording::rec_w_shapeid(buf, shape_id);
94            crate::recording::rec_w_position(buf, point);
95        },
96        |buf| crate::recording::rec_w_bool(buf, result),
97    );
98
99    result
100}
101
102/// Ray cast a shape directly. (b2Shape_RayCast)
103pub fn shape_ray_cast(
104    world: &mut World,
105    shape_id: ShapeId,
106    origin: Pos,
107    translation: Vec2,
108) -> WorldCastOutput {
109    debug_assert!(is_valid_position(origin));
110    debug_assert!(is_valid_vec2(translation));
111
112    let shape_index = get_shape_index(world, shape_id);
113    let shape = &world.shapes[shape_index as usize];
114
115    // Re-center on the origin so the cast runs in float precision
116    let transform = to_relative_transform(get_body_transform(world, shape.body_id), origin);
117
118    // The ray starts at the origin, so its origin in the re-centered frame is zero
119    let input = RayCastInput {
120        origin: VEC2_ZERO,
121        translation,
122        max_fraction: 1.0,
123    };
124
125    // Lift the re-centered float result back to a world position
126    let local = ray_cast_shape(&input, shape, transform);
127    let output = WorldCastOutput {
128        normal: local.normal,
129        point: offset_pos(origin, local.point),
130        fraction: local.fraction,
131        iterations: local.iterations,
132        hit: local.hit,
133    };
134
135    crate::recording::record_query_result(
136        world,
137        crate::recording::OP_SHAPE_RAY_CAST,
138        |buf| {
139            crate::recording::rec_w_shapeid(buf, shape_id);
140            crate::recording::rec_w_position(buf, origin);
141            crate::recording::rec_w_vec2(buf, translation);
142        },
143        |buf| crate::recording::rec_w_worldcastoutput(buf, output),
144    );
145
146    output
147}
148
149/// Set the mass density of a shape, usually in kg/m^2. (b2Shape_SetDensity)
150pub fn shape_set_density(
151    world: &mut World,
152    shape_id: ShapeId,
153    density: f32,
154    update_body_mass: bool,
155) {
156    crate::recording::record_op(world, |rec, _| {
157        crate::recording::write_shape_set_density(rec, shape_id, density, update_body_mass)
158    });
159    debug_assert!(is_valid_float(density) && density >= 0.0);
160
161    debug_assert!(!world.locked);
162    if world.locked {
163        return;
164    }
165
166    let shape_index = get_shape_index(world, shape_id);
167    let shape = &mut world.shapes[shape_index as usize];
168    if density == shape.density {
169        // early return to avoid expensive function
170        return;
171    }
172
173    shape.density = density;
174
175    if update_body_mass {
176        let body_id = shape.body_id;
177        update_body_mass_data(world, body_id);
178    }
179}
180
181/// Get the density of a shape, usually in kg/m^2. (b2Shape_GetDensity)
182pub fn shape_get_density(world: &World, shape_id: ShapeId) -> f32 {
183    let shape_index = get_shape_index(world, shape_id);
184    world.shapes[shape_index as usize].density
185}
186
187/// Set the friction on a shape. (b2Shape_SetFriction)
188pub fn shape_set_friction(world: &mut World, shape_id: ShapeId, friction: f32) {
189    crate::recording::record_op(world, |rec, _| {
190        crate::recording::write_shape_f32(
191            rec,
192            crate::recording::OP_SHAPE_SET_FRICTION,
193            shape_id,
194            friction,
195        )
196    });
197    debug_assert!(is_valid_float(friction) && friction >= 0.0);
198
199    debug_assert!(!world.locked);
200    if world.locked {
201        return;
202    }
203
204    let shape_index = get_shape_index(world, shape_id);
205    world.shapes[shape_index as usize].material.friction = friction;
206}
207
208/// Get the friction of a shape. (b2Shape_GetFriction)
209pub fn shape_get_friction(world: &World, shape_id: ShapeId) -> f32 {
210    let shape_index = get_shape_index(world, shape_id);
211    world.shapes[shape_index as usize].material.friction
212}
213
214/// Set the shape restitution. (b2Shape_SetRestitution)
215pub fn shape_set_restitution(world: &mut World, shape_id: ShapeId, restitution: f32) {
216    crate::recording::record_op(world, |rec, _| {
217        crate::recording::write_shape_f32(
218            rec,
219            crate::recording::OP_SHAPE_SET_RESTITUTION,
220            shape_id,
221            restitution,
222        )
223    });
224    debug_assert!(is_valid_float(restitution) && restitution >= 0.0);
225
226    debug_assert!(!world.locked);
227    if world.locked {
228        return;
229    }
230
231    let shape_index = get_shape_index(world, shape_id);
232    world.shapes[shape_index as usize].material.restitution = restitution;
233}
234
235/// Get the shape restitution. (b2Shape_GetRestitution)
236pub fn shape_get_restitution(world: &World, shape_id: ShapeId) -> f32 {
237    let shape_index = get_shape_index(world, shape_id);
238    world.shapes[shape_index as usize].material.restitution
239}
240
241/// Set the shape's user material identifier. (b2Shape_SetUserMaterial)
242pub fn shape_set_user_material(world: &mut World, shape_id: ShapeId, material: u64) {
243    crate::recording::record_op(world, |rec, _| {
244        crate::recording::write_shape_set_user_material(rec, shape_id, material)
245    });
246    debug_assert!(!world.locked);
247    if world.locked {
248        return;
249    }
250
251    let shape_index = get_shape_index(world, shape_id);
252    world.shapes[shape_index as usize].material.user_material_id = material;
253}
254
255/// Get the shape's user material identifier. (b2Shape_GetUserMaterial)
256pub fn shape_get_user_material(world: &World, shape_id: ShapeId) -> u64 {
257    let shape_index = get_shape_index(world, shape_id);
258    world.shapes[shape_index as usize].material.user_material_id
259}
260
261/// Get the shape's surface material. (b2Shape_GetSurfaceMaterial)
262pub fn shape_get_surface_material(world: &World, shape_id: ShapeId) -> SurfaceMaterial {
263    let shape_index = get_shape_index(world, shape_id);
264    world.shapes[shape_index as usize].material
265}
266
267/// Set the shape's surface material. (b2Shape_SetSurfaceMaterial)
268pub fn shape_set_surface_material(
269    world: &mut World,
270    shape_id: ShapeId,
271    surface_material: SurfaceMaterial,
272) {
273    crate::recording::record_op(world, |rec, _| {
274        crate::recording::write_shape_set_surface_material(rec, shape_id, surface_material)
275    });
276    let shape_index = get_shape_index(world, shape_id);
277    world.shapes[shape_index as usize].material = surface_material;
278}
279
280/// Get the shape filter. (b2Shape_GetFilter)
281pub fn shape_get_filter(world: &World, shape_id: ShapeId) -> Filter {
282    let shape_index = get_shape_index(world, shape_id);
283    world.shapes[shape_index as usize].filter
284}
285
286/// Destroy this shape's contacts and refresh its broad-phase presence after a
287/// filter or geometry change. (static b2ResetProxy)
288pub(crate) fn reset_proxy(
289    world: &mut World,
290    shape_index: i32,
291    wake_bodies: bool,
292    destroy_proxy: bool,
293) {
294    let body_id = world.shapes[shape_index as usize].body_id;
295
296    // destroy all contacts associated with this shape
297    let mut contact_key = world.bodies[body_id as usize].head_contact_key;
298    while contact_key != NULL_INDEX {
299        let contact_id = contact_key >> 1;
300        let edge_index = contact_key & 1;
301
302        contact_key = world.contacts[contact_id as usize].edges[edge_index as usize].next_key;
303
304        let (contact_shape_a, contact_shape_b) = {
305            let contact = &world.contacts[contact_id as usize];
306            (contact.shape_id_a, contact.shape_id_b)
307        };
308        if contact_shape_a == shape_index || contact_shape_b == shape_index {
309            crate::contact::destroy_contact(world, contact_id, wake_bodies);
310        }
311    }
312
313    let transform = get_body_transform_quick(world, &world.bodies[body_id as usize]);
314    let proxy_key = world.shapes[shape_index as usize].proxy_key;
315    if proxy_key != NULL_INDEX {
316        let proxy_type = crate::broad_phase::proxy_type(proxy_key);
317        update_shape_aabbs(
318            &mut world.shapes[shape_index as usize],
319            transform,
320            proxy_type,
321        );
322
323        if destroy_proxy {
324            world.broad_phase.destroy_proxy(proxy_key);
325
326            let (fat_aabb, category_bits) = {
327                let shape = &world.shapes[shape_index as usize];
328                (shape.fat_aabb, shape.filter.category_bits)
329            };
330            let force_pair_creation = true;
331            world.shapes[shape_index as usize].proxy_key = world.broad_phase.create_proxy(
332                proxy_type,
333                fat_aabb,
334                category_bits,
335                shape_index,
336                force_pair_creation,
337            );
338        } else {
339            let fat_aabb = world.shapes[shape_index as usize].fat_aabb;
340            world.broad_phase.move_proxy(proxy_key, fat_aabb);
341        }
342    } else {
343        let proxy_type = world.bodies[body_id as usize].type_;
344        update_shape_aabbs(
345            &mut world.shapes[shape_index as usize],
346            transform,
347            proxy_type,
348        );
349    }
350
351    world.validate_solver_sets();
352}
353
354/// Set the current filter. This is almost as expensive as recreating the
355/// shape. Sensor overlaps are not updated until the next time step.
356/// (b2Shape_SetFilter)
357pub fn shape_set_filter(world: &mut World, shape_id: ShapeId, filter: Filter) {
358    crate::recording::record_op(world, |rec, _| {
359        crate::recording::write_shape_set_filter(rec, shape_id, filter)
360    });
361    debug_assert!(!world.locked);
362    if world.locked {
363        return;
364    }
365
366    let shape_index = get_shape_index(world, shape_id);
367    {
368        let shape = &world.shapes[shape_index as usize];
369        if filter.mask_bits == shape.filter.mask_bits
370            && filter.category_bits == shape.filter.category_bits
371            && filter.group_index == shape.filter.group_index
372        {
373            return;
374        }
375    }
376
377    // If the category bits change, I need to destroy the proxy because it
378    // affects the tree sorting.
379    let destroy_proxy =
380        filter.category_bits != world.shapes[shape_index as usize].filter.category_bits;
381
382    world.shapes[shape_index as usize].filter = filter;
383
384    // need to wake bodies because a filter change may destroy contacts
385    let wake_bodies = true;
386    reset_proxy(world, shape_index, wake_bodies, destroy_proxy);
387
388    // note: this does not immediately update sensor overlaps. Instead sensor
389    // overlaps are updated the next time step
390}
391
392/// Enable sensor events for this shape. (b2Shape_EnableSensorEvents)
393pub fn shape_enable_sensor_events(world: &mut World, shape_id: ShapeId, flag: bool) {
394    crate::recording::record_op(world, |rec, _| {
395        crate::recording::write_shape_bool(
396            rec,
397            crate::recording::OP_SHAPE_ENABLE_SENSOR_EVENTS,
398            shape_id,
399            flag,
400        )
401    });
402    debug_assert!(!world.locked);
403    if world.locked {
404        return;
405    }
406
407    let shape_index = get_shape_index(world, shape_id);
408    world.shapes[shape_index as usize].enable_sensor_events = flag;
409}
410
411/// Returns true if sensor events are enabled. (b2Shape_AreSensorEventsEnabled)
412pub fn shape_are_sensor_events_enabled(world: &World, shape_id: ShapeId) -> bool {
413    let shape_index = get_shape_index(world, shape_id);
414    world.shapes[shape_index as usize].enable_sensor_events
415}
416
417/// Enable contact events for this shape. (b2Shape_EnableContactEvents)
418pub fn shape_enable_contact_events(world: &mut World, shape_id: ShapeId, flag: bool) {
419    crate::recording::record_op(world, |rec, _| {
420        crate::recording::write_shape_bool(
421            rec,
422            crate::recording::OP_SHAPE_ENABLE_CONTACT_EVENTS,
423            shape_id,
424            flag,
425        )
426    });
427    debug_assert!(!world.locked);
428    if world.locked {
429        return;
430    }
431
432    let shape_index = get_shape_index(world, shape_id);
433    world.shapes[shape_index as usize].enable_contact_events = flag;
434}
435
436/// Returns true if contact events are enabled.
437/// (b2Shape_AreContactEventsEnabled)
438pub fn shape_are_contact_events_enabled(world: &World, shape_id: ShapeId) -> bool {
439    let shape_index = get_shape_index(world, shape_id);
440    world.shapes[shape_index as usize].enable_contact_events
441}
442
443/// Enable pre-solve contact events for this shape. Only applies to dynamic
444/// bodies. These are expensive and must be carefully handled due to
445/// multithreading. (b2Shape_EnablePreSolveEvents)
446pub fn shape_enable_pre_solve_events(world: &mut World, shape_id: ShapeId, flag: bool) {
447    crate::recording::record_op(world, |rec, _| {
448        crate::recording::write_shape_bool(
449            rec,
450            crate::recording::OP_SHAPE_ENABLE_PRE_SOLVE_EVENTS,
451            shape_id,
452            flag,
453        )
454    });
455    debug_assert!(!world.locked);
456    if world.locked {
457        return;
458    }
459
460    let shape_index = get_shape_index(world, shape_id);
461    world.shapes[shape_index as usize].enable_pre_solve_events = flag;
462}
463
464/// Returns true if pre-solve events are enabled.
465/// (b2Shape_ArePreSolveEventsEnabled)
466pub fn shape_are_pre_solve_events_enabled(world: &World, shape_id: ShapeId) -> bool {
467    let shape_index = get_shape_index(world, shape_id);
468    world.shapes[shape_index as usize].enable_pre_solve_events
469}
470
471/// Enable contact hit events for this shape. (b2Shape_EnableHitEvents)
472pub fn shape_enable_hit_events(world: &mut World, shape_id: ShapeId, flag: bool) {
473    crate::recording::record_op(world, |rec, _| {
474        crate::recording::write_shape_bool(
475            rec,
476            crate::recording::OP_SHAPE_ENABLE_HIT_EVENTS,
477            shape_id,
478            flag,
479        )
480    });
481    debug_assert!(!world.locked);
482    if world.locked {
483        return;
484    }
485
486    let shape_index = get_shape_index(world, shape_id);
487    world.shapes[shape_index as usize].enable_hit_events = flag;
488}
489
490/// Returns true if hit events are enabled. (b2Shape_AreHitEventsEnabled)
491pub fn shape_are_hit_events_enabled(world: &World, shape_id: ShapeId) -> bool {
492    let shape_index = get_shape_index(world, shape_id);
493    world.shapes[shape_index as usize].enable_hit_events
494}
495
496/// Get the type of a shape. (b2Shape_GetType)
497pub fn shape_get_type(world: &World, shape_id: ShapeId) -> ShapeType {
498    let shape_index = get_shape_index(world, shape_id);
499    world.shapes[shape_index as usize].shape_type()
500}
501
502/// Get the maximum capacity required for retrieving all the touching contacts
503/// on a shape. (b2Shape_GetContactCapacity)
504pub fn shape_get_contact_capacity(world: &World, shape_id: ShapeId) -> i32 {
505    debug_assert!(!world.locked);
506    if world.locked {
507        return 0;
508    }
509
510    let shape_index = get_shape_index(world, shape_id);
511    let shape = &world.shapes[shape_index as usize];
512    if shape.sensor_index != NULL_INDEX {
513        return 0;
514    }
515
516    // Conservative and fast
517    world.bodies[shape.body_id as usize].contact_count
518}
519
520/// Get the touching contact data for a shape. (b2Shape_GetContactData —
521/// returns a Vec instead of filling a caller array)
522pub fn shape_get_contact_data(
523    world: &World,
524    shape_id: ShapeId,
525    capacity: usize,
526) -> Vec<ContactData> {
527    debug_assert!(!world.locked);
528    if world.locked {
529        return Vec::new();
530    }
531
532    let shape_index = get_shape_index(world, shape_id);
533    let shape = &world.shapes[shape_index as usize];
534    if shape.sensor_index != NULL_INDEX {
535        return Vec::new();
536    }
537
538    let mut out = Vec::new();
539    let mut contact_key = world.bodies[shape.body_id as usize].head_contact_key;
540    while contact_key != NULL_INDEX && out.len() < capacity {
541        let contact_id = contact_key >> 1;
542        let edge_index = contact_key & 1;
543
544        let contact = &world.contacts[contact_id as usize];
545        contact_key = contact.edges[edge_index as usize].next_key;
546
547        // Does contact involve this shape and is it touching?
548        if (contact.shape_id_a == shape_index || contact.shape_id_b == shape_index)
549            && (contact.flags & crate::contact::contact_flags::TOUCHING) != 0
550        {
551            let shape_a = &world.shapes[contact.shape_id_a as usize];
552            let shape_b = &world.shapes[contact.shape_id_b as usize];
553
554            let contact_sim = if contact.set_index == AWAKE_SET && contact.color_index != NULL_INDEX
555            {
556                &world.constraint_graph.colors[contact.color_index as usize].contact_sims
557                    [contact.local_index as usize]
558            } else {
559                &world.solver_sets[contact.set_index as usize].contact_sims
560                    [contact.local_index as usize]
561            };
562
563            out.push(ContactData {
564                contact_id: ContactId {
565                    index1: contact_id + 1,
566                    world0: world.world_id,
567                    padding: 0,
568                    generation: contact.generation,
569                },
570                shape_id_a: ShapeId {
571                    index1: shape_a.id + 1,
572                    world0: world.world_id,
573                    generation: shape_a.generation,
574                },
575                shape_id_b: ShapeId {
576                    index1: shape_b.id + 1,
577                    world0: world.world_id,
578                    generation: shape_b.generation,
579                },
580                manifold: contact_sim.manifold,
581            });
582        }
583    }
584
585    out
586}
587
588/// Get the maximum capacity required for retrieving all the overlapped shapes
589/// on a sensor shape. Returns 0 if the shape is not a sensor.
590/// (b2Shape_GetSensorCapacity)
591pub fn shape_get_sensor_capacity(world: &World, shape_id: ShapeId) -> i32 {
592    debug_assert!(!world.locked);
593    if world.locked {
594        return 0;
595    }
596
597    let shape_index = get_shape_index(world, shape_id);
598    let shape = &world.shapes[shape_index as usize];
599    if shape.sensor_index == NULL_INDEX {
600        return 0;
601    }
602
603    world.sensors[shape.sensor_index as usize].overlaps2.len() as i32
604}
605
606/// Get the overlapped shapes for a sensor shape. Overlaps may contain
607/// destroyed shapes, so use [`shape_is_valid`] to confirm each overlap.
608/// (b2Shape_GetSensorData — returns a Vec instead of filling a caller array)
609pub fn shape_get_sensor_data(world: &World, shape_id: ShapeId, capacity: usize) -> Vec<ShapeId> {
610    debug_assert!(!world.locked);
611    if world.locked {
612        return Vec::new();
613    }
614
615    let shape_index = get_shape_index(world, shape_id);
616    let shape = &world.shapes[shape_index as usize];
617    if shape.sensor_index == NULL_INDEX {
618        return Vec::new();
619    }
620
621    let sensor = &world.sensors[shape.sensor_index as usize];
622    let count = sensor.overlaps2.len().min(capacity);
623    sensor.overlaps2[..count]
624        .iter()
625        .map(|visitor| ShapeId {
626            index1: visitor.shape_id + 1,
627            world0: world.world_id,
628            generation: visitor.generation,
629        })
630        .collect()
631}
632
633/// Get the current world AABB. This is the axis-aligned bounding box in world
634/// coordinates. (b2Shape_GetAABB)
635pub fn shape_get_aabb(world: &World, shape_id: ShapeId) -> Aabb {
636    let shape_index = get_shape_index(world, shape_id);
637    world.shapes[shape_index as usize].aabb
638}
639
640/// Compute the mass data for a shape. (b2Shape_ComputeMassData)
641pub fn shape_compute_mass_data(world: &World, shape_id: ShapeId) -> MassData {
642    let shape_index = get_shape_index(world, shape_id);
643    compute_shape_mass(&world.shapes[shape_index as usize])
644}
645
646/// Get the closest point on a shape to a target point. Target and result are
647/// in world space. (b2Shape_GetClosestPoint)
648pub fn shape_get_closest_point(world: &World, shape_id: ShapeId, target: Pos) -> Pos {
649    let shape_index = get_shape_index(world, shape_id);
650    let shape = &world.shapes[shape_index as usize];
651    let body = &world.bodies[shape.body_id as usize];
652    let transform = get_body_transform_quick(world, body);
653
654    // The target rides in as the frame of proxy B, so the relative pose is
655    // differenced in double and the result stays exact far from the origin
656    let zero = VEC2_ZERO;
657    let target_transform = crate::math_functions::WorldTransform {
658        p: target,
659        q: ROT_IDENTITY,
660    };
661
662    let input = DistanceInput {
663        proxy_a: make_shape_distance_proxy(shape),
664        proxy_b: make_proxy(&[zero], 0.0),
665        transform: inv_mul_world_transforms(transform, target_transform),
666        use_radii: true,
667    };
668
669    let mut cache = SimplexCache::default();
670    let output = shape_distance(&input, &mut cache, None);
671
672    transform_world_point(transform, output.point_a)
673}