dunge 0.1.8

Simple and portable 3d render library
Documentation
use {
    crate::{layout::Plain, r#loop::Error, shader},
    wgpu::{BindGroup, BindGroupLayout, Buffer, Device, Queue},
};

#[derive(Clone, Copy, Default)]
pub struct Source {
    pub pos: [f32; 3],
    pub rad: f32,
    pub col: [f32; 3],
    pub mode: LightMode,
    pub kind: LightKind,
}

#[derive(Clone, Copy, Default)]
pub enum LightMode {
    #[default]
    Smooth,
    Sharp,
}

#[derive(Clone, Copy, Default)]
pub enum LightKind {
    #[default]
    Glow,
    Gloom,
}

pub(crate) struct Light {
    sources_buffer: Buffer,
    n_sources: usize,
    bind_group: BindGroup,
}

impl Light {
    const MAX_N_SOURCES: usize = 64;

    pub fn new(
        srcs: &[SourceModel],
        device: &Device,
        layout: &BindGroupLayout,
    ) -> Result<Self, Error> {
        use wgpu::{
            util::{BufferInitDescriptor, DeviceExt},
            BindGroupDescriptor, BindGroupEntry, BufferUsages,
        };

        if srcs.len() > Self::MAX_N_SOURCES {
            return Err(Error::TooManySources);
        }

        let sources_buffer = {
            let default = [SourceModel::default()];
            let uniform = if srcs.is_empty() { &default } else { srcs };
            device.create_buffer_init(&BufferInitDescriptor {
                label: Some("sources buffer"),
                contents: uniform.as_bytes(),
                usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
            })
        };

        let n_sources_buffer = {
            let len = srcs.len() as u32;
            device.create_buffer_init(&BufferInitDescriptor {
                label: Some("n sources buffer"),
                contents: len.as_bytes(),
                usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
            })
        };

        let bind_group = device.create_bind_group(&BindGroupDescriptor {
            layout,
            entries: &[
                BindGroupEntry {
                    binding: shader::SOURCES_BINDING,
                    resource: sources_buffer.as_entire_binding(),
                },
                BindGroupEntry {
                    binding: shader::N_SOURCES_BINDING,
                    resource: n_sources_buffer.as_entire_binding(),
                },
            ],
            label: Some("lights bind group"),
        });

        Ok(Self {
            sources_buffer,
            n_sources: srcs.len(),
            bind_group,
        })
    }

    pub fn update(&self, srcs: &[SourceModel], queue: &Queue) -> bool {
        if srcs.is_empty() || self.n_sources != srcs.len() {
            return false;
        }

        queue.write_buffer(&self.sources_buffer, 0, srcs.as_bytes());
        true
    }

    pub fn update_nth(&self, n: usize, source: SourceModel, queue: &Queue) -> Result<(), Error> {
        use std::mem;

        if n >= self.n_sources {
            return Err(Error::SourceNotFound);
        }

        queue.write_buffer(
            &self.sources_buffer,
            (mem::size_of::<SourceModel>() * n) as _,
            source.as_bytes(),
        );

        Ok(())
    }

    pub fn bind_group(&self) -> &BindGroup {
        &self.bind_group
    }
}

#[repr(C)]
#[derive(Copy, Clone, Default)]
pub(crate) struct SourceModel {
    pos: [f32; 3],
    rad: f32,
    col: [f32; 3],
    flags: u32,
}

impl SourceModel {
    pub fn new(src: Source) -> Self {
        Self {
            pos: src.pos,
            rad: src.rad,
            col: src.col,
            flags: {
                let sharp = match src.mode {
                    LightMode::Smooth => 0,
                    LightMode::Sharp => 1,
                };

                let gloom = match src.kind {
                    LightKind::Glow => 0,
                    LightKind::Gloom => 1,
                };

                sharp | gloom << 1
            },
        }
    }
}

unsafe impl Plain for SourceModel {}