enginerenderer 0.0.1

A zero-dependency offline rendering engine in pure Rust — CPU path tracing, BVH acceleration, 16-band spectral rendering, PBR materials, animation & video export.
Documentation
use crate::core::engine::rendering::raytracing::Vec3;
use crate::core::engine::rendering::terrain::cdlod::HeightMap;

#[derive(Debug, Clone, Copy)]
pub struct FoliageInstance {
    pub position: Vec3,
    pub scale: f64,
    pub rotation_y: f64,
    pub lod: u32,
    pub wind_phase: f64,
}

#[derive(Debug, Clone)]
pub struct FoliageLayer {
    pub density_map: Vec<f64>,
    pub map_width: usize,
    pub map_height: usize,
    pub wind_strength: f64,
    pub wind_frequency: f64,
    pub wind_direction: Vec3,
    pub scale_min: f64,
    pub scale_max: f64,
    pub max_slope: f64,
    pub lod_distances: Vec<f64>,
}

impl FoliageLayer {
    pub fn new(map_width: usize, map_height: usize) -> Self {
        Self {
            density_map: vec![1.0; map_width * map_height],
            map_width,
            map_height,
            wind_strength: 0.15,
            wind_frequency: 1.2,
            wind_direction: Vec3::new(1.0, 0.0, 0.3).normalize(),
            scale_min: 0.8,
            scale_max: 1.2,
            max_slope: 0.7,
            lod_distances: vec![20.0, 50.0, 100.0],
        }
    }

    pub fn density_at(&self, u: f64, v: f64) -> f64 {
        let px = (u * (self.map_width - 1) as f64).clamp(0.0, (self.map_width - 1) as f64) as usize;
        let pz =
            (v * (self.map_height - 1) as f64).clamp(0.0, (self.map_height - 1) as f64) as usize;
        self.density_map[pz * self.map_width + px]
    }
}

pub struct FoliageSystem;

impl FoliageSystem {
    pub fn scatter(
        terrain: &HeightMap,
        layer: &FoliageLayer,
        grid_spacing: f64,
        seed: u32,
    ) -> Vec<FoliageInstance> {
        let world_w = terrain.world_scale.x;
        let world_d = terrain.world_scale.z;
        let cols = (world_w / grid_spacing) as usize;
        let rows = (world_d / grid_spacing) as usize;
        let mut instances = Vec::with_capacity(cols * rows);
        let mut rng = seed;

        for row in 0..rows {
            for col in 0..cols {
                rng = rng.wrapping_mul(0x9E37_79B9).wrapping_add(0x6C62_272E);
                let jitter_x = lcg_f64(&mut rng) - 0.5;
                let jitter_z = lcg_f64(&mut rng) - 0.5;
                let world_x = (col as f64 + jitter_x) * grid_spacing;
                let world_z = (row as f64 + jitter_z) * grid_spacing;

                if world_x < 0.0 || world_x >= world_w || world_z < 0.0 || world_z >= world_d {
                    continue;
                }

                let u = world_x / world_w;
                let v = world_z / world_d;
                let density = layer.density_at(u, v);
                if lcg_f64(&mut rng) > density {
                    continue;
                }

                let normal = terrain.normal_at(world_x, world_z);
                if normal.y < layer.max_slope {
                    continue;
                }

                let world_y = terrain.sample(world_x, world_z);
                let scale =
                    layer.scale_min + (layer.scale_max - layer.scale_min) * lcg_f64(&mut rng);
                let rotation_y = lcg_f64(&mut rng) * std::f64::consts::TAU;
                let wind_phase = lcg_f64(&mut rng) * std::f64::consts::TAU;

                instances.push(FoliageInstance {
                    position: Vec3::new(world_x, world_y, world_z),
                    scale,
                    rotation_y,
                    lod: 0,
                    wind_phase,
                });
            }
        }
        instances
    }

    pub fn update_lod(instances: &mut [FoliageInstance], camera_pos: Vec3, lod_distances: &[f64]) {
        for inst in instances.iter_mut() {
            let dist = (inst.position - camera_pos).length();
            inst.lod = lod_distances
                .iter()
                .position(|&d| dist < d)
                .map(|i| i as u32)
                .unwrap_or(lod_distances.len() as u32);
        }
    }

    pub fn animate_wind(
        instances: &[FoliageInstance],
        layer: &FoliageLayer,
        time: f64,
    ) -> Vec<Vec3> {
        instances
            .iter()
            .map(|inst| {
                let phase = inst.wind_phase + time * layer.wind_frequency;
                let sin_val = (phase).sin();
                let gust = (phase * 0.37 + 1.0).sin() * 0.3;
                let amplitude = layer.wind_strength * (1.0 + gust) * inst.scale;
                layer.wind_direction * (sin_val * amplitude)
            })
            .collect()
    }
}

fn lcg_f64(seed: &mut u32) -> f64 {
    *seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
    (*seed >> 8) as f64 / 16_777_216.0
}