spottedcat 0.5.5

Rusty SpottedCat simple game engine
Documentation
use spottedcat::{Context, DrawOption3D, Spot, WindowConfig};
use spottedcat::graphics::{Bone, identity};
use spottedcat::model::{Vertex, Model};

struct GltfApp {
    model: Model,
    rotation: f32,
}

impl Spot for GltfApp {
    fn initialize(context: &mut Context) -> Self {
        // 1. Setup scene-wide PBR lighting
        context.set_ambient_light([0.2, 0.2, 0.2, 1.0]);
        // A bright directional light from the top-right
        context.set_light(0, [10.0, 10.0, 10.0, 0.0], [1.0, 1.0, 1.0, 1.0]);
        // Set camera position (matching the hardcoded view matrix in render.rs)
        context.set_camera_pos([0.0, 0.0, 5.0]);

        // 2. Create a model (using a sphere for PBR demonstration)
        let model = Model::sphere(1.0).unwrap();
        
        Self {
            model,
            rotation: 0.0,
        }
    }

    fn update(&mut self, _context: &mut Context, dt: std::time::Duration) {
        self.rotation += dt.as_secs_f32() * 0.5;
    }

    fn draw(&mut self, context: &mut Context) {
        let opts = DrawOption3D::default()
            .with_position([0.0, 0.0, 0.0]) // Already at -5 in view space
            .with_rotation([0.0, self.rotation, 0.0]);

        self.model.draw(context, opts);
    }

    fn remove(&self) {}
}

fn main() {
    spottedcat::run::<GltfApp>(WindowConfig::default());
}

/// A reference implementation of a glTF loader using the `gltf` crate.
/// This would live in your application layer.
pub fn load_gltf(ctx: &Context, path: &str) -> anyhow::Result<(Model, u32)> {
    let (document, buffers, _) = gltf::import(path)?;
    
    let mut all_vertices = Vec::new();
    let mut all_indices = Vec::new();

    // 1. Extract Mesh Data
    for mesh in document.meshes() {
        for primitive in mesh.primitives() {
            let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()]));
            
            let pos_iter = reader.read_positions().ok_or_else(|| anyhow::anyhow!("No positions"))?;
            let mut uv_iter = reader.read_tex_coords(0).map(|v| v.into_f32());
            let mut norm_iter = reader.read_normals();
            let mut joint_iter = reader.read_joints(0).map(|v| v.into_u16());
            let mut weight_iter = reader.read_weights(0).map(|v| v.into_f32());

            let base_idx = all_vertices.len() as u32;

            for pos in pos_iter {
                let uv = uv_iter.as_mut().and_then(|i| i.next()).unwrap_or([0.0, 0.0]);
                let norm = norm_iter.as_mut().and_then(|i| i.next()).unwrap_or([0.0, 0.0, 1.0]);
                let joints = joint_iter.as_mut().and_then(|i| i.next()).unwrap_or([0, 0, 0, 0]);
                let weights = weight_iter.as_mut().and_then(|i| i.next()).unwrap_or([0.0, 0.0, 0.0, 0.0]);

                all_vertices.push(Vertex {
                    pos,
                    uv,
                    normal: norm,
                    tangent: [1.0, 0.0, 0.0], // Default tangent
                    joint_indices: [joints[0] as u32, joints[1] as u32, joints[2] as u32, joints[3] as u32],
                    joint_weights: weights,
                });
            }

            if let Some(indices_reader) = reader.read_indices() {
                for idx in indices_reader.into_u32() {
                    all_indices.push(base_idx + idx);
                }
            }
        }
    }

    let model = Model::new(&all_vertices, &all_indices)?;

    // 2. Extract Skin Data
    let mut skin_id = 0;
    if let Some(skin) = document.skins().next() {
        let reader = skin.reader(|buffer| Some(&buffers[buffer.index()]));
        let ibms: Vec<[[f32; 4]; 4]> = reader.read_inverse_bind_matrices()
            .map(|i| i.collect()).unwrap_or_default();
        
        // Build hierarchy
        let mut node_parents = std::collections::HashMap::new();
        for node in document.nodes() {
            for child in node.children() {
                node_parents.insert(child.index(), node.index());
            }
        }

        let mut bones = Vec::new();
        let skin_joints: Vec<_> = skin.joints().collect();
        for (i, joint_node) in skin_joints.iter().enumerate() {
            let ibm = ibms.get(i).copied().unwrap_or(identity());
            let parent_index = node_parents.get(&joint_node.index()).and_then(|p_idx| {
                skin_joints.iter().position(|node| node.index() == *p_idx)
            });

            bones.push(Bone {
                parent_index,
                inverse_bind_matrix: ibm,
            });
        }

        let initial_matrices = vec![identity(); bones.len()];
        skin_id = ctx.create_skin(bones, initial_matrices);
    }

    Ok((model, skin_id))
}