Skip to main content

subtr_actor/
geometry.rs

1use crate::{SubtrActorError, SubtrActorErrorVariant, SubtrActorResult};
2
3pub fn vec_to_glam(v: &boxcars::Vector3f) -> glam::f32::Vec3 {
4    glam::f32::Vec3::new(v.x, v.y, v.z)
5}
6
7pub fn glam_to_vec(v: &glam::f32::Vec3) -> boxcars::Vector3f {
8    boxcars::Vector3f {
9        x: v.x,
10        y: v.y,
11        z: v.z,
12    }
13}
14
15pub fn quat_to_glam(q: &boxcars::Quaternion) -> glam::Quat {
16    glam::Quat::from_xyzw(q.x, q.y, q.z, q.w)
17}
18
19pub fn glam_to_quat(rotation: &glam::Quat) -> boxcars::Quaternion {
20    boxcars::Quaternion {
21        x: rotation.x,
22        y: rotation.y,
23        z: rotation.z,
24        w: rotation.w,
25    }
26}
27
28pub fn apply_velocities_to_rigid_body(
29    rigid_body: &boxcars::RigidBody,
30    time_delta: f32,
31) -> boxcars::RigidBody {
32    let mut interpolated = *rigid_body;
33    if time_delta == 0.0 {
34        return interpolated;
35    }
36    let linear_velocity = interpolated.linear_velocity.unwrap_or(boxcars::Vector3f {
37        x: 0.0,
38        y: 0.0,
39        z: 0.0,
40    });
41    let location = vec_to_glam(&rigid_body.location) + (time_delta * vec_to_glam(&linear_velocity));
42    interpolated.location = glam_to_vec(&location);
43    interpolated.rotation = apply_angular_velocity(rigid_body, time_delta);
44    interpolated
45}
46
47/// Ranks how plausible it is that `player_body` was the car that touched the
48/// ball near the current frame, using constant-velocity closest approach.
49///
50/// The frame's ball state can already be slightly post-contact, so we do not
51/// just compare current distance. Instead we look for the minimum ball/car
52/// separation over a short window centered slightly before the frame time.
53pub(crate) fn touch_candidate_rank(
54    ball_body: &boxcars::RigidBody,
55    player_body: &boxcars::RigidBody,
56) -> Option<(f32, f32)> {
57    const TOUCH_LOOKBACK_SECONDS: f32 = 0.12;
58    const TOUCH_LOOKAHEAD_SECONDS: f32 = 0.03;
59
60    let relative_position = vec_to_glam(&player_body.location) - vec_to_glam(&ball_body.location);
61    let current_distance = relative_position.length();
62    if !current_distance.is_finite() {
63        return None;
64    }
65
66    let relative_velocity =
67        vec_to_glam(&player_body.linear_velocity.unwrap_or(boxcars::Vector3f {
68            x: 0.0,
69            y: 0.0,
70            z: 0.0,
71        })) - vec_to_glam(&ball_body.linear_velocity.unwrap_or(boxcars::Vector3f {
72            x: 0.0,
73            y: 0.0,
74            z: 0.0,
75        }));
76    let relative_speed_squared = relative_velocity.length_squared();
77    let closest_time = if relative_speed_squared > f32::EPSILON {
78        (-relative_position.dot(relative_velocity) / relative_speed_squared)
79            .clamp(-TOUCH_LOOKBACK_SECONDS, TOUCH_LOOKAHEAD_SECONDS)
80    } else {
81        0.0
82    };
83    let closest_distance = (relative_position + relative_velocity * closest_time).length();
84    if !closest_distance.is_finite() {
85        return None;
86    }
87
88    Some((closest_distance, current_distance))
89}
90
91fn apply_angular_velocity(rigid_body: &boxcars::RigidBody, time_delta: f32) -> boxcars::Quaternion {
92    // XXX: This approach seems to give some unexpected results. There may be a
93    // unit mismatch or some other type of issue.
94    let rbav = rigid_body.angular_velocity.unwrap_or(boxcars::Vector3f {
95        x: 0.0,
96        y: 0.0,
97        z: 0.0,
98    });
99    let angular_velocity = glam::Vec3::new(rbav.x, rbav.y, rbav.z);
100    let magnitude = angular_velocity.length();
101    let angular_velocity_unit_vector = angular_velocity.normalize_or_zero();
102
103    let mut rotation = glam::Quat::from_xyzw(
104        rigid_body.rotation.x,
105        rigid_body.rotation.y,
106        rigid_body.rotation.z,
107        rigid_body.rotation.w,
108    );
109
110    if angular_velocity_unit_vector.length() != 0.0 {
111        let delta_rotation =
112            glam::Quat::from_axis_angle(angular_velocity_unit_vector, magnitude * time_delta);
113        rotation *= delta_rotation;
114    }
115
116    boxcars::Quaternion {
117        x: rotation.x,
118        y: rotation.y,
119        z: rotation.z,
120        w: rotation.w,
121    }
122}
123
124/// Interpolates between two [`boxcars::RigidBody`] states based on the provided time.
125///
126/// # Arguments
127///
128/// * `start_body` - The initial `RigidBody` state.
129/// * `start_time` - The timestamp of the initial `RigidBody` state.
130/// * `end_body` - The final `RigidBody` state.
131/// * `end_time` - The timestamp of the final `RigidBody` state.
132/// * `time` - The desired timestamp to interpolate to.
133///
134/// # Returns
135///
136/// A new [`boxcars::RigidBody`] that represents the interpolated state at the specified time.
137pub fn get_interpolated_rigid_body(
138    start_body: &boxcars::RigidBody,
139    start_time: f32,
140    end_body: &boxcars::RigidBody,
141    end_time: f32,
142    time: f32,
143) -> SubtrActorResult<boxcars::RigidBody> {
144    if !(start_time <= time && time <= end_time) {
145        return SubtrActorError::new_result(SubtrActorErrorVariant::InterpolationTimeOrderError {
146            start_time,
147            time,
148            end_time,
149        });
150    }
151
152    let duration = end_time - start_time;
153    let interpolation_amount = (time - start_time) / duration;
154    let start_position = vec_to_glam(&start_body.location);
155    let end_position = vec_to_glam(&end_body.location);
156    let interpolated_location = start_position.lerp(end_position, interpolation_amount);
157    let start_rotation = quat_to_glam(&start_body.rotation);
158    let end_rotation = quat_to_glam(&end_body.rotation);
159    let interpolated_rotation = start_rotation.slerp(end_rotation, interpolation_amount);
160
161    Ok(boxcars::RigidBody {
162        location: glam_to_vec(&interpolated_location),
163        rotation: glam_to_quat(&interpolated_rotation),
164        sleeping: start_body.sleeping,
165        linear_velocity: start_body.linear_velocity,
166        angular_velocity: start_body.angular_velocity,
167    })
168}