Skip to content

Mesh picking does not work with manual mesh #17987

@notmd

Description

@notmd

Bevy version

Main

What you did

I modify mesh2d_manual example to add MeshPickingPlugin and a handler

Code ```rs //! This example shows how to manually render 2d items using "mid level render apis" with a custom //! pipeline for 2d meshes. //! It doesn't use the [`Material2d`] abstraction, but changes the vertex buffer to include vertex color. //! Check out the "mesh2d" example for simpler / higher level 2d meshes. //! //! [`Material2d`]: bevy::sprite::Material2d

use bevy::{
asset::weak_handle,
color::palettes::basic::YELLOW,
core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT},
math::{ops, FloatOrd},
prelude::*,
render::{
mesh::{Indices, MeshVertexAttribute, RenderMesh},
render_asset::{RenderAssetUsages, RenderAssets},
render_phase::{
AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline,
ViewSortedRenderPhases,
},
render_resource::{
BlendState, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState,
DepthStencilState, Face, FragmentState, FrontFace, MultisampleState, PipelineCache,
PolygonMode, PrimitiveState, PrimitiveTopology, RenderPipelineDescriptor,
SpecializedRenderPipeline, SpecializedRenderPipelines, StencilFaceState, StencilState,
TextureFormat, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode,
},
sync_world::MainEntityHashMap,
view::{ExtractedView, RenderVisibleEntities, ViewTarget},
Extract, Render, RenderApp, RenderSet,
},
sprite::{
extract_mesh2d, DrawMesh2d, Material2dBindGroupId, Mesh2dPipeline, Mesh2dPipelineKey,
Mesh2dTransforms, MeshFlags, RenderMesh2dInstance, SetMesh2dBindGroup,
SetMesh2dViewBindGroup,
},
};
use std::f32::consts::PI;

fn main() {
App::new()
.add_plugins((DefaultPlugins, ColoredMesh2dPlugin, MeshPickingPlugin))
.add_systems(Startup, star)
.run();
}

fn star(
mut commands: Commands,
// We will add a new Mesh for the star being created
mut meshes: ResMut<Assets>,
) {
// Let's define the mesh for the object we want to draw: a nice star.
// We will specify here what kind of topology is used to define the mesh,
// that is, how triangles are built from the vertices. We will use a
// triangle list, meaning that each vertex of the triangle has to be
// specified. We set RenderAssetUsages::RENDER_WORLD, meaning this mesh
// will not be accessible in future frames from the meshes resource, in
// order to save on memory once it has been uploaded to the GPU.
let mut star = Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::RENDER_WORLD,
);

// Vertices need to have a position attribute. We will use the following
// vertices (I hope you can spot the star in the schema).
//
//        1
//
//     10   2
// 9      0      3
//     8     4
//        6
//   7        5
//
// These vertices are specified in 3D space.
let mut v_pos = vec![[0.0, 0.0, 0.0]];
for i in 0..10 {
    // The angle between each vertex is 1/10 of a full rotation.
    let a = i as f32 * PI / 5.0;
    // The radius of inner vertices (even indices) is 100. For outer vertices (odd indices) it's 200.
    let r = (1 - i % 2) as f32 * 100.0 + 100.0;
    // Add the vertex position.
    v_pos.push([r * ops::sin(a), r * ops::cos(a), 0.0]);
}
// Set the position attribute
star.insert_attribute(Mesh::ATTRIBUTE_POSITION, v_pos);
// And a RGB color attribute as well. A built-in `Mesh::ATTRIBUTE_COLOR` exists, but we
// use a custom vertex attribute here for demonstration purposes.
let mut v_color: Vec<u32> = vec![LinearRgba::BLACK.as_u32()];
v_color.extend_from_slice(&[LinearRgba::from(YELLOW).as_u32(); 10]);
star.insert_attribute(
    MeshVertexAttribute::new("Vertex_Color", 1, VertexFormat::Uint32),
    v_color,
);

// Now, we specify the indices of the vertex that are going to compose the
// triangles in our star. Vertices in triangles have to be specified in CCW
// winding (that will be the front face, colored). Since we are using
// triangle list, we will specify each triangle as 3 vertices
//   First triangle: 0, 2, 1
//   Second triangle: 0, 3, 2
//   Third triangle: 0, 4, 3
//   etc
//   Last triangle: 0, 1, 10
let mut indices = vec![0, 1, 10];
for i in 2..=10 {
    indices.extend_from_slice(&[0, i, i - 1]);
}
star.insert_indices(Indices::U32(indices));

// We can now spawn the entities for the star and the camera
commands
    .spawn((
        // We use a marker component to identify the custom colored meshes
        ColoredMesh2d,
        // The `Handle<Mesh>` needs to be wrapped in a `Mesh2d` for 2D rendering
        Mesh2d(meshes.add(star)),
    ))
    .observe(|trigger: Trigger<Pointer<Over>>| {
        info!("mouse over");
    });

commands.spawn(Camera2d);

}

/// A marker component for colored 2d meshes
#[derive(Component, Default)]
pub struct ColoredMesh2d;

/// Custom pipeline for 2d meshes with vertex colors
#[derive(Resource)]
pub struct ColoredMesh2dPipeline {
/// This pipeline wraps the standard [Mesh2dPipeline]
mesh2d_pipeline: Mesh2dPipeline,
}

impl FromWorld for ColoredMesh2dPipeline {
fn from_world(world: &mut World) -> Self {
Self {
mesh2d_pipeline: Mesh2dPipeline::from_world(world),
}
}
}

// We implement SpecializedPipeline to customize the default rendering from Mesh2dPipeline
impl SpecializedRenderPipeline for ColoredMesh2dPipeline {
type Key = Mesh2dPipelineKey;

fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
    // Customize how to store the meshes' vertex attributes in the vertex buffer
    // Our meshes only have position and color
    let formats = vec![
        // Position
        VertexFormat::Float32x3,
        // Color
        VertexFormat::Uint32,
    ];

    let vertex_layout =
        VertexBufferLayout::from_vertex_formats(VertexStepMode::Vertex, formats);

    let format = match key.contains(Mesh2dPipelineKey::HDR) {
        true => ViewTarget::TEXTURE_FORMAT_HDR,
        false => TextureFormat::bevy_default(),
    };

    RenderPipelineDescriptor {
        vertex: VertexState {
            // Use our custom shader
            shader: COLORED_MESH2D_SHADER_HANDLE,
            entry_point: "vertex".into(),
            shader_defs: vec![],
            // Use our custom vertex buffer
            buffers: vec![vertex_layout],
        },
        fragment: Some(FragmentState {
            // Use our custom shader
            shader: COLORED_MESH2D_SHADER_HANDLE,
            shader_defs: vec![],
            entry_point: "fragment".into(),
            targets: vec![Some(ColorTargetState {
                format,
                blend: Some(BlendState::ALPHA_BLENDING),
                write_mask: ColorWrites::ALL,
            })],
        }),
        // Use the two standard uniforms for 2d meshes
        layout: vec![
            // Bind group 0 is the view uniform
            self.mesh2d_pipeline.view_layout.clone(),
            // Bind group 1 is the mesh uniform
            self.mesh2d_pipeline.mesh_layout.clone(),
        ],
        push_constant_ranges: vec![],
        primitive: PrimitiveState {
            front_face: FrontFace::Ccw,
            cull_mode: Some(Face::Back),
            unclipped_depth: false,
            polygon_mode: PolygonMode::Fill,
            conservative: false,
            topology: key.primitive_topology(),
            strip_index_format: None,
        },
        depth_stencil: Some(DepthStencilState {
            format: CORE_2D_DEPTH_FORMAT,
            depth_write_enabled: false,
            depth_compare: CompareFunction::GreaterEqual,
            stencil: StencilState {
                front: StencilFaceState::IGNORE,
                back: StencilFaceState::IGNORE,
                read_mask: 0,
                write_mask: 0,
            },
            bias: DepthBiasState {
                constant: 0,
                slope_scale: 0.0,
                clamp: 0.0,
            },
        }),
        multisample: MultisampleState {
            count: key.msaa_samples(),
            mask: !0,
            alpha_to_coverage_enabled: false,
        },
        label: Some("colored_mesh2d_pipeline".into()),
        zero_initialize_workgroup_memory: false,
    }
}

}

// This specifies how to render a colored 2d mesh
type DrawColoredMesh2d = (
// Set the pipeline
SetItemPipeline,
// Set the view uniform as bind group 0
SetMesh2dViewBindGroup<0>,
// Set the mesh uniform as bind group 1
SetMesh2dBindGroup<1>,
// Draw the mesh
DrawMesh2d,
);

// The custom shader can be inline like here, included from another file at build time
// using include_str!(), or loaded like any other asset with asset_server.load().
const COLORED_MESH2D_SHADER: &str = r"
// Import the standard 2d mesh uniforms and set their bind groups
#import bevy_sprite::mesh2d_functions

// The structure of the vertex buffer is as specified in specialize()
struct Vertex {
@Builtin(instance_index) instance_index: u32,
@location(0) position: vec3,
@location(1) color: u32,
};

struct VertexOutput {
// The vertex shader must set the on-screen position of the vertex
@Builtin(position) clip_position: vec4,
// We pass the vertex color to the fragment shader in location 0
@location(0) color: vec4,
};

/// Entry point for the vertex shader
@vertex
fn vertex(vertex: Vertex) -> VertexOutput {
var out: VertexOutput;
// Project the world position of the mesh into screen position
let model = mesh2d_functions::get_world_from_local(vertex.instance_index);
out.clip_position = mesh2d_functions::mesh2d_position_local_to_clip(model, vec4(vertex.position, 1.0));
// Unpack the u32 from the vertex buffer into the vec4<f32> used by the fragment shader
out.color = vec4((vec4(vertex.color) >> vec4(0u, 8u, 16u, 24u)) & vec4(255u)) / 255.0;
return out;
}

// The input of the fragment shader must correspond to the output of the vertex shader for all locations
struct FragmentInput {
// The color is interpolated between vertices by default
@location(0) color: vec4,
};

/// Entry point for the fragment shader
@Fragment
fn fragment(in: FragmentInput) -> @location(0) vec4 {
return in.color;
}
";

/// Plugin that renders [ColoredMesh2d]s
pub struct ColoredMesh2dPlugin;

/// Handle to the custom shader with a unique random ID
pub const COLORED_MESH2D_SHADER_HANDLE: Handle =
weak_handle!("f48b148f-7373-4638-9900-392b3b3ccc66");

/// Our custom pipeline needs its own instance storage
#[derive(Resource, Deref, DerefMut, Default)]
pub struct RenderColoredMesh2dInstances(MainEntityHashMap);

impl Plugin for ColoredMesh2dPlugin {
fn build(&self, app: &mut App) {
// Load our custom shader
let mut shaders = app.world_mut().resource_mut::<Assets>();
shaders.insert(
&COLORED_MESH2D_SHADER_HANDLE,
Shader::from_wgsl(COLORED_MESH2D_SHADER, file!()),
);

    // Register our custom draw function, and add our render systems
    app.get_sub_app_mut(RenderApp)
        .unwrap()
        .add_render_command::<Transparent2d, DrawColoredMesh2d>()
        .init_resource::<SpecializedRenderPipelines<ColoredMesh2dPipeline>>()
        .init_resource::<RenderColoredMesh2dInstances>()
        .add_systems(
            ExtractSchedule,
            extract_colored_mesh2d.after(extract_mesh2d),
        )
        .add_systems(Render, queue_colored_mesh2d.in_set(RenderSet::QueueMeshes));
}

fn finish(&self, app: &mut App) {
    // Register our custom pipeline
    app.get_sub_app_mut(RenderApp)
        .unwrap()
        .init_resource::<ColoredMesh2dPipeline>();
}

}

/// Extract the [ColoredMesh2d] marker component into the render app
pub fn extract_colored_mesh2d(
mut commands: Commands,
mut previous_len: Local,
// When extracting, you must use Extract to mark the SystemParams
// which should be taken from the main world.
query: Extract<
Query<(Entity, &ViewVisibility, &GlobalTransform, &Mesh2d), With>,
>,
mut render_mesh_instances: ResMut,
) {
let mut values = Vec::with_capacity(*previous_len);
for (entity, view_visibility, transform, handle) in &query {
if !view_visibility.get() {
continue;
}

    let transforms = Mesh2dTransforms {
        world_from_local: (&transform.affine()).into(),
        flags: MeshFlags::empty().bits(),
    };

    values.push((entity, ColoredMesh2d));
    render_mesh_instances.insert(
        entity.into(),
        RenderMesh2dInstance {
            mesh_asset_id: handle.0.id(),
            transforms,
            material_bind_group_id: Material2dBindGroupId::default(),
            automatic_batching: false,
            tag: 0,
        },
    );
}
*previous_len = values.len();
commands.insert_or_spawn_batch(values);

}

/// Queue the 2d meshes marked with [ColoredMesh2d] using our custom pipeline and draw function
pub fn queue_colored_mesh2d(
transparent_draw_functions: Res<DrawFunctions>,
colored_mesh2d_pipeline: Res,
mut pipelines: ResMut<SpecializedRenderPipelines>,
pipeline_cache: Res,
render_meshes: Res<RenderAssets>,
render_mesh_instances: Res,
mut transparent_render_phases: ResMut<ViewSortedRenderPhases>,
views: Query<(&RenderVisibleEntities, &ExtractedView, &Msaa)>,
) {
if render_mesh_instances.is_empty() {
return;
}
// Iterate each view (a camera is a view)
for (visible_entities, view, msaa) in &views {
let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
else {
continue;
};

    let draw_colored_mesh2d = transparent_draw_functions.read().id::<DrawColoredMesh2d>();

    let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
        | Mesh2dPipelineKey::from_hdr(view.hdr);

    // Queue all entities visible to that view
    for (render_entity, visible_entity) in visible_entities.iter::<Mesh2d>() {
        if let Some(mesh_instance) = render_mesh_instances.get(visible_entity) {
            let mesh2d_handle = mesh_instance.mesh_asset_id;
            let mesh2d_transforms = &mesh_instance.transforms;
            // Get our specialized pipeline
            let mut mesh2d_key = mesh_key;
            let Some(mesh) = render_meshes.get(mesh2d_handle) else {
                continue;
            };
            mesh2d_key |= Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology());

            let pipeline_id =
                pipelines.specialize(&pipeline_cache, &colored_mesh2d_pipeline, mesh2d_key);

            let mesh_z = mesh2d_transforms.world_from_local.translation.z;
            transparent_phase.add(Transparent2d {
                entity: (*render_entity, *visible_entity),
                draw_function: draw_colored_mesh2d,
                pipeline: pipeline_id,
                // The 2d render items are sorted according to their z value before rendering,
                // in order to get correct transparency
                sort_key: FloatOrd(mesh_z),
                // This material is not batched
                batch_range: 0..1,
                extra_index: PhaseItemExtraIndex::None,
                indexed: mesh.indexed(),
            });
        }
    }
}

}

</details>

## What went wrong

The handler doesn't run, nothing gets logged in the console.

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-BugAn unexpected or incorrect behaviorS-Needs-TriageThis issue needs to be labelled

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions