scena 1.0.0

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::{GeometryDesc, GeometryError, GeometryVertex};

#[derive(Debug, Clone, PartialEq)]
pub struct GeometrySkin {
    joints: Vec<[usize; 4]>,
    weights: Vec<[f32; 4]>,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SkinningMatrix {
    rows: [[f32; 4]; 4],
}

impl GeometryDesc {
    pub fn with_skin(mut self, skin: GeometrySkin) -> Result<Self, GeometryError> {
        if skin.joints.len() != self.vertices.len() {
            return Err(GeometryError::InvalidSkinJointVertexCount {
                vertex_count: self.vertices.len(),
                joint_count: skin.joints.len(),
            });
        }
        if skin.weights.len() != self.vertices.len() {
            return Err(GeometryError::InvalidSkinWeightVertexCount {
                vertex_count: self.vertices.len(),
                weight_count: skin.weights.len(),
            });
        }
        self.skin = Some(skin);
        Ok(self)
    }

    pub fn skin(&self) -> Option<&GeometrySkin> {
        self.skin.as_ref()
    }

    pub fn skinned_vertices(
        &self,
        source_vertices: &[GeometryVertex],
        joint_matrices: &[SkinningMatrix],
    ) -> Result<Option<Vec<GeometryVertex>>, GeometryError> {
        let Some(skin) = &self.skin else {
            return Ok(None);
        };
        if source_vertices.len() != self.vertices.len() {
            return Err(GeometryError::InvalidSkinSourceVertexCount {
                vertex_count: self.vertices.len(),
                source_count: source_vertices.len(),
            });
        }
        let mut vertices = Vec::with_capacity(source_vertices.len());
        for (vertex_index, source) in source_vertices.iter().enumerate() {
            let mut position = Vec3::ZERO;
            let mut normal = Vec3::ZERO;
            for influence in 0..4 {
                let weight = skin.weights[vertex_index][influence];
                if weight == 0.0 {
                    continue;
                }
                let joint = skin.joints[vertex_index][influence];
                let matrix =
                    joint_matrices
                        .get(joint)
                        .ok_or(GeometryError::InvalidSkinJointIndex {
                            vertex_index,
                            joint,
                            joint_count: joint_matrices.len(),
                        })?;
                position = add_vec3(
                    position,
                    scale_vec3(matrix.transform_position(source.position), weight),
                );
                normal = add_vec3(
                    normal,
                    scale_vec3(matrix.transform_direction(source.normal), weight),
                );
            }
            vertices.push(GeometryVertex {
                position,
                normal: normalize_or(normal, source.normal),
            });
        }
        Ok(Some(vertices))
    }
}

impl GeometrySkin {
    pub fn new(joints: Vec<[usize; 4]>, weights: Vec<[f32; 4]>) -> Self {
        Self { joints, weights }
    }

    pub fn joints(&self) -> &[[usize; 4]] {
        &self.joints
    }

    pub fn weights(&self) -> &[[f32; 4]] {
        &self.weights
    }
}

impl SkinningMatrix {
    pub const IDENTITY: Self = Self {
        rows: [
            [1.0, 0.0, 0.0, 0.0],
            [0.0, 1.0, 0.0, 0.0],
            [0.0, 0.0, 1.0, 0.0],
            [0.0, 0.0, 0.0, 1.0],
        ],
    };

    pub fn from_gltf_column_major(values: [f32; 16]) -> Self {
        Self {
            rows: [
                [values[0], values[4], values[8], values[12]],
                [values[1], values[5], values[9], values[13]],
                [values[2], values[6], values[10], values[14]],
                [values[3], values[7], values[11], values[15]],
            ],
        }
    }

    pub fn from_transform(transform: Transform) -> Self {
        let translation = Self::translation(transform.translation);
        let rotation = Self::rotation(transform.rotation);
        let scale = Self::scale(transform.scale);
        translation.then(rotation).then(scale)
    }

    pub fn inverse_from_transform(transform: Transform) -> Self {
        let scale = Self::scale(Vec3::new(
            reciprocal_or_zero(transform.scale.x),
            reciprocal_or_zero(transform.scale.y),
            reciprocal_or_zero(transform.scale.z),
        ));
        let rotation = Self::rotation(inverse_quat(transform.rotation));
        let translation = Self::translation(Vec3::new(
            -transform.translation.x,
            -transform.translation.y,
            -transform.translation.z,
        ));
        scale.then(rotation).then(translation)
    }

    pub fn then(self, other: Self) -> Self {
        let mut rows = [[0.0; 4]; 4];
        for (row_index, row) in rows.iter_mut().enumerate() {
            for (column_index, value) in row.iter_mut().enumerate() {
                *value = self.rows[row_index][0] * other.rows[0][column_index]
                    + self.rows[row_index][1] * other.rows[1][column_index]
                    + self.rows[row_index][2] * other.rows[2][column_index]
                    + self.rows[row_index][3] * other.rows[3][column_index];
            }
        }
        Self { rows }
    }

    pub fn transform_position(self, position: Vec3) -> Vec3 {
        Vec3::new(
            self.rows[0][0] * position.x
                + self.rows[0][1] * position.y
                + self.rows[0][2] * position.z
                + self.rows[0][3],
            self.rows[1][0] * position.x
                + self.rows[1][1] * position.y
                + self.rows[1][2] * position.z
                + self.rows[1][3],
            self.rows[2][0] * position.x
                + self.rows[2][1] * position.y
                + self.rows[2][2] * position.z
                + self.rows[2][3],
        )
    }

    pub fn transform_direction(self, direction: Vec3) -> Vec3 {
        Vec3::new(
            self.rows[0][0] * direction.x
                + self.rows[0][1] * direction.y
                + self.rows[0][2] * direction.z,
            self.rows[1][0] * direction.x
                + self.rows[1][1] * direction.y
                + self.rows[1][2] * direction.z,
            self.rows[2][0] * direction.x
                + self.rows[2][1] * direction.y
                + self.rows[2][2] * direction.z,
        )
    }

    fn translation(translation: Vec3) -> Self {
        let mut matrix = Self::IDENTITY;
        matrix.rows[0][3] = translation.x;
        matrix.rows[1][3] = translation.y;
        matrix.rows[2][3] = translation.z;
        matrix
    }

    fn scale(scale: Vec3) -> Self {
        Self {
            rows: [
                [scale.x, 0.0, 0.0, 0.0],
                [0.0, scale.y, 0.0, 0.0],
                [0.0, 0.0, scale.z, 0.0],
                [0.0, 0.0, 0.0, 1.0],
            ],
        }
    }

    fn rotation(rotation: Quat) -> Self {
        let rotation = normalize_quat(rotation);
        let x2 = rotation.x + rotation.x;
        let y2 = rotation.y + rotation.y;
        let z2 = rotation.z + rotation.z;
        let xx = rotation.x * x2;
        let xy = rotation.x * y2;
        let xz = rotation.x * z2;
        let yy = rotation.y * y2;
        let yz = rotation.y * z2;
        let zz = rotation.z * z2;
        let wx = rotation.w * x2;
        let wy = rotation.w * y2;
        let wz = rotation.w * z2;
        Self {
            rows: [
                [1.0 - (yy + zz), xy - wz, xz + wy, 0.0],
                [xy + wz, 1.0 - (xx + zz), yz - wx, 0.0],
                [xz - wy, yz + wx, 1.0 - (xx + yy), 0.0],
                [0.0, 0.0, 0.0, 1.0],
            ],
        }
    }
}

fn reciprocal_or_zero(value: f32) -> f32 {
    if value.abs() <= f32::EPSILON || !value.is_finite() {
        0.0
    } else {
        value.recip()
    }
}

fn inverse_quat(value: Quat) -> Quat {
    let normalized = normalize_quat(value);
    Quat::from_xyzw(-normalized.x, -normalized.y, -normalized.z, normalized.w)
}

fn normalize_quat(value: Quat) -> Quat {
    let length_squared =
        value.x * value.x + value.y * value.y + value.z * value.z + value.w * value.w;
    if length_squared <= f32::EPSILON || !length_squared.is_finite() {
        return Quat::IDENTITY;
    }
    let inverse_length = length_squared.sqrt().recip();
    Quat::from_xyzw(
        value.x * inverse_length,
        value.y * inverse_length,
        value.z * inverse_length,
        value.w * inverse_length,
    )
}

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

fn scale_vec3(vector: Vec3, scale: f32) -> Vec3 {
    Vec3::new(vector.x * scale, vector.y * scale, vector.z * scale)
}

fn normalize_or(vector: Vec3, fallback: Vec3) -> Vec3 {
    let length = (vector.x * vector.x + vector.y * vector.y + vector.z * vector.z).sqrt();
    if length <= f32::EPSILON || !length.is_finite() {
        fallback
    } else {
        Vec3::new(vector.x / length, vector.y / length, vector.z / length)
    }
}