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::{ClippingPlane, SectionBox, Vec3};

use super::camera::CameraProjection;
use super::cpu::{CpuFrame, write_label_overlay_pixel};
use super::prepare::{PreparedLabelAtlas, PreparedLabelQuad};
use super::target::RasterTarget;

pub(super) fn draw_label_atlas_cpu(
    cpu_frame: &mut CpuFrame<'_>,
    labels: &PreparedLabelAtlas,
    clipping_planes: &[ClippingPlane],
    section_box: Option<SectionBox>,
    camera: &CameraProjection,
) {
    for quad in labels.quads() {
        draw_label_quad_cpu(
            cpu_frame,
            labels,
            quad,
            clipping_planes,
            section_box,
            camera,
        );
    }
}

fn draw_label_quad_cpu(
    cpu_frame: &mut CpuFrame<'_>,
    atlas: &PreparedLabelAtlas,
    quad: &PreparedLabelQuad,
    clipping_planes: &[ClippingPlane],
    section_box: Option<SectionBox>,
    camera: &CameraProjection,
) {
    let [x0, y0, x1, y1] = quad.rect_px();
    let [u0, v0, u1, v1] = quad.uv_rect();
    let bottom_left = label_vertex(quad, x0, y0);
    let bottom_right = label_vertex(quad, x1, y0);
    let top_right = label_vertex(quad, x1, y1);
    let top_left = label_vertex(quad, x0, y1);
    draw_label_triangle_cpu(
        cpu_frame,
        atlas,
        [
            (bottom_left, u0, v1),
            (bottom_right, u1, v1),
            (top_right, u1, v0),
        ],
        quad,
        clipping_planes,
        section_box,
        camera,
    );
    draw_label_triangle_cpu(
        cpu_frame,
        atlas,
        [
            (bottom_left, u0, v1),
            (top_right, u1, v0),
            (top_left, u0, v0),
        ],
        quad,
        clipping_planes,
        section_box,
        camera,
    );
}

fn draw_label_triangle_cpu(
    cpu_frame: &mut CpuFrame<'_>,
    atlas: &PreparedLabelAtlas,
    vertices: [(Vec3, f32, f32); 3],
    quad: &PreparedLabelQuad,
    clipping_planes: &[ClippingPlane],
    section_box: Option<SectionBox>,
    camera: &CameraProjection,
) {
    let Some(a) = ScreenLabelVertex::new(vertices[0], cpu_frame.target, camera) else {
        return;
    };
    let Some(b) = ScreenLabelVertex::new(vertices[1], cpu_frame.target, camera) else {
        return;
    };
    let Some(c) = ScreenLabelVertex::new(vertices[2], cpu_frame.target, camera) else {
        return;
    };
    let min_x = a.x.min(b.x).min(c.x).floor().max(0.0) as u32;
    let max_x =
        a.x.max(b.x)
            .max(c.x)
            .ceil()
            .min(cpu_frame.target.width as f32 - 1.0) as u32;
    let min_y = a.y.min(b.y).min(c.y).floor().max(0.0) as u32;
    let max_y =
        a.y.max(b.y)
            .max(c.y)
            .ceil()
            .min(cpu_frame.target.height as f32 - 1.0) as u32;
    let area = edge(a, b, c.x, c.y);
    if area.abs() <= f32::EPSILON {
        return;
    }
    for y in min_y..=max_y {
        for x in min_x..=max_x {
            let px = x as f32 + 0.5;
            let py = y as f32 + 0.5;
            let w0 = edge(b, c, px, py) / area;
            let w1 = edge(c, a, px, py) / area;
            let w2 = edge(a, b, px, py) / area;
            if w0 < 0.0 || w1 < 0.0 || w2 < 0.0 {
                continue;
            }
            let position = mix_position(a.position, b.position, c.position, w0, w1, w2);
            if is_clipped(position, clipping_planes, section_box) {
                continue;
            }
            let (u, v) = mix_uv(a, b, c, w0, w1, w2);
            let coverage = if quad.solid_coverage() {
                1.0
            } else {
                sample_label_atlas_coverage(atlas, u, v)
            };
            if coverage <= 0.0 {
                continue;
            }
            let depth = mix_depth(a.depth, b.depth, c.depth, w0, w1, w2);
            write_label_overlay_pixel(cpu_frame, x, y, quad.final_color(), coverage, depth);
        }
    }
}

fn label_vertex(quad: &PreparedLabelQuad, x_px: f32, y_px: f32) -> Vec3 {
    quad.anchor()
        + quad.right() * (x_px * quad.world_units_per_px())
        + quad.up() * (y_px * quad.world_units_per_px())
}

#[derive(Debug, Clone, Copy)]
struct ScreenLabelVertex {
    x: f32,
    y: f32,
    depth: f32,
    inv_depth: f32,
    position: Vec3,
    u: f32,
    v: f32,
}

impl ScreenLabelVertex {
    fn new(
        vertex: (Vec3, f32, f32),
        target: RasterTarget,
        camera: &CameraProjection,
    ) -> Option<Self> {
        let projected = camera.project(vertex.0)?;
        let width = target.width.saturating_sub(1) as f32;
        let height = target.height.saturating_sub(1) as f32;
        Some(Self {
            x: (projected.ndc_x * 0.5 + 0.5) * width,
            y: (1.0 - (projected.ndc_y * 0.5 + 0.5)) * height,
            depth: projected.depth,
            inv_depth: projected.view_depth.recip(),
            position: vertex.0,
            u: vertex.1,
            v: vertex.2,
        })
    }
}

fn edge(a: ScreenLabelVertex, b: ScreenLabelVertex, x: f32, y: f32) -> f32 {
    (x - a.x) * (b.y - a.y) - (y - a.y) * (b.x - a.x)
}

fn mix_uv(
    a: ScreenLabelVertex,
    b: ScreenLabelVertex,
    c: ScreenLabelVertex,
    w0: f32,
    w1: f32,
    w2: f32,
) -> (f32, f32) {
    let iw0 = w0 * a.inv_depth;
    let iw1 = w1 * b.inv_depth;
    let iw2 = w2 * c.inv_depth;
    let inv_sum = iw0 + iw1 + iw2;
    if inv_sum.abs() <= f32::EPSILON || !inv_sum.is_finite() {
        return (
            a.u * w0 + b.u * w1 + c.u * w2,
            a.v * w0 + b.v * w1 + c.v * w2,
        );
    }
    let w0 = iw0 / inv_sum;
    let w1 = iw1 / inv_sum;
    let w2 = iw2 / inv_sum;
    (
        a.u * w0 + b.u * w1 + c.u * w2,
        a.v * w0 + b.v * w1 + c.v * w2,
    )
}

fn sample_label_atlas_coverage(atlas: &PreparedLabelAtlas, u: f32, v: f32) -> f32 {
    let width = atlas.width().max(1);
    let height = atlas.height().max(1);
    let x = u.clamp(0.0, 1.0) * width as f32 - 0.5;
    let y = v.clamp(0.0, 1.0) * height as f32 - 0.5;
    let x0 = x.floor();
    let y0 = y.floor();
    let tx = x - x0;
    let ty = y - y0;
    let x0 = x0 as i32;
    let y0 = y0 as i32;
    let sample = |sx: i32, sy: i32| -> f32 {
        let sx = sx.clamp(0, width.saturating_sub(1) as i32) as u32;
        let sy = sy.clamp(0, height.saturating_sub(1) as i32) as u32;
        let offset = sy
            .saturating_mul(width)
            .saturating_add(sx)
            .saturating_mul(4)
            .saturating_add(3) as usize;
        atlas
            .rgba8()
            .get(offset)
            .copied()
            .map(|value| f32::from(value) / 255.0)
            .unwrap_or(0.0)
    };
    let c00 = sample(x0, y0);
    let c10 = sample(x0 + 1, y0);
    let c01 = sample(x0, y0 + 1);
    let c11 = sample(x0 + 1, y0 + 1);
    let top = c00 + (c10 - c00) * tx;
    let bottom = c01 + (c11 - c01) * tx;
    (top + (bottom - top) * ty).clamp(0.0, 1.0)
}

fn mix_position(a: Vec3, b: Vec3, c: Vec3, w0: f32, w1: f32, w2: f32) -> Vec3 {
    Vec3::new(
        a.x * w0 + b.x * w1 + c.x * w2,
        a.y * w0 + b.y * w1 + c.y * w2,
        a.z * w0 + b.z * w1 + c.z * w2,
    )
}

fn mix_depth(a: f32, b: f32, c: f32, w0: f32, w1: f32, w2: f32) -> f32 {
    a * w0 + b * w1 + c * w2
}

fn is_clipped(
    position: Vec3,
    clipping_planes: &[ClippingPlane],
    section_box: Option<SectionBox>,
) -> bool {
    clipping_planes
        .iter()
        .any(|plane| !plane.contains(position))
        || section_box.is_some_and(|section| section.clips(position))
}