use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Texture3D {
pub width: u32,
pub height: u32,
pub data: Vec<u8>, }
impl Texture3D {
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
data: vec![255; (width * height * 4) as usize],
}
}
pub fn from_color(width: u32, height: u32, color: [u8; 4]) -> Self {
let mut data = Vec::with_capacity((width * height * 4) as usize);
for _ in 0..(width * height) {
data.extend_from_slice(&color);
}
Self { width, height, data }
}
pub fn checkerboard(size: u32, color1: [u8; 4], color2: [u8; 4]) -> Self {
let mut data = Vec::with_capacity((size * size * 4) as usize);
for y in 0..size {
for x in 0..size {
let checker = ((x / 8) + (y / 8)) % 2 == 0;
let color = if checker { color1 } else { color2 };
data.extend_from_slice(&color);
}
}
Self { width: size, height: size, data }
}
pub fn gradient_horizontal(width: u32, height: u32, color1: [u8; 4], color2: [u8; 4]) -> Self {
let mut data = Vec::with_capacity((width * height * 4) as usize);
for _y in 0..height {
for x in 0..width {
let t = x as f32 / width as f32;
let r = (color1[0] as f32 * (1.0 - t) + color2[0] as f32 * t) as u8;
let g = (color1[1] as f32 * (1.0 - t) + color2[1] as f32 * t) as u8;
let b = (color1[2] as f32 * (1.0 - t) + color2[2] as f32 * t) as u8;
let a = (color1[3] as f32 * (1.0 - t) + color2[3] as f32 * t) as u8;
data.extend_from_slice(&[r, g, b, a]);
}
}
Self { width, height, data }
}
pub fn sample(&self, u: f32, v: f32) -> [u8; 4] {
let u = u.fract();
let v = v.fract();
let x = (u * self.width as f32) as u32 % self.width;
let y = (v * self.height as f32) as u32 % self.height;
let idx = ((y * self.width + x) * 4) as usize;
if idx + 3 < self.data.len() {
[
self.data[idx],
self.data[idx + 1],
self.data[idx + 2],
self.data[idx + 3],
]
} else {
[255, 255, 255, 255]
}
}
pub fn sample_bilinear(&self, u: f32, v: f32) -> [u8; 4] {
let u = u.fract();
let v = v.fract();
let x = u * (self.width - 1) as f32;
let y = v * (self.height - 1) as f32;
let x0 = x.floor() as u32;
let y0 = y.floor() as u32;
let x1 = (x0 + 1).min(self.width - 1);
let y1 = (y0 + 1).min(self.height - 1);
let fx = x - x0 as f32;
let fy = y - y0 as f32;
let c00 = self.get_pixel(x0, y0);
let c10 = self.get_pixel(x1, y0);
let c01 = self.get_pixel(x0, y1);
let c11 = self.get_pixel(x1, y1);
let mut result = [0u8; 4];
for i in 0..4 {
let top = c00[i] as f32 * (1.0 - fx) + c10[i] as f32 * fx;
let bottom = c01[i] as f32 * (1.0 - fx) + c11[i] as f32 * fx;
result[i] = (top * (1.0 - fy) + bottom * fy) as u8;
}
result
}
fn get_pixel(&self, x: u32, y: u32) -> [u8; 4] {
let idx = ((y * self.width + x) * 4) as usize;
if idx + 3 < self.data.len() {
[
self.data[idx],
self.data[idx + 1],
self.data[idx + 2],
self.data[idx + 3],
]
} else {
[255, 255, 255, 255]
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Material3D {
pub albedo: [u8; 4],
pub metallic: f32,
pub roughness: f32,
pub emission: [u8; 4],
pub emission_strength: f32,
pub texture: Option<Texture3D>,
pub normal_map: Option<Texture3D>,
pub use_texture: bool,
}
impl Material3D {
pub fn new(color: [u8; 4]) -> Self {
Self {
albedo: color,
metallic: 0.0,
roughness: 0.5,
emission: [0, 0, 0, 0],
emission_strength: 0.0,
texture: None,
normal_map: None,
use_texture: false,
}
}
pub fn with_texture(mut self, texture: Texture3D) -> Self {
self.texture = Some(texture);
self.use_texture = true;
self
}
pub fn with_normal_map(mut self, normal_map: Texture3D) -> Self {
self.normal_map = Some(normal_map);
self
}
pub fn with_metallic(mut self, metallic: f32) -> Self {
self.metallic = metallic.clamp(0.0, 1.0);
self
}
pub fn with_roughness(mut self, roughness: f32) -> Self {
self.roughness = roughness.clamp(0.0, 1.0);
self
}
pub fn with_emission(mut self, color: [u8; 4], strength: f32) -> Self {
self.emission = color;
self.emission_strength = strength;
self
}
pub fn get_color(&self, u: f32, v: f32) -> [u8; 4] {
if self.use_texture {
if let Some(tex) = &self.texture {
return tex.sample_bilinear(u, v);
}
}
self.albedo
}
}
impl Material3D {
pub fn metal() -> Self {
Self::new([200, 200, 200, 255])
.with_metallic(1.0)
.with_roughness(0.2)
}
pub fn plastic() -> Self {
Self::new([255, 255, 255, 255])
.with_metallic(0.0)
.with_roughness(0.4)
}
pub fn rubber() -> Self {
Self::new([50, 50, 50, 255])
.with_metallic(0.0)
.with_roughness(0.9)
}
pub fn glass() -> Self {
Self::new([255, 255, 255, 100])
.with_metallic(0.0)
.with_roughness(0.0)
}
pub fn emissive(color: [u8; 4], strength: f32) -> Self {
Self::new(color)
.with_emission(color, strength)
}
}