Skip to main content

ling_graphics/
material.rs

1use crate::color::Color;
2
3/// CPU-side RGBA image data.
4#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
5pub struct TextureData {
6    pub width: usize,
7    pub height: usize,
8    /// RGBA bytes, row-major, top-left origin.
9    pub data: Vec<u8>,
10}
11
12impl TextureData {
13    pub fn new(width: usize, height: usize) -> Self {
14        Self { width, height, data: vec![0u8; width * height * 4] }
15    }
16
17    pub fn from_color(width: usize, height: usize, color: Color) -> Self {
18        let bytes = color.to_rgba_bytes();
19        let data = bytes
20            .iter()
21            .cloned()
22            .cycle()
23            .take(width * height * 4)
24            .collect();
25        Self { width, height, data }
26    }
27
28    pub fn set_pixel(&mut self, x: usize, y: usize, color: Color) {
29        if x >= self.width || y >= self.height {
30            return;
31        }
32        let idx = (y * self.width + x) * 4;
33        let b = color.to_rgba_bytes();
34        self.data[idx..idx + 4].copy_from_slice(&b);
35    }
36
37    pub fn get_pixel(&self, x: usize, y: usize) -> Color {
38        if x >= self.width || y >= self.height {
39            return Color::TRANSPARENT;
40        }
41        let idx = (y * self.width + x) * 4;
42        Color::new(
43            self.data[idx] as f32 / 255.0,
44            self.data[idx + 1] as f32 / 255.0,
45            self.data[idx + 2] as f32 / 255.0,
46            self.data[idx + 3] as f32 / 255.0,
47        )
48    }
49
50    /// Bilinear sample with UV coordinates in [0, 1].
51    pub fn sample(&self, u: f32, v: f32) -> Color {
52        let u = u.fract().abs();
53        let v = v.fract().abs();
54        let fx = u * (self.width as f32 - 1.0);
55        let fy = v * (self.height as f32 - 1.0);
56        let x0 = fx as usize;
57        let y0 = fy as usize;
58        let x1 = (x0 + 1).min(self.width - 1);
59        let y1 = (y0 + 1).min(self.height - 1);
60        let tx = fx - x0 as f32;
61        let ty = fy - y0 as f32;
62        let c00 = self.get_pixel(x0, y0);
63        let c10 = self.get_pixel(x1, y0);
64        let c01 = self.get_pixel(x0, y1);
65        let c11 = self.get_pixel(x1, y1);
66        let top = c00.lerp(c10, tx);
67        let bottom = c01.lerp(c11, tx);
68        top.lerp(bottom, ty)
69    }
70}
71
72// ── Alpha mode ────────────────────────────────────────────────────────────────
73
74#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
75pub enum AlphaMode {
76    Opaque,
77    /// Alpha tested: fragments with alpha < cutoff are discarded.
78    Mask {
79        cutoff: f32,
80    },
81    /// Alpha blended (order-dependent).
82    Blend,
83    /// Premultiplied alpha blending.
84    Premultiplied,
85}
86
87// ── Material ──────────────────────────────────────────────────────────────────
88
89#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
90pub struct Material {
91    pub albedo_color: Color,
92    pub albedo_texture: Option<TextureData>,
93    pub normal_texture: Option<TextureData>,
94    pub metallic: f32,
95    pub roughness: f32,
96    pub emissive: Color,
97    pub alpha_mode: AlphaMode,
98    /// Colorization: multiply sampled color by this tint.
99    pub tint: Color,
100    pub double_sided: bool,
101}
102
103impl Material {
104    pub fn new(color: Color) -> Self {
105        Self {
106            albedo_color: color,
107            albedo_texture: None,
108            normal_texture: None,
109            metallic: 0.0,
110            roughness: 0.8,
111            emissive: Color::BLACK,
112            alpha_mode: AlphaMode::Opaque,
113            tint: Color::WHITE,
114            double_sided: false,
115        }
116    }
117
118    pub fn with_texture(mut self, tex: TextureData) -> Self {
119        self.albedo_texture = Some(tex);
120        self
121    }
122
123    pub fn with_metallic_roughness(mut self, m: f32, r: f32) -> Self {
124        self.metallic = m;
125        self.roughness = r;
126        self
127    }
128
129    pub fn with_alpha(mut self, mode: AlphaMode) -> Self {
130        self.alpha_mode = mode;
131        self
132    }
133
134    pub fn with_emissive(mut self, c: Color) -> Self {
135        self.emissive = c;
136        self
137    }
138
139    /// Sample the effective albedo at texture coordinates (u, v).
140    pub fn sample_albedo(&self, u: f32, v: f32) -> Color {
141        let base = match &self.albedo_texture {
142            Some(tex) => tex.sample(u, v),
143            None => self.albedo_color,
144        };
145        base * self.tint
146    }
147}
148
149impl Default for Material {
150    fn default() -> Self {
151        Self::new(Color::WHITE)
152    }
153}