scena 1.7.2

A Rust-native scene-graph renderer with typed scene state, glTF assets, and explicit prepare/render lifecycles.
Documentation
use crate::scene::Vec3;

use super::RasterTarget;
use super::camera::CameraProjection;
use super::screen_space_reflections::{MaterialReflectionPixel, ScreenSpaceReflectionConfig};

pub(super) struct MaterialReflectionRecord<'a> {
    pub(super) target: RasterTarget,
    pub(super) camera: &'a CameraProjection,
    pub(super) material_reflections: &'a mut [MaterialReflectionPixel],
    pub(super) x: u32,
    pub(super) y: u32,
    pub(super) position: Vec3,
    pub(super) normal: Vec3,
    pub(super) reflection: super::prepare::PreparedMaterialReflection,
    pub(super) config: ScreenSpaceReflectionConfig,
}

pub(super) fn record_material_reflection_pixel(input: MaterialReflectionRecord<'_>) {
    let strength = input.config.strength().clamp(0.0, 1.0);
    if strength <= 0.001 {
        return;
    }
    let view = normalize_or(input.camera.camera_position() - input.position, Vec3::Z);
    let normal = normalize_or(input.normal, Vec3::Z);
    let reflected = (-view).reflect(normal);
    let reflect_scale = (0.12 + (1.0 - input.reflection.roughness()) * 0.24) * strength;
    let raw_sample_x = input.x as f32 + reflected.x * reflect_scale * input.target.width as f32;
    let raw_sample_y = input.y as f32 - reflected.y * reflect_scale * input.target.height as f32;
    let edge_fade = reflection_edge_fade(
        raw_sample_x / input.target.width.max(1) as f32,
        raw_sample_y / input.target.height.max(1) as f32,
    );
    if edge_fade <= 0.0 {
        return;
    }
    let sample_x = raw_sample_x.clamp(0.0, input.target.width.saturating_sub(1) as f32);
    let sample_y = raw_sample_y.clamp(0.0, input.target.height.saturating_sub(1) as f32);
    let n_dot_v = normal.dot(view).max(0.0);
    let fresnel = (1.0 - n_dot_v.clamp(0.0, 1.0)).powi(5);
    let weight = (strength
        * input.reflection.metallic()
        * (0.38 + fresnel * 0.52)
        * (1.0 - input.reflection.roughness() * 0.55)
        * edge_fade)
        .clamp(0.0, 0.88);
    let Some(pixel) =
        MaterialReflectionPixel::new(sample_x, sample_y, weight, input.reflection.roughness())
    else {
        return;
    };
    let index = input.target.pixel_index(input.x, input.y);
    input.material_reflections[index] = pixel;
}

fn reflection_edge_fade(u: f32, v: f32) -> f32 {
    let distance = u.min(v).min(1.0 - u).min(1.0 - v);
    smoothstep(0.0, 0.02, distance)
}

fn smoothstep(edge0: f32, edge1: f32, x: f32) -> f32 {
    let t = ((x - edge0) / (edge1 - edge0)).clamp(0.0, 1.0);
    t * t * (3.0 - 2.0 * t)
}

fn normalize_or(value: Vec3, fallback: Vec3) -> Vec3 {
    let length_sq = value.length_squared();
    if length_sq <= f32::EPSILON || !length_sq.is_finite() {
        fallback
    } else {
        value / length_sq.sqrt()
    }
}