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
// Showcase completo das features 3D v0.2.8
use sevenx_engine::*;

struct Complete3DShowcase {
    camera: Camera3D,
    skybox: Skybox,
    terrain: Terrain,
    physics: Physics3D,
    octree: Octree<usize>,
    billboards: BillboardBatch,
    frustum: Frustum,
    time: f32,
    show_stats: bool,
}

impl GameState for Complete3DShowcase {
    fn new() -> Self {
        let mut camera = Camera3D::new(800.0 / 600.0);
        camera.position = Vec3::new(0.0, 20.0, 30.0);
        camera.look_at(Vec3::new(0.0, 0.0, 0.0));

        // Skybox
        let mut skybox = Skybox::day_sky();
        skybox.set_rotation_speed(0.1);

        // Terreno procedural
        let terrain = Terrain::hills(50, 50, 15.0);

        // Física 3D
        let mut physics = Physics3D::new();
        
        // Adiciona algumas esferas físicas
        for i in 0..5 {
            let pos = Vec3::new(
                (i as f32 - 2.0) * 3.0,
                20.0 + i as f32 * 2.0,
                0.0,
            );
            let body = RigidBody3D::dynamic(pos, 1.0);
            let collider = Collider3D::sphere(1.0);
            physics.add_body(body, collider);
        }

        // Chão estático
        let ground_body = RigidBody3D::static_body(Vec3::new(0.0, -1.0, 0.0));
        let ground_collider = Collider3D::box_collider(Vec3::new(50.0, 1.0, 50.0));
        physics.add_body(ground_body, ground_collider);

        // Octree para spatial partitioning
        let bounds = BoundingBox::new(
            Vec3::new(-50.0, -10.0, -50.0),
            Vec3::new(50.0, 50.0, 50.0),
        );
        let mut octree = Octree::new(bounds, 4, 8);
        
        // Adiciona objetos ao octree
        for i in 0..100 {
            let x = (i % 10) as f32 * 5.0 - 25.0;
            let z = (i / 10) as f32 * 5.0 - 25.0;
            octree.insert(Vec3::new(x, 0.0, z), i);
        }

        // Billboards (árvores, partículas, etc)
        let mut billboards = BillboardBatch::new();
        for i in 0..20 {
            let angle = (i as f32 / 20.0) * std::f32::consts::PI * 2.0;
            let radius = 15.0;
            let pos = Vec3::new(
                angle.cos() * radius,
                5.0,
                angle.sin() * radius,
            );
            billboards.add(
                Billboard::cylindrical(pos, 2.0, 4.0)
                    .with_color([50, 150, 50, 255])
            );
        }

        let frustum = Frustum::from_camera(&camera, 800.0 / 600.0);

        Self {
            camera,
            skybox,
            terrain,
            physics,
            octree,
            billboards,
            frustum,
            time: 0.0,
            show_stats: true,
        }
    }

    fn update(&mut self, dt: f32, input: &InputHandler, _world: &mut World) {
        self.time += dt;

        // Controles da câmera
        let speed = 15.0;
        if input.is_key_down(KeyCode::KeyW) {
            self.camera.position = self.camera.position + self.camera.get_forward() * speed * dt;
        }
        if input.is_key_down(KeyCode::KeyS) {
            self.camera.position = self.camera.position - self.camera.get_forward() * speed * dt;
        }
        if input.is_key_down(KeyCode::KeyA) {
            self.camera.position = self.camera.position - self.camera.get_right() * speed * dt;
        }
        if input.is_key_down(KeyCode::KeyD) {
            self.camera.position = self.camera.position + self.camera.get_right() * speed * dt;
        }
        if input.is_key_down(KeyCode::Space) {
            self.camera.position.y += speed * dt;
        }
        if input.is_key_down(KeyCode::ShiftLeft) {
            self.camera.position.y -= speed * dt;
        }

        // Rotação da câmera
        if input.is_key_down(KeyCode::ArrowLeft) {
            self.camera.yaw -= 90.0 * dt;
        }
        if input.is_key_down(KeyCode::ArrowRight) {
            self.camera.yaw += 90.0 * dt;
        }
        if input.is_key_down(KeyCode::ArrowUp) {
            self.camera.pitch += 90.0 * dt;
        }
        if input.is_key_down(KeyCode::ArrowDown) {
            self.camera.pitch -= 90.0 * dt;
        }

        // Toggle stats
        if input.is_key_pressed(KeyCode::F1) {
            self.show_stats = !self.show_stats;
        }

        // Troca skybox
        if input.is_key_pressed(KeyCode::Digit1) {
            self.skybox = Skybox::day_sky();
        }
        if input.is_key_pressed(KeyCode::Digit2) {
            self.skybox = Skybox::night_sky();
        }
        if input.is_key_pressed(KeyCode::Digit3) {
            self.skybox = Skybox::sunset_sky();
        }

        // Raycast com mouse
        if input.is_mouse_button_pressed(MouseBtn::Left) {
            let ray_origin = self.camera.position;
            let ray_direction = self.camera.get_forward();
            
            if let Some(hit) = self.physics.raycast(ray_origin, ray_direction, 100.0) {
                println!("Hit at: {:?}, distance: {:.2}", hit.point, hit.distance);
            }
        }

        // Atualiza física
        self.physics.update(dt);

        // Atualiza skybox
        self.skybox.update(dt);

        // Atualiza frustum
        self.frustum = Frustum::from_camera(&self.camera, 800.0 / 600.0);

        // Ordena billboards por distância (para alpha blending correto)
        self.billboards.sort_by_distance(self.camera.position);
    }

    fn draw(&mut self, _world: &World, frame_buffer: &mut [u8]) {
        // Limpa com cor do skybox
        for pixel in frame_buffer.chunks_exact_mut(4) {
            pixel[0] = 135;
            pixel[1] = 206;
            pixel[2] = 235;
            pixel[3] = 255;
        }

        if self.show_stats {
            self.draw_stats();
        }
    }
}

impl Complete3DShowcase {
    fn draw_stats(&self) {
        println!("\n=== Complete 3D Showcase Stats ===");
        println!("Camera: ({:.1}, {:.1}, {:.1})", 
            self.camera.position.x, 
            self.camera.position.y, 
            self.camera.position.z
        );
        
        // Stats de física
        println!("\n--- Physics ---");
        println!("Bodies: {}", self.physics.bodies.len());
        println!("Gravity: ({:.1}, {:.1}, {:.1})", 
            self.physics.gravity.x,
            self.physics.gravity.y,
            self.physics.gravity.z
        );

        // Stats do octree
        let octree_stats = self.octree.get_stats();
        println!("\n--- Octree ---");
        println!("Nodes: {}", octree_stats.total_nodes);
        println!("Objects: {}", octree_stats.total_objects);
        println!("Max Depth: {}", octree_stats.max_depth_reached);
        println!("Avg Objects/Node: {:.2}", octree_stats.average_objects_per_node);

        // Stats do terreno
        println!("\n--- Terrain ---");
        println!("Size: {}x{}", self.terrain.width, self.terrain.depth);
        println!("Vertices: {}", self.terrain.mesh.vertices.len());
        println!("Triangles: {}", self.terrain.mesh.indices.len() / 3);

        // Stats dos billboards
        println!("\n--- Billboards ---");
        println!("Count: {}", self.billboards.billboards.len());

        println!("\n--- Controls ---");
        println!("WASD - Move | Space/Shift - Up/Down");
        println!("Arrows - Rotate Camera");
        println!("1/2/3 - Change Skybox");
        println!("Left Click - Raycast");
        println!("F1 - Toggle Stats");
    }
}

fn main() {
    let config = EngineConfig {
        window_title: "Complete 3D Showcase - v0.2.8".to_string(),
        window_width: 800,
        window_height: 600,
        clear_color: [135, 206, 235, 255],
        ..Default::default()
    };

    Engine::with_config(config).run::<Complete3DShowcase>();
}