dreamwell-matter 1.0.0

DreamMatter benchmark — GPU physics materialization demo and profiler
Documentation
// Stress test scene — 100K+ objects for GPU-driven rendering benchmark.
//
// Procedurally generates a dense grid of primitive objects at varying distances
// to stress-test GPU culling, LOD selection, instancing, and multi-draw indirect.
// All objects share PBR materials with varied roughness/metallic for visual variety.

use dreamwell_gpu::gpu_driven::{GpuObjectData, MergedMeshBuffer};
use dreamwell_engine::game_object::PrimitiveKind;

/// Generate stress test object data for GPU-driven rendering.
/// Returns a Vec of GpuObjectData ready for storage buffer upload.
pub fn generate_stress_scene(count: u32) -> Vec<GpuObjectData> {
    let mut objects = Vec::with_capacity(count as usize);

    // Ground plane (object 0)
    objects.push(make_object(
        PrimitiveKind::Plane,
        [0.0, 0.0, 0.0],
        [200.0, 1.0, 200.0],
        [0.3, 0.3, 0.35, 1.0],
        0.9, 0.0,
    ));

    // Fill remaining slots with a grid of mixed primitives
    let remaining = (count - 1) as usize;
    let grid_side = (remaining as f32).sqrt().ceil() as usize;
    let spacing = 3.0_f32;
    let half = (grid_side as f32) * spacing * 0.5;

    let shapes = PrimitiveKind::ALL;

    for i in 0..remaining {
        let gx = (i % grid_side) as f32;
        let gz = (i / grid_side) as f32;

        let x = gx * spacing - half;
        let z = gz * spacing - half;
        let y = 0.5 + (i as f32 * 0.137).sin().abs() * 2.0; // varied height

        let shape = shapes[i % shapes.len()];
        let scale_base = 0.3 + (i as f32 * 0.73).sin().abs() * 0.7;

        // Vary material per-object for visual diversity
        let hue = (i as f32 * 0.618) % 1.0; // golden ratio for color spread
        let color = hsv_to_rgb(hue, 0.5, 0.8);
        let roughness = 0.1 + (i as f32 * 0.31).sin().abs() * 0.8;
        let metallic = if i % 5 == 0 { 0.8 } else { 0.1 };

        objects.push(make_object(
            shape,
            [x, y, z],
            [scale_base, scale_base, scale_base],
            [color[0], color[1], color[2], 1.0],
            roughness, metallic,
        ));
    }

    log::info!(
        "Stress scene: {} objects, {}x{} grid, {:.0}m span",
        objects.len(), grid_side, grid_side, half * 2.0,
    );

    objects
}

fn make_object(
    kind: PrimitiveKind,
    position: [f32; 3],
    scale: [f32; 3],
    color: [f32; 4],
    roughness: f32,
    metallic: f32,
) -> GpuObjectData {
    // Build column-major 4x4 model matrix (scale + translate, no rotation)
    #[rustfmt::skip]
    let model = [
        scale[0], 0.0,      0.0,      0.0,
        0.0,      scale[1], 0.0,      0.0,
        0.0,      0.0,      scale[2], 0.0,
        position[0], position[1], position[2], 1.0,
    ];

    // Bounding sphere: center at position, radius = max scale component
    let radius = scale[0].max(scale[1]).max(scale[2]) * 0.75;

    GpuObjectData {
        model,
        base_color: color,
        roughness,
        metallic,
        emissive_strength: 0.0,
        normal_scale: 1.0,
        bounding_center: position,
        bounding_radius: radius,
        mesh_id: MergedMeshBuffer::mesh_id_lod0(kind),
        _pad: [0; 3],
    }
}

/// Simple HSV to RGB conversion for procedural color variety.
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> [f32; 3] {
    let c = v * s;
    let h2 = h * 6.0;
    let x = c * (1.0 - ((h2 % 2.0) - 1.0).abs());
    let m = v - c;

    let (r, g, b) = if h2 < 1.0 { (c, x, 0.0) }
        else if h2 < 2.0 { (x, c, 0.0) }
        else if h2 < 3.0 { (0.0, c, x) }
        else if h2 < 4.0 { (0.0, x, c) }
        else if h2 < 5.0 { (x, 0.0, c) }
        else { (c, 0.0, x) };

    [r + m, g + m, b + m]
}

/// Setup lighting for the stress test scene.
pub fn setup_stress_lights(fabric: &mut dreamwell_fabric::DreamFabric) {
    // Sun
    fabric.gpu_scene_mut().scene_lights.add_directional(
        dreamwell_engine::lighting::DirectionalLightDesc {
            direction: [0.3, -0.7, 0.5],
            color: [1.0, 0.95, 0.85],
            intensity_lux: 2.5,
        },
    );

    // Sky fill
    fabric.gpu_scene_mut().scene_lights.add_directional(
        dreamwell_engine::lighting::DirectionalLightDesc {
            direction: [-0.3, -0.4, -0.5],
            color: [0.5, 0.55, 0.7],
            intensity_lux: 0.6,
        },
    );

    // Center point light
    fabric.gpu_scene_mut().scene_lights.add_point(
        dreamwell_engine::lighting::PointLightDesc {
            position: [0.0, 20.0, 0.0],
            color: [1.0, 0.95, 0.9],
            intensity_lumens: 2000.0,
            range: 100.0,
        },
    );
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn stress_scene_100() {
        let objects = generate_stress_scene(100);
        assert_eq!(objects.len(), 100);
    }

    #[test]
    fn stress_scene_has_ground() {
        let objects = generate_stress_scene(10);
        // First object is ground plane (y=0, large scale)
        assert_eq!(objects[0].bounding_center[1], 0.0);
    }

    #[test]
    fn stress_scene_colors_vary() {
        let objects = generate_stress_scene(20);
        // Objects should have varied colors
        let colors: Vec<[f32; 4]> = objects[1..].iter().map(|o| o.base_color).collect();
        let unique: std::collections::HashSet<u32> = colors.iter()
            .map(|c| (c[0] * 1000.0) as u32)
            .collect();
        assert!(unique.len() > 3, "Expected color variety, got {}", unique.len());
    }
}