Skip to main content

traaains/sim/train/
systems.rs

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
27/// Updates trains acceleration based on its current velocity, power and mass
28pub 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
50/// System to apply brakes to trains. Little bit of a hack, since brakes would in reality slow down the wheels which would in turn
51/// deliver less force/acceleration to the train.
52pub 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
66/// Returns true if the bogie was successfully handled (not derailed)
67fn 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
83/// System that moves carriage's bogies with respect to the carriage velocity
84pub 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            // if any of these calls to q_bogies fails, then they derailed and we want to:
100            // - skip the rest of the bogies
101            // - despawn this train
102            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
129/// System that moves bogies to different track segments if they exceed the segment's domain
130pub 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 guard for only trains that have left their respective segments
138        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            // curr segment isnt active in the node's conn, derail
159            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        // account for the fact that different curves have different lengths and thus ratios will have different meaning
167        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        // we will cheat here and say that this is good enough for the length calculcation since the add_offset should be really small anyway
177        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
197/// System that moves carriages to their bogies, aligning them properly
198pub 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}