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 Terreno Procedural
use crate::mesh3d::{Mesh3D, Vec3, Vertex};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Terrain {
    pub mesh: Mesh3D,
    pub width: u32,
    pub depth: u32,
    pub height_scale: f32,
    pub heightmap: Vec<Vec<f32>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TerrainConfig {
    pub width: u32,
    pub depth: u32,
    pub height_scale: f32,
    pub octaves: u32,
    pub persistence: f32,
    pub lacunarity: f32,
    pub seed: u32,
}

impl Terrain {
    pub fn new(config: TerrainConfig) -> Self {
        let heightmap = Self::generate_heightmap(&config);
        let mesh = Self::create_mesh_from_heightmap(&heightmap, &config);
        
        Self {
            mesh,
            width: config.width,
            depth: config.depth,
            height_scale: config.height_scale,
            heightmap,
        }
    }

    pub fn flat(width: u32, depth: u32) -> Self {
        let config = TerrainConfig {
            width,
            depth,
            height_scale: 0.0,
            octaves: 1,
            persistence: 0.5,
            lacunarity: 2.0,
            seed: 0,
        };
        Self::new(config)
    }

    pub fn hills(width: u32, depth: u32, height: f32) -> Self {
        let config = TerrainConfig {
            width,
            depth,
            height_scale: height,
            octaves: 4,
            persistence: 0.5,
            lacunarity: 2.0,
            seed: 42,
        };
        Self::new(config)
    }

    pub fn mountains(width: u32, depth: u32, height: f32) -> Self {
        let config = TerrainConfig {
            width,
            depth,
            height_scale: height,
            octaves: 6,
            persistence: 0.6,
            lacunarity: 2.5,
            seed: 123,
        };
        Self::new(config)
    }

    fn generate_heightmap(config: &TerrainConfig) -> Vec<Vec<f32>> {
        let mut heightmap = vec![vec![0.0; config.depth as usize]; config.width as usize];

        for x in 0..config.width {
            for z in 0..config.depth {
                let mut height = 0.0;
                let mut amplitude = 1.0;
                let mut frequency = 1.0;

                for _ in 0..config.octaves {
                    let sample_x = x as f32 * frequency / config.width as f32;
                    let sample_z = z as f32 * frequency / config.depth as f32;

                    let noise = Self::perlin_noise(sample_x, sample_z, config.seed);
                    height += noise * amplitude;

                    amplitude *= config.persistence;
                    frequency *= config.lacunarity;
                }

                heightmap[x as usize][z as usize] = height * config.height_scale;
            }
        }

        heightmap
    }

    fn perlin_noise(x: f32, z: f32, seed: u32) -> f32 {
        // Implementação simplificada de Perlin noise
        let xi = x.floor() as i32;
        let zi = z.floor() as i32;
        
        let xf = x - xi as f32;
        let zf = z - zi as f32;

        let n00 = Self::hash(xi, zi, seed);
        let n10 = Self::hash(xi + 1, zi, seed);
        let n01 = Self::hash(xi, zi + 1, seed);
        let n11 = Self::hash(xi + 1, zi + 1, seed);

        let nx0 = Self::lerp(n00, n10, xf);
        let nx1 = Self::lerp(n01, n11, xf);

        Self::lerp(nx0, nx1, zf)
    }

    fn hash(x: i32, z: i32, seed: u32) -> f32 {
        let mut n = (x.wrapping_mul(374761393) + z.wrapping_mul(668265263)) as u32;
        n = n.wrapping_add(seed);
        n = (n ^ (n >> 13)).wrapping_mul(1274126177);
        n = n ^ (n >> 16);
        (n as f32 / u32::MAX as f32) * 2.0 - 1.0
    }

    fn lerp(a: f32, b: f32, t: f32) -> f32 {
        a + (b - a) * t
    }

    fn create_mesh_from_heightmap(heightmap: &[Vec<f32>], config: &TerrainConfig) -> Mesh3D {
        let mut vertices = Vec::new();
        let mut indices = Vec::new();

        let width = config.width as usize;
        let depth = config.depth as usize;

        // Cria vértices
        for x in 0..width {
            for z in 0..depth {
                let height = heightmap[x][z];
                let pos = Vec3::new(
                    x as f32 - width as f32 / 2.0,
                    height,
                    z as f32 - depth as f32 / 2.0,
                );

                // Calcula normal (simplificado)
                let normal = Self::calculate_normal(heightmap, x, z, width, depth);

                // Cor baseada na altura
                let color = Self::height_to_color(height, config.height_scale);

                vertices.push(Vertex {
                    position: pos,
                    normal,
                    uv: (x as f32 / width as f32, z as f32 / depth as f32),
                    color,
                });
            }
        }

        // Cria índices
        for x in 0..(width - 1) {
            for z in 0..(depth - 1) {
                let i0 = (x * depth + z) as u32;
                let i1 = ((x + 1) * depth + z) as u32;
                let i2 = ((x + 1) * depth + z + 1) as u32;
                let i3 = (x * depth + z + 1) as u32;

                indices.extend_from_slice(&[i0, i1, i2, i2, i3, i0]);
            }
        }

        Mesh3D::finalize(vertices, indices)
    }

    fn calculate_normal(heightmap: &[Vec<f32>], x: usize, z: usize, width: usize, depth: usize) -> Vec3 {
        let h = heightmap[x][z];
        
        let hx = if x < width - 1 { heightmap[x + 1][z] } else { h };
        let hz = if z < depth - 1 { heightmap[x][z + 1] } else { h };

        let dx = Vec3::new(1.0, hx - h, 0.0);
        let dz = Vec3::new(0.0, hz - h, 1.0);

        dx.cross(&dz).normalize()
    }

    fn height_to_color(height: f32, max_height: f32) -> [u8; 4] {
        let normalized = (height / max_height).max(0.0).min(1.0);
        
        if normalized < 0.3 {
            // Água/baixo - azul
            [50, 100, 200, 255]
        } else if normalized < 0.5 {
            // Areia - amarelo
            [200, 180, 100, 255]
        } else if normalized < 0.7 {
            // Grama - verde
            [50, 150, 50, 255]
        } else if normalized < 0.9 {
            // Pedra - cinza
            [100, 100, 100, 255]
        } else {
            // Neve - branco
            [255, 255, 255, 255]
        }
    }

    pub fn get_height_at(&self, x: f32, z: f32) -> f32 {
        let grid_x = (x + self.width as f32 / 2.0) as usize;
        let grid_z = (z + self.depth as f32 / 2.0) as usize;

        if grid_x < self.width as usize && grid_z < self.depth as usize {
            self.heightmap[grid_x][grid_z]
        } else {
            0.0
        }
    }

    pub fn smooth(&mut self, iterations: u32) {
        for _ in 0..iterations {
            let mut new_heightmap = self.heightmap.clone();
            
            for x in 1..(self.width as usize - 1) {
                for z in 1..(self.depth as usize - 1) {
                    let sum = self.heightmap[x - 1][z]
                        + self.heightmap[x + 1][z]
                        + self.heightmap[x][z - 1]
                        + self.heightmap[x][z + 1]
                        + self.heightmap[x][z];
                    
                    new_heightmap[x][z] = sum / 5.0;
                }
            }
            
            self.heightmap = new_heightmap;
        }

        // Reconstrói mesh
        let config = TerrainConfig {
            width: self.width,
            depth: self.depth,
            height_scale: self.height_scale,
            octaves: 1,
            persistence: 0.5,
            lacunarity: 2.0,
            seed: 0,
        };
        self.mesh = Self::create_mesh_from_heightmap(&self.heightmap, &config);
    }
}

impl Default for TerrainConfig {
    fn default() -> Self {
        Self {
            width: 50,
            depth: 50,
            height_scale: 10.0,
            octaves: 4,
            persistence: 0.5,
            lacunarity: 2.0,
            seed: 42,
        }
    }
}