resonance 0.1.0

A modular game engine. Heavy work in progress.
Documentation

use crate::assets::loader::{AssetLoader, LoadError};
use glam::{Vec2, Vec3};
use std::path::Path;

#[derive(Clone, Debug)]
pub struct MeshData {
    pub positions: Vec<Vec3>,
    pub normals: Vec<Vec3>,
    pub uvs: Vec<Vec2>,
    pub indices: Vec<u32>,
}

impl MeshData {
    pub fn new() -> Self {
        Self {
            positions: Vec::new(),
            normals: Vec::new(),
            uvs: Vec::new(),
            indices: Vec::new(),
        }
    }

    pub fn vertex_count(&self) -> usize {
        self.positions.len()
    }

    pub fn triangle_count(&self) -> usize {
        self.indices.len() / 3
    }
}

impl Default for MeshData {
    fn default() -> Self {
        Self::new()
    }
}

pub struct ObjLoader;

impl AssetLoader for ObjLoader {
    type Asset = Vec<MeshData>;

    fn load(&self, path: &Path) -> Result<Self::Asset, LoadError> {
        let (models, _materials) = tobj::load_obj(
            path,
            &tobj::LoadOptions {
                triangulate: true,
                single_index: true,
                ..Default::default()
            },
        )
        .map_err(|e| LoadError::LoadFailed(format!("Failed to load OBJ: {}", e)))?;

        let mut meshes = Vec::new();

        for model in models {
            let mesh = &model.mesh;

            let positions: Vec<Vec3> = mesh
                .positions
                .chunks(3)
                .map(|p| Vec3::new(p[0], p[1], p[2]))
                .collect();

            let normals: Vec<Vec3> = if mesh.normals.is_empty() {

                vec![Vec3::Y; positions.len()]
            } else {
                mesh.normals
                    .chunks(3)
                    .map(|n| Vec3::new(n[0], n[1], n[2]))
                    .collect()
            };

            let uvs: Vec<Vec2> = if mesh.texcoords.is_empty() {

                vec![Vec2::ZERO; positions.len()]
            } else {
                mesh.texcoords
                    .chunks(2)
                    .map(|uv| Vec2::new(uv[0], uv[1]))
                    .collect()
            };

            meshes.push(MeshData {
                positions,
                normals,
                uvs,
                indices: mesh.indices.clone(),
            });
        }

        if meshes.is_empty() {
            return Err(LoadError::LoadFailed("No meshes found in OBJ file".into()));
        }

        log::info!(
            "Loaded OBJ with {} mesh(es) from {:?}",
            meshes.len(),
            path
        );

        Ok(meshes)
    }

    fn extensions(&self) -> &[&str] {
        &["obj"]
    }
}

pub struct GltfLoader;

impl AssetLoader for GltfLoader {
    type Asset = Vec<MeshData>;

    fn load(&self, path: &Path) -> Result<Self::Asset, LoadError> {
        let (document, buffers, _images) = gltf::import(path)
            .map_err(|e| LoadError::LoadFailed(format!("Failed to load GLTF: {}", e)))?;

        let mut meshes = Vec::new();

        for mesh in document.meshes() {
            for primitive in mesh.primitives() {
                let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()]));

                let positions: Vec<Vec3> = reader
                    .read_positions()
                    .ok_or_else(|| LoadError::LoadFailed("Missing positions in GLTF mesh".into()))?
                    .map(|p| Vec3::from_array(p))
                    .collect();

                let normals: Vec<Vec3> = reader
                    .read_normals()
                    .map(|iter| iter.map(|n| Vec3::from_array(n)).collect())
                    .unwrap_or_else(|| vec![Vec3::Y; positions.len()]);

                let uvs: Vec<Vec2> = reader
                    .read_tex_coords(0)
                    .map(|iter| {
                        iter.into_f32()
                            .map(|uv| Vec2::from_array(uv))
                            .collect()
                    })
                    .unwrap_or_else(|| vec![Vec2::ZERO; positions.len()]);

                let indices: Vec<u32> = reader
                    .read_indices()
                    .ok_or_else(|| LoadError::LoadFailed("Missing indices in GLTF mesh".into()))?
                    .into_u32()
                    .collect();

                meshes.push(MeshData {
                    positions,
                    normals,
                    uvs,
                    indices,
                });
            }
        }

        if meshes.is_empty() {
            return Err(LoadError::LoadFailed("No meshes found in GLTF file".into()));
        }

        log::info!(
            "Loaded GLTF with {} mesh(es) from {:?}",
            meshes.len(),
            path
        );

        Ok(meshes)
    }

    fn extensions(&self) -> &[&str] {
        &["gltf", "glb"]
    }
}

pub fn load_mesh_from_bytes(_bytes: &[u8], format: MeshFormat) -> Result<Vec<MeshData>, LoadError> {
    match format {
        MeshFormat::Obj => {

            Err(LoadError::UnsupportedType(
                "Loading OBJ from bytes not supported yet".into(),
            ))
        }
        MeshFormat::Gltf => {

            Err(LoadError::UnsupportedType(
                "Loading GLTF from bytes not supported yet".into(),
            ))
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub enum MeshFormat {
    Obj,
    Gltf,
}