Skip to content

Composite shapes #281

@telans

Description

@telans

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:

gh.mp4
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);
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions