enginerenderer 0.0.1

A zero-dependency offline rendering engine in pure Rust — CPU path tracing, BVH acceleration, 16-band spectral rendering, PBR materials, animation & video export.
Documentation
use crate::core::engine::rendering::mesh::skinning::Skeleton;
use crate::core::engine::rendering::raytracing::Vec3;

#[derive(Debug, Clone)]
pub struct SpringBone {
    pub rest_position: Vec3,
    pub position: Vec3,
    pub velocity: Vec3,
    pub stiffness: f64,
    pub damping: f64,
    pub mass: f64,
}

impl SpringBone {
    pub fn new(rest_position: Vec3, stiffness: f64, damping: f64, mass: f64) -> Self {
        Self {
            rest_position,
            position: rest_position,
            velocity: Vec3::ZERO,
            stiffness,
            damping,
            mass,
        }
    }

    pub fn update(&mut self, parent_world_pos: Vec3, dt: f64) {
        let target = parent_world_pos + self.rest_position;
        let spring_force = (target - self.position) * self.stiffness;
        let damping_force = self.velocity * (-self.damping);
        let total_force = spring_force + damping_force;
        let accel = total_force * (1.0 / self.mass.max(f64::EPSILON));
        self.velocity += accel * dt;
        self.position += self.velocity * dt;
    }

    pub fn displacement(&self) -> Vec3 {
        self.position - self.rest_position
    }
}

#[derive(Debug, Clone)]
pub struct JiggleBone {
    pub spring: SpringBone,
    pub max_angle_rad: f64,
}

impl JiggleBone {
    pub fn new(
        rest_position: Vec3,
        stiffness: f64,
        damping: f64,
        mass: f64,
        max_angle_rad: f64,
    ) -> Self {
        Self {
            spring: SpringBone::new(rest_position, stiffness, damping, mass),
            max_angle_rad,
        }
    }

    pub fn update(&mut self, parent_world_pos: Vec3, dt: f64) {
        self.spring.update(parent_world_pos, dt);
        let disp = self.spring.displacement();
        let len = disp.length();
        if len > f64::EPSILON {
            let max_disp =
                self.max_angle_rad.tan() * self.spring.rest_position.length().max(f64::EPSILON);
            if len > max_disp {
                let clamped = disp * (max_disp / len);
                self.spring.position = self.spring.rest_position + clamped;
                self.spring.velocity = self.spring.velocity * 0.5;
            }
        }
    }

    pub fn apply_to_bone_position(&self, bone_idx: usize, skeleton: &mut Skeleton) {
        if bone_idx < skeleton.bones.len() {
            let disp = self.spring.displacement();
            skeleton.bones[bone_idx].local_transform.cols[3][0] += disp.x;
            skeleton.bones[bone_idx].local_transform.cols[3][1] += disp.y;
            skeleton.bones[bone_idx].local_transform.cols[3][2] += disp.z;
        }
    }
}

pub struct SecondaryMotionSystem {
    pub bones: Vec<(usize, JiggleBone)>,
}

impl SecondaryMotionSystem {
    pub fn new() -> Self {
        Self { bones: Vec::new() }
    }

    pub fn add_jiggle_bone(&mut self, bone_idx: usize, jiggle: JiggleBone) {
        self.bones.push((bone_idx, jiggle));
    }

    pub fn update(&mut self, skeleton: &mut Skeleton, dt: f64) {
        let world_transforms: Vec<_> = skeleton.world_transforms.clone();
        for (bone_idx, jiggle) in &mut self.bones {
            let parent_idx = skeleton.bones[*bone_idx].parent;
            let parent_pos = match parent_idx {
                Some(pi) => Vec3::new(
                    world_transforms[pi].cols[3][0],
                    world_transforms[pi].cols[3][1],
                    world_transforms[pi].cols[3][2],
                ),
                None => Vec3::ZERO,
            };
            jiggle.update(parent_pos, dt);
            jiggle.apply_to_bone_position(*bone_idx, skeleton);
        }
        skeleton.update_transforms();
    }

    pub fn bone_count(&self) -> usize {
        self.bones.len()
    }
}