scena 1.7.2

A Rust-native scene-graph renderer with typed scene state, glTF assets, and explicit prepare/render lifecycles.
Documentation
use super::*;
use crate::scene::{
    Angle, AreaLight, AreaLightShape, DirectionalLight, PointLight, SpotLight, Transform,
};

#[test]
fn gpu_light_uniform_consumes_prepared_environment_lighting() {
    let scene = Scene::new();
    let environment = PreparedEnvironmentLighting::default();

    let uniform = collect_gpu_light_uniform(&scene, Vec3::ZERO, &environment, false);

    assert_eq!(uniform.environment_diffuse_intensity, [0.0; 4]);
    assert_eq!(uniform.environment_specular_intensity, [0.0; 4]);
}

#[test]
fn gpu_light_uniform_encodes_multiple_lights_per_type() {
    let mut scene = Scene::new();
    for index in 0..MAX_GPU_LIGHTS_PER_TYPE {
        scene
            .directional_light(
                DirectionalLight::default()
                    .with_color(Color::from_linear_rgb(index as f32 + 0.1, 0.2, 0.3))
                    .with_illuminance_lux(1_000.0 + index as f32)
                    .with_shadows(index == 1),
            )
            .transform(Transform::default())
            .add()
            .expect("directional light inserts");
        scene
            .point_light(
                PointLight::default()
                    .with_color(Color::from_linear_rgb(0.1, index as f32 + 0.2, 0.3))
                    .with_intensity_candela(100.0 + index as f32)
                    .with_range(10.0 + index as f32),
            )
            .transform(Transform::at(Vec3::new(index as f32, 2.0, 3.0)))
            .add()
            .expect("point light inserts");
        scene
            .spot_light(
                SpotLight::default()
                    .with_color(Color::from_linear_rgb(0.1, 0.2, index as f32 + 0.3))
                    .with_intensity_candela(200.0 + index as f32)
                    .with_range(20.0 + index as f32)
                    .with_inner_cone_angle(Angle::from_degrees(10.0))
                    .with_outer_cone_angle(Angle::from_degrees(25.0)),
            )
            .transform(Transform::at(Vec3::new(1.0, index as f32, 3.0)))
            .add()
            .expect("spot light inserts");
    }
    let environment = PreparedEnvironmentLighting::default();

    let uniform = collect_gpu_light_uniform(&scene, Vec3::ZERO, &environment, false);

    assert_eq!(uniform.light_counts, [16.0, 16.0, 16.0, 0.0]);
    assert_eq!(uniform.directional_light_color[1], [1.1, 0.2, 0.3, 0.0]);
    assert_eq!(uniform.directional_shadow_control[1][0], 1.0);
    assert_eq!(uniform.directional_light_color[3], [3.1, 0.2, 0.3, 0.0]);
    assert_eq!(
        uniform.point_light_position_intensity[2][0..3],
        [2.0, 2.0, 3.0]
    );
    assert_eq!(uniform.point_light_color_range[2], [0.1, 2.2, 0.3, 12.0]);
    assert_eq!(
        uniform.spot_light_position_intensity[2][0..3],
        [1.0, 2.0, 3.0]
    );
    assert_eq!(uniform.spot_light_color_range[2], [0.1, 0.2, 2.3, 0.0]);
    assert!(uniform.spot_light_cone_range[2][0] > uniform.spot_light_cone_range[2][1]);
}

#[test]
fn gpu_lighting_stats_accept_many_point_lights_for_tiled_assignment() {
    let mut scene = Scene::new();
    for index in 0..=MAX_GPU_LIGHTS_PER_TYPE {
        scene
            .point_light(PointLight::default().with_intensity_candela(100.0))
            .transform(Transform::at(Vec3::new(index as f32, 0.0, 1.0)))
            .add()
            .expect("point light inserts");
    }

    super::super::collect_lighting_stats(&scene, crate::diagnostics::Backend::HeadlessGpu)
        .expect("GPU lighting stats must allow many point lights for tiled assignment");
    assert!(
        super::super::collect_lighting_stats(&scene, crate::diagnostics::Backend::Headless).is_ok(),
        "the CPU path can shade all point lights and should not inherit the GPU uniform cap"
    );
}

#[test]
fn area_lights_use_separate_gpu_capacity_from_point_lights() {
    let mut scene = Scene::new();
    scene
        .area_light(
            AreaLight::default()
                .with_luminous_flux_lumens(400.0)
                .with_shape(AreaLightShape::rect(2.0, 1.0)),
        )
        .transform(Transform::default())
        .add()
        .expect("area light inserts");
    scene
        .point_light(PointLight::default().with_intensity_candela(75.0))
        .transform(Transform::at(Vec3::new(0.0, 1.0, 1.5)))
        .add()
        .expect("point light inserts");

    super::super::collect_lighting_stats(&scene, crate::diagnostics::Backend::HeadlessGpu)
        .expect("one area light plus one point light must not exceed the point-light cap");
    let uniform = collect_gpu_light_uniform(
        &scene,
        Vec3::ZERO,
        &PreparedEnvironmentLighting::default(),
        false,
    );

    assert_eq!(uniform.light_counts, [0.0, 1.0, 0.0, 1.0]);
    assert_eq!(
        uniform.point_light_position_intensity[0][0..3],
        [0.0, 1.0, 1.5]
    );
    assert!(
        uniform.area_light_position_flux[0][3] > 0.0,
        "area light carries luminous flux in the area-light uniform lane"
    );
}

#[test]
fn area_light_shape_encodes_gpu_visible_area_lane() {
    let mut scene = Scene::new();
    scene
        .area_light(
            AreaLight::default()
                .with_luminous_flux_lumens(400.0)
                .with_shape(AreaLightShape::rect(2.0, 1.0)),
        )
        .transform(Transform::default())
        .add()
        .expect("area light inserts");
    let environment = PreparedEnvironmentLighting::default();

    let uniform = collect_gpu_light_uniform(&scene, Vec3::ZERO, &environment, false);

    assert_eq!(uniform.light_counts, [0.0, 0.0, 0.0, 1.0]);
    assert_eq!(
        uniform.point_light_position_intensity,
        [[0.0; 4]; MAX_GPU_LIGHTS_PER_TYPE]
    );
    assert_eq!(uniform.area_light_position_flux[0], [0.0, 0.0, 0.0, 400.0]);
    assert_eq!(uniform.area_light_axis_x_shape[0], [1.0, 0.0, 0.0, 0.0]);
    assert_eq!(uniform.area_light_axis_y_range[0], [0.0, 0.5, 0.0, 0.0]);
    assert_eq!(uniform.area_light_color[0], [1.0, 1.0, 1.0, 0.0]);
}