aevyrie / bevy_mod_picking

Picking and pointer events for Bevy.
https://crates.io/crates/bevy_mod_picking
Apache License 2.0
760 stars 169 forks source link

Picking doesnt work on adjacent 2d meshes (`backend_raycast`) #341

Open Zizico2 opened 2 months ago

Zizico2 commented 2 months ago

In 2d, when I have 2 Meshes that are exactly adjacent to each other, I can position the mouse right on the edge between them, and the click won't be captured. Is this how it is supposed to work? User error?

For example, creating a chessboard like this (every square is exactly 50 wide, and 50 away from each other, you can put the mouse right between the squares and the click won't be recognized.

use bevy::{
    prelude::*,
    sprite::{MaterialMesh2dBundle, Mesh2dHandle},
    window::WindowResolution,
};
use bevy_mod_picking::prelude::*;
#[derive(Component)]
struct MainCamera;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins.set(WindowPlugin {
            primary_window: Some(Window {
                resolution: WindowResolution::new(400., 400.).with_scale_factor_override(1.0),
                resizable: false,
                ..default()
            }),
            ..default()
        }))
        .add_plugins(DefaultPickingPlugins)
        .add_systems(Startup, setup)
        .run();
}

fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<ColorMaterial>>,
) {
    commands.spawn((
        Camera2dBundle {
            transform: Transform {
                translation: Vec3 {
                    x: 200.,
                    y: 200.,
                    z: 0.,
                },
                ..default()
            },
            ..default()
        },
        MainCamera,
    ));

    let mut color = Color::BEIGE;
    for x in 0..8_usize {
        for y in 0..8_usize {
            commands.spawn((
                PickableBundle::default(),
                On::<Pointer<Click>>::run(move || {
                    info!("({x}, {y})");
                }),
                MaterialMesh2dBundle {
                    mesh: Mesh2dHandle(meshes.add(Rectangle::new(50.0, 50.0))),
                    transform: Transform::from_translation(Vec3 {
                        x: ((x * 50) + 25) as f32,
                        y: ((y * 50) + 25) as f32,
                        z: 0.,
                    }),
                    material: materials.add(color),
                    ..default()
                },
            ));
            match color {
                Color::BEIGE => color = Color::DARK_GREEN,
                Color::DARK_GREEN => color = Color::BEIGE,
                _ => panic!("wtf is going on"),
            }
        }
        match color {
            Color::BEIGE => color = Color::DARK_GREEN,
            Color::DARK_GREEN => color = Color::BEIGE,
            _ => panic!("wtf is going on"),
        }
    }
}
[dependencies]
bevy = { version = "0.13.2" }
bevy_mod_picking = { version = "0.19.0", features = [
    "backend_raycast",
], default-features = false }
StrikeForceZero commented 1 month ago

I can reproduce it with your example. If I had to guess, it's some kind of floating-point math error, but when logging the cursor position and x/y, I get whole numbers, so I'm not entirely sure.

When the stars align, the ray cast appears like it's failing to resolve anything on some of the squares (note "some" as it only happens to the right-hand side of the board for me, so maybe as x increases, the margin of error increases?). Still, if you move everything by 1, it no longer has an issue AFAICT. Alternatively, if you set your camera translation to (0,0,0), it appears to be fine, at least until you maximize the window. Maybe it's something to when coordinates get to a specific size?

Vertical adjacencies don't seem to be an issue, or maybe you need a larger grid to see them.

Modified Example with input mapped to moving the camera and boards ```rust use std::fmt::Debug; use bevy::{ prelude::*, sprite::{MaterialMesh2dBundle, Mesh2dHandle}, window::WindowResolution, }; use bevy::color::palettes::css::{BEIGE, DARK_GREEN}; use bevy::window::PrimaryWindow; use bevy_mod_picking::prelude::*; #[derive(Component, Debug)] struct MainCamera; #[derive(Component, Debug)] struct Board; #[derive(Component)] struct Moveable; fn is_key_pressed_factory(key_code: KeyCode) -> impl Fn(Res>) -> bool { move |input: Res>| -> bool { input.just_pressed(key_code) } } fn main() { App::new() .add_plugins(DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { resolution: WindowResolution::new(800., 400.).with_scale_factor_override(1.0), // resizable: false, ..default() }), ..default() })) .add_plugins(DefaultPickingPlugins) .add_systems(Startup, setup) .add_systems( Update, move_left::.run_if(is_key_pressed_factory(KeyCode::KeyA)), ) .add_systems( Update, move_right::.run_if(is_key_pressed_factory(KeyCode::KeyD)), ) .add_systems( Update, move_left::.run_if(is_key_pressed_factory(KeyCode::KeyQ)), ) .add_systems( Update, move_right::.run_if(is_key_pressed_factory(KeyCode::KeyE)), ) .add_systems(Update, log_click) .run(); } fn move_right(mut q: Query<(&T, &mut Transform), With>) { for (comp, mut transform) in q.iter_mut() { println!("moving {comp:?} right"); transform.translation.x += 1.0; } } fn move_left(mut q: Query<(&T, &mut Transform), With>) { for (comp, mut transform) in q.iter_mut() { println!("moving {comp:?} left"); transform.translation.x -= 1.0; } } fn setup( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, ) { let camera_entity = commands .spawn(( Camera2dBundle { transform: Transform::from_xyz(200.0, 200.0, 0.0), ..default() }, MainCamera, Moveable, )) .id(); let square_size = 50.0; let width = 8usize; let height = 8usize; let mesh = Mesh2dHandle(meshes.add(Rectangle::new(square_size, square_size))); let dark_mat = materials.add(Color::from(BEIGE)); let light_mat = materials.add(Color::from(DARK_GREEN)); fn is_dark_square(x: usize, y: usize) -> bool { x % 2 == 0 && y % 2 == 0 || x % 2 != 0 && y % 2 != 0 } let mut render_board = |offset: Vec2, movable: bool| { let mut entity = commands.spawn((Board, SpatialBundle::default())); entity.with_children(|parent| { for x in 0..width { for y in 0..height { let material = if is_dark_square(x, y) { dark_mat.clone() } else { light_mat.clone() }; parent.spawn(( PickableBundle::default(), On::>::run(move || { info!("({x}, {y})"); }), MaterialMesh2dBundle { mesh: mesh.clone(), transform: Transform::from_translation(Vec3 { x: x as f32 * square_size + offset.x, y: y as f32 * square_size + offset.y, z: 1.0, }), material, ..default() }, )); } } }); if movable { entity.insert(Moveable); } }; render_board(Vec2::new(0.0, 0.0), true); // render_board(Vec2::new(-square_size * width as f32, 0.0), false); // render_board(Vec2::new(square_size * width as f32 / 2.0, 0.0), true); let mut ui = commands.spawn(( TargetCamera(camera_entity), NodeBundle { style: Style { width: Val::Percent(100.0), flex_direction: FlexDirection::Row, ..default() }, ..default() }, )); ui.with_children(|parent| { parent.spawn(TextBundle { text: Text::from_section( "KeyMap: A/D = board, Q/E = camera", TextStyle { font_size: 20.0, color: Color::WHITE, ..default() }, ), ..default() }); }); } fn log_click(input: Res>, q_windows: Query<&Window, With>) { if input.just_pressed(MouseButton::Left) { if let Some(position) = q_windows.single().cursor_position() { println!("Cursor is inside the primary window, at {:?}", position); } else { println!("Cursor is not in the game window."); } } } ```
StrikeForceZero commented 1 month ago

I believe I have found the potential root cause and opened https://github.com/aevyrie/bevy_mod_raycast/issues/118