Skip to main content

box3d_rust/body/
forces.rs

1//! Body force/impulse and transform/awake public API from body.c.
2//!
3//! SPDX-FileCopyrightText: 2025 Erin Catto
4//! SPDX-License-Identifier: MIT
5
6use super::lifecycle::{get_body_full_id, get_body_sim_mut, wake_body, wake_body_with_lock};
7use crate::constants::speculative_distance;
8use crate::core::NULL_INDEX;
9use crate::id::BodyId;
10use crate::island::split_island;
11use crate::math_functions::{
12    aabb_contains, add, conjugate, cross, dot_quat, inv_rotate_vector, is_valid_position,
13    is_valid_quat, is_valid_vec3, is_valid_world_transform, length, length_squared,
14    make_matrix_from_quat, mul, mul_add, mul_mm, mul_mv, mul_quat, mul_sv, negate_quat, normalize,
15    rotate_vector, sub, sub_pos, transform_world_point, transpose, Aabb, Pos, Quat, Vec3,
16    WorldTransform,
17};
18use crate::shape::compute_fat_shape_aabb;
19use crate::solver_set::{try_sleep_island, AWAKE_SET, DISABLED_SET, FIRST_SLEEPING_SET};
20use crate::types::BodyType;
21use crate::world::World;
22
23/// (b3Body_ApplyForce)
24pub fn body_apply_force(world: &mut World, body_id: BodyId, force: Vec3, point: Pos, wake: bool) {
25    crate::recording::with_recording(world, |rec| {
26        rec.write_body_apply_force(body_id, force, point, wake);
27    });
28    debug_assert!(is_valid_vec3(force));
29
30    let body_index = get_body_full_id(world, body_id);
31
32    if wake && world.bodies[body_index as usize].set_index >= FIRST_SLEEPING_SET {
33        wake_body_with_lock(world, body_index);
34    }
35
36    if world.bodies[body_index as usize].set_index == AWAKE_SET {
37        let body_sim = get_body_sim_mut(world, body_index);
38        body_sim.force = add(body_sim.force, force);
39        body_sim.torque = add(
40            body_sim.torque,
41            cross(sub_pos(point, body_sim.center), force),
42        );
43    }
44}
45
46/// (b3Body_ApplyForceToCenter)
47pub fn body_apply_force_to_center(world: &mut World, body_id: BodyId, force: Vec3, wake: bool) {
48    crate::recording::with_recording(world, |rec| {
49        rec.write_body_apply_force_to_center(body_id, force, wake);
50    });
51    debug_assert!(is_valid_vec3(force));
52
53    let body_index = get_body_full_id(world, body_id);
54
55    if wake && world.bodies[body_index as usize].set_index >= FIRST_SLEEPING_SET {
56        wake_body_with_lock(world, body_index);
57    }
58
59    if world.bodies[body_index as usize].set_index == AWAKE_SET {
60        let body_sim = get_body_sim_mut(world, body_index);
61        body_sim.force = add(body_sim.force, force);
62    }
63}
64
65/// (b3Body_ApplyTorque)
66pub fn body_apply_torque(world: &mut World, body_id: BodyId, torque: Vec3, wake: bool) {
67    crate::recording::with_recording(world, |rec| {
68        rec.write_body_apply_torque(body_id, torque, wake);
69    });
70    debug_assert!(is_valid_vec3(torque));
71
72    let body_index = get_body_full_id(world, body_id);
73
74    if wake && world.bodies[body_index as usize].set_index >= FIRST_SLEEPING_SET {
75        wake_body_with_lock(world, body_index);
76    }
77
78    if world.bodies[body_index as usize].set_index == AWAKE_SET {
79        let body_sim = get_body_sim_mut(world, body_index);
80        body_sim.torque = add(body_sim.torque, torque);
81    }
82}
83
84/// (b3Body_ApplyLinearImpulse)
85pub fn body_apply_linear_impulse(
86    world: &mut World,
87    body_id: BodyId,
88    impulse: Vec3,
89    point: Pos,
90    wake: bool,
91) {
92    crate::recording::with_recording(world, |rec| {
93        rec.write_body_apply_linear_impulse(body_id, impulse, point, wake);
94    });
95    debug_assert!(is_valid_vec3(impulse));
96    debug_assert!(is_valid_position(point));
97
98    let body_index = get_body_full_id(world, body_id);
99
100    if wake && world.bodies[body_index as usize].set_index >= FIRST_SLEEPING_SET {
101        wake_body_with_lock(world, body_index);
102    }
103
104    if world.bodies[body_index as usize].set_index == AWAKE_SET {
105        let local_index = world.bodies[body_index as usize].local_index;
106        let max_linear_speed = world.max_linear_speed;
107        let set = &mut world.solver_sets[AWAKE_SET as usize];
108        let state = &mut set.body_states[local_index as usize];
109        let body_sim = &set.body_sims[local_index as usize];
110
111        state.linear_velocity = mul_add(state.linear_velocity, body_sim.inv_mass, impulse);
112
113        if length_squared(state.linear_velocity) > max_linear_speed * max_linear_speed {
114            state.linear_velocity = mul_sv(max_linear_speed, normalize(state.linear_velocity));
115        }
116
117        let delta = mul_mv(
118            body_sim.inv_inertia_world,
119            cross(sub_pos(point, body_sim.center), impulse),
120        );
121        state.angular_velocity = add(state.angular_velocity, delta);
122    }
123}
124
125/// (b3Body_ApplyLinearImpulseToCenter)
126pub fn body_apply_linear_impulse_to_center(
127    world: &mut World,
128    body_id: BodyId,
129    impulse: Vec3,
130    wake: bool,
131) {
132    crate::recording::with_recording(world, |rec| {
133        rec.write_body_apply_linear_impulse_to_center(body_id, impulse, wake);
134    });
135    debug_assert!(is_valid_vec3(impulse));
136
137    let body_index = get_body_full_id(world, body_id);
138
139    if wake && world.bodies[body_index as usize].set_index >= FIRST_SLEEPING_SET {
140        wake_body_with_lock(world, body_index);
141    }
142
143    if world.bodies[body_index as usize].set_index == AWAKE_SET {
144        let local_index = world.bodies[body_index as usize].local_index;
145        let max_linear_speed = world.max_linear_speed;
146        let set = &mut world.solver_sets[AWAKE_SET as usize];
147        let state = &mut set.body_states[local_index as usize];
148        let body_sim = &set.body_sims[local_index as usize];
149        state.linear_velocity = mul_add(state.linear_velocity, body_sim.inv_mass, impulse);
150
151        if length_squared(state.linear_velocity) > max_linear_speed * max_linear_speed {
152            state.linear_velocity = mul_sv(max_linear_speed, normalize(state.linear_velocity));
153        }
154    }
155}
156
157/// (b3Body_ApplyAngularImpulse)
158pub fn body_apply_angular_impulse(world: &mut World, body_id: BodyId, impulse: Vec3, wake: bool) {
159    crate::recording::with_recording(world, |rec| {
160        rec.write_body_apply_angular_impulse(body_id, impulse, wake);
161    });
162    debug_assert!(is_valid_vec3(impulse));
163    debug_assert!(super::lifecycle::body_is_valid(world, body_id));
164
165    let body_index = get_body_full_id(world, body_id);
166
167    if wake && world.bodies[body_index as usize].set_index >= FIRST_SLEEPING_SET {
168        // this will not invalidate body pointer
169        wake_body_with_lock(world, body_index);
170    }
171
172    if world.bodies[body_index as usize].set_index == AWAKE_SET {
173        let local_index = world.bodies[body_index as usize].local_index;
174        let set = &mut world.solver_sets[AWAKE_SET as usize];
175        let state = &mut set.body_states[local_index as usize];
176        let body_sim = &set.body_sims[local_index as usize];
177
178        let local_impulse = inv_rotate_vector(body_sim.transform.q, impulse);
179        let local_angular_velocity_delta = mul_mv(body_sim.inv_inertia_local, local_impulse);
180        state.angular_velocity = add(
181            state.angular_velocity,
182            rotate_vector(body_sim.transform.q, local_angular_velocity_delta),
183        );
184    }
185}
186
187/// (b3Body_SetTransform)
188pub fn body_set_transform(world: &mut World, body_id: BodyId, position: Pos, rotation: Quat) {
189    crate::recording::with_recording(world, |rec| {
190        rec.write_body_set_transform(body_id, position, rotation);
191    });
192    debug_assert!(is_valid_position(position));
193    debug_assert!(is_valid_quat(rotation));
194    debug_assert!(super::lifecycle::body_is_valid(world, body_id));
195    debug_assert!(!world.locked);
196
197    let body_index = get_body_full_id(world, body_id);
198
199    {
200        let body_sim = get_body_sim_mut(world, body_index);
201        body_sim.transform.p = position;
202        body_sim.transform.q = rotation;
203        body_sim.center = transform_world_point(body_sim.transform, body_sim.local_center);
204
205        let rotation_matrix = make_matrix_from_quat(body_sim.transform.q);
206        body_sim.inv_inertia_world = mul_mm(
207            mul_mm(rotation_matrix, body_sim.inv_inertia_local),
208            transpose(rotation_matrix),
209        );
210
211        body_sim.rotation0 = body_sim.transform.q;
212        body_sim.center0 = body_sim.center;
213    }
214
215    let transform = world.solver_sets[world.bodies[body_index as usize].set_index as usize]
216        .body_sims[world.bodies[body_index as usize].local_index as usize]
217        .transform;
218    let speculative = speculative_distance();
219
220    let mut shape_id = world.bodies[body_index as usize].head_shape_id;
221    while shape_id != NULL_INDEX {
222        let next_shape_id;
223        let aabb;
224        let needs_move;
225        let fat_aabb;
226        let proxy_key;
227        {
228            let shape = &world.shapes[shape_id as usize];
229            next_shape_id = shape.next_shape_id;
230            aabb = compute_fat_shape_aabb(shape, transform, speculative);
231            needs_move = !aabb_contains(shape.fat_aabb, aabb);
232            let margin = shape.aabb_margin;
233            fat_aabb = Aabb {
234                lower_bound: crate::math_functions::Vec3 {
235                    x: aabb.lower_bound.x - margin,
236                    y: aabb.lower_bound.y - margin,
237                    z: aabb.lower_bound.z - margin,
238                },
239                upper_bound: crate::math_functions::Vec3 {
240                    x: aabb.upper_bound.x + margin,
241                    y: aabb.upper_bound.y + margin,
242                    z: aabb.upper_bound.z + margin,
243                },
244            };
245            proxy_key = shape.proxy_key;
246        }
247
248        {
249            let shape = &mut world.shapes[shape_id as usize];
250            shape.aabb = aabb;
251            if needs_move {
252                shape.fat_aabb = fat_aabb;
253            }
254        }
255
256        // The body could be disabled
257        if needs_move && proxy_key != NULL_INDEX {
258            world.broad_phase.move_proxy(proxy_key, fat_aabb);
259        }
260
261        shape_id = next_shape_id;
262    }
263}
264
265/// (b3Body_SetAwake)
266pub fn body_set_awake(world: &mut World, body_id: BodyId, awake: bool) {
267    crate::recording::with_recording(world, |rec| {
268        rec.write_body_set_awake(body_id, awake);
269    });
270    debug_assert!(!world.locked);
271    if world.locked {
272        return;
273    }
274
275    world.locked = true;
276
277    let body_index = get_body_full_id(world, body_id);
278    let set_index = world.bodies[body_index as usize].set_index;
279    let island_id = world.bodies[body_index as usize].island_id;
280
281    if awake && set_index >= FIRST_SLEEPING_SET {
282        wake_body(world, body_index);
283    } else if !awake && set_index == AWAKE_SET {
284        if world.islands[island_id as usize].constraint_remove_count > 0 {
285            // Must split the island before sleeping. This is expensive.
286            split_island(world, island_id);
287        }
288
289        try_sleep_island(world, island_id);
290    }
291
292    world.locked = false;
293}
294
295/// Set a kinematic/dynamic body velocity so it reaches `target` in `time_step`.
296/// (b3Body_SetTargetTransform)
297pub fn body_set_target_transform(
298    world: &mut World,
299    body_id: BodyId,
300    target: WorldTransform,
301    time_step: f32,
302    wake: bool,
303) {
304    crate::recording::with_recording(world, |rec| {
305        rec.write_body_set_target_transform(body_id, target, time_step, wake);
306    });
307    debug_assert!(is_valid_world_transform(target));
308
309    let body_index = get_body_full_id(world, body_id);
310
311    {
312        let body = &world.bodies[body_index as usize];
313        if body.set_index == DISABLED_SET {
314            return;
315        }
316
317        if body.type_ == BodyType::Static || time_step <= 0.0 {
318            return;
319        }
320
321        if body.set_index != AWAKE_SET && !wake {
322            return;
323        }
324    }
325
326    let (sim_local_center, sim_center, sim_q, sim_max_extent) = {
327        let body = &world.bodies[body_index as usize];
328        let sim = &world.solver_sets[body.set_index as usize].body_sims[body.local_index as usize];
329        (
330            sim.local_center,
331            sim.center,
332            sim.transform.q,
333            sim.max_extent,
334        )
335    };
336
337    // Compute linear velocity. The center difference is taken in world precision then demoted.
338    let center2 = transform_world_point(target, sim_local_center);
339    let inv_time_step = 1.0 / time_step;
340    let linear_velocity = mul_sv(inv_time_step, sub_pos(center2, sim_center));
341
342    // Compute angular velocity:
343    // q' = 0.5 * w * q
344    // <~> ( q2 - q1 ) / dt =  0.5 * w * q1
345    // <=> w = 2 * ( q2 - q1 ) * Conjugate( q1 ) / dt
346    let q1 = sim_q;
347    let mut q2 = target.q;
348
349    // Use the shortest arc quaternion
350    if dot_quat(q1, q2) < 0.0 {
351        q2 = negate_quat(q2);
352    }
353
354    let dq = Quat {
355        v: sub(q2.v, q1.v),
356        s: q2.s - q1.s,
357    };
358    let omega = mul_quat(dq, conjugate(q1));
359    let angular_velocity = mul_sv(2.0 * inv_time_step, omega.v);
360
361    // Early out if the body is asleep already and the desired movement is small
362    if world.bodies[body_index as usize].set_index != AWAKE_SET {
363        let max_velocity = length(linear_velocity) + length(mul(angular_velocity, sim_max_extent));
364
365        // Return if velocity would be sleepy
366        if max_velocity < world.bodies[body_index as usize].sleep_threshold {
367            return;
368        }
369
370        // Must wake for state to exist
371        wake_body_with_lock(world, body_index);
372    }
373
374    debug_assert!(world.bodies[body_index as usize].set_index == AWAKE_SET);
375
376    let local_index = world.bodies[body_index as usize].local_index;
377    let state = &mut world.solver_sets[AWAKE_SET as usize].body_states[local_index as usize];
378    state.linear_velocity = linear_velocity;
379    state.angular_velocity = angular_velocity;
380}