1use super::{default_friction_callback, default_restitution_callback, World};
18use super::{CustomFilterFcn, PreSolveFcn, Profile};
19use crate::aabb::offset_aabb;
20use crate::body::{get_body_transform_quick, wake_body};
21use crate::constants::GRAPH_COLOR_COUNT;
22use crate::distance::{make_proxy, shape_distance, DistanceInput, SimplexCache};
23use crate::events::{BodyMoveEvent, ContactEvents, JointEvent, SensorEvents};
24use crate::math_functions::{
25 aabb_union, clamp_float, cross, inv_transform_world_point, is_valid_float, is_valid_position,
26 left_perp, length_squared, max_int, mul_add, mul_sv, normalize, rotate_vector, sub, Aabb, Pos,
27 Vec2, TRANSFORM_IDENTITY,
28};
29use crate::shape::{get_shape_centroid, get_shape_projected_perimeter, make_shape_distance_proxy};
30use crate::solver_set::{AWAKE_SET, FIRST_SLEEPING_SET};
31use crate::types::{
32 BodyType, Capacity, Counters, ExplosionDef, FrictionCallback, RestitutionCallback,
33};
34
35pub fn world_is_valid(world: &World) -> bool {
41 world.in_use
42}
43
44pub fn compute_world_bounds(world: &World) -> (Aabb, bool) {
47 let mut world_bounds = Aabb::default();
48 let mut have_bounds = false;
49
50 for tree in world.broad_phase.trees.iter() {
51 if tree.proxy_count() == 0 {
52 continue;
53 }
54
55 let tree_bounds = tree.root_bounds();
56 world_bounds = if have_bounds {
57 aabb_union(world_bounds, tree_bounds)
58 } else {
59 tree_bounds
60 };
61 have_bounds = true;
62 }
63
64 (world_bounds, have_bounds)
65}
66
67pub fn world_get_bounds(world: &World) -> Aabb {
70 debug_assert!(!world.locked);
71 if world.locked {
72 return Aabb::default();
73 }
74
75 let (bounds, _) = compute_world_bounds(world);
76 bounds
77}
78
79pub fn world_get_body_events(world: &World) -> &[BodyMoveEvent] {
82 debug_assert!(!world.locked);
83 if world.locked {
84 return &[];
85 }
86
87 &world.body_move_events
88}
89
90pub fn world_get_sensor_events(world: &World) -> SensorEvents<'_> {
93 debug_assert!(!world.locked);
94 if world.locked {
95 return SensorEvents {
96 begin_events: &[],
97 end_events: &[],
98 };
99 }
100
101 let end_event_array_index = 1 - world.end_event_array_index;
103
104 SensorEvents {
105 begin_events: &world.sensor_begin_events,
106 end_events: &world.sensor_end_events[end_event_array_index as usize],
107 }
108}
109
110pub fn world_get_contact_events(world: &World) -> ContactEvents<'_> {
113 debug_assert!(!world.locked);
114 if world.locked {
115 return ContactEvents {
116 begin_events: &[],
117 end_events: &[],
118 hit_events: &[],
119 };
120 }
121
122 let end_event_array_index = 1 - world.end_event_array_index;
124
125 ContactEvents {
126 begin_events: &world.contact_begin_events,
127 end_events: &world.contact_end_events[end_event_array_index as usize],
128 hit_events: &world.contact_hit_events,
129 }
130}
131
132pub fn world_get_joint_events(world: &World) -> &[JointEvent] {
135 debug_assert!(!world.locked);
136 if world.locked {
137 return &[];
138 }
139
140 &world.joint_events
141}
142
143pub fn world_enable_sleeping(world: &mut World, flag: bool) {
147 debug_assert!(!world.locked);
148 if world.locked {
149 return;
150 }
151
152 crate::recording::record_op(world, |rec, id| {
153 crate::recording::write_world_bool(
154 rec,
155 crate::recording::OP_WORLD_ENABLE_SLEEPING,
156 id,
157 flag,
158 )
159 });
160
161 if flag == world.enable_sleep {
162 return;
163 }
164
165 world.enable_sleep = flag;
166
167 if !flag {
168 let set_count = world.solver_sets.len();
169 for i in (FIRST_SLEEPING_SET as usize)..set_count {
170 if !world.solver_sets[i].body_sims.is_empty() {
171 crate::solver_set::wake_solver_set(world, i as i32);
172 }
173 }
174 }
175}
176
177pub fn world_is_sleeping_enabled(world: &World) -> bool {
179 world.enable_sleep
180}
181
182pub fn world_enable_warm_starting(world: &mut World, flag: bool) {
186 debug_assert!(!world.locked);
187 if world.locked {
188 return;
189 }
190
191 crate::recording::record_op(world, |rec, id| {
192 crate::recording::write_world_bool(
193 rec,
194 crate::recording::OP_WORLD_ENABLE_WARM_STARTING,
195 id,
196 flag,
197 )
198 });
199
200 world.enable_warm_starting = flag;
201}
202
203pub fn world_is_warm_starting_enabled(world: &World) -> bool {
205 world.enable_warm_starting
206}
207
208pub fn world_get_awake_body_count(world: &World) -> i32 {
210 world.solver_sets[AWAKE_SET as usize].body_sims.len() as i32
211}
212
213pub fn world_enable_continuous(world: &mut World, flag: bool) {
218 debug_assert!(!world.locked);
219 if world.locked {
220 return;
221 }
222
223 crate::recording::record_op(world, |rec, id| {
224 crate::recording::write_world_bool(
225 rec,
226 crate::recording::OP_WORLD_ENABLE_CONTINUOUS,
227 id,
228 flag,
229 )
230 });
231
232 world.enable_continuous = flag;
233}
234
235pub fn world_is_continuous_enabled(world: &World) -> bool {
237 world.enable_continuous
238}
239
240pub fn world_set_restitution_threshold(world: &mut World, value: f32) {
244 debug_assert!(!world.locked);
245 if world.locked {
246 return;
247 }
248
249 crate::recording::record_op(world, |rec, id| {
250 crate::recording::write_world_f32(
251 rec,
252 crate::recording::OP_WORLD_SET_RESTITUTION_THRESHOLD,
253 id,
254 value,
255 )
256 });
257
258 world.restitution_threshold = clamp_float(value, 0.0, f32::MAX);
259}
260
261pub fn world_get_restitution_threshold(world: &World) -> f32 {
264 world.restitution_threshold
265}
266
267pub fn world_set_hit_event_threshold(world: &mut World, value: f32) {
271 debug_assert!(!world.locked);
272 if world.locked {
273 return;
274 }
275
276 crate::recording::record_op(world, |rec, id| {
277 crate::recording::write_world_f32(
278 rec,
279 crate::recording::OP_WORLD_SET_HIT_EVENT_THRESHOLD,
280 id,
281 value,
282 )
283 });
284
285 world.hit_event_threshold = clamp_float(value, 0.0, f32::MAX);
286}
287
288pub fn world_get_hit_event_threshold(world: &World) -> f32 {
291 world.hit_event_threshold
292}
293
294pub fn world_set_contact_tuning(
297 world: &mut World,
298 hertz: f32,
299 damping_ratio: f32,
300 push_speed: f32,
301) {
302 debug_assert!(!world.locked);
303 if world.locked {
304 return;
305 }
306
307 crate::recording::record_op(world, |rec, id| {
308 crate::recording::write_world_set_contact_tuning(rec, id, hertz, damping_ratio, push_speed)
309 });
310
311 world.contact_hertz = clamp_float(hertz, 0.0, f32::MAX);
312 world.contact_damping_ratio = clamp_float(damping_ratio, 0.0, f32::MAX);
313 world.contact_speed = clamp_float(push_speed, 0.0, f32::MAX);
314}
315
316pub fn world_set_contact_recycle_distance(world: &mut World, recycle_distance: f32) {
320 debug_assert!(!world.locked);
321 if world.locked {
322 return;
323 }
324
325 crate::recording::record_op(world, |rec, id| {
326 crate::recording::write_world_f32(
327 rec,
328 crate::recording::OP_WORLD_SET_CONTACT_RECYCLE_DISTANCE,
329 id,
330 recycle_distance,
331 )
332 });
333
334 world.contact_recycle_distance = clamp_float(recycle_distance, 0.0, f32::MAX);
335}
336
337pub fn world_get_contact_recycle_distance(world: &World) -> f32 {
339 world.contact_recycle_distance
340}
341
342pub fn world_set_maximum_linear_speed(world: &mut World, maximum_linear_speed: f32) {
345 debug_assert!(is_valid_float(maximum_linear_speed) && maximum_linear_speed > 0.0);
346
347 debug_assert!(!world.locked);
348 if world.locked {
349 return;
350 }
351
352 crate::recording::record_op(world, |rec, id| {
353 crate::recording::write_world_f32(
354 rec,
355 crate::recording::OP_WORLD_SET_MAXIMUM_LINEAR_SPEED,
356 id,
357 maximum_linear_speed,
358 )
359 });
360
361 world.max_linear_speed = maximum_linear_speed;
362}
363
364pub fn world_get_maximum_linear_speed(world: &World) -> f32 {
367 world.max_linear_speed
368}
369
370pub fn world_get_profile(world: &World) -> Profile {
372 world.profile
373}
374
375pub fn world_get_counters(world: &World) -> Counters {
377 let mut s = Counters {
378 body_count: world.body_id_pool.id_count(),
379 shape_count: world.shape_id_pool.id_count(),
380 contact_count: world.contact_id_pool.id_count(),
381 joint_count: world.joint_id_pool.id_count(),
382 island_count: world.island_id_pool.id_count(),
383 ..Default::default()
384 };
385
386 let static_tree = &world.broad_phase.trees[BodyType::Static as usize];
387 s.static_tree_height = static_tree.height();
388
389 let dynamic_tree = &world.broad_phase.trees[BodyType::Dynamic as usize];
390 let kinematic_tree = &world.broad_phase.trees[BodyType::Kinematic as usize];
391 s.tree_height = max_int(dynamic_tree.height(), kinematic_tree.height());
392
393 for i in 0..world.worker_count as usize {
397 s.recycled_contact_count += world.task_contexts[i].recycled_contact_count;
398 }
399
400 for i in 0..GRAPH_COLOR_COUNT as usize {
401 let color = &world.constraint_graph.colors[i];
402 s.color_counts[i] = (color.contact_sims.len() + color.joint_sims.len()) as i32;
403 s.awake_contact_count += color.contact_sims.len() as i32;
404 }
405 s.awake_contact_count += world.solver_sets[AWAKE_SET as usize].contact_sims.len() as i32;
406
407 s
408}
409
410pub fn world_get_max_capacity(world: &World) -> Capacity {
412 world.max_capacity
413}
414
415pub fn world_set_user_data(world: &mut World, user_data: u64) {
417 world.user_data = user_data;
418}
419
420pub fn world_get_user_data(world: &World) -> u64 {
422 world.user_data
423}
424
425pub fn world_set_friction_callback(world: &mut World, callback: Option<FrictionCallback>) {
428 if world.locked {
429 return;
430 }
431
432 world.friction_callback = Some(callback.unwrap_or(default_friction_callback));
433}
434
435pub fn world_set_restitution_callback(world: &mut World, callback: Option<RestitutionCallback>) {
438 if world.locked {
439 return;
440 }
441
442 world.restitution_callback = Some(callback.unwrap_or(default_restitution_callback));
443}
444
445pub fn world_set_custom_filter_callback(
448 world: &mut World,
449 fcn: Option<CustomFilterFcn>,
450 context: u64,
451) {
452 world.custom_filter_fcn = fcn;
453 world.custom_filter_context = context;
454}
455
456pub fn world_set_pre_solve_callback(world: &mut World, fcn: Option<PreSolveFcn>, context: u64) {
459 world.pre_solve_fcn = fcn;
460 world.pre_solve_context = context;
461}
462
463pub fn world_set_gravity(world: &mut World, gravity: Vec2) {
467 crate::recording::record_op(world, |rec, id| {
468 crate::recording::write_world_set_gravity(rec, id, gravity)
469 });
470 world.gravity = gravity;
471}
472
473pub fn world_get_gravity(world: &World) -> Vec2 {
475 world.gravity
476}
477
478pub fn world_explode(world: &mut World, explosion_def: &ExplosionDef) {
481 let mask_bits = explosion_def.mask_bits;
482 let position = explosion_def.position;
483 let radius = explosion_def.radius;
484 let falloff = explosion_def.falloff;
485 let impulse_per_length = explosion_def.impulse_per_length;
486
487 debug_assert!(is_valid_position(position));
488 debug_assert!(is_valid_float(radius) && radius >= 0.0);
489 debug_assert!(is_valid_float(falloff) && falloff >= 0.0);
490 debug_assert!(is_valid_float(impulse_per_length));
491
492 debug_assert!(!world.locked);
493 if world.locked {
494 return;
495 }
496
497 crate::recording::record_op(world, |rec, id| {
498 crate::recording::write_world_explode(rec, id, explosion_def)
499 });
500
501 let extent = radius + falloff;
504 let local_box = Aabb {
505 lower_bound: Vec2 {
506 x: -extent,
507 y: -extent,
508 },
509 upper_bound: Vec2 {
510 x: extent,
511 y: extent,
512 },
513 };
514 let aabb = offset_aabb(local_box, position);
515
516 let mut shape_ids: Vec<i32> = Vec::new();
522 world.broad_phase.trees[BodyType::Dynamic as usize].query(aabb, mask_bits, |_, user_data| {
523 shape_ids.push(user_data as i32);
524 true
525 });
526
527 for shape_id in shape_ids {
528 explosion_callback(
529 world,
530 shape_id,
531 position,
532 radius,
533 falloff,
534 impulse_per_length,
535 );
536 }
537}
538
539fn explosion_callback(
542 world: &mut World,
543 shape_id: i32,
544 position: Pos,
545 radius: f32,
546 falloff: f32,
547 impulse_per_length: f32,
548) {
549 let shape = &world.shapes[shape_id as usize];
550 let body_id = shape.body_id;
551 let body = &world.bodies[body_id as usize];
552 debug_assert!(body.type_ == BodyType::Dynamic);
553
554 let xf = get_body_transform_quick(world, body);
555
556 let local_position = inv_transform_world_point(xf, position);
560
561 let input = DistanceInput {
562 proxy_a: make_shape_distance_proxy(shape),
563 proxy_b: make_proxy(&[local_position], 0.0),
564 transform: TRANSFORM_IDENTITY,
565 use_radii: true,
566 };
567
568 let mut cache = SimplexCache::default();
569 let output = shape_distance(&input, &mut cache, None);
570
571 if output.distance > radius + falloff {
572 return;
573 }
574
575 let mut closest_point = output.point_a;
578 if output.distance == 0.0 {
579 closest_point = get_shape_centroid(shape);
580 }
581
582 let mut direction = sub(closest_point, local_position);
583 if length_squared(direction) > 100.0 * f32::EPSILON * f32::EPSILON {
584 direction = normalize(direction);
585 } else {
586 direction = Vec2 { x: 1.0, y: 0.0 };
587 }
588
589 let local_line = left_perp(direction);
590 let perimeter = get_shape_projected_perimeter(shape, local_line);
591 let mut scale = 1.0;
592 if output.distance > radius && falloff > 0.0 {
593 scale = clamp_float((radius + falloff - output.distance) / falloff, 0.0, 1.0);
594 }
595
596 let magnitude = impulse_per_length * perimeter * scale;
597 let impulse = mul_sv(magnitude, rotate_vector(xf.q, direction));
598
599 wake_body(world, body_id);
600
601 let body = &world.bodies[body_id as usize];
602 if body.set_index != AWAKE_SET {
603 return;
604 }
605
606 let local_index = body.local_index;
607 let set = &mut world.solver_sets[AWAKE_SET as usize];
608 let (inv_mass, inv_inertia, local_center) = {
609 let body_sim = &set.body_sims[local_index as usize];
610 (
611 body_sim.inv_mass,
612 body_sim.inv_inertia,
613 body_sim.local_center,
614 )
615 };
616 let state = &mut set.body_states[local_index as usize];
617 state.linear_velocity = mul_add(state.linear_velocity, inv_mass, impulse);
618
619 let r = rotate_vector(xf.q, sub(closest_point, local_center));
621 state.angular_velocity += inv_inertia * cross(r, impulse);
622}
623
624pub fn world_rebuild_static_tree(world: &mut World) {
626 debug_assert!(!world.locked);
627 if world.locked {
628 return;
629 }
630
631 crate::recording::record_op(world, |rec, id| {
632 crate::recording::write_world_marker(
633 rec,
634 crate::recording::OP_WORLD_REBUILD_STATIC_TREE,
635 id,
636 )
637 });
638
639 world.broad_phase.trees[BodyType::Static as usize].rebuild(true);
640}
641
642pub fn world_enable_speculative(world: &mut World, flag: bool) {
644 crate::recording::record_op(world, |rec, id| {
645 crate::recording::write_world_bool(
646 rec,
647 crate::recording::OP_WORLD_ENABLE_SPECULATIVE,
648 id,
649 flag,
650 )
651 });
652 world.enable_speculative = flag;
653}