📄 sink/src/main.rs
//! `kinect-view` application entry point.
//!
//! Runs the pipeline on the configured source — synthetic (the
//! default), a recording, or the live Kinect rig — and shows the egui
//! viewer. Everything is configurable: CLI arguments, environment
//! variables, and a TOML config file — see `sink::config`.

use eframe::egui;

fn main() -> eframe::Result<()> {
    env_logger::init();

    let resolved = match sink::config::load().and_then(|args| args.resolve()) {
        Ok(resolved) => resolved,
        Err(error) => {
            eprintln!("configuration error: {error:#}");
            std::process::exit(2);
        }
    };

    // The frame source: raw depth frames → preprocessing → surfels.
    // The source's own rig is the authority (a recording carries its
    // cameras; the live Kinect contributes factory intrinsics).
    let source: Box<dyn rig::FrameSource>;
    let rig = match &resolved.source {
        sink::config::SourceSelection::Synthetic => {
            let rig = resolved.rig.clone();
            let raw = replay::synth::dev_source_with(&rig, resolved.subject, resolved.fps)
                .with_noise(resolved.noise);
            source = Box::new(splatgen::SplatSource::new(
                rig.clone(),
                raw,
                splatgen::SplatGen::new(resolved.splatgen),
            ));
            rig
        }
        sink::config::SourceSelection::Recording(path) => {
            let player = match replay::RecordingSource::load(path) {
                Ok(player) => player,
                Err(error) => {
                    eprintln!("loading recording {}: {error:#}", path.display());
                    std::process::exit(2);
                }
            };
            let rig = player.rig().clone();
            source = Box::new(splatgen::SplatSource::new(
                player.rig().clone(),
                player,
                splatgen::SplatGen::new(resolved.splatgen),
            ));
            rig
        }
        sink::config::SourceSelection::Kinect => {
            let live = match capture::KinectRigSource::open(&resolved.rig) {
                Ok(live) => live,
                Err(error) => {
                    eprintln!("opening the live rig: {error:#}");
                    std::process::exit(2);
                }
            };
            use rig::DepthSource;
            let rig = live.rig();
            source = Box::new(splatgen::SplatSource::new(
                live.rig(),
                live,
                splatgen::SplatGen::new(resolved.splatgen),
            ));
            rig
        }
    };

    let renderer = render::SplatRenderer::new(resolved.background);
    let model = sink::ViewerModel::new(rig, source, renderer);
    let app = sink::KinectViewApp { model };

    let options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default()
            .with_title("kinect-view")
            .with_inner_size([1024.0, 768.0]),
        ..Default::default()
    };

    eframe::run_native(
        "kinect-view",
        options,
        Box::new(move |_cc| Ok(Box::new(app))),
    )
}