1use bevy::{
2 ecs::{
3 entity::Entity,
4 query::{With, Without},
5 system::{Commands, Query, Res},
6 },
7 log::tracing,
8 math::Quat,
9 time::Time,
10 transform::components::Transform,
11};
12use bezier::Bezier;
13
14use crate::{
15 sim::{
16 Acceleration, Controls, GRAVITY, STEEL_SLIP, Velocity,
17 track::{
18 node::{ConnectionLocation, TrackNode},
19 pos::TrackPos,
20 segment::TrackSegment,
21 },
22 train::{BRAKING_COEFF, Bogie, Carriage, Train, commands::RemoveTrain},
23 },
24 utils::Dir,
25};
26
27pub fn update_trains_acceleration(
29 q_trains: Query<(&mut Acceleration, &Velocity, &Train, &Controls)>,
30) {
31 const ZERO_TOLERANCE: f32 = 1e-6;
32
33 for (mut acc, vel, train, controls) in q_trains {
34 let scaled_power = train.total_power * controls.throttle;
35 let mut power_force = if vel.0.abs() < ZERO_TOLERANCE {
36 if controls.throttle == 0.0 {
37 0.0
38 } else {
39 f32::MAX
40 }
41 } else {
42 scaled_power / vel.0
43 };
44 power_force = power_force.clamp_magnitude(GRAVITY * train.total_mass * STEEL_SLIP);
45
46 acc.0 = (power_force) / train.total_mass;
47 }
48}
49
50pub fn apply_brakes(q_trains: Query<(&mut Velocity, &Controls)>) {
53 for (mut vel, controls) in q_trains {
54 vel.0 += (-vel.0.signum() * controls.brake * BRAKING_COEFF).clamp_magnitude(vel.0);
55 }
56}
57
58fn move_track_pos_by_meters(i: f32, m: f32, track_pos: &mut TrackPos, segment: &TrackSegment) {
59 let delta = segment.shape.displace_by(i, m);
60 track_pos.idx = match track_pos.dir.as_ref().unwrap() {
61 Dir::Positive => delta,
62 Dir::Negative => -delta,
63 };
64}
65
66fn handle_bogie(
68 q_bogies: &mut Query<&mut TrackPos>,
69 q_segments: Query<&TrackSegment>,
70 meters: f32,
71 bogie: Entity,
72) -> bool {
73 if let Ok(mut bogie) = q_bogies.get_mut(bogie)
74 && let Ok(segment) = q_segments.get(bogie.segment)
75 {
76 move_track_pos_by_meters(bogie.idx, meters, &mut bogie, segment);
77 true
78 } else {
79 false
80 }
81}
82
83pub fn move_trains_with_velocity(
85 mut cmd: Commands,
86 q_trains: Query<(Entity, &Train, &Velocity)>,
87 q_carriages: Query<&Carriage>,
88 mut q_bogies: Query<&mut TrackPos>,
89 q_segments: Query<&TrackSegment>,
90 time: Res<Time>,
91) {
92 for (train_entity, train, vel) in q_trains {
93 let meters = vel.0 * time.delta_secs();
94
95 let mut derailed = false;
96 for carriage_entity in train.carriages.iter() {
97 let carriage = q_carriages.get(*carriage_entity).unwrap();
98
99 match &carriage.bogies {
103 super::CarriageBogies::Single(bogie) => {
104 if !handle_bogie(&mut q_bogies, q_segments, meters, *bogie) {
105 derailed = true;
106 break;
107 }
108 }
109 super::CarriageBogies::Double { front, back } => {
110 if !handle_bogie(&mut q_bogies, q_segments, meters, *front) {
111 derailed = true;
112 break;
113 }
114
115 if !handle_bogie(&mut q_bogies, q_segments, meters, *back) {
116 derailed = true;
117 break;
118 }
119 }
120 }
121 }
122
123 if derailed {
124 cmd.queue(RemoveTrain::new(train_entity));
125 }
126 }
127}
128
129pub fn move_on_rail_to_diff_segment(
131 mut cmd: Commands,
132 q_onrail: Query<(Entity, &mut TrackPos)>,
133 q_segments: Query<&TrackSegment>,
134 q_nodes: Query<&TrackNode>,
135) {
136 for (on_rail_entity, mut on_rail_pos) in q_onrail {
137 if (0.0..=1.0).contains(&on_rail_pos.idx) {
139 continue;
140 }
141
142 tracing::debug!("moving {}", on_rail_entity);
143
144 let Ok(segment) = q_segments.get(on_rail_pos.segment) else {
145 continue;
146 };
147
148 let final_node = q_nodes
149 .get(match on_rail_pos.dir {
150 Some(Dir::Positive) => segment.to_node,
151 Some(Dir::Negative) => segment.from_node,
152 None => panic!("On rail entities must always have direction set!"),
153 })
154 .unwrap();
155
156 let Some(other_connection) = final_node.connections.get_active_for(on_rail_pos.segment)
157 else {
158 tracing::info!("Current segment is not a part of an active connection, derailing...");
160 cmd.entity(on_rail_entity).remove::<TrackPos>();
161 continue;
162 };
163
164 tracing::debug!(" to active conn: {:?}", other_connection);
165
166 let starting_idx = other_connection.location.starting_index();
168 let starting_dir = other_connection.location.starting_dir();
169
170 let add_offset = if on_rail_pos.idx > 1.0 {
171 on_rail_pos.idx - 1.0
172 } else {
173 -on_rail_pos.idx
174 };
175
176 let add_meters = segment.shape.len() * add_offset;
178 let add_ratio = q_segments
179 .get(other_connection.segment)
180 .unwrap()
181 .shape
182 .displace_by(starting_idx, add_meters);
183
184 tracing::debug!("additional meters: {}", add_meters);
185 tracing::debug!("additional ratio: {}", add_ratio);
186
187 on_rail_pos.segment = other_connection.segment;
188 on_rail_pos.idx = starting_idx
189 + match other_connection.location {
190 ConnectionLocation::Start => add_ratio,
191 ConnectionLocation::End => -add_ratio,
192 };
193 on_rail_pos.dir = Some(starting_dir);
194 }
195}
196
197pub fn move_carriages_to_bogies(
199 q_carriages: Query<(&Carriage, &mut Transform), Without<Bogie>>,
200 q_bogies: Query<&Transform, With<Bogie>>,
201) {
202 for (carriage, mut transform) in q_carriages {
203 match &carriage.bogies {
204 super::CarriageBogies::Single(bogie) => {
205 *transform = *q_bogies.get(*bogie).unwrap();
206 }
207 super::CarriageBogies::Double { front, back } => {
208 let front_trans = *q_bogies.get(*front).unwrap();
209 let back_trans = *q_bogies.get(*back).unwrap();
210
211 transform.translation = (front_trans.translation + back_trans.translation) / 2.0;
212 let dir = front_trans.translation.truncate() - back_trans.translation.truncate();
213 transform.rotation = Quat::from_rotation_z(f32::atan2(dir.y, dir.x));
214 }
215 }
216 }
217}