diff --git a/Cargo.lock b/Cargo.lock index c4ab5f55..0730be91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -800,8 +800,7 @@ dependencies = [ [[package]] name = "bevy_mod_outline" version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2d0f7a7608e581b5703c531182e37be991aa80266e902dc3f39d695a953511" +source = "git+https://github.com/arjo129/bevy_mod_outline.git?branch=09_compatibility#b09a667744cedb78347bad18459467b0de1bd151" dependencies = [ "bevy", "bitfield", @@ -867,6 +866,15 @@ dependencies = [ "radsort", ] +[[package]] +name = "bevy_points" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b23dc30813ffb5613b906393fed2393e84d1599e46d9aac2ff7a9e8c966f02" +dependencies = [ + "bevy", +] + [[package]] name = "bevy_polyline" version = "0.4.0" @@ -1221,6 +1229,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitfield" version = "0.14.0" @@ -1754,6 +1768,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-mac" version = "0.10.1" @@ -2087,6 +2107,22 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "exr" +version = "1.71.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8" +dependencies = [ + "bit_field", + "flume 0.11.0", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -2168,6 +2204,15 @@ dependencies = [ "spinning_top", ] +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -2249,6 +2294,17 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-io" version = "0.3.28" @@ -2404,6 +2460,16 @@ dependencies = [ "polyval", ] +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gilrs" version = "0.10.2" @@ -2643,6 +2709,15 @@ dependencies = [ "surf", ] +[[package]] +name = "half" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] + [[package]] name = "hash32" version = "0.2.1" @@ -2832,10 +2907,13 @@ dependencies = [ "bytemuck", "byteorder", "color_quant", + "exr", + "gif", "jpeg-decoder", "num-rational", "num-traits", "png", + "qoi", "tiff", ] @@ -2940,7 +3018,7 @@ dependencies = [ "crossbeam-utils", "curl", "curl-sys", - "flume", + "flume 0.9.2", "futures-lite", "http", "log", @@ -3018,6 +3096,9 @@ name = "jpeg-decoder" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +dependencies = [ + "rayon", +] [[package]] name = "js-sys" @@ -3080,6 +3161,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "lewton" version = "0.10.2" @@ -3977,6 +4064,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + [[package]] name = "polyval" version = "0.4.5" @@ -4034,6 +4127,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f89dff0959d98c9758c88826cc002e2c3d0b9dfac4139711d1f30de442f1139b" +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + [[package]] name = "quote" version = "1.0.33" @@ -4167,6 +4269,26 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "rectangle-pack" version = "0.4.2" @@ -4288,6 +4410,7 @@ dependencies = [ "bevy_mod_picking", "bevy_mod_raycast", "bevy_obj", + "bevy_points", "bevy_polyline", "bevy_rapier3d", "bevy_stl", @@ -4297,11 +4420,15 @@ dependencies = [ "console_error_panic_hook", "crossbeam-channel", "dirs 4.0.0", + "futures-intrusive", "futures-lite", "gz-fuel", + "image", "itertools", "lyon", "pathdiff", + "pollster", + "rand 0.8.5", "rfd", "rmf_site_format", "sdformat_rs", @@ -4318,6 +4445,7 @@ dependencies = [ "utm", "wasm-bindgen", "web-sys", + "wgpu", ] [[package]] @@ -4698,6 +4826,15 @@ dependencies = [ "smallvec", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "spinning_top" version = "0.2.5" @@ -5937,3 +6074,12 @@ dependencies = [ "syn 1.0.109", "xml-rs", ] + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] diff --git a/rmf_site_editor/Cargo.toml b/rmf_site_editor/Cargo.toml index b985bc4d..8936cc13 100644 --- a/rmf_site_editor/Cargo.toml +++ b/rmf_site_editor/Cargo.toml @@ -19,7 +19,7 @@ path = "examples/extending_menu.rs" bevy_egui = "0.19" bevy_mod_picking = "0.11" bevy_mod_raycast = "0.7" -bevy_mod_outline = "0.3.3" +bevy_mod_outline = { git = "https://github.com/arjo129/bevy_mod_outline.git", branch= "09_compatibility"} bevy_infinite_grid = "0.6" bevy_polyline = "0.4" bevy_stl = "0.7.0" @@ -49,6 +49,12 @@ rfd = "0.11" urdf-rs = "0.7" utm = "0.1.6" sdformat_rs = { git = "https://github.com/open-rmf/sdf_rust_experimental", rev = "f86344f"} +pollster = "0.3.0" +futures-intrusive = "0.5.0" +wgpu="0.14.2" +image = "0.24.6" +rand = "0.8.5" +bevy_points = "0.1.3" gz-fuel = { git = "https://github.com/open-rmf/gz-fuel-rs", branch = "first_implementation" } pathdiff = "*" diff --git a/rmf_site_editor/src/interaction/anchor.rs b/rmf_site_editor/src/interaction/anchor.rs index 90160806..216e477d 100644 --- a/rmf_site_editor/src/interaction/anchor.rs +++ b/rmf_site_editor/src/interaction/anchor.rs @@ -20,7 +20,7 @@ use crate::{ interaction::IntersectGroundPlaneParams, interaction::*, keyboard::DebugMode, - site::{Anchor, Category, Delete, Dependents, SiteAssets, Subordinate}, + site::{Anchor, Category, Delete, Dependents, PointAsset, SiteAssets, Subordinate}, }; use bevy::prelude::*; @@ -38,6 +38,8 @@ pub fn add_anchor_visual_cues( >, categories: Query<&Category>, site_assets: Res, + point_assets: Res, + interaction_assets: Res, ) { for (e, parent, subordinate, anchor) in &new_anchors { let body_mesh = match categories.get(parent.get()).unwrap() { @@ -47,6 +49,10 @@ pub fn add_anchor_visual_cues( }; let mut entity_commands = commands.entity(e); + entity_commands.insert(LimitScaleFactor { + distance_to_start_scaling: 10.0, + original_scale: 1.0, + }); let body = entity_commands.add_children(|parent| { let mut body = parent.spawn(PbrBundle { mesh: body_mesh, @@ -54,6 +60,12 @@ pub fn add_anchor_visual_cues( ..default() }); body.insert(Selectable::new(e)); + body.insert(MaterialMeshBundle { + mesh: point_assets.bevy_point_mesh.clone(), + material: point_assets.bevy_point_material.clone(), + ..default() + }); + body.insert(ScreenSpaceSelection::Point(e)); if subordinate.is_none() { body.insert(DragPlaneBundle::new(e, Vec3::Z)); } diff --git a/rmf_site_editor/src/interaction/camera_controls.rs b/rmf_site_editor/src/interaction/camera_controls.rs index 0ed89d2f..68390e11 100644 --- a/rmf_site_editor/src/interaction/camera_controls.rs +++ b/rmf_site_editor/src/interaction/camera_controls.rs @@ -49,13 +49,19 @@ pub const HOVERED_OUTLINE_LAYER: u8 = 4; /// The X-Ray layer is used to show visual cues that need to be rendered /// above anything that would be obstructing them. pub const XRAY_RENDER_LAYER: u8 = 5; +/// The Line Picking layer contains an entity based color map for picking +/// polylines which are drawn in screen space. +pub const LINE_PICKING_LAYER: u8 = 6; +/// The Line Picking layer contains an entity based color map for picking +/// points which are drawn in screen space. +pub const POINT_PICKING_LAYER: u8 = 7; /// The Model Preview layer is used by model previews to spawn and render /// models in the engine without having them being visible to general cameras -pub const MODEL_PREVIEW_LAYER: u8 = 6; +pub const MODEL_PREVIEW_LAYER: u8 = 8; #[derive(Resource)] -struct MouseLocation { - previous: Vec2, +pub struct MouseLocation { + pub previous: Vec2, } impl Default for MouseLocation { diff --git a/rmf_site_editor/src/interaction/color_based_picker/camera_capture.rs b/rmf_site_editor/src/interaction/color_based_picker/camera_capture.rs new file mode 100644 index 00000000..9b1eee39 --- /dev/null +++ b/rmf_site_editor/src/interaction/color_based_picker/camera_capture.rs @@ -0,0 +1,184 @@ +// Taken from https://github.com/bevyengine/bevy/pull/5550/files +// Might be useful to move this to a utils folder. +use std::sync::Arc; + +use bevy::prelude::*; +use bevy::render::render_asset::RenderAssets; +use bevy::render::render_graph::{self, NodeRunError, RenderGraph, RenderGraphContext}; +use bevy::render::renderer::{RenderContext, RenderDevice, RenderQueue}; +use bevy::render::{Extract, RenderApp, RenderStage}; + +use bevy::render::render_resource::{ + Buffer, BufferDescriptor, BufferUsages, CommandEncoderDescriptor, Extent3d, ImageCopyBuffer, + ImageDataLayout, +}; +use pollster::FutureExt; + +use std::sync::atomic::{AtomicBool, Ordering}; + +pub fn receive_images( + image_copiers: Query<&ImageCopier>, + mut images: ResMut>, + render_device: Res, +) { + for image_copier in image_copiers.iter() { + if !image_copier.enabled() { + continue; + } + // Derived from: https://sotrh.github.io/learn-wgpu/showcase/windowless/#a-triangle-without-a-window + // We need to scope the mapping variables so that we can + // unmap the buffer + async { + let buffer_slice = image_copier.buffer.slice(..); + + // NOTE: We have to create the mapping THEN device.poll() before await + // the future. Otherwise the application will freeze. + let (tx, rx) = futures_intrusive::channel::shared::oneshot_channel(); + buffer_slice.map_async(wgpu::MapMode::Read, move |result| { + tx.send(result).unwrap(); + }); + render_device.poll(wgpu::Maintain::Wait); + rx.receive().await.unwrap().unwrap(); + if let Some(mut image) = images.get_mut(&image_copier.dst_image) { + image.data = buffer_slice.get_mapped_range().to_vec(); + } + + image_copier.buffer.unmap(); + } + .block_on(); + } +} + +pub const IMAGE_COPY: &str = "image_copy"; + +pub struct ImageCopyPlugin; +impl Plugin for ImageCopyPlugin { + fn build(&self, app: &mut App) { + let render_app = app.add_system(receive_images).sub_app_mut(RenderApp); + + render_app.add_system_to_stage(RenderStage::Extract, image_copy_extract); + + let mut graph = render_app.world.get_resource_mut::().unwrap(); + + graph.add_node(IMAGE_COPY, ImageCopyDriver::default()); + + graph + .add_node_edge(IMAGE_COPY, bevy::render::main_graph::node::CAMERA_DRIVER) + .unwrap(); + } +} + +#[derive(Clone, Default, Resource, Deref, DerefMut)] +pub struct ImageCopiers(pub Vec); + +#[derive(Clone, Component)] +pub struct ImageCopier { + buffer: Buffer, + enabled: Arc, + src_image: Handle, + dst_image: Handle, +} + +impl ImageCopier { + pub fn new( + src_image: Handle, + dst_image: Handle, + size: Extent3d, + render_device: &RenderDevice, + ) -> ImageCopier { + let padded_bytes_per_row = + RenderDevice::align_copy_bytes_per_row((size.width) as usize) * 4; + + let cpu_buffer = render_device.create_buffer(&BufferDescriptor { + label: None, + size: padded_bytes_per_row as u64 * size.height as u64, + usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + ImageCopier { + buffer: cpu_buffer, + src_image, + dst_image, + enabled: Arc::new(AtomicBool::new(true)), + } + } + + pub fn enable(&self) { + self.enabled.store(true, Ordering::Relaxed); + } + + pub fn disable(&self) { + self.enabled.store(false, Ordering::Relaxed); + } + + pub fn enabled(&self) -> bool { + self.enabled.load(Ordering::Relaxed) + } +} + +pub fn image_copy_extract(mut commands: Commands, image_copiers: Extract>) { + commands.insert_resource(ImageCopiers( + image_copiers.iter().cloned().collect::>(), + )); +} + +#[derive(Default)] +pub struct ImageCopyDriver; + +impl render_graph::Node for ImageCopyDriver { + fn run( + &self, + _graph: &mut RenderGraphContext, + render_context: &mut RenderContext, + world: &World, + ) -> Result<(), NodeRunError> { + let image_copiers = world.get_resource::().unwrap(); + let gpu_images = world.get_resource::>().unwrap(); + + for image_copier in image_copiers.iter() { + if !image_copier.enabled() { + continue; + } + + let src_image = gpu_images.get(&image_copier.src_image).unwrap(); + + let mut encoder = render_context + .render_device + .create_command_encoder(&CommandEncoderDescriptor::default()); + + let format = src_image.texture_format.describe(); + + let padded_bytes_per_row = RenderDevice::align_copy_bytes_per_row( + (src_image.size.x as usize / format.block_dimensions.0 as usize) + * format.block_size as usize, + ); + + let texture_extent = Extent3d { + width: src_image.size.x as u32, + height: src_image.size.y as u32, + depth_or_array_layers: 1, + }; + + encoder.copy_texture_to_buffer( + src_image.texture.as_image_copy(), + ImageCopyBuffer { + buffer: &image_copier.buffer, + layout: ImageDataLayout { + offset: 0, + bytes_per_row: Some( + std::num::NonZeroU32::new(padded_bytes_per_row as u32).unwrap(), + ), + rows_per_image: None, + }, + }, + texture_extent, + ); + + let render_queue = world.get_resource::().unwrap(); + render_queue.submit(std::iter::once(encoder.finish())); + } + + Ok(()) + } +} diff --git a/rmf_site_editor/src/interaction/color_based_picker/color_entity_mapping.rs b/rmf_site_editor/src/interaction/color_based_picker/color_entity_mapping.rs new file mode 100644 index 00000000..a4d6317d --- /dev/null +++ b/rmf_site_editor/src/interaction/color_based_picker/color_entity_mapping.rs @@ -0,0 +1,256 @@ +use bevy::{prelude::*, render::view::RenderLayers, utils::hashbrown::HashMap}; +use bevy_points::prelude::PointsMaterial; +use bevy_polyline::prelude::*; + +use std::collections::BTreeMap; +use std::process::Child; + +use crate::interaction::{DontPropagateVisualCue, LINE_PICKING_LAYER, POINT_PICKING_LAYER}; +use crate::site::PointAsset; + +use super::ImageToSave; + +const SCREEN_SPACE_POINT_SIZE_SELECTION: f32 = 40.0; + +#[derive(Debug, Clone)] +pub struct ScreenspacePolyline { + pub start: Vec3, + pub end: Vec3, + thickness: f32, +} + +/// Label items which need to be selected. +#[derive(Component, Debug, Clone)] +pub enum ScreenSpaceSelection { + Polyline(ScreenspacePolyline), + Point(Entity), +} + +impl ScreenSpaceSelection { + pub fn polyline(start_anchor: Vec3, end_anchor: Vec3, thickness: f32) -> Self { + Self::Polyline(ScreenspacePolyline { + start: start_anchor, + end: end_anchor, + thickness: thickness, + }) + } +} + +/// Label items which need to be selected. +#[derive(Resource, Debug, Clone, Default)] +pub struct ColorEntityMap { + entity_to_polyline_material_map: HashMap>, + entity_to_point_material_map: HashMap>, + color_to_entity_map: BTreeMap<(u8, u8, u8), Entity>, +} + +impl ColorEntityMap { + fn allocate_new_polyline_color( + &mut self, + entity: &Entity, + polyline_materials: &mut ResMut>, + thickness: f32, + ) -> Handle { + // TODO(arjo): This takes indeterminate amount of time. Just use a counter + let mut r = rand::random::(); + let mut g = rand::random::(); + let mut b = rand::random::(); + + while self.color_to_entity_map.get(&(r, g, b)).is_some() + || (r == u8::MAX && g == u8::MAX && b == u8::MAX) + { + r = rand::random::(); + g = rand::random::(); + b = rand::random::(); + } + self.color_to_entity_map.insert((r, g, b), *entity); + + let color = Color::rgb_u8(r, g, b); + + let material = polyline_materials.add(PolylineMaterial { + width: thickness, + color, + perspective: false, + ..default() + }); + + self.entity_to_polyline_material_map + .insert(*entity, material.clone()); + + material + } + + fn allocate_new_point_material( + &mut self, + entity: &Entity, + point_materials: &mut ResMut>, + ) -> Handle { + let mut r = rand::random::(); + let mut g = rand::random::(); + let mut b = rand::random::(); + + while self.color_to_entity_map.get(&(r, g, b)).is_some() + || (r == u8::MAX && g == u8::MAX && b == u8::MAX) + { + r = rand::random::(); + g = rand::random::(); + b = rand::random::(); + } + self.color_to_entity_map.insert((r, g, b), *entity); + + let color = Color::rgb_u8(r, g, b); + + let material = point_materials.add(PointsMaterial { + point_size: SCREEN_SPACE_POINT_SIZE_SELECTION, // Defines the size of the points. + perspective: false, // Specify whether points' size is attenuated by the camera depth. + circle: true, + use_vertex_color: false, + color, + ..default() + }); + + self.entity_to_point_material_map + .insert(*entity, material.clone()); + + material + } + + pub fn get_polyline_material( + &mut self, + entity: &Entity, + polyline_materials: &mut ResMut>, + thickness: f32, + ) -> Handle { + if let Some(color) = self.entity_to_polyline_material_map.get(entity) { + color.clone() + } else { + self.allocate_new_polyline_color(entity, polyline_materials, thickness) + } + } + + pub fn get_points_material( + &mut self, + entity: &Entity, + points_material: &mut ResMut>, + ) -> Handle { + if let Some(color) = self.entity_to_point_material_map.get(entity) { + color.clone() + } else { + self.allocate_new_point_material(entity, points_material) + } + } + + pub fn get_entity(&self, key: &(u8, u8, u8)) -> Option<&Entity> { + self.color_to_entity_map.get(key) + } +} + +/// Label items which need to be selected. +#[derive(Component, Debug, Clone)] +pub struct MarkAsDrawnToSelectionBuffer; + +/// Label entities whic are part of the selection buffer +#[derive(Component, Debug, Clone)] +pub struct SelectionBufferDrawing; + +/// This system handles drawing new entities in the selection +pub fn new_objectcolor_entity_mapping( + mut commands: Commands, + screen_space_lines: Query< + (&ScreenSpaceSelection, Entity), + Without>, + >, + mut polyline_materials: ResMut>, + mut point_materials: ResMut>, + mut polylines: ResMut>, + mut color_map: ResMut, + images_to_save: Query<&ImageToSave>, + point_assets: Res, +) { + let scale = if let Ok(image_parameters) = images_to_save.get_single() { + image_parameters.3 + } else { + 1.0 + }; + + // Redraw parameters. + for (screenspace_shape, entity) in &screen_space_lines { + match screenspace_shape { + ScreenSpaceSelection::Polyline(shape) => { + let thickness = shape.thickness * scale; + let thickness = if thickness < 10.0 { 10.0 } else { thickness }; + if Layer == LINE_PICKING_LAYER { + commands.entity(entity).with_children(|parent| { + parent.spawn(( + PolylineBundle { + polyline: polylines.add(Polyline { + vertices: vec![shape.start, shape.end], + }), + material: color_map.get_polyline_material( + &entity, + &mut polyline_materials, + thickness, + ), + ..default() + }, + RenderLayers::layer(LINE_PICKING_LAYER), + DontPropagateVisualCue, + SelectionBufferDrawing, + )); + }); + commands + .entity(entity) + .insert(MarkAsDrawnToSelectionBuffer::); + } + } + ScreenSpaceSelection::Point(_) => { + if Layer == POINT_PICKING_LAYER { + commands.entity(entity).with_children(|parent| { + parent.spawn(( + MaterialMeshBundle { + mesh: point_assets.bevy_point_mesh.clone(), + material: color_map + .get_points_material(&entity, &mut point_materials) + .clone(), + ..default() + }, + RenderLayers::layer(POINT_PICKING_LAYER), + DontPropagateVisualCue, + SelectionBufferDrawing, + )); + }); + commands + .entity(entity) + .insert(MarkAsDrawnToSelectionBuffer::); + } + } + } + } +} + +/// This system synchronizes the polylines in the selection buffer +/// and the rendering system. +pub fn sync_polyline_selection_buffer( + screen_space_lines: Query< + (&ScreenSpaceSelection, &Children), + With>, + >, + polylines: Query<(&Handle, &SelectionBufferDrawing, &RenderLayers)>, + mut polyline_assets: ResMut>, +) { + for (selection, children) in screen_space_lines.iter() { + let ScreenSpaceSelection::Polyline(line) = selection else { + continue; + }; + + for child in children.iter() { + let Ok((handle, _, layers)) = polylines.get(*child) else { + continue; + }; + let Some(mut polyline) = polyline_assets.get_mut(handle) else { + continue; + }; + polyline.vertices = vec![line.start, line.end]; + } + } +} diff --git a/rmf_site_editor/src/interaction/color_based_picker/mod.rs b/rmf_site_editor/src/interaction/color_based_picker/mod.rs new file mode 100644 index 00000000..9fd0a614 --- /dev/null +++ b/rmf_site_editor/src/interaction/color_based_picker/mod.rs @@ -0,0 +1,37 @@ +use bevy::prelude::*; + +pub mod rendering_helper; +pub use rendering_helper::*; + +pub mod color_entity_mapping; +pub use color_entity_mapping::*; + +pub mod camera_capture; +pub use camera_capture::*; + +use super::{LINE_PICKING_LAYER, POINT_PICKING_LAYER}; + +pub struct ColorBasedPicker; + +pub struct GPUPickItem(pub Entity); + +impl Plugin for ColorBasedPicker { + fn build(&self, app: &mut bevy::prelude::App) { + app.add_system(resize_notificator::) + .add_system(resize_notificator::) + .add_system_to_stage( + CoreStage::PostUpdate, + buffer_to_selection::, + ) + .add_system_to_stage( + CoreStage::PostUpdate, + buffer_to_selection::, + ) + .init_resource::() + .add_event::() + .add_system(new_objectcolor_entity_mapping::) + .add_system(new_objectcolor_entity_mapping::) + .add_system(sync_polyline_selection_buffer) + .add_plugin(ImageCopyPlugin); + } +} diff --git a/rmf_site_editor/src/interaction/color_based_picker/rendering_helper.rs b/rmf_site_editor/src/interaction/color_based_picker/rendering_helper.rs new file mode 100644 index 00000000..44522f64 --- /dev/null +++ b/rmf_site_editor/src/interaction/color_based_picker/rendering_helper.rs @@ -0,0 +1,266 @@ +use bevy::core_pipeline::clear_color::ClearColorConfig; +use bevy::core_pipeline::fxaa::Fxaa; +use bevy::core_pipeline::tonemapping::Tonemapping; +use bevy::prelude::*; +use bevy::render::camera::RenderTarget; +use bevy::render::render_resource::*; +use bevy::render::renderer::RenderDevice; +use bevy::render::view::RenderLayers; +use bevy::window::WindowResized; +use image::{save_buffer_with_format, Pixel}; +use rmf_site_format::Anchor; + +use super::{ColorEntityMap, GPUPickItem, ScreenSpaceSelection}; +use crate::interaction::camera_controls::MouseLocation; +use crate::interaction::*; +use crate::interaction::{CameraControls, ProjectionMode, POINT_PICKING_LAYER}; +use crate::keyboard::DebugMode; +use crate::site::LaneSegments; + +#[derive(Component, Clone, Debug, Default)] +pub struct RenderingBufferDetails { + selection_cam_entity: Option, + image: Handle, + copier_entity: Option, + image_parameters: Option, +} + +#[derive(Component)] +pub struct ImageToSave(Handle, u32, u32, pub f32); + +pub fn resize_notificator( + mut resize_event: EventReader, + mut render_buffer_details: Local, + mut commands: Commands, + mut images: ResMut>, + camera_controls: Res, + cameras: Query<(&Camera, &GlobalTransform)>, + render_device: Res, + handles: Query<(&ImageToSave, Entity)>, +) { + let view_cam_entity = match camera_controls.mode() { + ProjectionMode::Perspective => camera_controls.perspective_camera_entities[0], + ProjectionMode::Orthographic => camera_controls.orthographic_camera_entities[0], + }; + + if let Ok((camera, _)) = cameras.get(view_cam_entity) { + for _e in resize_event.iter() { + //Despawn old allocations + if let Some(camera) = render_buffer_details.selection_cam_entity { + commands.entity(camera).despawn(); + images.remove(&render_buffer_details.image); + + for (_handle, entity) in handles.iter() { + //if handle.0 == camera_entity.image { + commands.entity(entity).despawn(); + //} + } + + if let Some(copier) = render_buffer_details.copier_entity { + commands.entity(copier).despawn(); + } + + if let Some(image_buffer) = render_buffer_details.image_parameters { + commands.entity(image_buffer).despawn(); + } + } + + let viewport_size = camera.logical_viewport_size().unwrap(); + //let scale_ratio = 1.0; + let scale_ratio = 256.0 / viewport_size.x; + let height = (viewport_size.y * scale_ratio) as u32; + let width = (viewport_size.x * scale_ratio) as u32; + let size = Extent3d { + width, //e.width as u32, + height, //e.height as u32, + ..default() + }; + // This is the texture that will be rendered to. + let mut image = Image { + texture_descriptor: TextureDescriptor { + label: None, + size, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8UnormSrgb, + mip_level_count: 1, + sample_count: 1, + usage: TextureUsages::COPY_SRC + | TextureUsages::COPY_DST + | TextureUsages::TEXTURE_BINDING + | TextureUsages::RENDER_ATTACHMENT, + }, + ..default() + }; + + // fill image.data with zeroes + image.resize(size); + let render_target_image_handle = images.add(image); + + // This is the CPU image + let mut cpu_image = Image { + texture_descriptor: TextureDescriptor { + label: None, + size, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8UnormSrgb, + mip_level_count: 1, + sample_count: 1, + usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING, + }, + ..Default::default() + }; + cpu_image.resize(size); + let cpu_image_handle = images.add(cpu_image); + render_buffer_details.image = render_target_image_handle.clone(); + + let image = commands + .spawn(ImageToSave::( + cpu_image_handle.clone(), + size.width, + size.height, + scale_ratio, + )) + .id(); + render_buffer_details.image_parameters = Some(image); + + let camera_entity = commands + .spawn(( + Camera3dBundle { + tonemapping: Tonemapping::Disabled, + camera_3d: Camera3d { + clear_color: ClearColorConfig::Custom(Color::BLACK), + ..default() + }, + camera: Camera { + // render before the "main pass" camera + viewport: camera.viewport.clone(), + target: RenderTarget::Image(render_target_image_handle.clone()), + ..default() + }, + ..default() + }, + RenderLayers::layer(Layer), + )) + .remove::() + .id(); + // By making it a child of the camera, the transforms should be inherited. + commands + .entity(view_cam_entity) + .push_children(&[camera_entity]); + render_buffer_details.selection_cam_entity = Some(camera_entity); + + let copier_entity = commands + .spawn(ImageCopier::new( + render_target_image_handle, + cpu_image_handle.clone(), + size, + &render_device, + )) + .id(); + render_buffer_details.copier_entity = Some(copier_entity); + + println!( + "Resize render pipeline {} {}", + viewport_size.x, viewport_size.y + ); + } + } +} + +pub fn buffer_to_selection( + images_to_save: Query<&ImageToSave>, + camera_controls: Res, + cameras: Query<&Camera>, + mut images: ResMut>, + mut color_map: ResMut, + debug: ResMut, + mouse_location: Res, + anchors: Query<&Anchor>, + lane_segments: Query<(Entity, &LaneSegments)>, + selections: Query<(&ScreenSpaceSelection, &Parent)>, + mut pick_event: EventWriter, +) { + let view_cam_entity = match camera_controls.mode() { + ProjectionMode::Perspective => camera_controls.perspective_camera_entities[0], + ProjectionMode::Orthographic => camera_controls.orthographic_camera_entities[0], + }; + + let offset = if let Ok(camera) = cameras.get(view_cam_entity) { + let Some((viewport_min, viewport_max)) = camera.logical_viewport_rect() else { + return; + }; + let screen_size = camera.logical_target_size().unwrap(); + Vec2::new(viewport_min.x, screen_size.y - viewport_max.y) + } else { + Vec2::ZERO + }; + let mouse_position = mouse_location.previous - offset; + + if let Some(image) = images_to_save.iter().next() { + let data = &images.get_mut(&image.0).unwrap().data; + + let Some(img) = image::ImageBuffer::, &[u8]>::from_raw( + image.1, + image.2, + data.as_slice(), + ) else { + return; + }; + + let mx = (mouse_position.x * image.3) as u32; + let my = (mouse_position.y * image.3) as u32; + + // Rust panics if there is integer overflow. + // Since my and mx are unsigned we should make sure they fall within + // the bounds of the picking buffer. + if my > image.2 { + return; + } + + // y-axis seems flipped + let my = image.2 - my; + + if debug.0 { + let mut img = image::ImageBuffer::, _>::from_raw( + image.1, + image.2, + Vec::from_iter(data.clone()[..5760000].iter().map(|f| *f)), + ) + .expect("failed to unwrap image"); + + if mx > 50 || my > 50 { + for i in mx - 50..mx + 50 { + for j in my - 50..my + 50 { + if i > image.1 || j > image.2 { + continue; + } + img.put_pixel(i, j, image::Rgba([255, 255, 255, 255])); + } + } + } + + let result = save_buffer_with_format( + format!("picking_layer_{:?}.png", Layer), + &img.into_raw(), + image.1, + image.2, + image::ColorType::Rgba8, + image::ImageFormat::Png, + ); + if let Err(something) = result { + println!("{:?}", something); + } + } + + if let Some(pixel) = img.get_pixel_checked(mx, my) { + if pixel.0[0] != 0 || pixel.0[1] != 0 || pixel.0[2] != 0 { + let Some(entity) = color_map.get_entity(&(pixel.0[0], pixel.0[1], pixel.0[2])) + else { + return; + }; + + pick_event.send(GPUPickItem(*entity)); + } + } + } +} diff --git a/rmf_site_editor/src/interaction/mod.rs b/rmf_site_editor/src/interaction/mod.rs index 0c702aa5..bed264fc 100644 --- a/rmf_site_editor/src/interaction/mod.rs +++ b/rmf_site_editor/src/interaction/mod.rs @@ -78,6 +78,12 @@ pub use popup::*; pub mod preview; pub use preview::*; +pub mod scale_factor_limiting; +pub use scale_factor_limiting::*; + +pub mod color_based_picker; +pub use color_based_picker::*; + pub mod select; pub use select::*; @@ -171,6 +177,7 @@ impl Plugin for InteractionPlugin { .add_plugin(CategoryVisibilityPlugin::::visible(true)) .add_plugin(CategoryVisibilityPlugin::::visible(true)) .add_plugin(CameraControlsPlugin) + .add_plugin(ColorBasedPicker) .add_plugin(ModelPreviewPlugin) .add_system_set( SystemSet::on_update(InteractionState::Enable) diff --git a/rmf_site_editor/src/interaction/picking.rs b/rmf_site_editor/src/interaction/picking.rs index ae06ace7..c5485d5d 100644 --- a/rmf_site_editor/src/interaction/picking.rs +++ b/rmf_site_editor/src/interaction/picking.rs @@ -128,6 +128,7 @@ pub fn update_picked( mut picked: ResMut, mut change_pick: EventWriter, current_workspace: Res, + mut gpu_pick_event: EventReader, ) { if let Some(blockers) = blockers { if blockers.blocking() { @@ -183,6 +184,12 @@ pub fn update_picked( break 'current_picked Some(topmost); } } + // Use GPU picking if nothing is picked via normal picking + if !gpu_pick_event.is_empty() { + if let Some(item) = gpu_pick_event.iter().next() { + break 'current_picked Some(item.0); + } + } None }; diff --git a/rmf_site_editor/src/interaction/scale_factor_limiting.rs b/rmf_site_editor/src/interaction/scale_factor_limiting.rs new file mode 100644 index 00000000..e0e1c170 --- /dev/null +++ b/rmf_site_editor/src/interaction/scale_factor_limiting.rs @@ -0,0 +1,60 @@ +use bevy::prelude::*; + +use super::{CameraControls, ProjectionMode}; + +/// Adding this component will ensure that your 3d model has +/// a minimum size on the screen. This is useful for things like +/// tooltips and gizmos. Think of it as like setting a `min_width` +/// in CSS. +#[derive(Debug, Clone, Component)] +pub struct LimitScaleFactor { + /// If the camera is closer than this, the size of the object + /// will not apply any correction. Beyong this distance the `limit_size` + /// system will rescale the object to make sure that the object is + /// always visible in the screen space. + pub distance_to_start_scaling: f32, + /// Original scale of the object. + pub original_scale: f32, +} + +/// This system limits the amount of shrinkining that a model +/// undergoes in screen space. This allows users to zoom out +/// but also ensures that the item is visible no matter how far +/// the user is from the mesh. This is really useful for things +/// like gizmos and tool tips. +pub fn limit_size( + item_to_limit_scale: Query<(&LimitScaleFactor, Entity)>, + camera_controls: Res, + transforms: Query<&GlobalTransform>, + mut editable_transforms: Query<&mut Transform>, +) { + let view_cam_entity = match camera_controls.mode() { + ProjectionMode::Perspective => camera_controls.perspective_camera_entities[0], + ProjectionMode::Orthographic => camera_controls.orthographic_camera_entities[0], + }; + + let Ok(camera_transform) = transforms.get(view_cam_entity) else { + return; + }; + + for (limits, entity) in item_to_limit_scale.iter() { + let Ok(item_to_scale_transform) = transforms.get(entity) else { + continue; + }; + + let dist = camera_transform.translation() - item_to_scale_transform.translation(); + + if dist.length() > limits.distance_to_start_scaling { + let Ok(mut item_to_scale) = editable_transforms.get_mut(entity) else { + continue; + }; + item_to_scale.scale = Vec3::splat(dist.length() / limits.distance_to_start_scaling) + * limits.original_scale; + } else { + let Ok(mut item_to_scale) = editable_transforms.get_mut(entity) else { + continue; + }; + item_to_scale.scale = Vec3::ONE * limits.original_scale; + } + } +} diff --git a/rmf_site_editor/src/interaction/visual_cue.rs b/rmf_site_editor/src/interaction/visual_cue.rs index a6dc4f7a..662a1ff3 100644 --- a/rmf_site_editor/src/interaction/visual_cue.rs +++ b/rmf_site_editor/src/interaction/visual_cue.rs @@ -124,10 +124,14 @@ impl VisualCue { } } +#[derive(Component)] +pub struct DontPropagateVisualCue; + /// This system propagates visual cue tags and the correct RenderLayer to all /// entities that are meant to be visual cues. This system makes two assumptions: /// 1. Any entity that is a VisualCue will be a VisualCue forever -/// 2. Any descendents of a VisualCue should also be VisualCues. +/// 2. Any descendents of a VisualCue should also be VisualCues, unless +/// one of the descendents is marked with `DontPropagateVisualCue`. /// We will need to change the implementation of this system if we ever want to /// loosen either of those assumptions. pub fn propagate_visual_cues( @@ -135,11 +139,16 @@ pub fn propagate_visual_cues( changed_cues: Query<(Entity, &VisualCue), Or<(Changed, Changed)>>, children: Query<&Children>, existing_cues: Query<(), With>, + stop_visualcue_prop: Query<(), With>, ) { for (e, root_cue) in &changed_cues { let mut queue = SmallVec::<[(Entity, VisualCue); 5]>::new(); queue.push((e, root_cue.clone())); while let Some((top, top_cue)) = queue.pop() { + if stop_visualcue_prop.contains(top) { + continue; + } + commands .entity(top) .insert(top_cue.layers()) diff --git a/rmf_site_editor/src/osm_slippy_map.rs b/rmf_site_editor/src/osm_slippy_map.rs index 9157807e..e6c0870d 100644 --- a/rmf_site_editor/src/osm_slippy_map.rs +++ b/rmf_site_editor/src/osm_slippy_map.rs @@ -33,10 +33,10 @@ fn haversine_distance(lat1: f32, lon1: f32, lat2: f32, lon2: f32) -> f32 { let lat2 = lat2.to_radians(); let lon2 = lon2.to_radians(); - let dLat = lat2 - lat1; - let dLon = lon2 - lon1; + let d_lat = lat2 - lat1; + let d_lon = lon2 - lon1; - let a = (dLat / 2.0).sin().powi(2) + lat1.cos() * lat2.cos() * (dLon / 2.0).sin().powi(2); + let a = (d_lat / 2.0).sin().powi(2) + lat1.cos() * lat2.cos() * (d_lon / 2.0).sin().powi(2); let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt()); return c * EARTH_RADIUS; } diff --git a/rmf_site_editor/src/site/assets.rs b/rmf_site_editor/src/site/assets.rs index bb706c81..db423ed8 100644 --- a/rmf_site_editor/src/site/assets.rs +++ b/rmf_site_editor/src/site/assets.rs @@ -17,6 +17,7 @@ use crate::{shapes::*, site::*}; use bevy::{math::Affine3A, prelude::*}; +use bevy_points::prelude::{PointsMaterial, PointsMesh}; #[derive(Resource)] pub struct SiteAssets { @@ -278,3 +279,29 @@ impl SiteAssets { } } } + +#[derive(Resource)] +pub struct PointAsset { + pub bevy_point_material: Handle, + pub bevy_point_mesh: Handle, +} + +impl FromWorld for PointAsset { + fn from_world(world: &mut World) -> Self { + let mut points_materials = world.get_resource_mut::>().unwrap(); + let bevy_point_material = points_materials.add(PointsMaterial { + point_size: 100.0, // Defines the size of the points. + perspective: false, // Specify whether points' size is attenuated by the camera depth. + circle: true, // Specify whether the shape of points is circular or rectangular. + ..Default::default() + }); + + let mut meshes = world.get_resource_mut::>().unwrap(); + let bevy_point_mesh = meshes.add(PointsMesh::from_iter((0..1).map(|i| Vec3::ZERO)).into()); + + Self { + bevy_point_material, + bevy_point_mesh, + } + } +} diff --git a/rmf_site_editor/src/site/lane.rs b/rmf_site_editor/src/site/lane.rs index 0af32206..08099519 100644 --- a/rmf_site_editor/src/site/lane.rs +++ b/rmf_site_editor/src/site/lane.rs @@ -15,12 +15,16 @@ * */ +use crate::interaction::ScreenSpaceSelection; +use crate::interaction::Selectable; use crate::site::*; use crate::{CurrentWorkspace, Issue, ValidateWorkspace}; use bevy::{prelude::*, utils::Uuid}; use rmf_site_format::{Edge, LaneMarker}; use std::collections::{BTreeSet, HashMap}; +use bevy_polyline::prelude::*; + pub const SELECTED_LANE_OFFSET: f32 = 0.001; pub const HOVERED_LANE_OFFSET: f32 = 0.002; pub const LANE_LAYER_START: f32 = FLOOR_LAYER_START + 0.001; @@ -36,6 +40,8 @@ pub struct LaneSegments { pub start: Entity, pub mid: Entity, pub end: Entity, + pub polyline: Entity, + pub picker: Entity, pub outlines: [Entity; 3], } @@ -94,6 +100,8 @@ pub fn add_lane_visuals( mut dependents: Query<&mut Dependents, With>, assets: Res, current_level: Res, + mut polyline_materials: ResMut>, + mut polylines: ResMut>, ) { for (e, edge, associated_graphs) in &lanes { for anchor in &edge.array() { @@ -119,79 +127,120 @@ pub fn add_lane_visuals( .point_in_parent_frame_of(edge.end(), Category::Lane, e) .unwrap(); let mut commands = commands.entity(e); - let (layer, start, mid, end, outlines) = commands.add_children(|parent| { - // Create a "layer" entity that manages the height of the lane, - // determined by the DisplayHeight of the graph. - let mut layer_cmd = parent.spawn(SpatialBundle { - transform: Transform::from_xyz(0.0, 0.0, height), - ..default() - }); - - let (start, mid, end, outlines) = layer_cmd.add_children(|parent| { - let mut start = parent.spawn(PbrBundle { - mesh: assets.lane_end_mesh.clone(), - material: lane_material.clone(), - transform: Transform::from_translation(start_anchor), + let (layer, start, mid, end, polyline, picker, outlines) = + commands.add_children(|parent| { + // Create a "layer" entity that manages the height of the lane, + // determined by the DisplayHeight of the graph. + let mut layer_cmd = parent.spawn(SpatialBundle { + transform: Transform::from_xyz(0.0, 0.0, height), ..default() }); - let start_outline = start.add_children(|start| { - start - .spawn(PbrBundle { - mesh: assets.lane_end_outline.clone(), - transform: Transform::from_translation(-0.000_5 * Vec3::Z), - visibility: Visibility { is_visible: false }, - ..default() - }) - .id() - }); - let start = start.id(); - let mut mid = parent.spawn(PbrBundle { - mesh: assets.lane_mid_mesh.clone(), - material: lane_material.clone(), - transform: line_stroke_transform(&start_anchor, &end_anchor, LANE_WIDTH), - ..default() - }); - let mid_outline = mid.add_children(|mid| { - mid.spawn(PbrBundle { - mesh: assets.lane_mid_outline.clone(), - transform: Transform::from_translation(-0.000_5 * Vec3::Z), - visibility: Visibility { is_visible: false }, - ..default() - }) - .id() - }); - let mid = mid.id(); - - let mut end = parent.spawn(PbrBundle { - mesh: assets.lane_end_mesh.clone(), - material: lane_material.clone(), - transform: Transform::from_translation(end_anchor), - ..default() - }); - let end_outline = end.add_children(|end| { - end.spawn(PbrBundle { - mesh: assets.lane_end_outline.clone(), - transform: Transform::from_translation(-0.000_5 * Vec3::Z), - visibility: Visibility { is_visible: false }, - ..default() - }) - .id() - }); - let end = end.id(); - - (start, mid, end, [start_outline, mid_outline, end_outline]) + let (start, mid, end, polyline, picker, outlines) = + layer_cmd.add_children(|parent| { + let mut start = parent.spawn(PbrBundle { + mesh: assets.lane_end_mesh.clone(), + material: lane_material.clone(), + transform: Transform::from_translation(start_anchor), + ..default() + }); + let start_outline = start.add_children(|start| { + start + .spawn(PbrBundle { + mesh: assets.lane_end_outline.clone(), + transform: Transform::from_translation(-0.000_5 * Vec3::Z), + visibility: Visibility { is_visible: false }, + ..default() + }) + .id() + }); + let start = start.id(); + + let mut mid = parent.spawn(PbrBundle { + mesh: assets.lane_mid_mesh.clone(), + material: lane_material.clone(), + transform: line_stroke_transform( + &start_anchor, + &end_anchor, + LANE_WIDTH, + ), + ..default() + }); + let mid_outline = mid.add_children(|mid| { + mid.spawn(PbrBundle { + mesh: assets.lane_mid_outline.clone(), + transform: Transform::from_translation(-0.000_5 * Vec3::Z), + visibility: Visibility { is_visible: false }, + ..default() + }) + .id() + }); + let mid = mid.id(); + + let mut end = parent.spawn(PbrBundle { + mesh: assets.lane_end_mesh.clone(), + material: lane_material.clone(), + transform: Transform::from_translation(end_anchor), + ..default() + }); + + let end_outline = end.add_children(|end| { + end.spawn(PbrBundle { + mesh: assets.lane_end_outline.clone(), + transform: Transform::from_translation(-0.000_5 * Vec3::Z), + visibility: Visibility { is_visible: false }, + ..default() + }) + .id() + }); + let end = end.id(); + + let polyline = parent + .spawn(PolylineBundle { + polyline: polylines.add(Polyline { + vertices: vec![start_anchor, end_anchor], + }), + material: polyline_materials.add(PolylineMaterial { + width: 10.0, + color: Color::RED, + perspective: false, + ..default() + }), + ..default() + }) + .insert(Selectable::new(e)) + .insert(( + ScreenSpaceSelection::polyline(start_anchor, end_anchor, 10.0), + Transform::IDENTITY, + GlobalTransform::IDENTITY, + ComputedVisibility::default(), + Visibility::VISIBLE, + RenderLayers::layer(LINE_PICKING_LAYER), + )) + .id(); + let picker = polyline.clone(); + + ( + start, + mid, + end, + polyline, + picker, + [start_outline, mid_outline, end_outline], + ) + }); + + (layer_cmd.id(), start, mid, end, polyline, picker, outlines) }); - (layer_cmd.id(), start, mid, end, outlines) - }); - commands .insert(LaneSegments { layer, start, mid, end, + polyline, + picker, outlines, }) .insert(SpatialBundle { @@ -210,6 +259,9 @@ fn update_lane_visuals( segments: &LaneSegments, anchors: &AnchorParams, transforms: &mut Query<&mut Transform>, + polylines: &Query<&Handle>, + polyline_assets: &mut Assets, + picker: &mut Query<&mut ScreenSpaceSelection>, ) { let start_anchor = anchors .point_in_parent_frame_of(edge.left(), Category::Lane, entity) @@ -227,6 +279,24 @@ fn update_lane_visuals( if let Some(mut tf) = transforms.get_mut(segments.end).ok() { *tf = Transform::from_translation(end_anchor); } + if let Ok(polyline) = polylines.get(segments.polyline) { + if let Some(mut polyline) = polyline_assets.get_mut(polyline) { + polyline.vertices = vec![start_anchor, end_anchor]; + } + } + + if let Ok(polyline) = polylines.get(segments.polyline) { + if let Some(mut polyline) = polyline_assets.get_mut(polyline) { + polyline.vertices = vec![start_anchor, end_anchor]; + } + } + + if let Ok(mut picker) = picker.get_mut(segments.picker) { + if let ScreenSpaceSelection::Polyline(ply) = picker.as_mut() { + ply.start = start_anchor; + ply.end = end_anchor; + } + } } pub fn update_changed_lane( @@ -245,10 +315,22 @@ pub fn update_changed_lane( levels: Query<(), With>, graphs: GraphSelect, mut transforms: Query<&mut Transform>, + polylines: Query<&Handle>, + mut polyline_assets: ResMut>, current_level: Res, + mut picker: Query<&mut ScreenSpaceSelection>, ) { for (e, edge, associated, segments, mut visibility) in &mut lanes { - update_lane_visuals(e, edge, segments, &anchors, &mut transforms); + update_lane_visuals( + e, + edge, + segments, + &anchors, + &mut transforms, + &polylines, + &mut polyline_assets, + &mut picker, + ); let is_visible = should_display_lane(edge, associated, &parents, &levels, ¤t_level, &graphs); @@ -269,11 +351,23 @@ pub fn update_lane_for_moved_anchor( ), >, mut transforms: Query<&mut Transform>, + polylines: Query<&Handle>, + mut polyline_assets: ResMut>, + mut picker: Query<&mut ScreenSpaceSelection>, ) { for dependents in &changed_anchors { for dependent in dependents.iter() { if let Ok((e, edge, segments)) = lanes.get(*dependent) { - update_lane_visuals(e, edge, segments, &anchors, &mut transforms); + update_lane_visuals( + e, + edge, + segments, + &anchors, + &mut transforms, + &polylines, + &mut polyline_assets, + &mut picker, + ); } } } diff --git a/rmf_site_editor/src/site/mod.rs b/rmf_site_editor/src/site/mod.rs index 08195a3f..30203de3 100644 --- a/rmf_site_editor/src/site/mod.rs +++ b/rmf_site_editor/src/site/mod.rs @@ -117,11 +117,20 @@ pub use util::*; pub mod wall; pub use wall::*; -use crate::recency::{RecencyRank, RecencyRankingPlugin}; +use bevy_points::prelude::PointsPlugin; + use crate::{clear_old_issues_on_new_validate_event, RegisterIssueType}; +use crate::{ + interaction::{limit_size, LINE_PICKING_LAYER, POINT_PICKING_LAYER}, + recency::{RecencyRank, RecencyRankingPlugin}, +}; pub use rmf_site_format::*; -use bevy::{prelude::*, render::view::visibility::VisibilitySystems, transform::TransformSystem}; +use bevy::{ + prelude::*, + render::view::{visibility::VisibilitySystems, RenderLayers}, + transform::TransformSystem, +}; #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub enum SiteState { @@ -151,6 +160,7 @@ pub struct SitePlugin; impl Plugin for SitePlugin { fn build(&self, app: &mut App) { app.add_state(SiteState::Off) + .add_plugin(PointsPlugin) .add_stage_after( CoreStage::Update, SiteUpdateStage::AssignOrphans, @@ -161,6 +171,7 @@ impl Plugin for SitePlugin { .add_state_to_stage(SiteUpdateStage::AssignOrphans, SiteState::Off) .add_state_to_stage(CoreStage::PostUpdate, SiteState::Off) .insert_resource(ClearColor(Color::rgb(0., 0., 0.))) + .init_resource::() .init_resource::() .init_resource::() .init_resource::() @@ -240,6 +251,8 @@ impl Plugin for SitePlugin { .with_system(update_lift_cabin) .with_system(update_lift_edge) .with_system(update_model_tentative_formats) + .with_system(update_material_for_display_color) + .with_system(limit_size) .with_system(check_for_duplicated_door_names) .with_system(check_for_duplicated_lift_names) .with_system(check_for_duplicated_dock_names)