scena 1.7.2

A Rust-native scene-graph renderer with typed scene state, glTF assets, and explicit prepare/render lifecycles.
Documentation
use crate::material::Color;
use crate::scene::{ClippingPlane, SectionBox, Vec3};

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

const STROKE_AA_RADIUS_PX: f32 = 3.25;

pub(super) fn draw_overlay_layers_cpu(
    cpu_frame: &mut CpuFrame<'_>,
    strokes: &[PreparedStrokeSegment],
    labels: &PreparedLabelAtlas,
    clipping_planes: &[ClippingPlane],
    section_box: Option<SectionBox>,
    camera: &CameraProjection,
) {
    draw_strokes_cpu(cpu_frame, strokes, clipping_planes, section_box, camera);
    super::cpu_labels::draw_label_atlas_cpu(
        cpu_frame,
        labels,
        clipping_planes,
        section_box,
        camera,
    );
}

pub(super) fn draw_strokes_cpu(
    cpu_frame: &mut CpuFrame<'_>,
    strokes: &[PreparedStrokeSegment],
    clipping_planes: &[ClippingPlane],
    section_box: Option<SectionBox>,
    camera: &CameraProjection,
) {
    for stroke in strokes {
        draw_stroke_cpu(cpu_frame, stroke, clipping_planes, section_box, camera);
    }
}

fn draw_stroke_cpu(
    cpu_frame: &mut CpuFrame<'_>,
    stroke: &PreparedStrokeSegment,
    clipping_planes: &[ClippingPlane],
    section_box: Option<SectionBox>,
    camera: &CameraProjection,
) {
    let start = transform_point(stroke.world_from_model(), stroke.start());
    let end = transform_point(stroke.world_from_model(), stroke.end());
    let Some(start_screen) = ScreenStrokePoint::project(start, cpu_frame.target, camera) else {
        return;
    };
    let Some(end_screen) = ScreenStrokePoint::project(end, cpu_frame.target, camera) else {
        return;
    };
    let dx = end_screen.x - start_screen.x;
    let dy = end_screen.y - start_screen.y;
    let length_squared = dx * dx + dy * dy;
    if length_squared <= f32::EPSILON {
        return;
    }

    let half_width = (stroke.width_px().max(0.001) * 0.5).max(0.5);
    let aa_radius = STROKE_AA_RADIUS_PX;
    let radius = half_width + aa_radius;
    let min_x = (start_screen.x.min(end_screen.x) - radius).floor().max(0.0) as u32;
    let max_x = (start_screen.x.max(end_screen.x) + radius)
        .ceil()
        .min(cpu_frame.target.width.saturating_sub(1) as f32) as u32;
    let min_y = (start_screen.y.min(end_screen.y) - radius).floor().max(0.0) as u32;
    let max_y = (start_screen.y.max(end_screen.y) + radius)
        .ceil()
        .min(cpu_frame.target.height.saturating_sub(1) as f32) as u32;
    let color = multiply_color(stroke.color(), stroke.tint());

    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 t = (((px - start_screen.x) * dx + (py - start_screen.y) * dy) / length_squared)
                .clamp(0.0, 1.0);
            let closest_x = start_screen.x + dx * t;
            let closest_y = start_screen.y + dy * t;
            let distance = ((px - closest_x).powi(2) + (py - closest_y).powi(2)).sqrt();
            let coverage = stroke_coverage(distance, half_width);
            if coverage <= 0.0 {
                continue;
            }
            let position = start + (end - start) * t;
            if is_clipped(position, clipping_planes, section_box) {
                continue;
            }
            let depth = start_screen.depth + (end_screen.depth - start_screen.depth) * t;
            write_label_overlay_pixel(cpu_frame, x, y, color, coverage, depth);
        }
    }
}

#[derive(Debug, Clone, Copy)]
struct ScreenStrokePoint {
    x: f32,
    y: f32,
    depth: f32,
}

impl ScreenStrokePoint {
    fn project(position: Vec3, target: RasterTarget, camera: &CameraProjection) -> Option<Self> {
        let projected = camera.project(position)?;
        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,
        })
    }
}

fn stroke_coverage(distance_px: f32, half_width_px: f32) -> f32 {
    if distance_px <= half_width_px {
        1.0
    } else {
        let t = ((distance_px - half_width_px) / STROKE_AA_RADIUS_PX).clamp(0.0, 1.0);
        1.0 - t * t * (3.0 - 2.0 * t)
    }
}

fn transform_point(matrix: [f32; 16], point: Vec3) -> Vec3 {
    Vec3::new(
        matrix[0] * point.x + matrix[4] * point.y + matrix[8] * point.z + matrix[12],
        matrix[1] * point.x + matrix[5] * point.y + matrix[9] * point.z + matrix[13],
        matrix[2] * point.x + matrix[6] * point.y + matrix[10] * point.z + matrix[14],
    )
}

fn multiply_color(color: Color, tint: Color) -> Color {
    Color::from_linear_rgba(
        color.r * tint.r,
        color.g * tint.g,
        color.b * tint.b,
        color.a * tint.a,
    )
}

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))
}