use crate::camera3d::Camera3D;
use crate::mesh3d::{Mesh3D, Vec3};
use crate::shader3d::PhongShader;
use crate::particles3d::ParticleSystem3D;
pub struct Renderer3D {
pub camera: Camera3D,
pub wireframe: bool,
pub backface_culling: bool,
pub use_lighting: bool,
pub shader: Option<PhongShader>,
pub zbuffer: Vec<f32>,
pub width: u32,
pub height: u32,
pub fog_enabled: bool,
pub fog_start: f32,
pub fog_end: f32,
pub fog_color: [u8; 4],
}
impl Renderer3D {
pub fn new(width: u32, height: u32) -> Self {
let zbuffer_size = (width * height) as usize;
Self {
camera: Camera3D::new(width, height),
wireframe: false,
backface_culling: true,
use_lighting: false,
shader: None,
zbuffer: vec![f32::INFINITY; zbuffer_size],
width,
height,
fog_enabled: true,
fog_start: 10.0,
fog_end: 50.0,
fog_color: [100, 120, 150, 255],
}
}
pub fn with_fog(mut self, enabled: bool, start: f32, end: f32, color: [u8; 4]) -> Self {
self.fog_enabled = enabled;
self.fog_start = start;
self.fog_end = end;
self.fog_color = color;
self
}
pub fn with_lighting(mut self, shader: PhongShader) -> Self {
self.use_lighting = true;
self.shader = Some(shader);
self
}
pub fn clear_zbuffer(&mut self) {
for z in &mut self.zbuffer {
*z = f32::INFINITY;
}
}
pub fn render_particles(&self, particles: &ParticleSystem3D, frame: &mut [u8], width: u32, height: u32) {
for particle in &particles.particles {
if let Some((sx, sy, depth)) = self.camera.project(&particle.position, width, height) {
let size = (particle.size * 100.0 / depth) as i32;
for dy in -size..=size {
for dx in -size..=size {
if dx * dx + dy * dy <= size * size {
let x = sx + dx;
let y = sy + dy;
if x >= 0 && x < width as i32 && y >= 0 && y < height as i32 {
let idx = ((y as u32 * width + x as u32) * 4) as usize;
if idx + 3 < frame.len() {
let alpha = particle.color[3] as f32 / 255.0;
frame[idx] = ((1.0 - alpha) * frame[idx] as f32 + alpha * particle.color[0] as f32) as u8;
frame[idx + 1] = ((1.0 - alpha) * frame[idx + 1] as f32 + alpha * particle.color[1] as f32) as u8;
frame[idx + 2] = ((1.0 - alpha) * frame[idx + 2] as f32 + alpha * particle.color[2] as f32) as u8;
}
}
}
}
}
}
}
}
pub fn render_mesh(&self, mesh: &Mesh3D, frame: &mut [u8], width: u32, height: u32) {
for i in (0..mesh.indices.len()).step_by(3) {
let idx0 = mesh.indices[i] as usize;
let idx1 = mesh.indices[i + 1] as usize;
let idx2 = mesh.indices[i + 2] as usize;
if idx0 >= mesh.vertices.len() || idx1 >= mesh.vertices.len() || idx2 >= mesh.vertices.len() {
continue;
}
let v0 = &mesh.vertices[idx0];
let v1 = &mesh.vertices[idx1];
let v2 = &mesh.vertices[idx2];
let p0 = self.transform_vertex(&v0.position, mesh);
let p1 = self.transform_vertex(&v1.position, mesh);
let p2 = self.transform_vertex(&v2.position, mesh);
if self.backface_culling {
let edge1 = Vec3::new(p1.x - p0.x, p1.y - p0.y, p1.z - p0.z);
let edge2 = Vec3::new(p2.x - p0.x, p2.y - p0.y, p2.z - p0.z);
let normal = edge1.cross(&edge2);
let to_camera = Vec3::new(
self.camera.position.x - p0.x,
self.camera.position.y - p0.y,
self.camera.position.z - p0.z,
);
if normal.dot(&to_camera) < 0.0 {
continue;
}
}
if let (Some(s0), Some(s1), Some(s2)) = (
self.camera.project(&p0, width, height),
self.camera.project(&p1, width, height),
self.camera.project(&p2, width, height),
) {
let avg_depth = (s0.2 + s1.2 + s2.2) / 3.0;
let fog_factor = (1.0 - (avg_depth / 50.0).min(1.0)).max(0.0);
let color = [
(v0.color[0] as f32 * fog_factor) as u8,
(v0.color[1] as f32 * fog_factor) as u8,
(v0.color[2] as f32 * fog_factor) as u8,
v0.color[3],
];
if self.wireframe {
self.draw_line(frame, width, height, s0.0, s0.1, s1.0, s1.1, color);
self.draw_line(frame, width, height, s1.0, s1.1, s2.0, s2.1, color);
self.draw_line(frame, width, height, s2.0, s2.1, s0.0, s0.1, color);
} else {
self.draw_triangle(frame, width, height, s0, s1, s2, color);
}
}
}
}
fn transform_vertex(&self, vertex: &Vec3, mesh: &Mesh3D) -> Vec3 {
let mut v = Vec3::new(
vertex.x * mesh.scale.x,
vertex.y * mesh.scale.y,
vertex.z * mesh.scale.z,
);
let cos_x = mesh.rotation.x.cos();
let sin_x = mesh.rotation.x.sin();
let cos_y = mesh.rotation.y.cos();
let sin_y = mesh.rotation.y.sin();
let cos_z = mesh.rotation.z.cos();
let sin_z = mesh.rotation.z.sin();
let temp_x = v.x * cos_y + v.z * sin_y;
let temp_z = -v.x * sin_y + v.z * cos_y;
v.x = temp_x;
v.z = temp_z;
let temp_y = v.y * cos_x - v.z * sin_x;
let temp_z = v.y * sin_x + v.z * cos_x;
v.y = temp_y;
v.z = temp_z;
let temp_x = v.x * cos_z - v.y * sin_z;
let temp_y = v.x * sin_z + v.y * cos_z;
v.x = temp_x;
v.y = temp_y;
v.x += mesh.position.x;
v.y += mesh.position.y;
v.z += mesh.position.z;
v
}
fn draw_line(&self, frame: &mut [u8], width: u32, height: u32, x0: i32, y0: i32, x1: i32, y1: i32, color: [u8; 4]) {
let dx = (x1 - x0).abs();
let dy = (y1 - y0).abs();
let sx = if x0 < x1 { 1 } else { -1 };
let sy = if y0 < y1 { 1 } else { -1 };
let mut err = dx - dy;
let mut x = x0;
let mut y = y0;
loop {
if x >= 0 && x < width as i32 && y >= 0 && y < height as i32 {
let idx = ((y as u32 * width + x as u32) * 4) as usize;
if idx + 3 < frame.len() {
frame[idx] = color[0];
frame[idx + 1] = color[1];
frame[idx + 2] = color[2];
frame[idx + 3] = color[3];
}
}
if x == x1 && y == y1 {
break;
}
let e2 = 2 * err;
if e2 > -dy {
err -= dy;
x += sx;
}
if e2 < dx {
err += dx;
y += sy;
}
}
}
fn draw_triangle(&self, frame: &mut [u8], width: u32, height: u32, p0: (i32, i32, f32), p1: (i32, i32, f32), p2: (i32, i32, f32), color: [u8; 4]) {
let min_x = p0.0.min(p1.0).min(p2.0).max(0);
let max_x = p0.0.max(p1.0).max(p2.0).min(width as i32 - 1);
let min_y = p0.1.min(p1.1).min(p2.1).max(0);
let max_y = p0.1.max(p1.1).max(p2.1).min(height as i32 - 1);
if min_x >= width as i32 || max_x < 0 || min_y >= height as i32 || max_y < 0 {
return;
}
for y in min_y..=max_y {
for x in min_x..=max_x {
if self.point_in_triangle(x, y, p0, p1, p2) {
let idx = ((y as u32 * width + x as u32) * 4) as usize;
if idx + 3 < frame.len() {
let alpha = color[3] as f32 / 255.0;
if alpha >= 0.99 {
frame[idx] = color[0];
frame[idx + 1] = color[1];
frame[idx + 2] = color[2];
frame[idx + 3] = 255;
} else {
frame[idx] = ((color[0] as f32 * alpha) + (frame[idx] as f32 * (1.0 - alpha))) as u8;
frame[idx + 1] = ((color[1] as f32 * alpha) + (frame[idx + 1] as f32 * (1.0 - alpha))) as u8;
frame[idx + 2] = ((color[2] as f32 * alpha) + (frame[idx + 2] as f32 * (1.0 - alpha))) as u8;
}
}
}
}
}
}
fn point_in_triangle(&self, px: i32, py: i32, p0: (i32, i32, f32), p1: (i32, i32, f32), p2: (i32, i32, f32)) -> bool {
let sign = |p1: (i32, i32), p2: (i32, i32), p3: (i32, i32)| -> i32 {
(p1.0 - p3.0) * (p2.1 - p3.1) - (p2.0 - p3.0) * (p1.1 - p3.1)
};
let d1 = sign((px, py), (p0.0, p0.1), (p1.0, p1.1));
let d2 = sign((px, py), (p1.0, p1.1), (p2.0, p2.1));
let d3 = sign((px, py), (p2.0, p2.1), (p0.0, p0.1));
let has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
let has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);
!(has_neg && has_pos)
}
}