ambient_rect 0.2.0-rc5

Ambient rectangle definition. Host-only.
Documentation
use std::sync::Arc;

use ambient_core::{
    asset_cache, mesh,
    transform::{local_to_world, mesh_to_local, mesh_to_world, rotation, scale, translation},
};
use ambient_ecs::{ensure_has_component, ensure_has_component_with_default, query, Entity, SystemGroup};
use ambient_gpu::{
    gpu::GpuKey,
    shader_module::{BindGroupDesc, ShaderModule},
    typed_buffer::TypedBuffer,
};
use ambient_layout::{gpu_ui_size, height, mesh_to_local_from_size, width};
use ambient_meshes::{UIRectMeshKey, UnitQuadMeshKey};
use ambient_renderer::{
    gpu_primitives_lod, gpu_primitives_mesh, material, primitives, renderer_shader, Material, MaterialShader, RendererConfig,
    RendererShader, SharedMaterial, StandardShaderKey, MATERIAL_BIND_GROUP,
};
use ambient_std::{
    asset_cache::{AssetCache, SyncAssetKey, SyncAssetKeyExt},
    cb,
    color::Color,
    friendly_id, include_file,
};
use glam::{vec4, Quat, UVec3, Vec3, Vec4};
use wgpu::{BindGroup, BindGroupLayoutEntry};

pub use ambient_ecs::generated::components::core::rect::{
    background_color, border_color, border_radius, border_thickness, line_from, line_to, line_width, rect,
};

#[repr(C)]
#[derive(Debug, Clone, Copy, Default, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Corners {
    pub top_left: f32,
    pub top_right: f32,
    pub bottom_left: f32,
    pub bottom_right: f32,
}
impl Corners {
    pub fn even(value: f32) -> Self {
        Self { top_left: value, top_right: value, bottom_left: value, bottom_right: value }
    }
}
impl From<Corners> for Vec4 {
    fn from(value: Corners) -> Self {
        vec4(value.top_left, value.top_right, value.bottom_left, value.bottom_right)
    }
}
impl From<Vec4> for Corners {
    fn from(value: Vec4) -> Self {
        Self { top_left: value.x, top_right: value.y, bottom_left: value.z, bottom_right: value.w }
    }
}

pub fn systems() -> SystemGroup {
    SystemGroup::new(
        "ui/rect",
        vec![
            query((line_from().changed(), line_to().changed(), line_width().changed())).to_system(|q, world, qs, _| {
                for (id, (from, to, line_width)) in q.collect_cloned(world, qs) {
                    let dir = (to - from).normalize();
                    world
                        .add_components(
                            id,
                            Entity::new()
                                .with(translation(), (from + to) / 2.)
                                .with(rotation(), Quat::from_rotation_arc(Vec3::X, dir))
                                .with_default(rect())
                                .with(width(), (from - to).length())
                                .with(height(), line_width),
                        )
                        .unwrap();
                }
            }),
            ensure_has_component_with_default(rect(), primitives()),
            ensure_has_component_with_default(rect(), gpu_ui_size()),
            ensure_has_component_with_default(rect(), mesh_to_local()),
            ensure_has_component_with_default(rect(), mesh_to_world()),
            ensure_has_component_with_default(rect(), local_to_world()),
            ensure_has_component(rect(), scale(), Vec3::ONE),
            ensure_has_component_with_default(rect(), mesh_to_local_from_size()),
            ensure_has_component_with_default(rect(), gpu_primitives_mesh()),
            ensure_has_component_with_default(rect(), gpu_primitives_lod()),
            query(()).incl(rect()).excl(mesh()).to_system(|q, world, qs, _| {
                let assets = world.resource(asset_cache()).clone();
                for (id, _) in q.collect_cloned(world, qs) {
                    world
                        .add_components(
                            id,
                            Entity::new()
                                .with(
                                    mesh(),
                                    if world.has_component(id, line_from()) {
                                        UnitQuadMeshKey.get(&assets)
                                    } else {
                                        UIRectMeshKey.get(&assets)
                                    },
                                )
                                .with(renderer_shader(), cb(get_rect_shader)),
                        )
                        .unwrap();
                }
            }),
            query(())
                .incl(rect())
                .optional_changed(background_color())
                .optional_changed(border_color())
                .optional_changed(border_radius())
                .optional_changed(border_thickness())
                .to_system(|q, world, qs, _| {
                    let assets = world.resource(asset_cache()).clone();
                    for (id, _) in q.collect_cloned(world, qs) {
                        world
                            .add_component(
                                id,
                                material(),
                                RectMaterialKey {
                                    params: RectMaterialParams {
                                        background_color: world.get(id, background_color()).unwrap_or(Color::WHITE.into()),
                                        border_color: world.get(id, border_color()).unwrap_or(Color::WHITE.into()),
                                        border_radius: world.get(id, border_radius()).unwrap_or_default().into(),
                                        border_thickness: world.get(id, border_thickness()).unwrap_or(0.),
                                        _padding: Default::default(),
                                    },
                                }
                                .get(&assets),
                            )
                            .unwrap();
                    }
                }),
        ],
    )
}

#[derive(Debug)]
pub struct RectMaterialShaderKey;
impl SyncAssetKey<Arc<MaterialShader>> for RectMaterialShaderKey {
    fn load(&self, _assets: AssetCache) -> Arc<MaterialShader> {
        Arc::new(MaterialShader {
            shader: Arc::new(ShaderModule::new("RectMaterial", include_file!("rect.wgsl")).with_binding_desc(get_rect_layout())),

            id: "rect_material_shader".to_string(),
        })
    }
}

fn get_rect_layout() -> BindGroupDesc<'static> {
    BindGroupDesc {
        entries: vec![BindGroupLayoutEntry {
            binding: 0,
            visibility: wgpu::ShaderStages::FRAGMENT,
            ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None },
            count: None,
        }],
        label: MATERIAL_BIND_GROUP.into(),
    }
}

pub fn get_rect_shader(assets: &AssetCache, config: &RendererConfig) -> Arc<RendererShader> {
    StandardShaderKey { material_shader: RectMaterialShaderKey.get(assets), lit: false, shadow_cascades: config.shadow_cascades }
        .get(assets)
}

#[derive(Debug)]
pub struct RectMaterialKey {
    pub params: RectMaterialParams,
}
impl SyncAssetKey<SharedMaterial> for RectMaterialKey {
    fn load(&self, assets: AssetCache) -> SharedMaterial {
        SharedMaterial::new(RectMaterial::new(assets, self.params))
    }
}

#[repr(C)]
#[derive(Debug, Clone, Copy, Default, bytemuck::Pod, bytemuck::Zeroable)]
pub struct RectMaterialParams {
    pub background_color: Vec4,
    pub border_color: Vec4,
    pub border_radius: Corners,
    pub border_thickness: f32,
    pub _padding: UVec3,
}

pub struct RectMaterial {
    id: String,
    bind_group: wgpu::BindGroup,
    transparent: Option<bool>,
}
impl RectMaterial {
    pub fn new(assets: AssetCache, params: RectMaterialParams) -> Self {
        let gpu = GpuKey.get(&assets);
        let layout = get_rect_layout().get(&assets);

        let buffer = TypedBuffer::new_init(
            gpu.clone(),
            "RectMaterial.buffer",
            wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            &[params],
        );

        Self {
            id: friendly_id(),
            bind_group: gpu.device.create_bind_group(&wgpu::BindGroupDescriptor {
                layout: &layout,
                entries: &[wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::Buffer(buffer.buffer().as_entire_buffer_binding()),
                }],
                label: Some("RectMaterial.bind_group"),
            }),
            transparent: Some(params.background_color.w != 0. || params.border_color.w != 0.),
        }
    }
}

impl std::fmt::Debug for RectMaterial {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("RectMaterial").field("id", &self.id).finish()
    }
}

impl Material for RectMaterial {
    fn bind_group(&self) -> &BindGroup {
        &self.bind_group
    }
    fn id(&self) -> &str {
        &self.id
    }
    fn transparent(&self) -> Option<bool> {
        self.transparent
    }
}