1use super::{
9 default_friction_callback, default_restitution_callback, CustomFilterFcn, PreSolveFcn, Profile,
10 TaskContext, World,
11};
12use crate::body::{get_body_transform_quick, wake_body};
13use crate::constants::{GRAPH_COLOR_COUNT, MAX_WORKERS};
14use crate::distance::{make_proxy, shape_distance, DistanceInput, SimplexCache};
15use crate::events::{BodyMoveEvent, ContactEvents, JointEvent, SensorEvents};
16use crate::math_functions::{
17 aabb_union, add, clamp_float, clamp_int, cross, inv_transform_world_point, is_valid_float,
18 is_valid_position, length_squared, max_int, mul_add, mul_mv, mul_sv, normalize, offset_aabb,
19 rotate_vector, sub, Aabb, Pos, Vec3, TRANSFORM_IDENTITY, VEC3_ZERO,
20};
21use crate::sensor::SensorTaskContext;
22use crate::shape::{get_shape_centroid, get_shape_projected_area, make_shape_proxy};
23use crate::solver_set::{wake_solver_set, AWAKE_SET, FIRST_SLEEPING_SET};
24use crate::types::{
25 BodyType, Capacity, Counters, ExplosionDef, FrictionCallback, RestitutionCallback,
26 BODY_TYPE_COUNT,
27};
28
29pub fn world_is_valid(world: &World) -> bool {
35 world.in_use
36}
37
38pub fn world_get_body_events(world: &World) -> &[BodyMoveEvent] {
41 debug_assert!(!world.locked);
42 if world.locked {
43 return &[];
44 }
45
46 &world.body_move_events
47}
48
49pub fn world_get_sensor_events(world: &World) -> SensorEvents<'_> {
52 debug_assert!(!world.locked);
53 if world.locked {
54 return SensorEvents {
55 begin_events: &[],
56 end_events: &[],
57 };
58 }
59
60 let end_event_array_index = 1 - world.end_event_array_index;
62
63 SensorEvents {
64 begin_events: &world.sensor_begin_events,
65 end_events: &world.sensor_end_events[end_event_array_index as usize],
66 }
67}
68
69pub fn world_get_contact_events(world: &World) -> ContactEvents<'_> {
72 debug_assert!(!world.locked);
73 if world.locked {
74 return ContactEvents {
75 begin_events: &[],
76 end_events: &[],
77 hit_events: &[],
78 };
79 }
80
81 let end_event_array_index = 1 - world.end_event_array_index;
83
84 ContactEvents {
85 begin_events: &world.contact_begin_events,
86 end_events: &world.contact_end_events[end_event_array_index as usize],
87 hit_events: &world.contact_hit_events,
88 }
89}
90
91pub fn world_get_joint_events(world: &World) -> &[JointEvent] {
94 debug_assert!(!world.locked);
95 if world.locked {
96 return &[];
97 }
98
99 &world.joint_events
100}
101
102pub fn world_enable_sleeping(world: &mut World, flag: bool) {
104 debug_assert!(!world.locked);
105 if world.locked {
106 return;
107 }
108
109 if flag == world.enable_sleep {
110 return;
111 }
112
113 crate::recording::capture::rec(world, |rec, wid| {
114 rec.write_world_enable_sleeping(wid, flag);
115 });
116
117 world.enable_sleep = flag;
118
119 if !flag {
120 let set_count = world.solver_sets.len() as i32;
121 for i in FIRST_SLEEPING_SET..set_count {
122 if !world.solver_sets[i as usize].body_sims.is_empty() {
123 wake_solver_set(world, i);
124 }
125 }
126 }
127}
128
129pub fn world_is_sleeping_enabled(world: &World) -> bool {
131 world.enable_sleep
132}
133
134pub fn world_enable_warm_starting(world: &mut World, flag: bool) {
136 debug_assert!(!world.locked);
137 if world.locked {
138 return;
139 }
140
141 crate::recording::capture::rec(world, |rec, wid| {
142 rec.write_world_enable_warm_starting(wid, flag);
143 });
144
145 world.enable_warm_starting = flag;
146}
147
148pub fn world_is_warm_starting_enabled(world: &World) -> bool {
150 world.enable_warm_starting
151}
152
153pub fn world_get_awake_body_count(world: &World) -> i32 {
155 world.solver_sets[AWAKE_SET as usize].body_sims.len() as i32
156}
157
158pub fn world_enable_continuous(world: &mut World, flag: bool) {
160 debug_assert!(!world.locked);
161 if world.locked {
162 return;
163 }
164
165 crate::recording::capture::rec(world, |rec, wid| {
166 rec.write_world_enable_continuous(wid, flag);
167 });
168
169 world.enable_continuous = flag;
170}
171
172pub fn world_is_continuous_enabled(world: &World) -> bool {
174 world.enable_continuous
175}
176
177pub fn world_enable_speculative(world: &mut World, flag: bool) {
179 debug_assert!(!world.locked);
180 if world.locked {
181 return;
182 }
183
184 crate::recording::capture::rec(world, |rec, wid| {
185 rec.write_world_enable_speculative(wid, flag);
186 });
187
188 world.enable_speculative = flag;
189}
190
191pub fn world_is_speculative_enabled(world: &World) -> bool {
193 world.enable_speculative
194}
195
196pub fn world_set_restitution_threshold(world: &mut World, value: f32) {
198 debug_assert!(!world.locked);
199 if world.locked {
200 return;
201 }
202
203 crate::recording::capture::rec(world, |rec, wid| {
204 rec.write_world_set_restitution_threshold(wid, value);
205 });
206
207 world.restitution_threshold = clamp_float(value, 0.0, f32::MAX);
208}
209
210pub fn world_get_restitution_threshold(world: &World) -> f32 {
212 world.restitution_threshold
213}
214
215pub fn world_set_hit_event_threshold(world: &mut World, value: f32) {
217 debug_assert!(!world.locked);
218 if world.locked {
219 return;
220 }
221
222 crate::recording::capture::rec(world, |rec, wid| {
223 rec.write_world_set_hit_event_threshold(wid, value);
224 });
225
226 world.hit_event_threshold = clamp_float(value, 0.0, f32::MAX);
227}
228
229pub fn world_get_hit_event_threshold(world: &World) -> f32 {
231 world.hit_event_threshold
232}
233
234pub fn world_set_contact_tuning(
236 world: &mut World,
237 hertz: f32,
238 damping_ratio: f32,
239 contact_speed: f32,
240) {
241 crate::recording::capture::rec(world, |rec, wid| {
242 rec.write_world_set_contact_tuning(wid, hertz, damping_ratio, contact_speed);
243 });
244
245 debug_assert!(!world.locked);
246 if world.locked {
247 return;
248 }
249
250 world.contact_hertz = clamp_float(hertz, 0.0, f32::MAX);
251 world.contact_damping_ratio = clamp_float(damping_ratio, 0.0, f32::MAX);
252 world.contact_speed = clamp_float(contact_speed, 0.0, f32::MAX);
253}
254
255pub fn world_set_contact_recycle_distance(world: &mut World, recycle_distance: f32) {
257 debug_assert!(!world.locked);
258 if world.locked {
259 return;
260 }
261
262 crate::recording::capture::rec(world, |rec, wid| {
263 rec.write_world_set_contact_recycle_distance(wid, recycle_distance);
264 });
265
266 world.contact_recycle_distance = clamp_float(recycle_distance, 0.0, f32::MAX);
267}
268
269pub fn world_get_contact_recycle_distance(world: &World) -> f32 {
271 world.contact_recycle_distance
272}
273
274pub fn world_set_maximum_linear_speed(world: &mut World, maximum_linear_speed: f32) {
276 crate::recording::capture::rec(world, |rec, wid| {
277 rec.write_world_set_maximum_linear_speed(wid, maximum_linear_speed);
278 });
279
280 debug_assert!(is_valid_float(maximum_linear_speed) && maximum_linear_speed > 0.0);
281
282 debug_assert!(!world.locked);
283 if world.locked {
284 return;
285 }
286
287 world.max_linear_speed = maximum_linear_speed;
288}
289
290pub fn world_get_maximum_linear_speed(world: &World) -> f32 {
292 world.max_linear_speed
293}
294
295pub fn world_get_profile(world: &World) -> Profile {
297 world.profile
298}
299
300pub fn world_get_counters(world: &World) -> Counters {
302 let mut s = Counters {
303 body_count: world.body_id_pool.id_count(),
304 shape_count: world.shape_id_pool.id_count(),
305 contact_count: world.contact_id_pool.id_count(),
306 joint_count: world.joint_id_pool.id_count(),
307 island_count: world.island_id_pool.id_count(),
308 sat_call_count: world.sat_call_count,
309 sat_cache_hit_count: world.sat_cache_hit_count,
310 manifold_counts: world.manifold_counts,
311 ..Default::default()
312 };
313
314 let static_tree = &world.broad_phase.trees[BodyType::Static as usize];
315 s.static_tree_height = static_tree.height();
316
317 let dynamic_tree = &world.broad_phase.trees[BodyType::Dynamic as usize];
318 let kinematic_tree = &world.broad_phase.trees[BodyType::Kinematic as usize];
319 s.tree_height = max_int(dynamic_tree.height(), kinematic_tree.height());
320
321 for i in 0..world.worker_count as usize {
324 s.recycled_contact_count += world.task_contexts[i].recycled_contact_count;
325 s.distance_iterations = max_int(
326 s.distance_iterations,
327 world.task_contexts[i].distance_iterations,
328 );
329 s.push_back_iterations = max_int(
330 s.push_back_iterations,
331 world.task_contexts[i].push_back_iterations,
332 );
333 s.root_iterations = max_int(s.root_iterations, world.task_contexts[i].root_iterations);
334 }
335
336 for i in 0..GRAPH_COLOR_COUNT as usize {
337 let color = &world.constraint_graph.colors[i];
338 let color_contact_count = (color.convex_contacts.len() + color.contacts.len()) as i32;
339 s.color_counts[i] = color_contact_count + color.joint_sims.len() as i32;
340 s.awake_contact_count += color_contact_count;
341 }
342 s.awake_contact_count += world.solver_sets[AWAKE_SET as usize].contact_indices.len() as i32;
343
344 s
345}
346
347pub fn world_get_max_capacity(world: &World) -> Capacity {
349 world.max_capacity
350}
351
352pub fn world_set_user_data(world: &mut World, user_data: u64) {
354 world.user_data = user_data;
355}
356
357pub fn world_get_user_data(world: &World) -> u64 {
359 world.user_data
360}
361
362pub fn world_set_friction_callback(world: &mut World, callback: Option<FrictionCallback>) {
365 debug_assert!(!world.locked);
366 if world.locked {
367 return;
368 }
369
370 world.friction_callback = Some(callback.unwrap_or(default_friction_callback));
371}
372
373pub fn world_set_restitution_callback(world: &mut World, callback: Option<RestitutionCallback>) {
376 debug_assert!(!world.locked);
377 if world.locked {
378 return;
379 }
380
381 world.restitution_callback = Some(callback.unwrap_or(default_restitution_callback));
382}
383
384pub fn world_set_custom_filter_callback(
386 world: &mut World,
387 fcn: Option<CustomFilterFcn>,
388 context: u64,
389) {
390 world.custom_filter_fcn = fcn;
391 world.custom_filter_context = context;
392}
393
394pub fn world_set_pre_solve_callback(world: &mut World, fcn: Option<PreSolveFcn>, context: u64) {
396 world.pre_solve_fcn = fcn;
397 world.pre_solve_context = context;
398}
399
400pub fn world_set_gravity(world: &mut World, gravity: Vec3) {
402 crate::recording::capture::rec(world, |rec, wid| {
403 rec.write_world_set_gravity(wid, gravity);
404 });
405 world.gravity = gravity;
406}
407
408pub fn world_get_gravity(world: &World) -> Vec3 {
410 world.gravity
411}
412
413pub fn world_rebuild_static_tree(world: &mut World) {
415 debug_assert!(!world.locked);
416 if world.locked {
417 return;
418 }
419
420 crate::recording::capture::rec(world, |rec, wid| {
421 rec.write_world_rebuild_static_tree(wid);
422 });
423
424 world.broad_phase.trees[BodyType::Static as usize].rebuild(true);
425}
426
427pub fn world_explode(world: &mut World, explosion_def: &ExplosionDef) {
429 crate::recording::capture::rec(world, |rec, wid| {
430 rec.write_world_explode(wid, *explosion_def);
431 });
432
433 let mask_bits = explosion_def.mask_bits;
434 let position = explosion_def.position;
435 let radius = explosion_def.radius;
436 let falloff = explosion_def.falloff;
437 let impulse_per_area = explosion_def.impulse_per_area;
438
439 debug_assert!(is_valid_position(position));
440 debug_assert!(is_valid_float(radius) && radius >= 0.0);
441 debug_assert!(is_valid_float(falloff) && falloff >= 0.0);
442 debug_assert!(is_valid_float(impulse_per_area));
443
444 debug_assert!(!world.locked);
445 if world.locked {
446 return;
447 }
448
449 world.locked = true;
451
452 let extent = radius + falloff;
453 let local_box = Aabb {
454 lower_bound: Vec3 {
455 x: -extent,
456 y: -extent,
457 z: -extent,
458 },
459 upper_bound: Vec3 {
460 x: extent,
461 y: extent,
462 z: extent,
463 },
464 };
465 let aabb = offset_aabb(local_box, position);
466
467 let mut shape_ids: Vec<i32> = Vec::new();
470 world.broad_phase.trees[BodyType::Dynamic as usize].query(
471 aabb,
472 mask_bits,
473 false,
474 |_, user_data| {
475 shape_ids.push(user_data as i32);
476 true
477 },
478 );
479
480 for shape_id in shape_ids {
481 explode_shape(world, shape_id, position, radius, falloff, impulse_per_area);
482 }
483
484 world.locked = false;
485}
486
487fn explode_shape(
489 world: &mut World,
490 shape_id: i32,
491 position: Pos,
492 radius: f32,
493 falloff: f32,
494 impulse_per_area: f32,
495) {
496 let shape = &world.shapes[shape_id as usize];
497 if shape.explosion_scale == 0.0 {
498 return;
499 }
500
501 let body_id = shape.body_id;
502 let body = &world.bodies[body_id as usize];
503 debug_assert!(body.type_ == BodyType::Dynamic);
504
505 let xf = get_body_transform_quick(world, body);
506
507 let local_position = inv_transform_world_point(xf, position);
509
510 let input = DistanceInput {
511 proxy_a: make_shape_proxy(shape),
512 proxy_b: make_proxy(&[local_position], 0.0),
513 transform: TRANSFORM_IDENTITY,
514 use_radii: true,
515 };
516
517 let mut cache = SimplexCache::default();
518 let output = shape_distance(&input, &mut cache, None);
519
520 if output.distance > radius + falloff {
521 return;
522 }
523
524 let mut closest_point = output.point_a;
526 if output.distance == 0.0 {
527 closest_point = get_shape_centroid(shape);
528 }
529
530 let mut direction = sub(closest_point, local_position);
531 if length_squared(direction) > 100.0 * f32::EPSILON * f32::EPSILON {
532 direction = normalize(direction);
533 } else {
534 direction = Vec3 {
535 x: 1.0,
536 y: 0.0,
537 z: 0.0,
538 };
539 }
540
541 let area = get_shape_projected_area(shape, direction);
542 let mut scale = 1.0;
543 if output.distance > radius && falloff > 0.0 {
544 scale = clamp_float((radius + falloff - output.distance) / falloff, 0.0, 1.0);
545 }
546
547 let magnitude = impulse_per_area * area * scale * shape.explosion_scale;
548 let impulse = mul_sv(magnitude, rotate_vector(xf.q, direction));
549 let closest_for_torque = closest_point;
550 let direction_q = xf.q;
551
552 wake_body(world, body_id);
553
554 let body = &world.bodies[body_id as usize];
555 if body.set_index != AWAKE_SET {
556 return;
557 }
558
559 let local_index = body.local_index;
560 let set = &mut world.solver_sets[AWAKE_SET as usize];
561 let (inv_mass, local_center, inv_inertia_world) = {
563 let body_sim = &set.body_sims[local_index as usize];
564 (
565 body_sim.inv_mass,
566 body_sim.local_center,
567 body_sim.inv_inertia_world,
568 )
569 };
570 let state = &mut set.body_states[local_index as usize];
571 state.linear_velocity = mul_add(state.linear_velocity, inv_mass, impulse);
572
573 let r = rotate_vector(direction_q, sub(closest_for_torque, local_center));
575 state.angular_velocity = add(
576 state.angular_velocity,
577 mul_mv(inv_inertia_world, cross(r, impulse)),
578 );
579}
580
581pub fn world_get_bounds(world: &World) -> Aabb {
583 debug_assert!(!world.locked);
584 if world.locked {
585 return Aabb {
586 lower_bound: VEC3_ZERO,
587 upper_bound: VEC3_ZERO,
588 };
589 }
590
591 let mut world_bounds = Aabb {
592 lower_bound: VEC3_ZERO,
593 upper_bound: VEC3_ZERO,
594 };
595 let mut have_bounds = false;
596
597 for i in 0..BODY_TYPE_COUNT {
598 let tree = &world.broad_phase.trees[i];
599 if tree.proxy_count() == 0 {
600 continue;
601 }
602
603 let bounds = tree.root_bounds();
604 if have_bounds {
605 world_bounds = aabb_union(world_bounds, bounds);
606 } else {
607 world_bounds = bounds;
608 have_bounds = true;
609 }
610 }
611
612 world_bounds
613}
614
615pub fn world_set_worker_count(world: &mut World, count: i32) {
619 debug_assert!(!world.locked);
620 if world.locked {
621 return;
622 }
623
624 if count == world.worker_count {
625 return;
626 }
627
628 world.worker_count = clamp_int(count, 1, MAX_WORKERS);
629 world.task_contexts = vec![TaskContext::default(); world.worker_count as usize];
632 world.sensor_task_contexts = vec![SensorTaskContext::default(); world.worker_count as usize];
633}
634
635pub fn world_get_worker_count(world: &World) -> i32 {
637 debug_assert!(!world.locked);
638 if world.locked {
639 return 0;
640 }
641 world.worker_count
642}
643
644pub fn world_start_recording(world: &mut World, recording: &mut crate::recording::Recording) {
646 debug_assert!(world.recording.is_none());
647 if world.recording.is_some() {
648 return;
649 }
650 crate::recording::start_recording(world, recording);
651}
652
653pub fn world_stop_recording(world: &mut World) {
655 crate::recording::stop_recording(world);
656}