Skip to main content

traaains/editor/track/placing/states/
final_step.rs

1use bevy::{
2    asset::Assets,
3    ecs::{
4        query::{With, Without},
5        system::{Commands, Query, Res, ResMut, Single},
6    },
7    math::Vec2,
8    mesh::{Mesh, Mesh2d},
9    sprite_render::{ColorMaterial, MeshMaterial2d},
10    state::state::{ComputedStates, NextState},
11    transform::components::Transform,
12};
13use bezier::Linear;
14
15use crate::{
16    AssetType, AssetsManager,
17    editor::track::placing::{
18        LastConnectedBlock, NodeType, Placing, VirtualSegment,
19        states::{current_shape::CurrentShape, current_step::CurrentStep},
20    },
21    game::cursor::CursorPos,
22    shape::{BezierMeshProps, Shapes},
23    sim::{
24        logical_graph::LogicalBlock,
25        track::{
26            node::TrackNode,
27            segment::{TrackSegment, commands::SpawnSegment, signals_manager::SignalsManager},
28        },
29    },
30    utils::Meshable,
31};
32
33#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
34pub struct FinalStep;
35
36impl ComputedStates for FinalStep {
37    type SourceStates = (CurrentShape, CurrentStep);
38
39    fn compute(sources: Self::SourceStates) -> Option<Self> {
40        match sources {
41            (CurrentShape::Linear, CurrentStep::Linear) => Some(FinalStep),
42            (CurrentShape::Quadratic, CurrentStep::Quadratic) => Some(FinalStep),
43            (CurrentShape::Cubic, CurrentStep::Cubic) => Some(FinalStep),
44            _ => None,
45        }
46    }
47}
48
49pub fn on_enter_final_step(
50    mut v_segment: Single<&mut MeshMaterial2d<ColorMaterial>, With<VirtualSegment>>,
51    assets_manager: Res<AssetsManager>,
52) {
53    v_segment.0 = assets_manager
54        .materials
55        .get(&AssetType::TrackSegmentNormal)
56        .cloned()
57        .unwrap();
58}
59
60pub fn on_exit_final_step(
61    mut v_segment: Single<&mut MeshMaterial2d<ColorMaterial>, With<VirtualSegment>>,
62    assets_manager: Res<AssetsManager>,
63) {
64    v_segment.0 = assets_manager
65        .materials
66        .get(&AssetType::TrackSegmentGhost)
67        .cloned()
68        .unwrap();
69}
70
71/// System triggered by clicking left mouse button when in FinalStep state. Relies on current cursor position
72/// instead of virtual node position, which removes the sync issues. Calculates and spawns any needed nodes
73/// and then uses the [SpawnSegment] command, which also takes care of connecting the nodes and logical blocks.
74pub fn place_segment(
75    mut cmd: Commands,
76    mut v_segment: Single<(&mut VirtualSegment, &mut Transform, &mut Mesh2d)>,
77    q_nodes: Query<(&mut TrackNode, &Transform), Without<VirtualSegment>>,
78    q_segments: Query<&TrackSegment>,
79    cursor_pos: Res<CursorPos>,
80    mut last_connected_block: ResMut<LastConnectedBlock>,
81    mut next_step: ResMut<NextState<CurrentStep>>,
82    mut meshes: ResMut<Assets<Mesh>>,
83    assets_manager: Res<AssetsManager>,
84) {
85    // check node's number of connections
86    let (from_node, _) = q_nodes.get(v_segment.0.from).unwrap();
87    if !from_node.connections.has_free() {
88        return;
89    }
90
91    // compute to node entity based on its type
92    let (node_to, final_world_pos, node_to_lb) = match v_segment.0.to {
93        NodeType::Physical(entity) => {
94            let (to_node, to_node_transform) = q_nodes.get(entity).unwrap();
95
96            // check node's number of connections
97            if !to_node.connections.has_free() {
98                return;
99            }
100
101            // get random block from node
102            let lb = to_node
103                .connections
104                .iter()
105                .next()
106                .map(|c| q_segments.get(c.segment).map(|s| s.block).unwrap());
107
108            (entity, to_node_transform.translation.truncate(), lb)
109        }
110        NodeType::Virtual => {
111            let new_node = TrackNode::spawn(
112                &mut cmd,
113                Transform::from_translation(cursor_pos.0.extend(5.0)),
114                assets_manager
115                    .meshes
116                    .get(&AssetType::TrackNodeNormal)
117                    .cloned()
118                    .unwrap(),
119                assets_manager
120                    .materials
121                    .get(&AssetType::TrackNodeNormal)
122                    .cloned()
123                    .unwrap(),
124            );
125
126            (new_node, cursor_pos.0, None)
127        }
128    };
129
130    // remove placing tag from unfinished node
131    cmd.entity(v_segment.0.from).remove::<Placing>();
132
133    // compute logical blocks, spawn some if needed, and
134    let new_logical_block = last_connected_block.0.unwrap_or_else(|| {
135        let new_block = from_node
136            .connections
137            .iter()
138            .next()
139            .map(|c| q_segments.get(c.segment).map(|s| s.block).unwrap())
140            .unwrap_or_else(|| {
141                node_to_lb.unwrap_or_else(|| cmd.spawn(LogicalBlock::default()).id())
142            });
143
144        last_connected_block.0 = Some(new_block);
145        new_block
146    });
147
148    // === place the segment ===
149    // update the virtual segment according to latest cursor pos
150    let final_segment_pos = final_world_pos - v_segment.1.translation.truncate();
151    v_segment.0.shape.set_last_point(final_segment_pos);
152    let cloned_shape = v_segment.0.shape.clone();
153
154    cmd.queue(SpawnSegment::new(
155        TrackSegment::new(cloned_shape, v_segment.0.from, node_to, new_logical_block),
156        SignalsManager::default(),
157        *v_segment.1,
158    ));
159
160    // reset virtual segment
161    let reset_shape = Linear::new([Vec2::ZERO, Vec2::from((1.0, 1.0))]);
162    v_segment.2.0 = meshes.add(reset_shape.mesh(BezierMeshProps::default()));
163    v_segment.0.shape = Shapes::Linear(reset_shape);
164    v_segment.1.translation = final_world_pos.extend(0.0);
165    v_segment.0.from = node_to;
166    v_segment.0.to = NodeType::Virtual;
167
168    // reset step
169    next_step.set(CurrentStep::Linear);
170}