sevenx_engine 0.2.11

Engine de jogos 2D/3D completa com suporte Android, física, áudio, partículas, tilemap, UI, eventos e sistema 3D avançado com PBR.
Documentation
// Sistema de carregamento de modelos 3D (OBJ, FBX)
use crate::mesh3d::{Mesh3D, Vec3, Vertex};
use crate::texture3d::Material3D;
use std::collections::HashMap;
use std::path::Path;

#[derive(Debug)]
pub enum ModelError {
    FileNotFound(String),
    ParseError(String),
    UnsupportedFormat(String),
    IoError(String),
}

impl std::fmt::Display for ModelError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            ModelError::FileNotFound(path) => write!(f, "File not found: {}", path),
            ModelError::ParseError(msg) => write!(f, "Parse error: {}", msg),
            ModelError::UnsupportedFormat(format) => write!(f, "Unsupported format: {}", format),
            ModelError::IoError(msg) => write!(f, "IO error: {}", msg),
        }
    }
}

impl std::error::Error for ModelError {}

pub struct ModelLoader {
    cache: HashMap<String, Vec<Mesh3D>>,
    materials: HashMap<String, Material3D>,
}

impl ModelLoader {
    pub fn new() -> Self {
        Self {
            cache: HashMap::new(),
            materials: HashMap::new(),
        }
    }

    /// Carrega um modelo 3D de arquivo
    pub fn load(&mut self, path: &str) -> Result<Vec<Mesh3D>, ModelError> {
        // Verifica cache
        if let Some(meshes) = self.cache.get(path) {
            return Ok(meshes.clone());
        }

        // Detecta formato pelo extensão
        let extension = Path::new(path)
            .extension()
            .and_then(|s| s.to_str())
            .ok_or_else(|| ModelError::UnsupportedFormat("No extension".to_string()))?;

        let meshes = match extension.to_lowercase().as_str() {
            "obj" => self.load_obj(path)?,
            "fbx" => self.load_fbx(path)?,
            _ => return Err(ModelError::UnsupportedFormat(extension.to_string())),
        };

        // Adiciona ao cache
        self.cache.insert(path.to_string(), meshes.clone());

        Ok(meshes)
    }

    /// Carrega arquivo OBJ
    fn load_obj(&mut self, path: &str) -> Result<Vec<Mesh3D>, ModelError> {
        let (models, materials) = tobj::load_obj(
            path,
            &tobj::LoadOptions {
                single_index: true,
                triangulate: true,
                ignore_points: true,
                ignore_lines: true,
            },
        )
        .map_err(|e| ModelError::ParseError(format!("OBJ parse error: {:?}", e)))?;

        // Carrega materiais se disponíveis
        if let Ok(mats) = materials {
            for mat in mats {
                let material = self.convert_material(&mat);
                self.materials.insert(mat.name.clone(), material);
            }
        }

        let mut meshes = Vec::new();

        for model in models {
            let mesh_data = &model.mesh;
            let mut vertices = Vec::new();

            // Converte vértices
            for i in 0..mesh_data.positions.len() / 3 {
                let pos_idx = i * 3;
                let position = Vec3::new(
                    mesh_data.positions[pos_idx],
                    mesh_data.positions[pos_idx + 1],
                    mesh_data.positions[pos_idx + 2],
                );

                // Normais
                let normal = if !mesh_data.normals.is_empty() {
                    Vec3::new(
                        mesh_data.normals[pos_idx],
                        mesh_data.normals[pos_idx + 1],
                        mesh_data.normals[pos_idx + 2],
                    )
                } else {
                    Vec3::new(0.0, 1.0, 0.0)
                };

                // UVs
                let uv = if !mesh_data.texcoords.is_empty() && i * 2 + 1 < mesh_data.texcoords.len() {
                    (mesh_data.texcoords[i * 2], mesh_data.texcoords[i * 2 + 1])
                } else {
                    (0.0, 0.0)
                };

                vertices.push(Vertex {
                    position,
                    normal,
                    uv,
                    color: [255, 255, 255, 255],
                });
            }

            let mesh = Mesh3D::finalize(vertices, mesh_data.indices.clone());
            meshes.push(mesh);
        }

        Ok(meshes)
    }

    /// Carrega arquivo FBX (implementação básica)
    fn load_fbx(&mut self, _path: &str) -> Result<Vec<Mesh3D>, ModelError> {
        // FBX é um formato binário complexo
        // Por enquanto, vamos retornar um erro informativo
        Err(ModelError::UnsupportedFormat(
            "FBX support coming soon. Use OBJ format for now.".to_string()
        ))
    }

    /// Converte material do tobj para Material3D
    fn convert_material(&self, mat: &tobj::Material) -> Material3D {
        let diffuse = mat.diffuse.unwrap_or([1.0, 1.0, 1.0]);
        let mut material = Material3D::new([
            (diffuse[0] * 255.0) as u8,
            (diffuse[1] * 255.0) as u8,
            (diffuse[2] * 255.0) as u8,
            255,
        ]);

        // Metallic e roughness (aproximação)
        if let Some(shininess) = mat.shininess {
            material = material.with_roughness(1.0 - (shininess / 1000.0).min(1.0));
        }

        // Emissão
        if let Some(ambient) = mat.ambient {
            if ambient[0] > 0.0 || ambient[1] > 0.0 || ambient[2] > 0.0 {
                material = material.with_emission(
                    [
                        (ambient[0] * 255.0) as u8,
                        (ambient[1] * 255.0) as u8,
                        (ambient[2] * 255.0) as u8,
                        255,
                    ],
                    1.0,
                );
            }
        }

        material
    }

    /// Limpa o cache
    pub fn clear_cache(&mut self) {
        self.cache.clear();
    }

    /// Remove um modelo do cache
    pub fn remove_from_cache(&mut self, path: &str) {
        self.cache.remove(path);
    }

    /// Lista modelos em cache
    pub fn cached_models(&self) -> Vec<String> {
        self.cache.keys().cloned().collect()
    }

    /// Carrega modelo com transformações
    pub fn load_with_transform(
        &mut self,
        path: &str,
        position: Vec3,
        rotation: Vec3,
        scale: Vec3,
    ) -> Result<Vec<Mesh3D>, ModelError> {
        let mut meshes = self.load(path)?;
        
        for mesh in &mut meshes {
            mesh.position = position;
            mesh.rotation = rotation;
            mesh.scale = scale;
        }

        Ok(meshes)
    }

    /// Carrega modelo com material customizado
    pub fn load_with_material(
        &mut self,
        path: &str,
        material: Material3D,
    ) -> Result<Vec<Mesh3D>, ModelError> {
        let mut meshes = self.load(path)?;
        
        for mesh in &mut meshes {
            mesh.material = Some(material.clone());
        }

        Ok(meshes)
    }
}

// Builder para facilitar carregamento
pub struct ModelBuilder {
    path: String,
    position: Vec3,
    rotation: Vec3,
    scale: Vec3,
    material: Option<Material3D>,
}

impl ModelBuilder {
    pub fn new(path: &str) -> Self {
        Self {
            path: path.to_string(),
            position: Vec3::zero(),
            rotation: Vec3::zero(),
            scale: Vec3::new(1.0, 1.0, 1.0),
            material: None,
        }
    }

    pub fn position(mut self, x: f32, y: f32, z: f32) -> Self {
        self.position = Vec3::new(x, y, z);
        self
    }

    pub fn rotation(mut self, x: f32, y: f32, z: f32) -> Self {
        self.rotation = Vec3::new(x, y, z);
        self
    }

    pub fn scale(mut self, x: f32, y: f32, z: f32) -> Self {
        self.scale = Vec3::new(x, y, z);
        self
    }

    pub fn uniform_scale(mut self, scale: f32) -> Self {
        self.scale = Vec3::new(scale, scale, scale);
        self
    }

    pub fn material(mut self, material: Material3D) -> Self {
        self.material = Some(material);
        self
    }

    pub fn load(self, loader: &mut ModelLoader) -> Result<Vec<Mesh3D>, ModelError> {
        let mut meshes = loader.load(&self.path)?;
        
        for mesh in &mut meshes {
            mesh.position = self.position;
            mesh.rotation = self.rotation;
            mesh.scale = self.scale;
            
            if let Some(ref material) = self.material {
                mesh.material = Some(material.clone());
            }
        }

        Ok(meshes)
    }
}

// Funções auxiliares
impl ModelLoader {
    /// Cria um cubo e salva como OBJ
    pub fn export_cube_obj(path: &str, size: f32) -> Result<(), ModelError> {
        let s = size / 2.0;
        let obj_content = format!(
            "# Cube exported from SevenX Engine\n\
             v -{} -{} {}\n\
             v {} -{} {}\n\
             v {} {} {}\n\
             v -{} {} {}\n\
             v {} -{} -{}\n\
             v -{} -{} -{}\n\
             v -{} {} -{}\n\
             v {} {} -{}\n\
             \n\
             vn 0.0 0.0 1.0\n\
             vn 0.0 0.0 -1.0\n\
             vn 0.0 1.0 0.0\n\
             vn 0.0 -1.0 0.0\n\
             vn 1.0 0.0 0.0\n\
             vn -1.0 0.0 0.0\n\
             \n\
             f 1//1 2//1 3//1\n\
             f 1//1 3//1 4//1\n\
             f 5//2 6//2 7//2\n\
             f 5//2 7//2 8//2\n\
             f 4//3 3//3 8//3\n\
             f 4//3 8//3 7//3\n\
             f 6//4 5//4 2//4\n\
             f 6//4 2//4 1//4\n\
             f 2//5 5//5 8//5\n\
             f 2//5 8//5 3//5\n\
             f 6//6 1//6 4//6\n\
             f 6//6 4//6 7//6\n",
            s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s
        );

        std::fs::write(path, obj_content)
            .map_err(|e| ModelError::IoError(e.to_string()))
    }

    /// Exporta mesh para OBJ
    pub fn export_mesh_obj(mesh: &Mesh3D, path: &str) -> Result<(), ModelError> {
        let mut obj_content = String::from("# Mesh exported from SevenX Engine\n");

        // Vértices
        for vertex in &mesh.vertices {
            obj_content.push_str(&format!(
                "v {} {} {}\n",
                vertex.position.x, vertex.position.y, vertex.position.z
            ));
        }

        obj_content.push('\n');

        // Normais
        for vertex in &mesh.vertices {
            obj_content.push_str(&format!(
                "vn {} {} {}\n",
                vertex.normal.x, vertex.normal.y, vertex.normal.z
            ));
        }

        obj_content.push('\n');

        // UVs
        for vertex in &mesh.vertices {
            obj_content.push_str(&format!("vt {} {}\n", vertex.uv.0, vertex.uv.1));
        }

        obj_content.push('\n');

        // Faces
        for i in (0..mesh.indices.len()).step_by(3) {
            let i0 = mesh.indices[i] + 1;
            let i1 = mesh.indices[i + 1] + 1;
            let i2 = mesh.indices[i + 2] + 1;
            obj_content.push_str(&format!(
                "f {}/{}/{} {}/{}/{} {}/{}/{}\n",
                i0, i0, i0, i1, i1, i1, i2, i2, i2
            ));
        }

        std::fs::write(path, obj_content)
            .map_err(|e| ModelError::IoError(e.to_string()))
    }
}