bevy_prototype_lyon icon indicating copy to clipboard operation
bevy_prototype_lyon copied to clipboard

Composite shapes

Open telans opened this issue 8 months ago • 0 comments

I've tried to implement basic composite shapes following the layout of ReadyShapeBuilder in source. Leaving this here in case anyone else wants it. Works for my use case but I'm sure it breaks in wonderful ways. I'm mainly using it for stacking paths. It's just a component that re-meshes the shapes it holds every time they update.

The mesh/shape you want on top needs to be added with add_shape() last. I'm new to bevy/rust so not sure if this is even close to the ideal implementation of this (what do I call ShapeBuilderExt?). Something similar would be a good addition to the crate in my opinion. This is what the dynamic_shape example looks like when adapted:

https://github.com/user-attachments/assets/56f6b3be-98db-4e5f-be6f-df7eeb0ef67c

use crate::entity::Shape;
use crate::geometry::ReadyShapeBuilder;
use crate::plugin::COLOR_MATERIAL_HANDLE;
use bevy::prelude::*;
use lyon_tessellation::path::traits::Build;

pub struct CompositeShapeBuilder {
    shapes: Vec<Shape>,
}

impl CompositeShape {
    pub fn add(&mut self, shape: Shape) -> usize {
        let index = self.shapes.len();
        self.shapes.push(shape);
        index
    }
    
    pub fn get(&self, index: usize) -> Option<&Shape> {
        self.shapes.get(index)
    }

    pub fn get_mut(&mut self, index: usize) -> Option<&mut Shape> {
        self.shapes.get_mut(index)
    }
}

impl CompositeShapeBuilder {
    #[must_use]
    pub fn with(shape: Shape) -> Self {
        Self::new().add_shape(shape)
    }

    #[must_use]
    pub fn new() -> Self {
        Self { shapes: Vec::new() }
    }

    #[must_use]
    pub fn add_shape(mut self, shape: Shape) -> Self {
        self.shapes.push(shape);
        self
    }

    #[must_use]
    pub fn build(self) -> CompositeShape {
        CompositeShape {
            shapes: self.shapes,
        }
    }
}

#[derive(Component, Default, Clone)]
#[require(Mesh2d, MeshMaterial2d::<ColorMaterial>(COLOR_MATERIAL_HANDLE), Visibility)]
#[non_exhaustive]
pub struct CompositeShape {
    pub shapes: Vec<Shape>,
}

pub trait ShapeBuilderExt<GenericBuilder> {
    fn add_shape(self, other: ReadyShapeBuilder<GenericBuilder>) -> CompositeShapeBuilder;
}

impl<GenericBuilder> ShapeBuilderExt<GenericBuilder> for ReadyShapeBuilder<GenericBuilder>
where
    GenericBuilder: Build<PathType = lyon_tessellation::path::Path> + Clone,
{
    fn add_shape(self, other: Self) -> CompositeShapeBuilder {
        CompositeShapeBuilder::new()
            .add_shape(self.build())
            .add_shape(other.build())
    }
}
// src/plugin.rs
fn mesh_composite_shapes_system(
    mut meshes: ResMut<Assets<Mesh>>,
    mut fill_tess: ResMut<FillTessellator>,
    mut stroke_tess: ResMut<StrokeTessellator>,
    mut query: Query<(&CompositeShape, &mut Mesh2d), Changed<CompositeShape>>,
) {
    for (composite, mut mesh) in &mut query {
        let merged_mesh = if composite.shapes.is_empty() {
            build_mesh(&VertexBuffers::new())
        } else {
            composite
                .shapes
                .iter()
                .map(|shape| {
                    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);
                    }
                    build_mesh(&buffers)
                })
                .reduce(|mut merged, next| {
                    merged.merge(&next).unwrap();
                    merged
                })
                .unwrap()
        };
        mesh.0 = meshes.add(merged_mesh);
    }
}

telans avatar Apr 21 '25 14:04 telans