scena 1.7.2

A Rust-native scene-graph renderer with typed scene state, glTF assets, and explicit prepare/render lifecycles.
Documentation
use std::path::{Path, PathBuf};

use scena::{Assets, ExplodedView, Renderer, Scene, Vec3};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let out_dir = std::env::args()
        .nth(1)
        .map(PathBuf::from)
        .unwrap_or_else(|| PathBuf::from("target/gate-artifacts/guided-exploded-view"));
    std::fs::create_dir_all(&out_dir)?;

    let assets = Assets::new();
    let scene_asset =
        pollster::block_on(assets.load_scene("tests/assets/gltf/exploded_view_assembly.gltf"))?;
    let (mut scene, camera) = Scene::with_default_camera()?;
    let import = scene.instantiate(&scene_asset)?;
    scene.frame_import(camera, &import)?;

    let mut renderer = Renderer::headless(320, 220)?;
    render_ppm(
        &mut renderer,
        &mut scene,
        &assets,
        &out_dir.join("assembly-assembled.ppm"),
    )?;

    let assembly = import.node("Assembly")?;
    let exploded = ExplodedView::from_node(assembly)
        .along_axis(Vec3::X)
        .factor(1.0)
        .distance(0.35)
        .transforms(&scene, &assets)?;
    scene.set_transforms(&exploded.as_transform_updates())?;
    render_ppm(
        &mut renderer,
        &mut scene,
        &assets,
        &out_dir.join("assembly-exploded.ppm"),
    )?;

    scene.set_transforms(
        &exploded
            .updates()
            .iter()
            .map(|update| (update.node, update.original))
            .collect::<Vec<_>>(),
    )?;
    render_ppm(
        &mut renderer,
        &mut scene,
        &assets,
        &out_dir.join("assembly-restored.ppm"),
    )?;

    println!("{}", out_dir.display());
    Ok(())
}

fn render_ppm(
    renderer: &mut Renderer,
    scene: &mut Scene,
    assets: &Assets,
    path: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
    renderer.prepare_with_assets(scene, assets)?;
    renderer.render_active(scene)?;
    write_ppm(path, 320, 220, renderer.frame_rgba8())?;
    Ok(())
}

fn write_ppm(path: &Path, width: u32, height: u32, rgba: &[u8]) -> std::io::Result<()> {
    let mut ppm = format!("P6\n{} {}\n255\n", width, height).into_bytes();
    for pixel in rgba.chunks_exact(4) {
        ppm.extend_from_slice(&pixel[..3]);
    }
    std::fs::write(path, ppm)
}