supreme_replay_analyzer/
plane_intersection.rs1use crate::{
4 RowData,
5 models::{Level, LevelDataEntry, Position, Quaternion},
6};
7
8#[derive(Debug, Clone)]
9pub struct PlaneIntersectionInfo {
10 pub distance: f32,
11 pub frame1: usize,
12 pub frame2: usize,
13 pub exact_time_with_start_lag_ms: f32, pub object_name: String,
15}
16
17#[derive(Debug, Clone)]
18pub struct LevelPlaneIntersection {
19 pub name: String,
20 pub intersections: Vec<PlaneIntersectionInfo>,
21}
22
23#[must_use]
24pub fn get_plane_collisions(rows: &[RowData]) -> Vec<LevelPlaneIntersection> {
25 get_plane_intersections_with_level_data(rows, &[])
26}
27
28#[must_use]
29pub fn get_plane_intersections_with_level_data(
30 rows: &[RowData],
31 levels: &[Level],
32) -> Vec<LevelPlaneIntersection> {
33 let row_positions: Vec<Position> = rows
34 .iter()
35 .map(|r| Position {
36 x: r.x,
37 y: r.y,
38 z: r.z,
39 })
40 .collect();
41
42 let mut result = Vec::new();
43
44 for level in levels {
46 let plane_entries: Vec<&LevelDataEntry> = level
47 .entries
48 .iter()
49 .filter(|e| {
50 [
51 "Start_Point",
52 "Check_Point_1",
53 "Check_Point_2",
54 "Finish_Point",
55 ]
56 .contains(&e.name.as_str())
57 })
58 .collect();
59
60 let mut level_plane_intersection = LevelPlaneIntersection {
61 name: level.name.clone(),
62 intersections: Vec::new(),
63 };
64
65 for i in 0..row_positions.len() - 1 {
66 let p1 = &row_positions[i];
67 let p2 = &row_positions[i + 1];
68
69 for plane_entry in &plane_entries {
70 let plane_normal = get_plane_normal(&plane_entry.quat);
71
72 if let Some((intersection, t)) = does_line_segment_collide_with_plane_with_t(
74 &plane_entry.position,
75 &plane_normal,
76 p1,
77 p2,
78 ) {
79 let distance = calculate_distance(&intersection, &plane_entry.position);
80 #[allow(clippy::cast_precision_loss)]
83 let exact_time_ms = (i as f32 * 10.0) + (t * 10.0);
84
85 level_plane_intersection
86 .intersections
87 .push(PlaneIntersectionInfo {
88 distance,
89 frame1: i,
90 frame2: i + 1,
91 exact_time_with_start_lag_ms: exact_time_ms,
92 object_name: plane_entry.name.clone(),
93 });
94 }
95 }
96 }
97
98 result.push(level_plane_intersection);
99 }
100
101 result
102}
103
104fn get_plane_normal(quat: &Quaternion) -> Position {
105 Position {
106 x: 2.0 * (quat.x * quat.z + quat.w * quat.y),
107 y: 2.0 * (quat.y * quat.z - quat.w * quat.x),
108 z: 1.0 - 2.0 * (quat.x * quat.x + quat.y * quat.y),
109 }
110}
111
112fn does_line_segment_collide_with_plane_with_t(
113 plane_position: &Position,
114 plane_normal: &Position,
115 p1: &Position,
116 p2: &Position,
117) -> Option<(Position, f32)> {
118 let normal_length = (plane_normal.x * plane_normal.x
119 + plane_normal.y * plane_normal.y
120 + plane_normal.z * plane_normal.z)
121 .sqrt();
122
123 if normal_length < 1e-6 {
124 return None;
125 }
126
127 let normalized_normal = Position {
128 x: plane_normal.x / normal_length,
129 y: plane_normal.y / normal_length,
130 z: plane_normal.z / normal_length,
131 };
132
133 let direction = Position {
134 x: p2.x - p1.x,
135 y: p2.y - p1.y,
136 z: p2.z - p1.z,
137 };
138
139 let dot_normal_direction = normalized_normal.x * direction.x
140 + normalized_normal.y * direction.y
141 + normalized_normal.z * direction.z;
142
143 if dot_normal_direction.abs() < 1e-6 {
144 return None;
145 }
146
147 let to_start = Position {
148 x: p1.x - plane_position.x,
149 y: p1.y - plane_position.y,
150 z: p1.z - plane_position.z,
151 };
152
153 let dot_normal_to_start = normalized_normal.x * to_start.x
154 + normalized_normal.y * to_start.y
155 + normalized_normal.z * to_start.z;
156
157 let t = -dot_normal_to_start / dot_normal_direction;
158
159 if !(0.0..=1.0).contains(&t) {
160 return None;
161 }
162
163 Some((
164 Position {
165 x: p1.x + t * direction.x,
166 y: p1.y + t * direction.y,
167 z: p1.z + t * direction.z,
168 },
169 t,
170 ))
171}
172
173fn calculate_distance(p1: &Position, p2: &Position) -> f32 {
174 let dx = p2.x - p1.x;
175 let dy = p2.y - p1.y;
176 let dz = p2.z - p1.z;
177 (dx * dx + dy * dy + dz * dz).sqrt()
178}