bevy_prototype_lyon 0.16.0

Draw 2D shapes and paths in the Bevy game engine.
Documentation
//! Contains the plugin and its helper types.
//!
//! The [`ShapePlugin`] provides the creation of shapes with minimal
//! boilerplate.

use bevy::{
    asset::{RenderAssetUsages, uuid_handle},
    mesh::Indices,
    prelude::*,
    render::render_resource::PrimitiveTopology,
};
use lyon_tessellation::{self as tess, BuffersBuilder};

use crate::{
    draw::{Fill, Stroke},
    entity::Shape,
    vertex::{VertexBuffers, VertexConstructor},
};

pub(crate) const COLOR_MATERIAL_HANDLE: Handle<ColorMaterial> =
    uuid_handle!("7cc661a1-0cd6-c147-129a-2c01882d9580");

/// A plugin that provides resources and a system to draw shapes in Bevy with
/// less boilerplate.
pub struct ShapePlugin;

impl Plugin for ShapePlugin {
    fn build(&self, app: &mut App) {
        let fill_tess = tess::FillTessellator::new();
        let stroke_tess = tess::StrokeTessellator::new();
        app.insert_resource(FillTessellator(fill_tess))
            .insert_resource(StrokeTessellator(stroke_tess))
            .configure_sets(
                PostUpdate,
                BuildShapes
                    .after(bevy::transform::TransformSystems::Propagate)
                    .before(bevy::asset::AssetEventSystems),
            )
            .add_systems(PostUpdate, mesh_shapes_system.in_set(BuildShapes));

        if let Some(mut materials) = app.world_mut().get_resource_mut::<Assets<ColorMaterial>>() {
            // `insert` will not fail for uuid assets
            let _ = materials.insert(
                &COLOR_MATERIAL_HANDLE,
                ColorMaterial {
                    color: Color::WHITE,
                    ..default()
                },
            );
        } else {
            error!("Failed to get Assets<ColorMaterial> resource");
        }
    }
}

/// [`SystemSet`] for the system that builds the meshes for newly-added
/// or changed shapes. Resides in [`PostUpdate`] schedule.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, SystemSet)]
pub struct BuildShapes;

/// Queries all the [`Shape`]s and their related components
/// to mesh them when they are added
/// or re-mesh them when they are changed.
#[allow(clippy::type_complexity)]
fn mesh_shapes_system(
    mut meshes: ResMut<Assets<Mesh>>,
    mut fill_tess: ResMut<FillTessellator>,
    mut stroke_tess: ResMut<StrokeTessellator>,
    mut query: Query<(&Shape, &mut Mesh2d), Changed<Shape>>,
) {
    for (shape, mut mesh) in &mut query {
        let mut buffers = VertexBuffers::new();
        if let Some(fill_mode) = shape.fill {
            fill(&mut fill_tess, &shape.path, fill_mode, &mut buffers);
        }
        if let Some(stroke_mode) = shape.stroke {
            stroke(&mut stroke_tess, &shape.path, stroke_mode, &mut buffers);
        }
        mesh.0 = meshes.add(build_mesh(&buffers));
    }
}

#[allow(clippy::trivially_copy_pass_by_ref)] // lyon takes &FillOptions
fn fill(
    tess: &mut ResMut<FillTessellator>,
    path: &tess::path::Path,
    mode: Fill,
    buffers: &mut VertexBuffers,
) {
    if let Err(e) = tess.tessellate_path(
        path,
        &mode.options,
        &mut BuffersBuilder::new(buffers, VertexConstructor { color: mode.color }),
    ) {
        error!("FillTessellator error: {:?}", e);
    }
}

#[allow(clippy::trivially_copy_pass_by_ref)] // lyon takes &StrokeOptions
fn stroke(
    tess: &mut ResMut<StrokeTessellator>,
    path: &tess::path::Path,
    mode: Stroke,
    buffers: &mut VertexBuffers,
) {
    if let Err(e) = tess.tessellate_path(
        path,
        &mode.options,
        &mut BuffersBuilder::new(buffers, VertexConstructor { color: mode.color }),
    ) {
        error!("StrokeTessellator error: {:?}", e);
    }
}

fn build_mesh(buffers: &VertexBuffers) -> Mesh {
    let mut mesh = Mesh::new(
        PrimitiveTopology::TriangleList,
        RenderAssetUsages::default(),
    );
    mesh.insert_indices(Indices::U32(buffers.indices.clone()));
    mesh.insert_attribute(
        Mesh::ATTRIBUTE_POSITION,
        buffers
            .vertices
            .iter()
            .map(|v| [v.position[0], v.position[1], 0.0])
            .collect::<Vec<[f32; 3]>>(),
    );
    mesh.insert_attribute(
        Mesh::ATTRIBUTE_COLOR,
        buffers
            .vertices
            .iter()
            .map(|v| v.color)
            .collect::<Vec<[f32; 4]>>(),
    );

    mesh
}

#[derive(Resource, Deref, DerefMut)]
struct FillTessellator(lyon_tessellation::FillTessellator);

#[derive(Resource, Deref, DerefMut)]
struct StrokeTessellator(lyon_tessellation::StrokeTessellator);