1use std::cell::RefCell;
2
3use physics::advance_frame::frame_after;
4
5use crate::game::line::Line;
6use crate::game::vector::Vector2D;
7use crate::linestore::grid::Grid;
8use crate::rider::{Entity, EntityPoint};
9use crate::{physics, LineBuilder};
10use serde::{Deserialize, Serialize};
11
12#[derive(Serialize, Deserialize, Clone, Debug, Copy)]
13pub struct TrackMeta {
14 line_extension_ratio: f64,
15 gravity_well_height: f64,
16 remount: bool,
17 cell_size: f64,
18}
19
20impl Default for TrackMeta {
21 fn default() -> Self {
22 TrackMeta {
23 line_extension_ratio: 0.25,
24 cell_size: 14.0,
25 gravity_well_height: 10.0,
26 remount: false,
27 }
28 }
29}
30
31#[derive(Debug)]
33pub struct Track {
34 pub meta: TrackMeta,
35
36 grid: Grid,
37
38 precomputed_rider_positions: RefCell<Vec<Vec<Entity>>>,
39}
40
41impl Track {
42 pub fn new(starting_positions: Vec<Entity>, lines: Vec<Line>) -> Track {
43 let meta: TrackMeta = Default::default();
44 Track {
45 meta,
46 grid: Grid::new(lines, meta.cell_size),
47 precomputed_rider_positions: RefCell::new(vec![starting_positions]),
48 }
49 }
50 pub fn new_with_meta(
51 starting_positions: Vec<Entity>,
52 lines: Vec<Line>,
53 meta: TrackMeta,
54 ) -> Track {
55 Track {
56 meta,
57 grid: Grid::new(lines, meta.cell_size),
58 precomputed_rider_positions: RefCell::new(vec![starting_positions]),
59 }
60 }
61
62 pub fn line_builder(&self) -> LineBuilder {
63 Line::builder().extension_ratio(self.meta.line_extension_ratio)
64 }
65
66 pub fn all_lines(&self) -> &Vec<Line> {
68 self.grid.all_lines()
69 }
70
71 pub fn add_line(&mut self, line: Line) {
73 self.grid.add_line(line);
74 self.precomputed_rider_positions.borrow_mut().drain(1..);
75 }
76
77 pub fn remove_line(&mut self, line: &Line) {
79 self.grid.remove_line(line);
80 self.precomputed_rider_positions.borrow_mut().drain(1..);
81 }
82
83 pub fn lines_near(&self, point: Vector2D) -> Vec<&Line> {
85 self.grid.lines_near(point, 1)
86 }
87
88 pub fn lines_near_box(&self, p1: Vector2D, p2: Vector2D) -> Vec<&Line> {
90 self.grid.lines_near_box(p1, p2)
91 }
92
93 pub fn entity_positions_at(&self, frame: usize) -> Vec<Entity> {
95 let mut position_cache = self.precomputed_rider_positions.borrow_mut();
96 if let Some(riders) = position_cache.get(frame) {
97 riders.clone()
98 } else {
99 let len = position_cache.len();
100 for _ in len..=frame {
101 let next_positions = frame_after(position_cache.last().unwrap(), self);
102 position_cache.push(next_positions);
103 }
104
105 position_cache.last().unwrap().clone()
106 }
107 }
108
109 pub fn create_entity(&mut self, entity: Entity) {
111 let position_cache = self.precomputed_rider_positions.get_mut();
112 let initial_frame = position_cache.get_mut(0).unwrap();
113 initial_frame.push(entity);
114
115 position_cache.drain(1..);
116 }
117
118 pub fn remove_entity(&mut self, entity: Entity) -> Option<()> {
120 let position_cache = self.precomputed_rider_positions.get_mut();
121 let initial_frame = position_cache.get_mut(0).unwrap();
122 initial_frame.remove(initial_frame.iter().position(|e| *e == entity)?);
123
124 position_cache.drain(1..);
125 Some(())
126 }
127
128 pub fn snap_point(&self, max_dist: f64, to_snap: Vector2D) -> Vector2D {
131 let max_dist_sq = max_dist * max_dist;
132
133 self.lines_near(to_snap)
134 .iter()
135 .flat_map(|l| [l.ends.0.location, l.ends.1.location])
136 .map(|p| (p, p.distance_squared(to_snap)))
137 .filter(|(_, dist)| dist.total_cmp(&max_dist_sq).is_lt())
138 .min_by(|(_, d1), (_, d2)| d1.total_cmp(d2))
139 .unwrap_or((to_snap, 0.0))
140 .0
141 }
142
143 pub fn distance_below_line(&self, line: &Line, point: &EntityPoint) -> f64 {
151 let line_vec = line.as_vector2d();
152 let point_from_start = point.location - line.ends.0.location;
153 let perpendicular = line.perpendicular();
154
155 let is_moving_into_line = {
156 let dot = perpendicular.dot_product(point.momentum);
157 dot < 0.0
158 };
159 if !is_moving_into_line {
160 return 0.0;
161 }
162
163 let line_length = line_vec.length_squared().sqrt();
164 let line_normalized = line_vec / line_length;
165
166 let (ext_l, ext_r) = line.hitbox_extensions();
167 let parallel_component = point_from_start.dot_product(line_normalized);
168 if parallel_component < -ext_l || ext_r + line_length < parallel_component {
169 return 0.0;
170 }
171
172 let distance_below = (-perpendicular).dot_product(point_from_start);
173 if 0.0 < distance_below && distance_below < self.meta.gravity_well_height {
174 distance_below
175 } else {
176 0.0
177 }
178 }
179}
180
181impl Clone for Track {
182 fn clone(&self) -> Self {
183 Track {
184 meta: self.meta.clone(),
185 grid: self.grid.clone(),
186 precomputed_rider_positions: self.precomputed_rider_positions.clone(),
187 }
188 }
189}