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