scena 1.7.1

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

use super::Ray;

pub(super) fn ray_triangle_intersection(
    ray: Ray,
    a: Vec3,
    b: Vec3,
    c: Vec3,
) -> Option<(f32, f32, f32)> {
    const EPSILON: f32 = 1.0e-6;
    let edge1 = subtract_vec3(b, a);
    let edge2 = subtract_vec3(c, a);
    let p = cross(ray.direction, edge2);
    let determinant = dot(edge1, p);
    if determinant.abs() <= EPSILON {
        return None;
    }
    let inverse_determinant = determinant.recip();
    let t = subtract_vec3(ray.origin, a);
    let u = dot(t, p) * inverse_determinant;
    if !(0.0..=1.0).contains(&u) {
        return None;
    }
    let q = cross(t, edge1);
    let v = dot(ray.direction, q) * inverse_determinant;
    if v < 0.0 || u + v > 1.0 {
        return None;
    }
    let distance = dot(edge2, q) * inverse_determinant;
    (distance >= 0.0).then_some((distance, u, v))
}

pub(super) fn triangle_bounds(a: Vec3, b: Vec3, c: Vec3) -> (Vec3, Vec3) {
    (
        Vec3::new(
            a.x.min(b.x).min(c.x),
            a.y.min(b.y).min(c.y),
            a.z.min(b.z).min(c.z),
        ),
        Vec3::new(
            a.x.max(b.x).max(c.x),
            a.y.max(b.y).max(c.y),
            a.z.max(b.z).max(c.z),
        ),
    )
}

pub(super) fn ray_hits_bounds(ray: Ray, min: Vec3, max: Vec3) -> bool {
    let Some((x_min, x_max)) = axis_interval(ray.origin.x, ray.direction.x, min.x, max.x) else {
        return false;
    };
    let Some((y_min, y_max)) = axis_interval(ray.origin.y, ray.direction.y, min.y, max.y) else {
        return false;
    };
    let Some((z_min, z_max)) = axis_interval(ray.origin.z, ray.direction.z, min.z, max.z) else {
        return false;
    };
    let near = x_min.max(y_min).max(z_min);
    let far = x_max.min(y_max).min(z_max);
    far >= near.max(0.0)
}

fn axis_interval(origin: f32, direction: f32, min: f32, max: f32) -> Option<(f32, f32)> {
    const EPSILON: f32 = 1.0e-6;
    if direction.abs() <= EPSILON {
        return (origin >= min && origin <= max).then_some((f32::NEG_INFINITY, f32::INFINITY));
    }
    let first = (min - origin) / direction;
    let second = (max - origin) / direction;
    Some((first.min(second), first.max(second)))
}

pub(super) fn transform_point(point: Vec3, transform: Transform) -> Vec3 {
    let scaled = Vec3::new(
        point.x * transform.scale.x,
        point.y * transform.scale.y,
        point.z * transform.scale.z,
    );
    let rotated = rotate_vec3(transform.rotation, scaled);
    Vec3::new(
        rotated.x + transform.translation.x,
        rotated.y + transform.translation.y,
        rotated.z + transform.translation.z,
    )
}

pub(super) const fn add_vec3(left: Vec3, right: Vec3) -> Vec3 {
    Vec3::new(left.x + right.x, left.y + right.y, left.z + right.z)
}

pub(super) const fn subtract_vec3(left: Vec3, right: Vec3) -> Vec3 {
    Vec3::new(left.x - right.x, left.y - right.y, left.z - right.z)
}

pub(super) const fn scale_vec3(value: Vec3, scale: f32) -> Vec3 {
    Vec3::new(value.x * scale, value.y * scale, value.z * scale)
}

fn dot(left: Vec3, right: Vec3) -> f32 {
    left.x * right.x + left.y * right.y + left.z * right.z
}

pub(super) fn cross(left: Vec3, right: Vec3) -> Vec3 {
    Vec3::new(
        left.y * right.z - left.z * right.y,
        left.z * right.x - left.x * right.z,
        left.x * right.y - left.y * right.x,
    )
}

pub(super) fn normalize(value: Vec3) -> Vec3 {
    normalize_optional(value).unwrap_or(Vec3::new(0.0, 0.0, -1.0))
}

pub(super) fn normalize_optional(value: Vec3) -> Option<Vec3> {
    let length_squared = dot(value, value);
    if length_squared <= f32::EPSILON || !length_squared.is_finite() {
        return None;
    }
    Some(scale_vec3(value, length_squared.sqrt().recip()))
}

pub(super) fn rotate_vec3(rotation: Quat, vector: Vec3) -> Vec3 {
    let length_squared = rotation.x * rotation.x
        + rotation.y * rotation.y
        + rotation.z * rotation.z
        + rotation.w * rotation.w;
    if length_squared <= f32::EPSILON || !length_squared.is_finite() {
        return vector;
    }
    let inverse_length = length_squared.sqrt().recip();
    let qx = rotation.x * inverse_length;
    let qy = rotation.y * inverse_length;
    let qz = rotation.z * inverse_length;
    let qw = rotation.w * inverse_length;
    let tx = 2.0 * (qy * vector.z - qz * vector.y);
    let ty = 2.0 * (qz * vector.x - qx * vector.z);
    let tz = 2.0 * (qx * vector.y - qy * vector.x);
    Vec3::new(
        vector.x + qw * tx + (qy * tz - qz * ty),
        vector.y + qw * ty + (qz * tx - qx * tz),
        vector.z + qw * tz + (qx * ty - qy * tx),
    )
}