Skip to content

Commit 0c90f04

Browse files
authored
Add Automatic Directional Navigation Graph Generation (#21668)
## Objective Resolves #21661 Adds automatic directional navigation graph generation based on UI node positions and sizes, eliminating the need for tedious manual graph construction in dynamic UIs. ## Solution Implements a spatial navigation algorithm that automatically computes the nearest neighbor in each compass direction for UI elements, while respecting any manually-defined edges. ### Features - **Automatic edge generation**: Finds the best neighbor in each of 8 compass directions based on distance, alignment, and overlap - **Manual override support**: Manual edges always take precedence over auto-generated ones - **Configurable**: `AutoNavigationConfig` resource allows tuning alignment requirements, distance limits, and preference weighting - **Opt-in**: Entities must have `AutoDirectionalNavigation` component added to use, and therefore **not** a breaking change - **Generic**: Core algorithm works with any `Vec2` position/size data, not just `bevy_ui` ### Implementation **New Components & Resources** (`bevy_input_focus/src/directional_navigation.rs`): - `AutoDirectionalNavigation` - Marker component to enable auto-navigation - `AutoNavigationConfig` - Configuration resource with settings: - `min_alignment_factor`: Minimum perpendicular overlap (0.0-1.0) required for cardinal directions - `max_search_distance`: Optional distance limit for connections - `prefer_aligned`: Whether to strongly prefer well-aligned nodes **Core Algorithm**: ```rust pub fn auto_generate_navigation_edges( nav_map: &mut DirectionalNavigationMap, nodes: &[(Entity, Vec2, Vec2)], // (entity, center_pos, size) config: &AutoNavigationConfig, ) ``` For each node and each direction: 1. Filter candidates that are actually in that direction (cone-based check) 2. Calculate overlap factor for cardinal directions (horizontal overlap for N/S, vertical for E/W) 3. Score candidates based on: - Distance (closer is better) - Alignment with direction vector (more aligned is better) - Overlap factor (must meet minimum threshold) 4. Select the best-scoring candidate as the neighbor **Scoring Formula**: ``` score = distance + alignment_penalty where alignment_penalty = (1.0 - alignment) * distance * 2.0 ``` This makes misaligned nodes significantly less attractive while still considering distance. ### Usage **Before (manual)**: ```rust // Must manually specify all connections for row in 0..N_ROWS { let entities_in_row: Vec<Entity> = (0..N_COLS) .map(|col| button_entities.get(&(row, col)).unwrap()) .copied() .collect(); directional_nav_map.add_looping_edges(&entities_in_row, CompassOctant::East); } // Repeat for columns... for col in 0..N_COLS { let entities_in_column: Vec<Entity> = (0..N_ROWS) .map(|row| button_entities.get(&(row, col)).unwrap()) .copied() .collect(); directional_nav_map.add_edges(&entities_in_column, CompassOctant::South); } ``` **After**: ```rust // Just add the `AutoDirectionalNavigation` component! commands.spawn(( Button, Node { /* ... */ }, AutoDirectionalNavigation::default(), // ... other components )); ``` ## Testing - Added new example: `auto_directional_navigation` - Ran existing `directional_navigation` ## Showcase ### New Example: `auto_directional_navigation` Demonstrates automatic navigation with irregularly-positioned buttons. Unlike a regular grid, these buttons are scattered, but auto-navigation figures out the correct connections - also shows currently focused button, and the last "input" pressed to show the logical flow of navigating: ```bash cargo run --example auto_directional_navigation ``` https://github.com/user-attachments/assets/8dce1d8d-53b5-41b8-bc9a-7e32067f7978 **Key differences from manual `directional_navigation` example**: - No manual `add_edges()` or `add_looping_edges()` calls - Buttons positioned irregularly (not in a perfect grid) - Works with absolute positioning and dynamic layouts ## Migration Guide No breaking changes - this is a purely additive feature. To adopt automatic navigation: 1. Add `AutoDirectionalNavigation` component to focusable entities 2. Optionally configure `AutoNavigationConfig` resource
1 parent 2facb25 commit 0c90f04

File tree

8 files changed

+1385
-5
lines changed

8 files changed

+1385
-5
lines changed

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4704,6 +4704,17 @@ description = "Demonstration of Directional Navigation between UI elements"
47044704
category = "UI (User Interface)"
47054705
wasm = true
47064706

4707+
[[example]]
4708+
name = "auto_directional_navigation"
4709+
path = "examples/ui/auto_directional_navigation.rs"
4710+
doc-scrape-examples = true
4711+
4712+
[package.metadata.example.auto_directional_navigation]
4713+
name = "Automatic Directional Navigation"
4714+
description = "Demonstration of automatic directional navigation graph generation based on UI element positions"
4715+
category = "UI (User Interface)"
4716+
wasm = true
4717+
47074718
[[example]]
47084719
name = "clustered_decals"
47094720
path = "examples/3d/clustered_decals.rs"

crates/bevy_input_focus/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,12 @@ libm = ["bevy_math/libm", "bevy_window/libm"]
6161
[dependencies]
6262
# bevy
6363
bevy_app = { path = "../bevy_app", version = "0.18.0-dev", default-features = false }
64+
bevy_camera = { path = "../bevy_camera", version = "0.18.0-dev", default-features = false }
6465
bevy_ecs = { path = "../bevy_ecs", version = "0.18.0-dev", default-features = false }
6566
bevy_input = { path = "../bevy_input", version = "0.18.0-dev", default-features = false }
6667
bevy_math = { path = "../bevy_math", version = "0.18.0-dev", default-features = false }
6768
bevy_picking = { path = "../bevy_picking", version = "0.18.0-dev", default-features = false, optional = true }
69+
bevy_ui = { path = "../bevy_ui", version = "0.18.0-dev", default-features = false }
6870
bevy_window = { path = "../bevy_window", version = "0.18.0-dev", default-features = false }
6971
bevy_reflect = { path = "../bevy_reflect", version = "0.18.0-dev", features = [
7072
"glam",

0 commit comments

Comments
 (0)