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
This commit is contained in:
Dmitry Stepanov
2025-05-12 22:16:41 +03:00
parent 1a6234780a
commit 21c460afa8
9 changed files with 58 additions and 25 deletions
+19 -14
View File
@@ -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>, &Node) -> bool>;
#[derive(Default)]
pub struct PickingOptions<'a> {
pub cursor_pos: Vector2<f32>,
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::<Camera>() {
@@ -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,
});
}
}
}
}
+5 -1
View File
@@ -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<f32>,
frame_size: Vector2<f32>,
_settings: &Settings,
settings: &Settings,
) {
let Some(game_scene) = controller.downcast_mut::<GameScene>() 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)
+3 -1
View File
@@ -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<f32>,
_settings: &Settings,
settings: &Settings,
) {
let Some(game_scene) = controller.downcast_mut::<GameScene>() 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)
+4 -1
View File
@@ -79,7 +79,7 @@ impl InteractionMode for RotateInteractionMode {
engine: &mut Engine,
mouse_pos: Vector2<f32>,
_frame_size: Vector2<f32>,
_settings: &Settings,
settings: &Settings,
) {
let Some(game_scene) = controller.downcast_mut::<GameScene>() 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)
+5 -2
View File
@@ -78,7 +78,7 @@ impl InteractionMode for ScaleInteractionMode {
engine: &mut Engine,
mouse_pos: Vector2<f32>,
_frame_size: Vector2<f32>,
_settings: &Settings,
settings: &Settings,
) {
let Some(game_scene) = controller.downcast_mut::<GameScene>() 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<f32>,
_settings: &Settings,
settings: &Settings,
) {
let Some(game_scene) = controller.downcast_mut::<GameScene>() 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)
+10 -4
View File
@@ -376,7 +376,7 @@ impl InteractionMode for ColliderShapeInteractionMode {
engine: &mut Engine,
mouse_position: Vector2<f32>,
_frame_size: Vector2<f32>,
_settings: &Settings,
settings: &Settings,
) {
let Some(game_scene) = controller.downcast_mut::<GameScene>() 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<f32>,
_settings: &Settings,
settings: &Settings,
) {
let Some(game_scene) = controller.downcast_mut::<GameScene>() 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) {
+2
View File
@@ -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();
+9 -1
View File
@@ -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,
}
}
+1 -1
View File
@@ -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.