Skip to main content

leptos_verlet/plugins/modification/
plugin.rs

1use bevy::prelude::*;
2
3use crate::{
4    core::parameters::{SimulationSettings, Stick},
5    objects::{cloth::spawn_cloth, cube::spawn_cube, rope::spawn_rope, square::spawn_square},
6    plugins::{
7        info::plugin::ActiveInfoTarget,
8        modification::utils::{
9            cut_sticks, grab_point, lock_affected_points, perge_info_target, point_info,
10            purge_line, ray_coords_at, spawn_stick,
11        },
12        play_state::plugin::SimulationPlayState,
13        schedule::plugin::SimulationCycle,
14    },
15    prelude::{MaterialType, MeshType, Point},
16};
17
18pub struct ModificationPlugin;
19impl Plugin for ModificationPlugin {
20    fn build(&self, app: &mut App) {
21        app.init_state::<ModificationTarget>().add_systems(
22            Update,
23            (handle_target_change, handle_modification_event)
24                .chain()
25                .in_set(SimulationCycle::Preparation1),
26        );
27    }
28}
29
30#[derive(Event, Clone, PartialEq)]
31pub enum ModifyEventType {
32    Left(RelativeWindowPosition),
33    Right(RelativeWindowPosition),
34    Middle(RelativeWindowPosition),
35    Move(RelativeWindowPosition),
36    Release(RelativeWindowPosition),
37}
38#[derive(Clone, PartialEq)]
39pub struct RelativeWindowPosition {
40    pub event_x: f32,
41    pub event_y: f32,
42    pub container_h: f32,
43    pub container_w: f32,
44}
45impl RelativeWindowPosition {
46    /// Takes in some incoming event location (in pixels) and outputs the ray
47    /// that coincides with that point in Bevy unit space.
48    pub fn create_ray(&self, camera: &Camera, camera_transform: &GlobalTransform) -> Ray3d {
49        if let Ok(cast_ray) =
50            camera.viewport_to_world(camera_transform, Vec2::new(self.event_x, self.event_y))
51        {
52            cast_ray
53        } else {
54            Ray3d::new(Vec3::new(0., 0., 0.), Dir3::NEG_Z)
55        }
56    }
57}
58
59#[derive(Event, Clone, PartialEq, States, Debug, Hash, Eq, Default)]
60pub enum ModificationTarget {
61    /// Left click to drop a point.
62    Point,
63    /// Click on two points two create a stick between them.
64    Line,
65    /// Lock/fix a point in place.
66    Lock,
67    /// Cut is tracking the "desire" to eventually cut.
68    Cut,
69    /// Cutting is tracking to "activity" of cutting.
70    /// Cutting requires the left-click be held down and dragged over a stick.
71    Cutting,
72    /// Spawn a rope at a selected location.
73    SpawnRope,
74    /// Spawn a square at a selected location.
75    SpawnSquare,
76    /// Spawn a clock filling available sceen space.
77    SpawnCloth,
78    /// Spawn a cube at a selected location.
79    SpawnCube,
80    /// Right click on a point to delete it.
81    Delete,
82    PointInfo,
83    Grab,
84    #[default]
85    None,
86}
87
88#[derive(Component)]
89pub struct LineConnections {
90    pub p0: Option<Entity>,
91    pub p1: Option<Entity>,
92}
93
94pub fn handle_target_change(
95    mut next_state: ResMut<NextState<SimulationPlayState>>,
96    mut modification_target: ResMut<NextState<ModificationTarget>>,
97    mut event_reader: EventReader<ModificationTarget>,
98    mut commands: Commands,
99    line_query: Query<(Entity, &mut LineConnections)>,
100) {
101    for event in event_reader.read() {
102        // Pause the current simulation to open the gate for the editor logic
103        // Only pause if an actual selection was made
104        if event != &ModificationTarget::None {
105            next_state.set(SimulationPlayState::Paused);
106        }
107        if event == &ModificationTarget::Line {
108            purge_line(&line_query, &mut commands);
109        }
110        modification_target.set(event.clone())
111    }
112}
113
114pub fn handle_modification_event(
115    mut commands: Commands,
116    mut event_reader: EventReader<ModifyEventType>,
117    current_target: Res<State<ModificationTarget>>,
118    mut meshes: ResMut<Assets<Mesh>>,
119    mut materials: ResMut<Assets<StandardMaterial>>,
120    mut stick_query: Query<(Entity, &mut Stick)>,
121    mut params: ParamSet<(
122        Query<&Point>,
123        Query<(&mut MeshMaterial3d<StandardMaterial>, &mut Point)>,
124        Query<(Entity, &Point)>,
125        Query<(Entity, &Point), With<ActiveInfoTarget>>,
126        Query<(Entity, &mut Point)>,
127    )>,
128    mut next_target: ResMut<NextState<ModificationTarget>>,
129    mut line_query: Query<(Entity, &mut LineConnections)>,
130    sim_settings: Res<SimulationSettings>,
131    camera: Query<(&Camera, &GlobalTransform), With<Camera3d>>,
132) {
133    let point_material = MaterialType::Color([1., 1., 1., 1.]);
134    let stick_material = MaterialType::Color([1., 1., 1., 0.5]);
135
136    for event in event_reader.read() {
137        match event {
138            ModifyEventType::Left(relative_pos) => {
139                let (camera, camera_transform) = match camera.single() {
140                    Ok(queried_entity) => queried_entity,
141                    Err(_) => continue,
142                };
143
144                let ray = relative_pos.create_ray(camera, camera_transform);
145
146                let view_plane_world_pos = match ray_coords_at(ray, 0.) {
147                    Some(coordinates) => coordinates,
148                    None => continue,
149                };
150
151                match current_target.get() {
152                    ModificationTarget::Point => {
153                        let point = Point::new(view_plane_world_pos, view_plane_world_pos, false);
154                        point.spawn(
155                            &mut commands,
156                            &mut meshes,
157                            &mut materials,
158                            MeshType::Sphere,
159                            MaterialType::Color([1., 1., 1., 1.]),
160                        );
161                    }
162                    ModificationTarget::Line => spawn_stick(
163                        ray,
164                        &mut line_query,
165                        &params.p2(),
166                        &mut commands,
167                        &mut meshes,
168                        &mut materials,
169                        stick_material.clone(),
170                        sim_settings.interaction_radius,
171                    ),
172                    ModificationTarget::Lock => {
173                        lock_affected_points(
174                            ray,
175                            &mut params.p1(),
176                            &mut materials,
177                            sim_settings.interaction_radius,
178                        );
179                    }
180                    ModificationTarget::Cut => next_target.set(ModificationTarget::Cutting),
181                    ModificationTarget::SpawnSquare => {
182                        spawn_square(
183                            &mut commands,
184                            &mut meshes,
185                            &mut materials,
186                            point_material.clone(),
187                            stick_material.clone(),
188                            view_plane_world_pos,
189                            &sim_settings,
190                        );
191                    }
192                    ModificationTarget::SpawnRope => {
193                        spawn_rope(
194                            &mut commands,
195                            &mut meshes,
196                            &mut materials,
197                            point_material.clone(),
198                            stick_material.clone(),
199                            &sim_settings,
200                            view_plane_world_pos,
201                        );
202                    }
203                    ModificationTarget::SpawnCloth => spawn_cloth(
204                        &mut commands,
205                        &mut meshes,
206                        point_material.clone(),
207                        stick_material.clone(),
208                        &mut materials,
209                        &sim_settings,
210                    ),
211                    ModificationTarget::SpawnCube => spawn_cube(
212                        &mut commands,
213                        &mut meshes,
214                        &mut materials,
215                        point_material.clone(),
216                        stick_material.clone(),
217                        &view_plane_world_pos,
218                        &sim_settings,
219                    ),
220                    ModificationTarget::PointInfo => {
221                        // perge existing info targets
222                        perge_info_target(&params.p3(), &mut commands);
223                        point_info(
224                            ray,
225                            &params.p2(),
226                            &mut commands,
227                            sim_settings.interaction_radius,
228                        )
229                    }
230                    ModificationTarget::None => {
231                        // If there's no target and you left click - default to grabbing the point
232                        next_target.set(ModificationTarget::Grab)
233                    }
234                    _ => (),
235                }
236            }
237            ModifyEventType::Right(_relative_pos) => {}
238            ModifyEventType::Middle(_relative_pos) => {}
239            ModifyEventType::Move(relative_pos) => {
240                let (camera, camera_transform) = match camera.single() {
241                    Ok(queried_entity) => queried_entity,
242                    Err(_) => continue,
243                };
244
245                let ray = relative_pos.create_ray(camera, camera_transform);
246
247                match current_target.get() {
248                    ModificationTarget::Cutting => cut_sticks(
249                        ray,
250                        &mut stick_query,
251                        &params.p0(),
252                        &mut commands,
253                        sim_settings.interaction_radius,
254                    ),
255                    ModificationTarget::Grab => grab_point(params.p4(), ray),
256
257                    _ => (),
258                }
259            }
260            ModifyEventType::Release(_) => {
261                match current_target.get() {
262                    ModificationTarget::Cutting => {
263                        // When mouse is released, stop tracking cuts
264                        next_target.set(ModificationTarget::Cut)
265                    }
266                    ModificationTarget::Grab => next_target.set(ModificationTarget::None),
267                    _ => (),
268                }
269            }
270        }
271    }
272}