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 Billboard - Sprites 3D que sempre olham para a câmera
use crate::camera3d::Camera3D;
use crate::mesh3d::{Mesh3D, Vec3};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Billboard {
    pub position: Vec3,
    pub size: Vec3,
    pub texture_path: Option<String>,
    pub color: [u8; 4],
    pub billboard_type: BillboardType,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BillboardType {
    /// Sempre olha para a câmera (rotação completa)
    Spherical,
    /// Apenas rotaciona no eixo Y (árvores, postes)
    Cylindrical,
    /// Fixo, não rotaciona
    Fixed,
}

impl Billboard {
    pub fn new(position: Vec3, size: Vec3) -> Self {
        Self {
            position,
            size,
            texture_path: None,
            color: [255, 255, 255, 255],
            billboard_type: BillboardType::Spherical,
        }
    }

    pub fn spherical(position: Vec3, width: f32, height: f32) -> Self {
        Self {
            position,
            size: Vec3::new(width, height, 1.0),
            texture_path: None,
            color: [255, 255, 255, 255],
            billboard_type: BillboardType::Spherical,
        }
    }

    pub fn cylindrical(position: Vec3, width: f32, height: f32) -> Self {
        Self {
            position,
            size: Vec3::new(width, height, 1.0),
            texture_path: None,
            color: [255, 255, 255, 255],
            billboard_type: BillboardType::Cylindrical,
        }
    }

    pub fn with_texture(mut self, path: String) -> Self {
        self.texture_path = Some(path);
        self
    }

    pub fn with_color(mut self, color: [u8; 4]) -> Self {
        self.color = color;
        self
    }

    pub fn to_mesh(&self, camera: &Camera3D) -> Mesh3D {
        let mut mesh = Mesh3D::quad(self.size.x, self.size.y);
        mesh.position = self.position;
        mesh.set_color(self.color);

        // Calcula rotação para olhar para a câmera
        match self.billboard_type {
            BillboardType::Spherical => {
                let to_camera = (camera.position - self.position).normalize();
                mesh.rotation.y = to_camera.x.atan2(to_camera.z);
                mesh.rotation.x = -to_camera.y.asin();
            }
            BillboardType::Cylindrical => {
                let to_camera = camera.position - self.position;
                mesh.rotation.y = to_camera.x.atan2(to_camera.z);
            }
            BillboardType::Fixed => {
                // Não rotaciona
            }
        }

        mesh
    }
}

pub struct BillboardBatch {
    pub billboards: Vec<Billboard>,
}

impl BillboardBatch {
    pub fn new() -> Self {
        Self {
            billboards: Vec::new(),
        }
    }

    pub fn add(&mut self, billboard: Billboard) {
        self.billboards.push(billboard);
    }

    pub fn clear(&mut self) {
        self.billboards.clear();
    }

    pub fn sort_by_distance(&mut self, camera_position: Vec3) {
        self.billboards.sort_by(|a, b| {
            let dist_a = camera_position.distance(&a.position);
            let dist_b = camera_position.distance(&b.position);
            dist_b.partial_cmp(&dist_a).unwrap()
        });
    }

    pub fn to_meshes(&self, camera: &Camera3D) -> Vec<Mesh3D> {
        self.billboards
            .iter()
            .map(|billboard| billboard.to_mesh(camera))
            .collect()
    }
}

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