ling-graphics 2030.0.1

3D/4D rendering, geometry, animation, and font tools for the Ling ecosystem
Documentation
use std::sync::Arc;
use glam::{Vec3, Quat, Mat4};
use crate::geometry::Mesh;
use crate::material::Material;

// ── Transform ─────────────────────────────────────────────────────────────────

#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Transform {
    pub translation: Vec3,
    pub rotation: Quat,
    pub scale: Vec3,
}

impl Transform {
    pub const IDENTITY: Self = Self {
        translation: Vec3::ZERO,
        rotation: Quat::IDENTITY,
        scale: Vec3::ONE,
    };

    pub fn from_translation(t: Vec3) -> Self { Self { translation: t, ..Self::IDENTITY } }
    pub fn from_rotation(r: Quat) -> Self    { Self { rotation: r, ..Self::IDENTITY } }
    pub fn from_scale(s: Vec3) -> Self       { Self { scale: s, ..Self::IDENTITY } }

    pub fn from_trs(translation: Vec3, rotation: Quat, scale: Vec3) -> Self {
        Self { translation, rotation, scale }
    }

    pub fn matrix(&self) -> Mat4 {
        Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation)
    }

    pub fn global_matrix(&self, parent: &Mat4) -> Mat4 {
        *parent * self.matrix()
    }

    pub fn forward(&self) -> Vec3 { self.rotation * -Vec3::Z }
    pub fn right(&self)   -> Vec3 { self.rotation *  Vec3::X }
    pub fn up(&self)      -> Vec3 { self.rotation *  Vec3::Y }

    pub fn look_at(&mut self, target: Vec3, world_up: Vec3) {
        let dir = (target - self.translation).normalize();
        if dir.length_squared() < 1e-8 { return; }
        let mat = Mat4::look_at_rh(self.translation, target, world_up);
        let (_, rot, _) = mat.inverse().to_scale_rotation_translation();
        self.rotation = rot;
    }

    pub fn lerp(&self, other: &Self, t: f32) -> Self {
        Self {
            translation: self.translation.lerp(other.translation, t),
            rotation: self.rotation.slerp(other.rotation, t),
            scale: self.scale.lerp(other.scale, t),
        }
    }
}

impl Default for Transform {
    fn default() -> Self { Self::IDENTITY }
}

// ── Scene graph ───────────────────────────────────────────────────────────────

pub type NodeId = usize;

#[derive(Debug, Clone)]
pub struct SceneNode {
    pub name: String,
    pub transform: Transform,
    pub mesh: Option<Arc<Mesh>>,
    pub material: Option<Arc<Material>>,
    pub visible: bool,
    pub children: Vec<NodeId>,
    pub parent: Option<NodeId>,
}

impl SceneNode {
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            transform: Transform::IDENTITY,
            mesh: None,
            material: None,
            visible: true,
            children: Vec::new(),
            parent: None,
        }
    }

    pub fn with_mesh(mut self, mesh: Arc<Mesh>) -> Self { self.mesh = Some(mesh); self }
    pub fn with_material(mut self, mat: Arc<Material>) -> Self { self.material = Some(mat); self }
    pub fn with_transform(mut self, t: Transform) -> Self { self.transform = t; self }
}

#[derive(Debug, Default)]
pub struct Scene {
    nodes: Vec<SceneNode>,
    roots: Vec<NodeId>,
}

impl Scene {
    pub fn new() -> Self { Self::default() }

    pub fn add_node(&mut self, node: SceneNode) -> NodeId {
        let id = self.nodes.len();
        self.nodes.push(node);
        id
    }

    pub fn add_root(&mut self, id: NodeId) { self.roots.push(id); }

    pub fn add_child(&mut self, parent: NodeId, child: NodeId) {
        self.nodes[child].parent = Some(parent);
        self.nodes[parent].children.push(child);
    }

    pub fn node(&self, id: NodeId) -> &SceneNode { &self.nodes[id] }
    pub fn node_mut(&mut self, id: NodeId) -> &mut SceneNode { &mut self.nodes[id] }

    pub fn roots(&self) -> &[NodeId] { &self.roots }

    pub fn global_transform(&self, id: NodeId) -> Mat4 {
        let node = &self.nodes[id];
        match node.parent {
            None => node.transform.matrix(),
            Some(parent_id) => self.global_transform(parent_id) * node.transform.matrix(),
        }
    }

    /// Collect all visible (node, global_matrix) pairs in DFS order.
    pub fn collect_render_items(&self) -> Vec<(NodeId, Mat4)> {
        let mut out = Vec::new();
        for &root in &self.roots {
            self.collect_recursive(root, Mat4::IDENTITY, &mut out);
        }
        out
    }

    fn collect_recursive(&self, id: NodeId, parent_mat: Mat4, out: &mut Vec<(NodeId, Mat4)>) {
        let node = &self.nodes[id];
        if !node.visible { return; }
        let mat = parent_mat * node.transform.matrix();
        if node.mesh.is_some() {
            out.push((id, mat));
        }
        for &child in &node.children {
            self.collect_recursive(child, mat, out);
        }
    }

    /// Convenience: create a mesh node, add it as a root, and return its id.
    pub fn spawn_mesh(&mut self, name: impl Into<String>, mesh: Arc<Mesh>, material: Arc<Material>, transform: Transform) -> NodeId {
        let node = SceneNode::new(name)
            .with_mesh(mesh)
            .with_material(material)
            .with_transform(transform);
        let id = self.add_node(node);
        self.add_root(id);
        id
    }
}