bevy_entitiles 0.2.2

A tilemap library for bevy. With many algorithms built in.
use bevy::{
    core_pipeline::core_2d::Transparent2d,
    ecs::system::lifetimeless::{Read, SRes},
    render::{
        mesh::GpuBufferInfo,
        render_phase::{RenderCommand, RenderCommandResult},
        render_resource::PipelineCache,
        view::ViewUniformOffset,
    },
};

use super::{
    chunk::RenderChunkStorage,
    extract::ExtractedTilemap,
    queue::TileViewBindGroup,
    uniform::{DynamicUniformComponent, TilemapUniform},
    TilemapBindGroups,
};

pub type DrawTilemap = (
    SetPipeline,
    SetTilemapViewBindGroup<0>,
    SetTilemapDataBindGroup<1>,
    SetTilemapcolorTextureBindGroup<2>,
    DrawTileMesh,
);

pub type DrawTilemapPureColor = (
    SetPipeline,
    SetTilemapViewBindGroup<0>,
    SetTilemapDataBindGroup<1>,
    DrawTileMesh,
);

pub struct SetPipeline;
impl RenderCommand<Transparent2d> for SetPipeline {
    type Param = SRes<PipelineCache>;

    type ViewWorldQuery = ();

    type ItemWorldQuery = ();

    #[inline]
    fn render<'w>(
        item: &Transparent2d,
        _view: bevy::ecs::query::ROQueryItem<'w, Self::ViewWorldQuery>,
        _entity: bevy::ecs::query::ROQueryItem<'w, Self::ItemWorldQuery>,
        pipeline_cache: bevy::ecs::system::SystemParamItem<'w, '_, Self::Param>,
        pass: &mut bevy::render::render_phase::TrackedRenderPass<'w>,
    ) -> RenderCommandResult {
        let pipeline_cache = pipeline_cache.into_inner();
        if let Some(pipeline) = pipeline_cache.get_render_pipeline(item.pipeline) {
            pass.set_render_pipeline(pipeline);
            RenderCommandResult::Success
        } else {
            pipeline_cache.get_render_pipeline_state(item.pipeline);
            println!("Failed to get render pipeline!");
            panic!();
        }
    }
}

pub struct SetTilemapViewBindGroup<const I: usize>;
impl<const I: usize> RenderCommand<Transparent2d> for SetTilemapViewBindGroup<I> {
    type Param = ();

    type ViewWorldQuery = (Read<ViewUniformOffset>, Read<TileViewBindGroup>);

    type ItemWorldQuery = ();

    fn render<'w>(
        _item: &Transparent2d,
        (view_uniform_offset, view_bind_group): bevy::ecs::query::ROQueryItem<
            'w,
            Self::ViewWorldQuery,
        >,
        _entity: bevy::ecs::query::ROQueryItem<'w, Self::ItemWorldQuery>,
        _param: bevy::ecs::system::SystemParamItem<'w, '_, Self::Param>,
        pass: &mut bevy::render::render_phase::TrackedRenderPass<'w>,
    ) -> RenderCommandResult {
        pass.set_bind_group(I, &view_bind_group.value, &[view_uniform_offset.offset]);

        RenderCommandResult::Success
    }
}

pub struct SetTilemapDataBindGroup<const I: usize>;
impl<const I: usize> RenderCommand<Transparent2d> for SetTilemapDataBindGroup<I> {
    type Param = SRes<TilemapBindGroups>;

    type ViewWorldQuery = ();

    type ItemWorldQuery = (
        Read<ExtractedTilemap>,
        Read<DynamicUniformComponent<TilemapUniform>>,
    );

    fn render<'w>(
        _item: &Transparent2d,
        _view: bevy::ecs::query::ROQueryItem<'w, Self::ViewWorldQuery>,
        (tilemap, uniform_data): bevy::ecs::query::ROQueryItem<'w, Self::ItemWorldQuery>,
        bind_groups: bevy::ecs::system::SystemParamItem<'w, '_, Self::Param>,
        pass: &mut bevy::render::render_phase::TrackedRenderPass<'w>,
    ) -> RenderCommandResult {
        if let Some(tilemap_uniform_bind_group) = bind_groups
            .into_inner()
            .tilemap_uniform_bind_group
            .get(&tilemap.id)
        {
            pass.set_bind_group(I, tilemap_uniform_bind_group, &[uniform_data.index]);
            RenderCommandResult::Success
        } else {
            println!("Failed to get tilemap uniform bind group!");
            RenderCommandResult::Failure
        }
    }
}

pub struct SetTilemapcolorTextureBindGroup<const I: usize>;
impl<const I: usize> RenderCommand<Transparent2d> for SetTilemapcolorTextureBindGroup<I> {
    type Param = SRes<TilemapBindGroups>;

    type ViewWorldQuery = ();

    type ItemWorldQuery = Read<ExtractedTilemap>;

    #[inline]
    fn render<'w>(
        _item: &Transparent2d,
        _view: bevy::ecs::query::ROQueryItem<'w, Self::ViewWorldQuery>,
        tilemap: bevy::ecs::query::ROQueryItem<'w, Self::ItemWorldQuery>,
        bind_groups: bevy::ecs::system::SystemParamItem<'w, '_, Self::Param>,
        pass: &mut bevy::render::render_phase::TrackedRenderPass<'w>,
    ) -> RenderCommandResult {
        if let Some(texture) = &tilemap.texture {
            pass.set_bind_group(
                I,
                bind_groups
                    .into_inner()
                    .color_textures
                    .get(texture.handle())
                    .unwrap(),
                &[],
            );
        }

        RenderCommandResult::Success
    }
}

pub struct DrawTileMesh;
impl RenderCommand<Transparent2d> for DrawTileMesh {
    type Param = SRes<RenderChunkStorage>;

    type ViewWorldQuery = ();

    type ItemWorldQuery = Read<ExtractedTilemap>;

    #[inline]
    fn render<'w>(
        _item: &Transparent2d,
        _view: bevy::ecs::query::ROQueryItem<'w, Self::ViewWorldQuery>,
        tilemap: bevy::ecs::query::ROQueryItem<'w, Self::ItemWorldQuery>,
        render_chunks: bevy::ecs::system::SystemParamItem<'w, '_, Self::Param>,
        pass: &mut bevy::render::render_phase::TrackedRenderPass<'w>,
    ) -> RenderCommandResult {
        if let Some(chunks) = render_chunks.into_inner().get_chunks(tilemap.id) {
            for chunk in chunks.iter().rev() {
                let Some(c) = chunk else {
                    continue;
                };

                if !c.visible {
                    continue;
                }

                if let Some(gpu_mesh) = &c.gpu_mesh {
                    pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..));
                    match &gpu_mesh.buffer_info {
                        GpuBufferInfo::Indexed {
                            buffer,
                            count,
                            index_format,
                        } => {
                            pass.set_index_buffer(buffer.slice(..), 0, *index_format);
                            pass.draw_indexed(0..*count, 0, 0..1);
                        }
                        GpuBufferInfo::NonIndexed => {
                            pass.draw(0..gpu_mesh.vertex_count, 0..1);
                        }
                    }
                }
            }
        }

        RenderCommandResult::Success
    }
}