From 21c460afa8ac093c36043578ad18aaacb548734a Mon Sep 17 00:00:00 2001 From: Dmitry Stepanov Date: Mon, 12 May 2025 22:16:41 +0300 Subject: [PATCH] improved selection in the editor - select hull-less objects using small-sized aabb - fixed incorrect position of intersection points when selecting a hull-less object --- editor/src/camera/mod.rs | 33 +++++++++++++++------------ editor/src/interaction/move_mode.rs | 6 ++++- editor/src/interaction/navmesh/mod.rs | 4 +++- editor/src/interaction/rotate_mode.rs | 5 +++- editor/src/interaction/scale_mode.rs | 7 ++++-- editor/src/plugins/collider/mod.rs | 14 ++++++++---- editor/src/scene/mod.rs | 2 ++ editor/src/settings/selection.rs | 10 +++++++- fyrox-impl/src/scene/base.rs | 2 +- 9 files changed, 58 insertions(+), 25 deletions(-) diff --git a/editor/src/camera/mod.rs b/editor/src/camera/mod.rs index ab8aeda66..cb272b108 100644 --- a/editor/src/camera/mod.rs +++ b/editor/src/camera/mod.rs @@ -18,6 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +use crate::settings::selection::SelectionSettings; use crate::{ fyrox::{ core::{ @@ -121,7 +122,6 @@ struct PickContext { pub type PickingFilter<'a> = Option<&'a mut dyn FnMut(Handle, &Node) -> bool>; -#[derive(Default)] pub struct PickingOptions<'a> { pub cursor_pos: Vector2, pub editor_only: bool, @@ -129,6 +129,7 @@ pub struct PickingOptions<'a> { pub ignore_back_faces: bool, pub use_picking_loop: bool, pub only_meshes: bool, + pub settings: &'a SelectionSettings, } impl CameraController { @@ -672,6 +673,7 @@ impl CameraController { ignore_back_faces, use_picking_loop, only_meshes, + settings, } = options; if let Some(camera) = graph[self.camera].cast::() { @@ -726,7 +728,7 @@ impl CameraController { .transform(&node.global_transform()) }; // Do coarse, but fast, intersection test with bounding box first. - if let Some(points) = ray.aabb_intersection_points(&aabb) { + if ray.aabb_intersection_points(&aabb).is_some() { let result = precise_ray_test(node, camera, graph, &ray, ignore_back_faces); if result.has_hull() { if let Some(position) = result.pick_position { @@ -738,18 +740,21 @@ impl CameraController { } } else if !only_meshes { // Hull-less objects (light sources, cameras, etc.) can still be selected - // by coarse intersection test results. - let da = points[0].metric_distance(&ray.origin); - let db = points[1].metric_distance(&ray.origin); - let closest_distance = da.min(db); - context.pick_list.push(CameraPickResult { - position: transform_vertex( - if da < db { points[0] } else { points[1] }, - &node.global_transform(), - ), - node: handle, - toi: closest_distance, - }); + // by coarse intersection test with a simplified bounding box. + let simple_aabb = AxisAlignedBoundingBox::from_radius( + settings.hull_less_object_selection_radius, + ) + .transform(&node.global_transform()); + if let Some(points) = ray.aabb_intersection_points(&simple_aabb) { + let da = points[0].metric_distance(&ray.origin); + let db = points[1].metric_distance(&ray.origin); + let closest_distance = da.min(db); + context.pick_list.push(CameraPickResult { + position: if da < db { points[0] } else { points[1] }, + node: handle, + toi: closest_distance, + }); + } } } } diff --git a/editor/src/interaction/move_mode.rs b/editor/src/interaction/move_mode.rs index 98c413816..11fb1cf10 100644 --- a/editor/src/interaction/move_mode.rs +++ b/editor/src/interaction/move_mode.rs @@ -215,6 +215,7 @@ impl MoveContext { // We need info only about closest intersection. use_picking_loop: false, only_meshes: true, + settings: &settings.selection, }, ) { Some(result.position) @@ -309,7 +310,7 @@ impl InteractionMode for MoveInteractionMode { engine: &mut Engine, mouse_pos: Vector2, frame_size: Vector2, - _settings: &Settings, + settings: &Settings, ) { let Some(game_scene) = controller.downcast_mut::() else { return; @@ -327,6 +328,7 @@ impl InteractionMode for MoveInteractionMode { ignore_back_faces: false, use_picking_loop: true, only_meshes: false, + settings: &settings.selection, }, ) { if let Some(plane_kind) = self.move_gizmo.handle_pick(result.node, graph) { @@ -405,6 +407,7 @@ impl InteractionMode for MoveInteractionMode { ignore_back_faces: settings.selection.ignore_back_faces, use_picking_loop: true, only_meshes: false, + settings: &settings.selection, }, ) .map(|result| { @@ -470,6 +473,7 @@ impl InteractionMode for MoveInteractionMode { ignore_back_faces: false, use_picking_loop: false, only_meshes: false, + settings: &settings.selection, }, ) .map(|r| r.node) diff --git a/editor/src/interaction/navmesh/mod.rs b/editor/src/interaction/navmesh/mod.rs index 2a0193832..20db392f9 100644 --- a/editor/src/interaction/navmesh/mod.rs +++ b/editor/src/interaction/navmesh/mod.rs @@ -270,6 +270,7 @@ impl InteractionMode for EditNavmeshMode { ignore_back_faces: false, use_picking_loop: true, only_meshes: false, + settings: &settings.selection, }, ) .map(|r| r.node) @@ -414,7 +415,7 @@ impl InteractionMode for EditNavmeshMode { controller: &mut dyn SceneController, engine: &mut Engine, frame_size: Vector2, - _settings: &Settings, + settings: &Settings, ) { let Some(game_scene) = controller.downcast_mut::() else { return; @@ -435,6 +436,7 @@ impl InteractionMode for EditNavmeshMode { ignore_back_faces: false, use_picking_loop: true, only_meshes: false, + settings: &settings.selection, }, ) .map(|r| r.node) diff --git a/editor/src/interaction/rotate_mode.rs b/editor/src/interaction/rotate_mode.rs index bc9d3cd5f..b7e03483c 100644 --- a/editor/src/interaction/rotate_mode.rs +++ b/editor/src/interaction/rotate_mode.rs @@ -79,7 +79,7 @@ impl InteractionMode for RotateInteractionMode { engine: &mut Engine, mouse_pos: Vector2, _frame_size: Vector2, - _settings: &Settings, + settings: &Settings, ) { let Some(game_scene) = controller.downcast_mut::() else { return; @@ -97,6 +97,7 @@ impl InteractionMode for RotateInteractionMode { ignore_back_faces: false, use_picking_loop: true, only_meshes: false, + settings: &settings.selection, }, ) { if self.rotation_gizmo.handle_pick(result.node, graph) { @@ -163,6 +164,7 @@ impl InteractionMode for RotateInteractionMode { ignore_back_faces: settings.selection.ignore_back_faces, use_picking_loop: true, only_meshes: false, + settings: &settings.selection, }, ) .map(|result| { @@ -248,6 +250,7 @@ impl InteractionMode for RotateInteractionMode { ignore_back_faces: false, use_picking_loop: false, only_meshes: false, + settings: &settings.selection, }, ) .map(|r| r.node) diff --git a/editor/src/interaction/scale_mode.rs b/editor/src/interaction/scale_mode.rs index 99a619abb..5edf7193d 100644 --- a/editor/src/interaction/scale_mode.rs +++ b/editor/src/interaction/scale_mode.rs @@ -78,7 +78,7 @@ impl InteractionMode for ScaleInteractionMode { engine: &mut Engine, mouse_pos: Vector2, _frame_size: Vector2, - _settings: &Settings, + settings: &Settings, ) { let Some(game_scene) = controller.downcast_mut::() else { return; @@ -97,6 +97,7 @@ impl InteractionMode for ScaleInteractionMode { ignore_back_faces: false, use_picking_loop: true, only_meshes: false, + settings: &settings.selection, }, ) { if self.scale_gizmo.handle_pick(result.node, graph) { @@ -157,6 +158,7 @@ impl InteractionMode for ScaleInteractionMode { ignore_back_faces: settings.selection.ignore_back_faces, use_picking_loop: true, only_meshes: false, + settings: &settings.selection, }, ) .map(|result| { @@ -192,7 +194,7 @@ impl InteractionMode for ScaleInteractionMode { controller: &mut dyn SceneController, engine: &mut Engine, frame_size: Vector2, - _settings: &Settings, + settings: &Settings, ) { let Some(game_scene) = controller.downcast_mut::() else { return; @@ -229,6 +231,7 @@ impl InteractionMode for ScaleInteractionMode { ignore_back_faces: false, use_picking_loop: false, only_meshes: false, + settings: &settings.selection, }, ) .map(|r| r.node) diff --git a/editor/src/plugins/collider/mod.rs b/editor/src/plugins/collider/mod.rs index ee87dda10..8c3bc5ed4 100644 --- a/editor/src/plugins/collider/mod.rs +++ b/editor/src/plugins/collider/mod.rs @@ -376,7 +376,7 @@ impl InteractionMode for ColliderShapeInteractionMode { engine: &mut Engine, mouse_position: Vector2, _frame_size: Vector2, - _settings: &Settings, + settings: &Settings, ) { let Some(game_scene) = controller.downcast_mut::() else { return; @@ -390,7 +390,10 @@ impl InteractionMode for ColliderShapeInteractionMode { cursor_pos: mouse_position, editor_only: true, filter: Some(&mut |handle, _| handle != self.move_gizmo.origin), - ..Default::default() + ignore_back_faces: false, + use_picking_loop: false, + only_meshes: false, + settings: &settings.selection, }, ) { let initial_position = scene.graph[result.node].global_position(); @@ -507,7 +510,7 @@ impl InteractionMode for ColliderShapeInteractionMode { controller: &mut dyn SceneController, engine: &mut Engine, frame_size: Vector2, - _settings: &Settings, + settings: &Settings, ) { let Some(game_scene) = controller.downcast_mut::() else { return; @@ -524,7 +527,10 @@ impl InteractionMode for ColliderShapeInteractionMode { cursor_pos: mouse_position, editor_only: true, filter: Some(&mut |handle, _| handle != self.move_gizmo.origin), - ..Default::default() + ignore_back_faces: false, + use_picking_loop: false, + only_meshes: false, + settings: &settings.selection, }, ) { if self.shape_gizmo.has_handle(result.node) { diff --git a/editor/src/scene/mod.rs b/editor/src/scene/mod.rs index 8d3f19dd0..418de92ca 100644 --- a/editor/src/scene/mod.rs +++ b/editor/src/scene/mod.rs @@ -635,6 +635,7 @@ impl SceneController for GameScene { // We need info only about closest intersection. use_picking_loop: false, only_meshes: false, + settings: &settings.selection, }, ) { Some(result.position) @@ -721,6 +722,7 @@ impl SceneController for GameScene { ignore_back_faces: settings.selection.ignore_back_faces, use_picking_loop: true, only_meshes: false, + settings: &settings.selection, }, ) { let texture = tex.clone(); diff --git a/editor/src/settings/selection.rs b/editor/src/settings/selection.rs index e64b8381c..8db82e7a7 100644 --- a/editor/src/settings/selection.rs +++ b/editor/src/settings/selection.rs @@ -21,19 +21,27 @@ use crate::fyrox::core::reflect::prelude::*; use serde::{Deserialize, Serialize}; -#[derive(Deserialize, Serialize, PartialEq, Clone, Debug, Reflect, Eq)] +#[derive(Deserialize, Serialize, PartialEq, Clone, Debug, Reflect)] pub struct SelectionSettings { pub ignore_back_faces: bool, + #[serde(default = "default_hull_less_object_selection_radius")] + pub hull_less_object_selection_radius: f32, + // Hidden because there's a separate switch in world viewer for this. #[reflect(hidden)] pub track_selection: bool, } +fn default_hull_less_object_selection_radius() -> f32 { + 0.33 +} + impl Default for SelectionSettings { fn default() -> Self { Self { ignore_back_faces: false, + hull_less_object_selection_radius: default_hull_less_object_selection_radius(), track_selection: true, } } diff --git a/fyrox-impl/src/scene/base.rs b/fyrox-impl/src/scene/base.rs index 12a0eba4c..ca5e06fc4 100644 --- a/fyrox-impl/src/scene/base.rs +++ b/fyrox-impl/src/scene/base.rs @@ -742,7 +742,7 @@ impl Base { /// a placeholder, because there is not information to calculate actual bounding box. #[inline] pub fn local_bounding_box(&self) -> AxisAlignedBoundingBox { - AxisAlignedBoundingBox::default() + AxisAlignedBoundingBox::unit() } /// Returns current **world-space** bounding box.