#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum BlendMode {
#[default]
Opaque = 0,
AlphaBlend = 1,
Additive = 2,
}
impl BlendMode {
#[must_use]
pub fn from_u8(v: u8) -> Option<Self> {
match v {
0 => Some(Self::Opaque),
1 => Some(Self::AlphaBlend),
2 => Some(Self::Additive),
_ => None,
}
}
#[must_use]
pub fn as_u8(self) -> u8 {
self as u8
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Material {
pub alpha: u8,
pub mode: BlendMode,
}
impl Material {
pub const OPAQUE: Self = Self {
alpha: 255,
mode: BlendMode::Opaque,
};
#[must_use]
pub fn alpha_blend(alpha: u8) -> Self {
Self {
alpha,
mode: BlendMode::AlphaBlend,
}
}
#[must_use]
pub fn additive(alpha: u8) -> Self {
Self {
alpha,
mode: BlendMode::Additive,
}
}
#[must_use]
pub fn is_opaque(self) -> bool {
matches!(self.mode, BlendMode::Opaque)
}
}
impl Default for Material {
fn default() -> Self {
Self::OPAQUE
}
}
#[derive(Clone, Debug)]
pub struct MaterialTable {
materials: [Material; 256],
}
impl MaterialTable {
#[must_use]
pub fn new() -> Self {
Self {
materials: [Material::OPAQUE; 256],
}
}
pub fn set(&mut self, id: u8, mat: Material) -> bool {
if id == 0 {
return false;
}
self.materials[id as usize] = mat;
true
}
#[must_use]
pub fn get(&self, id: u8) -> Material {
self.materials[id as usize]
}
#[must_use]
pub fn all_opaque(&self) -> bool {
self.materials.iter().all(|m| m.is_opaque())
}
#[must_use]
pub fn as_array(&self) -> &[Material; 256] {
&self.materials
}
}
impl Default for MaterialTable {
fn default() -> Self {
Self::new()
}
}
#[must_use]
pub fn material_for_color(map: &[(u32, u8)], col: u32) -> u8 {
let rgb = col & 0x00ff_ffff;
for &(c, id) in map {
if c & 0x00ff_ffff == rgb {
return id;
}
}
0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blend_mode_round_trips() {
for m in [
BlendMode::Opaque,
BlendMode::AlphaBlend,
BlendMode::Additive,
] {
assert_eq!(BlendMode::from_u8(m.as_u8()), Some(m));
}
assert_eq!(BlendMode::from_u8(3), None);
assert_eq!(BlendMode::default(), BlendMode::Opaque);
}
#[test]
fn material_defaults_opaque() {
assert_eq!(Material::default(), Material::OPAQUE);
assert!(Material::OPAQUE.is_opaque());
assert!(!Material::alpha_blend(128).is_opaque());
assert!(!Material::additive(200).is_opaque());
}
#[test]
fn table_starts_all_opaque() {
let t = MaterialTable::new();
assert!(t.all_opaque());
for id in 0..=255u8 {
assert_eq!(t.get(id), Material::OPAQUE);
}
}
#[test]
fn table_set_and_get() {
let mut t = MaterialTable::new();
assert!(t.set(1, Material::alpha_blend(64)));
assert_eq!(t.get(1), Material::alpha_blend(64));
assert!(!t.all_opaque());
assert!(t.set(255, Material::additive(255)));
assert_eq!(t.get(255), Material::additive(255));
}
#[test]
fn id_zero_is_locked_opaque() {
let mut t = MaterialTable::new();
assert!(!t.set(0, Material::alpha_blend(0)));
assert_eq!(t.get(0), Material::OPAQUE);
assert!(t.all_opaque());
}
}