bosh_rs/game/
track.rs

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/// A track in linerider.
32#[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    /// Gets all lines in the track.
67    pub fn all_lines(&self) -> &Vec<Line> {
68        self.grid.all_lines()
69    }
70
71    /// Adds a line to the track.
72    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    /// Removes a single line from the track.
78    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    /// Gets all of the lines near a point.
84    pub fn lines_near(&self, point: Vector2D) -> Vec<&Line> {
85        self.grid.lines_near(point, 1)
86    }
87
88    /// Gets all of the lines in a rectangle.
89    pub fn lines_near_box(&self, p1: Vector2D, p2: Vector2D) -> Vec<&Line> {
90        self.grid.lines_near_box(p1, p2)
91    }
92
93    /// Gets the rider positions for a zero-indexed frame.
94    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    /// Adds a new rider to the track.
110    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    /// Removes a rider from the track.
119    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    /// Snaps a point to the nearest line ending, or returns `to_snap` if
129    /// there are no nearby points.
130    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    /// Returns the distance below the line, or 0 if applicable. "below" is the direction
144    /// 90 degrees to the right of the vector created from `self.points.0` to `self.points.1`.
145    ///
146    /// Returns 0 when:
147    ///  * the point is above the line
148    ///  * the point is moving "upward"
149    ///  * the point is outside of the line, including extensions
150    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}