commit 01799256e71b157a8a52cb9e837d77cfdc4cd021 Author: John Doe Date: Fri Oct 24 15:20:12 2025 +0200 Initial release v1.0.0 - Isometric Terrain Generator - Procedural terrain generation using Perlin noise - OpenGL 3D rendering with isometric view - Biome-based coloring system (7 biomes) - Real-time camera controls (zoom, height adjustment) - Modular architecture with config/settings.py - Complete Italian documentation (8 chapters) - Interactive controls: R to regenerate, arrows for camera diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..99fe592 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,314 @@ +# Copilot Instructions for GLTerrain Project + +## Project Overview + +**GLTerrain** is an isometric terrain generator using OpenGL, Pygame, and Perlin noise. It creates procedurally generated 3D terrains with RollerCoaster Tycoon-style isometric view, biome-based coloring, and real-time camera controls. + +**Version**: 1.0.0 +**Language**: Python 3.13+ +**Graphics**: OpenGL with Pygame + +## Python Virtual Environment Workflow + +**IMPORTANT**: This project uses a Python virtual environment located at `./venv`. + +### Before Running Any Python Script: + +1. **Always activate the virtual environment**: + ```bash + source venv/bin/activate && python + ``` + +2. **For installing packages**: + ```bash + source venv/bin/activate && pip install -r requirements.txt + ``` + +### Standard Command Pattern: + +```bash +cd /home/enne2/Sviluppo/shader && source venv/bin/activate && python main.py +``` + +### DO NOT: +- Run Python scripts without activating the virtual environment +- Install packages globally +- Modify code without testing with activated venv + +## Project Architecture + +### Directory Structure: +``` +shader/ +├── main.py # Entry point (uses modular architecture) +├── isometric_terrain.py # Legacy monolithic version (preserved) +├── requirements.txt # Python dependencies +├── config/ +│ └── settings.py # All configuration (TERRAIN, CAMERA, RENDERING, BIOMES) +├── src/ +│ ├── app.py # Main application coordinator +│ ├── camera/ +│ │ └── camera.py # Camera positioning and controls +│ ├── terrain/ +│ │ └── generator.py # Perlin noise terrain generation +│ └── rendering/ +│ └── terrain_renderer.py # OpenGL mesh rendering +└── docs/ # Italian documentation (8 chapters) + ├── README.md # Index with navigation + ├── 01-introduzione.md + ├── 02-concetti-base.md + ├── 03-opengl.md + ├── 04-perlin-noise.md + ├── 05-implementazione.md + ├── 06-rendering.md + ├── 07-camera.md + └── 08-personalizzazione.md +``` + +### Dependencies: +- **pygame** >= 2.5.0: Window management and event handling +- **PyOpenGL** >= 3.1.7: OpenGL bindings for 3D rendering +- **PyOpenGL-accelerate** >= 3.1.7: Performance optimization +- **numpy** >= 1.24.0: Array operations for heightmaps +- **noise**: Perlin noise generation (installed with pygame) + +## Key Configuration Files + +### config/settings.py + +**CRITICAL**: All project parameters are centralized here. Never hardcode values in source files. + +#### Main Sections: + +1. **TERRAIN** - Grid and generation parameters: + ```python + grid_size: 20 # 20×20 tiles + tile_width: 30 # 30px width + tile_height: 30 # 30px height + perlin_scale: 8.0 # Noise frequency (≈ grid_size / 2.5) + height_multiplier: 80.0 # Vertical scale + perlin_octaves: 4 # Detail levels + perlin_persistence: 0.6 # Detail influence + perlin_lacunarity: 2.5 # Detail density + ``` + +2. **CAMERA** - View parameters: + ```python + distance: 800 # Camera distance from center + height: 450 # Camera height (Y axis) + fov: 45 # Field of view (degrees) + zoom_speed: 10.0 # UP/DOWN key speed + height_speed: 10.0 # LEFT/RIGHT key speed + ``` + +3. **RENDERING** - Visual settings: + ```python + line_width: 5.0 # Tile border thickness + line_color: (0,0,0) # Border color (RGB) + light_position: (1,1,1,0) # Directional light + ``` + +4. **BIOME_COLORS** - Biome RGB colors: + ```python + water, sand, grass_low, grass_mid, grass_high, rock, snow + ``` + +5. **BIOME_THRESHOLDS** - Height thresholds for biome assignment: + ```python + water: -10, sand: 0, grass_low: 10, grass_mid: 25, + grass_high: 40, rock: 55, snow: 70 + ``` + +## Architecture Principles + +### Separation of Concerns: +- **TerrainGenerator**: Only generates heightmaps (Perlin noise) +- **TerrainRenderer**: Only renders geometry (OpenGL calls) +- **Camera**: Only handles view transformations and input +- **IsometricTerrainApp**: Orchestrates all components + +### Data Flow: +``` +User Input → Camera → App.update() +App → TerrainGenerator.generate() → heightmap array +App → TerrainRenderer.render(heightmap) → OpenGL drawing +``` + +### Key Design Patterns: +1. **Configuration Object**: All settings in one module +2. **Single Responsibility**: Each class has one clear purpose +3. **Dependency Injection**: Pass dependencies via __init__ +4. **Stateless Rendering**: Renderer doesn't store heightmap + +## Important Implementation Details + +### Perlin Noise Parameters + +**Rule of Thumb**: `perlin_scale ≈ grid_size / 2.5` + +- Grid 10×10 → scale = 4.0 +- Grid 20×20 → scale = 8.0 (current) +- Grid 30×30 → scale = 12.0 + +**Why?** Ensures terrain features span multiple tiles for natural appearance. + +### Isometric View Mathematics + +Camera positioned at **45° horizontal and vertical**: +```python +camera_position = (distance, height, distance) # X = Z for 45° +look_at = (0, 0, 0) # Center of terrain +up_vector = (0, 1, 0) # Y is up +``` + +### Tile Rendering + +Each tile is a **quad** with 4 vertices: +``` +Top face: 4 vertices at (x, height, z) corners +Side faces: Only drawn if neighbor is lower +``` + +### Biome Assignment + +```python +if height < threshold['water']: + color = BIOME_COLORS['water'] +elif height < threshold['sand']: + color = BIOME_COLORS['sand'] +# ... etc +``` + +## Common Modification Patterns + +### Changing Grid Size: +1. Update `TERRAIN['grid_size']` in settings.py +2. Adjust `TERRAIN['perlin_scale']` (new_size / 2.5) +3. Consider adjusting `CAMERA['distance']` for larger grids + +### Adding New Biome: +1. Add color to `BIOME_COLORS` +2. Add threshold to `BIOME_THRESHOLDS` +3. Update `get_color_for_height()` in terrain_renderer.py +4. Ensure thresholds are in ascending order + +### Modifying Camera Behavior: +1. Edit Camera class in `src/camera/camera.py` +2. All movement logic in `handle_input(keys)` +3. Projection setup in `setup_projection(aspect_ratio)` +4. View setup in `setup_modelview()` + +### Performance Optimization: +- Reduce `grid_size` (20 → 15) +- Reduce `perlin_octaves` (4 → 3) +- Disable `enable_smoothing` +- Reduce `far_clip` (5000 → 3000) + +## Testing Guidelines + +### Always Test After Changes: +```bash +source venv/bin/activate && python main.py +``` + +### Test Controls: +- **UP/DOWN**: Zoom (should be smooth) +- **LEFT/RIGHT**: Height (should move camera up/down) +- **R**: Regenerate terrain (should create new random terrain) +- **ESC**: Exit + +### Visual Validation: +- Check tile borders are visible (line_width > 3.0) +- Verify biome colors match settings +- Ensure no Z-fighting (flickering surfaces) +- Confirm terrain has variety (not flat) + +## Documentation + +### Italian Documentation (docs/): +Complete 8-chapter guide explaining: +1. Project overview and results +2. 3D graphics and isometric concepts +3. OpenGL technology +4. Perlin noise algorithm +5. Code architecture +6. Rendering system +7. Camera system +8. Customization guide + +**Target Audience**: Non-technical users, no OpenGL knowledge assumed + +**Update Rule**: When changing features, update relevant documentation chapter. + +## Git Workflow + +### Repository: +- Remote: `ssh://git@git.enne2.net:222/enne2/GLTerrain.git` +- Branch: `master` + +### Version Tagging: +```bash +git tag -a v1.x.x -m "Description" +git push origin master --tags +``` + +### .gitignore Includes: +- venv/, __pycache__/, *.pyc +- .vscode/, .idea/ +- *.png, *.jpg (generated screenshots) +- *_backup_*.py + +## Debugging Tips + +### Common Issues: + +1. **Flat Terrain**: Increase `height_multiplier` or reduce `perlin_scale` +2. **Too Chaotic**: Reduce `height_multiplier` or increase `perlin_scale` +3. **Invisible Borders**: Increase `line_width` (5.0 → 8.0) +4. **Camera Issues**: Check `distance` and `height` values match tile scale +5. **Performance**: Reduce `grid_size` or `perlin_octaves` + +### OpenGL Debugging: +- Enable depth testing: `glEnable(GL_DEPTH_TEST)` (already enabled) +- Check matrix mode: `glMatrixMode(GL_MODELVIEW)` before drawing +- Verify vertex order: Counter-clockwise for front faces + +## Future Enhancement Ideas + +### Easy Additions: +- Export heightmap to PNG (PIL/Pillow) +- Save/load terrain seeds +- Multiple camera presets +- Minimap in corner +- FPS counter + +### Medium Complexity: +- Smooth camera transitions (lerp) +- Camera rotation (Q/E keys) +- Day/night cycle (lighting animation) +- Biome gradients (blend colors) +- Water animation (vertex shader) + +### Advanced Features: +- Texture mapping (replace flat colors) +- Normal mapping (surface detail) +- Shadow mapping +- LOD system (level of detail) +- Terrain editing tools + +## Code Style Guidelines + +- **Imports**: Standard library → Third party → Local modules +- **Naming**: snake_case for functions/variables, PascalCase for classes +- **Constants**: UPPER_CASE in settings.py +- **Docstrings**: Include for all public methods +- **Comments**: Explain "why", not "what" +- **Line Length**: Max 100 characters +- **Type Hints**: Use where it improves clarity + +## Contact & Maintenance + +**Author**: enne2 +**Project**: GLTerrain (Isometric Terrain Generator) +**License**: (Add license info if applicable) +**Last Updated**: October 2025 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..19b4231 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python + +# Virtual Environment +venv/ +env/ +ENV/ +.venv + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Project specific +*.png +*.jpg +*.jpeg +heightmap_*.txt + +# Backup files +*.backup +*_backup_*.py +config/settings_backup_*.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..f202138 --- /dev/null +++ b/README.md @@ -0,0 +1,151 @@ +# Isometric Terrain Generator + +A RollerCoaster Tycoon-style isometric terrain generator using Python, Pygame, and OpenGL. + +## Project Structure + +``` +shader/ +├── config/ +│ ├── __init__.py +│ └── settings.py # Configuration settings for the entire application +├── src/ +│ ├── __init__.py +│ ├── app.py # Main application class +│ ├── camera/ +│ │ ├── __init__.py +│ │ └── camera.py # Camera control and positioning +│ ├── terrain/ +│ │ ├── __init__.py +│ │ └── generator.py # Terrain generation using Perlin noise +│ └── rendering/ +│ ├── __init__.py +│ └── terrain_renderer.py # OpenGL rendering logic +├── main.py # Application entry point +├── isometric_terrain.py # Original monolithic version (legacy) +├── requirements.txt +└── README.md +``` + +## Features + +- **20×20 grid** of 30px×30px isometric tiles +- **Procedural terrain generation** using Perlin noise +- **Multiple biomes** based on elevation (water, sand, grass, rock, snow) +- **Real-time camera controls** +- **Configurable settings** via `config/settings.py` + +## Installation + +1. Activate the virtual environment: +```bash +source venv/bin/activate +``` + +2. Install dependencies (if not already installed): +```bash +pip install -r requirements.txt +``` + +## Usage + +Run the new organized version: +```bash +source venv/bin/activate && python main.py +``` + +Or run the legacy version: +```bash +source venv/bin/activate && python isometric_terrain.py +``` + +## Controls + +- **UP/DOWN Arrow**: Zoom in/out +- **LEFT/RIGHT Arrow**: Adjust camera height +- **R Key**: Regenerate terrain (new version only) +- **ESC**: Exit application + +## Configuration + +All settings can be modified in `config/settings.py`: + +### Terrain Settings +- `grid_size`: Number of tiles (default: 20×20) +- `tile_width/tile_depth`: Size of each tile in pixels (default: 30px) +- `noise_scale`: Controls terrain variation frequency +- `height_multiplier`: Controls terrain elevation range +- `enable_smoothing`: Enable/disable terrain smoothing + +### Camera Settings +- `initial_distance`: Starting zoom level +- `initial_height`: Starting camera height +- `zoom_speed`: How fast zoom in/out works +- `min_distance/max_distance`: Zoom limits + +### Rendering Settings +- `background_color`: Sky color +- `grid_line_width`: Thickness of tile borders +- `light_position`: Light source position +- `light_ambient/light_diffuse`: Lighting properties + +### Biome Configuration +- `BIOME_COLORS`: RGB colors for each biome type +- `BIOME_THRESHOLDS`: Height thresholds for biome transitions + +## Architecture + +### Separation of Concerns + +The code is organized into logical modules: + +1. **Configuration** (`config/settings.py`) + - Centralized settings management + - Easy to modify without touching code + +2. **Camera** (`src/camera/camera.py`) + - Handles view projection and positioning + - Processes user input for camera movement + - Manages camera constraints + +3. **Terrain Generation** (`src/terrain/generator.py`) + - Procedural heightmap generation using Perlin noise + - Optional terrain smoothing + - Configurable noise parameters + +4. **Rendering** (`src/rendering/terrain_renderer.py`) + - OpenGL rendering of terrain mesh + - Biome coloring based on elevation + - Wireframe grid overlay + - Shaded side faces for depth + +5. **Application** (`src/app.py`) + - Main application loop + - Event handling + - Coordinates all components + +## Extending the Project + +### Adding New Biomes + +1. Add color to `BIOME_COLORS` in `config/settings.py` +2. Add threshold to `BIOME_THRESHOLDS` +3. Update `get_color_for_height()` in `terrain_renderer.py` + +### Modifying Terrain Generation + +Edit parameters in `config/settings.py`: +- Adjust `noise_scale` for more/less variation +- Change `noise_octaves` for detail level +- Modify `height_multiplier` for elevation range + +### Adding Camera Features + +Extend the `Camera` class in `src/camera/camera.py`: +- Add rotation controls +- Implement smooth camera transitions +- Add camera presets + +## License + +This project is provided as-is for educational purposes. diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..d020d50 --- /dev/null +++ b/config/__init__.py @@ -0,0 +1 @@ +"""Configuration package""" diff --git a/config/settings.py b/config/settings.py new file mode 100644 index 0000000..0f2eaa0 --- /dev/null +++ b/config/settings.py @@ -0,0 +1,89 @@ +""" +Configuration settings for the Isometric Terrain Generator +""" + +# Window settings +WINDOW_WIDTH = 1200 +WINDOW_HEIGHT = 800 +WINDOW_TITLE = "Isometric Terrain - RollerCoaster Tycoon Style" +FPS = 60 + +# Terrain generation settings +TERRAIN = { + 'grid_size': 20, # 20x20 matrix + 'tile_size': 1, # 1 tile per grid cell (so 20x20 tiles total) + 'tile_width': 30.0, # 30px tile width + 'tile_depth': 30.0, # 30px tile depth + + # Perlin noise parameters + 'noise_scale': 8.0, # Smaller scale = more variation in small area + 'noise_octaves': 4, + 'noise_persistence': 0.6, # Higher = more detail from higher octaves + 'noise_lacunarity': 2.5, # Higher = more frequency increase per octave + 'noise_repeat_x': 1024, + 'noise_repeat_y': 1024, + 'noise_base': 42, + + # Height scaling + 'height_multiplier': 80.0, # Multiplier for height variation + 'enable_smoothing': False, # Set to True for gentler terrain + 'smoothing_kernel_size': 3, +} + +# Camera settings +CAMERA = { + 'initial_distance': 800.0, + 'initial_height': 450.0, + 'initial_angle': 45.0, + + # Movement settings + 'zoom_speed': 10.0, + 'height_speed': 5.0, + 'min_distance': 200.0, + 'max_distance': 2000.0, + + # Perspective settings + 'fov': 45, # Field of view + 'near_clip': 0.1, + 'far_clip': 5000.0, +} + +# Rendering settings +RENDERING = { + 'background_color': (0.53, 0.81, 0.92, 1.0), # Sky blue + + # Grid/wireframe settings + 'grid_line_width': 5.0, + 'grid_line_color': (0.0, 0.0, 0.0), + + # Lighting settings + 'light_position': [1.0, 1.0, 1.0, 0.0], + 'light_ambient': [0.4, 0.4, 0.4, 1.0], + 'light_diffuse': [0.8, 0.8, 0.8, 1.0], + + # Shading multipliers for side faces + 'side_face_shading': 0.7, + 'back_face_shading': 0.8, +} + +# Biome colors (RCT style) +BIOME_COLORS = { + 'water': (0.2, 0.4, 0.8), + 'sand': (0.76, 0.7, 0.5), + 'grass_low': (0.2, 0.5, 0.2), + 'grass_mid': (0.25, 0.6, 0.25), + 'grass_high': (0.3, 0.65, 0.3), + 'rock': (0.5, 0.5, 0.5), + 'snow': (0.9, 0.9, 0.95) +} + +# Height thresholds for biomes +BIOME_THRESHOLDS = { + 'water': 10.0, + 'sand': 20.0, + 'grass_low': 30.0, + 'grass_mid': 45.0, + 'grass_high': 60.0, + 'rock': 70.0, + # snow is everything above rock +} diff --git a/docs/01-introduzione.md b/docs/01-introduzione.md new file mode 100644 index 0000000..fdad975 --- /dev/null +++ b/docs/01-introduzione.md @@ -0,0 +1,243 @@ +# Capitolo 1: Introduzione al Progetto + +## Cos'è Questo Progetto? + +Il **Generatore di Terreni Isometrici** è un'applicazione che crea paesaggi tridimensionali casuali visualizzati con una particolare prospettiva chiamata "isometrica". Pensa a videogiochi classici come RollerCoaster Tycoon, SimCity 2000, o Age of Empires II: tutti utilizzano questa prospettiva per mostrare il mondo di gioco. + +## Cosa Fa l'Applicazione? + +Quando avvii il programma, vedrai: + +1. **Una griglia di terreno 3D** composta da 20×20 tessere (chiamate "tile") +2. **Variazioni di altezza** che creano colline, valli e montagne +3. **Diversi colori** che rappresentano biomi (acqua, sabbia, erba, roccia, neve) +4. **Linee nere** che delimitano ogni tessera, creando un effetto griglia +5. **Una vista dall'alto** con un angolo di 45 gradi (vista isometrica) + +### Interattività + +Puoi interagire con il terreno usando la tastiera: +- **Freccia SU/GIÙ**: Zoom avanti/indietro +- **Freccia SINISTRA/DESTRA**: Alza/abbassa la camera +- **Tasto R**: Genera un nuovo terreno casuale +- **ESC**: Chiudi l'applicazione + +## A Cosa Serve? + +Questo progetto è: + +1. **Educativo**: Impara come funziona la grafica 3D, il rendering e la generazione procedurale +2. **Base per giochi**: Può essere esteso per creare giochi strategici o simulazioni +3. **Tool artistico**: Genera paesaggi casuali per ispirazione o materiale di riferimento +4. **Esempio di codice**: Dimostra buone pratiche di programmazione e architettura + +## Come Funziona (Panoramica Semplice)? + +Immagina di dover creare un paesaggio: + +### 1. Generazione del Terreno +``` +┌─────────────────────────────────────┐ +│ 1. Crea una griglia 20×20 │ +│ (400 punti totali) │ +│ │ +│ 2. Per ogni punto, calcola │ +│ un'altezza usando Perlin Noise │ +│ │ +│ 3. Ottieni una "mappa di altezza" │ +│ (heightmap) │ +└─────────────────────────────────────┘ +``` + +Esempio di heightmap (vista dall'alto): +``` + 10 12 15 18 20 <- Altezze + 8 10 13 16 18 + 5 7 10 13 15 + 3 5 8 11 13 + 2 3 5 8 10 +``` + +### 2. Assegnazione dei Colori + +Ogni altezza corrisponde a un bioma: + +``` +Altezza 0-10 → Blu (Acqua) 🌊 +Altezza 10-20 → Beige (Sabbia) 🏖️ +Altezza 20-30 → Verde chiaro (Erba bassa) 🌿 +Altezza 30-45 → Verde medio (Erba media) 🌲 +Altezza 45-60 → Verde scuro (Erba alta) 🌳 +Altezza 60-70 → Grigio (Roccia) ⛰️ +Altezza 70+ → Bianco (Neve) 🏔️ +``` + +### 3. Rendering 3D + +Il computer disegna ogni tessera come un quadrato in 3D: + +``` +Vista dall'alto: Vista isometrica: + + ┌──┬──┐ ◇──◇ + │ │ │ ╱│ ╱│ + ├──┼──┤ → ◇──◇ │ + │ │ │ │ ╱│ ╱ + └──┴──┘ ◇──◇ +``` + +Ogni quadrato (quad) è disegnato con: +- Una **faccia superiore** (quella che vediamo) +- **Facce laterali** (per mostrare l'altezza) +- **Colore** basato sull'altezza +- **Ombreggiatura** per dare profondità + +## Risultati Visivi + +### Cosa Vedrai + +Quando avvii l'applicazione, vedrai qualcosa del genere: + +``` + 🏔️ ⛰️ ⛰️ + ⛰️ ⛰️ 🌲 🌲 + 🌳 🌲 🌲 🌿 🌿 + 🌿 🌿 🌿 🏖️ 🏖️ + 🏖️ 🏖️ 🌊 🌊 🌊 +``` + +Con: +- **Linee nere** che separano ogni tessera +- **Ombreggiatura** sui lati delle tessere per creare profondità +- **Prospettiva isometrica** che rende tutto chiaro e leggibile +- **Colori vivaci** ispirati ai giochi classici + +## Tecnologie Utilizzate + +### Python +Il linguaggio di programmazione usato per scrivere tutto il codice. + +### Pygame +Una libreria per creare giochi e applicazioni grafiche. Gestisce: +- La finestra dell'applicazione +- L'input da tastiera +- Il loop principale del gioco + +### PyOpenGL +Le "binding" Python per OpenGL. Permette di: +- Disegnare geometria 3D +- Applicare illuminazione +- Gestire trasformazioni 3D + +### NumPy +Libreria per calcoli matematici veloci, usata per: +- Gestire la heightmap (array 2D) +- Operazioni matematiche efficienti + +### Noise (Perlin Noise) +Libreria che implementa l'algoritmo Perlin Noise per: +- Generare valori pseudo-casuali naturali +- Creare terreni realistici + +## Struttura del Progetto + +Il progetto è organizzato in moduli separati: + +``` +shader/ +├── config/ +│ └── settings.py → Tutte le impostazioni +├── src/ +│ ├── app.py → Applicazione principale +│ ├── camera/ +│ │ └── camera.py → Controllo della camera +│ ├── terrain/ +│ │ └── generator.py → Generazione del terreno +│ └── rendering/ +│ └── terrain_renderer.py → Disegno del terreno +└── main.py → Punto di ingresso +``` + +## Caratteristiche Principali + +### ✅ Generazione Procedurale +Il terreno è generato automaticamente usando algoritmi matematici. Ogni volta che premi R, ottieni un paesaggio completamente diverso! + +### ✅ Configurabilità +Tutto è personalizzabile tramite il file `config/settings.py`: +- Dimensioni della griglia +- Altezza delle montagne +- Colori dei biomi +- Velocità della camera +- E molto altro! + +### ✅ Interattività in Tempo Reale +Puoi muovere la camera e rigenerare il terreno istantaneamente, senza riavviare l'applicazione. + +### ✅ Codice Pulito e Modulare +Il codice è organizzato in modo logico, facile da leggere e da modificare. + +## Casi d'Uso + +### Apprendimento +- Impara la grafica 3D +- Comprendi OpenGL +- Studia algoritmi di generazione procedurale +- Pratica l'architettura software + +### Prototipazione +- Crea rapidamente paesaggi per giochi +- Testa diverse configurazioni di terreno +- Genera mappe per progetti + +### Estensione +- Aggiungi personaggi o oggetti +- Implementa gameplay (gioco strategico) +- Crea un editor di mappe +- Esporta terreni in altri formati + +## Performance + +Il progetto è ottimizzato per: +- **400 tessere** (20×20 griglia) +- **60 FPS** (aggiornamenti al secondo) +- **Generazione istantanea** del terreno + +Con hardware moderno, l'applicazione gira fluidamente anche su computer datati. + +## Limitazioni Attuali + +Il progetto base ha alcune limitazioni: +- ❌ Non c'è salvataggio/caricamento delle mappe +- ❌ Non puoi modificare il terreno interattivamente +- ❌ La griglia è fissa a 20×20 (modificabile nel config) +- ❌ Non ci sono texture, solo colori solidi +- ❌ Nessun sistema di gameplay + +Queste sono tutte cose che puoi aggiungere estendendo il progetto! + +## Prossimi Passi + +Ora che sai cosa fa il progetto, nei prossimi capitoli scoprirai: + +- **Capitolo 2**: Come funziona la grafica 3D in generale +- **Capitolo 3**: Cos'è OpenGL e come disegna le cose +- **Capitolo 4**: L'algoritmo Perlin Noise per terreni naturali +- **Capitoli 5-7**: Come è implementato nel codice +- **Capitolo 8**: Come personalizzare tutto + +## Riepilogo + +🎯 **Cosa Ricordare**: + +1. Il progetto genera **terreni 3D casuali** con vista isometrica +2. Usa **Perlin Noise** per creare altezze naturali +3. I colori rappresentano **biomi diversi** (acqua, erba, montagna, neve) +4. È completamente **interattivo e configurabile** +5. È un ottimo **punto di partenza** per progetti più complessi + +--- + +**Prossimo Capitolo**: [Concetti Base di Grafica 3D →](02-concetti-base.md) + +[← Torna all'indice](README.md) diff --git a/docs/02-concetti-base.md b/docs/02-concetti-base.md new file mode 100644 index 0000000..a7c4e34 --- /dev/null +++ b/docs/02-concetti-base.md @@ -0,0 +1,460 @@ +# Capitolo 2: Concetti Base di Grafica 3D + +## Introduzione + +Prima di capire come funziona OpenGL e questo progetto, dobbiamo comprendere alcuni concetti fondamentali della grafica 3D. Non preoccuparti se non hai mai lavorato con la 3D: spiegheremo tutto partendo da zero! + +## Coordinate 3D: Il Sistema XYZ + +### Dal 2D al 3D + +Nel mondo 2D (come un foglio di carta), usiamo due coordinate: +- **X**: orizzontale (sinistra-destra) +- **Y**: verticale (su-giù) + +``` + Y ↑ + | + | + |______→ X +``` + +Nel mondo 3D aggiungiamo una terza coordinata: +- **Z**: profondità (avanti-dietro) + +``` + Y ↑ + | + | + |______→ X + ╱ + ╱ Z +``` + +### Esempio Pratico + +Immagina di posizionare un oggetto nella tua stanza: +- **X = 3**: 3 metri verso destra dalla porta +- **Y = 1.5**: 1.5 metri da terra (altezza) +- **Z = 5**: 5 metri dentro la stanza + +Nel nostro progetto: +- **X**: posizione orizzontale sulla griglia +- **Y**: altezza del terreno (colline/valli) +- **Z**: altra dimensione orizzontale sulla griglia + +## Punti, Linee e Facce + +### Vertici (Vertices) + +Un **vertice** è semplicemente un punto nello spazio 3D. + +```python +vertice_1 = (0, 0, 0) # Origine +vertice_2 = (1, 0, 0) # Un metro a destra +vertice_3 = (0, 2, 0) # Due metri in alto +``` + +### Linee (Edges) + +Una **linea** connette due vertici. + +``` +v2 ●────────● v3 + \ / + \ / + \ / + ● v1 +``` + +### Facce (Faces) + +Una **faccia** è una superficie formata da 3 o più vertici. + +**Triangolo** (3 vertici): +``` + v3 ● + /│\ + / │ \ + / │ \ + v1●───┴───● v2 +``` + +**Quadrilatero** (4 vertici - quello che usiamo!): +``` + v4 ●────● v3 + │ │ + │ │ + v1 ●────● v2 +``` + +## Mesh: Unire le Facce + +Una **mesh** è un insieme di vertici e facce che forma un oggetto 3D. + +Esempio: cubo composto da 8 vertici e 6 facce: + +``` + v8────v7 + /│ /│ + / │ / │ + v5────v6 │ + │ v4─│─-v3 + │ / │ / + │/ │/ + v1────v2 +``` + +Nel nostro progetto, il terreno è una grande mesh composta da tante piccole facce quadrate (tile). + +## Vista Isometrica vs Prospettiva + +### Prospettiva Reale + +Nella vita reale, gli oggetti lontani appaiono più piccoli: + +``` +Prospettiva (come l'occhio umano): + + /│\ Vicino = grande + / │ \ + / │ \ Lontano = piccolo + / │ \ + /____|____\ +``` + +### Vista Isometrica + +Nella vista isometrica, gli oggetti mantengono la stessa dimensione indipendentemente dalla distanza: + +``` +Isometrica (dimensioni costanti): + + ╱╲ + ╱ ╲ + ╱ ╲ + ╱ ╲ +╱ ╲ +``` + +**Vantaggi dell'isometrica**: +- ✅ Ottima per giochi strategici +- ✅ Tutto è sempre leggibile +- ✅ Facilita la progettazione +- ✅ Stile classico e nostalgico + +**Esempi di giochi isometrici**: +- RollerCoaster Tycoon +- Age of Empires II +- SimCity 2000 +- Diablo II +- Civilization II + +### Angolo Isometrico + +Nel nostro progetto usiamo un angolo di **45 gradi** sia in orizzontale che in verticale, creando la classica vista "diamante": + +``` +Vista dall'alto (2D): Vista isometrica (3D): + + ┌─┬─┬─┐ ◇─◇─◇ + ├─┼─┼─┤ ╱│╱│╱│ + ├─┼─┼─┤ → ◇─◇─◇ │ + ├─┼─┼─┤ │╱│╱│╱ + └─┴─┴─┘ ◇─◇─◇ +``` + +## Colori in 3D + +### RGB: Red, Green, Blue + +I colori sono rappresentati come combinazioni di rosso, verde e blu: + +``` +RGB(255, 0, 0) = Rosso puro 🔴 +RGB(0, 255, 0) = Verde puro 🟢 +RGB(0, 0, 255) = Blu puro 🔵 +RGB(255, 255, 0) = Giallo 🟡 +RGB(0, 0, 0) = Nero ⚫ +RGB(255, 255, 255)= Bianco ⚪ +``` + +### Normalizzazione (0-1) + +In OpenGL, i colori vanno da 0.0 a 1.0 invece che da 0 a 255: + +```python +# Invece di RGB(128, 200, 64) +# Dividi per 255: +colore = (0.5, 0.78, 0.25) +``` + +### Colori nel Nostro Progetto + +Ogni bioma ha il suo colore: + +```python +'water': (0.2, 0.4, 0.8) # Blu 🌊 +'sand': (0.76, 0.7, 0.5) # Beige 🏖️ +'grass': (0.25, 0.6, 0.25) # Verde 🌿 +'rock': (0.5, 0.5, 0.5) # Grigio ⛰️ +'snow': (0.9, 0.9, 0.95) # Bianco 🏔️ +``` + +## Illuminazione e Shading + +### Perché l'Illuminazione? + +Senza luce, tutto apparirebbe piatto: + +``` +Senza luce: Con luce: + ┌──┐ ┌──┐ + │ │ │░░│ ← Lato in ombra + └──┘ └▓▓┘ ← Lato scuro +``` + +### Come Funziona + +La luce colpisce le superfici da diverse angolazioni: + +``` + Luce ☀ + │ + ↓ + ┌────────┐ + │ │ ← Faccia illuminata (chiara) + └────────┘ + │ + └─→ Ombra +``` + +### Componenti della Luce + +**1. Luce Ambientale** (Ambient) +Luce che colpisce tutto uniformemente, simula la luce diffusa nell'ambiente. + +**2. Luce Diffusa** (Diffuse) +Luce direzionale che illumina le superfici in base all'angolo. + +**3. Luce Speculare** (Specular) +Riflessi luminosi (non usati in questo progetto). + +### Shading nel Nostro Progetto + +Applichiamo ombreggiatura alle facce laterali delle tile: + +```python +# Faccia superiore = 100% luminosità +base_color = (0.3, 0.6, 0.3) + +# Faccia laterale destra = 70% luminosità +side_color = (0.21, 0.42, 0.21) # Moltiplicato per 0.7 + +# Faccia laterale retro = 80% luminosità +back_color = (0.24, 0.48, 0.24) # Moltiplicato per 0.8 +``` + +Questo crea un effetto 3D più realistico! + +## Rendering: Dal 3D al 2D + +### Il Problema + +Il monitor è 2D, ma vogliamo mostrare un mondo 3D. Come facciamo? + +### La Soluzione: Pipeline di Rendering + +``` +1. MODELLO 3D 2. TRASFORMAZIONI + (Vertici in spazio 3D) (Posizione, rotazione) + │ │ + ↓ ↓ + ┌─────────┐ ┌────────────┐ + │ Cubo 3D │ │ Ruota, │ + │ x,y,z │ → │ sposta, │ + └─────────┘ │ scala │ + └────────────┘ + │ + ↓ ↓ +3. CAMERA 4. PROIEZIONE + (Punto di vista) (3D → 2D) + │ │ + ↓ ↓ + ┌──────────┐ ┌───────────┐ + │ Guarda da│ │ Appiattisci│ + │ qui │ → │ su schermo │ + └──────────┘ └───────────┘ + │ + ↓ + 5. PIXEL SULLO SCHERMO + │ + ↓ + ┌──────────┐ + │ Immagine │ + │ finale │ + └──────────┘ +``` + +### Matrici di Trasformazione + +Le trasformazioni 3D usano la matematica matriciale (non preoccuparti, OpenGL lo fa per te!): + +- **Model Matrix**: Posiziona l'oggetto nel mondo +- **View Matrix**: Posiziona la camera +- **Projection Matrix**: Crea la proiezione 2D + +``` +Vertice 3D × Model × View × Projection = Pixel 2D +``` + +## Coordinate della Griglia nel Nostro Progetto + +### Sistema Locale + +Ogni tile è posizionata in una griglia: + +``` +Griglia 5×5 (esempio semplificato): + + j→ 0 1 2 3 4 +i↓ ┌───┬───┬───┬───┬───┐ +0 │0,0│0,1│0,2│0,3│0,4│ + ├───┼───┼───┼───┼───┤ +1 │1,0│1,1│1,2│1,3│1,4│ + ├───┼───┼───┼───┼───┤ +2 │2,0│2,1│2,2│2,3│2,4│ + ├───┼───┼───┼───┼───┤ +3 │3,0│3,1│3,2│3,3│3,4│ + ├───┼───┼───┼───┼───┤ +4 │4,0│4,1│4,2│4,3│4,4│ + └───┴───┴───┴───┴───┘ +``` + +### Conversione a Coordinate Mondo + +Per centrare la griglia sull'origine: + +```python +# Posizione in griglia +i = 5 +j = 10 + +# Converti a coordinate mondo (centrato) +x = (i - grid_size/2) * tile_width +z = (j - grid_size/2) * tile_depth +y = heightmap[i][j] +``` + +Esempio con griglia 20×20: +``` +Tile (0,0) → Mondo (-300, h, -300) +Tile (10,10)→ Mondo (0, h, 0) # Centro +Tile (19,19)→ Mondo (270, h, 270) +``` + +## Depth Buffer (Z-Buffer) + +### Il Problema dell'Ordine + +Quando disegni in 3D, devi sapere cosa sta davanti e cosa sta dietro: + +``` + ┌─────┐ + │ A │ + └─────┘ + ┌─────┐ + │ B │ ← B dovrebbe coprire A + └─────┘ +``` + +### La Soluzione: Depth Buffer + +OpenGL usa un **depth buffer** per tenere traccia della profondità di ogni pixel: + +``` +Per ogni pixel: +1. Calcola la distanza dalla camera (profondità Z) +2. Se Z è minore del valore nel buffer → disegna e aggiorna buffer +3. Se Z è maggiore → non disegnare (c'è qualcosa davanti) +``` + +Nel codice: +```python +glEnable(GL_DEPTH_TEST) # Abilita il depth buffer +glClear(GL_DEPTH_BUFFER_BIT) # Pulisci a ogni frame +``` + +## Wireframe: Visualizzare la Struttura + +Un **wireframe** mostra solo i bordi della geometria: + +``` +Solido: Wireframe: + ████████ ┌──┬──┐ + ████████ ├──┼──┤ + ████████ └──┴──┘ +``` + +Nel nostro progetto, disegniamo sia le facce solide che i bordi wireframe: + +```python +# 1. Disegna facce solide +for tile in terrain: + draw_solid_quad(tile) + +# 2. Disegna linee wireframe +glLineWidth(5.0) +for edge in grid: + draw_line(edge) +``` + +Questo crea l'effetto griglia caratteristico! + +## Riepilogo Concetti + +### Sistema di Coordinate +- **X, Y, Z**: tre dimensioni dello spazio +- **Origine**: punto (0, 0, 0) +- **Vertici**: punti nello spazio + +### Geometria +- **Vertici**: punti +- **Linee**: connessioni tra vertici +- **Facce**: superfici (triangoli o quad) +- **Mesh**: insieme di facce + +### Visualizzazione +- **Isometrica**: vista 45° senza prospettiva +- **Prospettiva**: come l'occhio umano vede +- **Camera**: punto di vista virtuale + +### Colori e Luce +- **RGB**: combinazione di rosso, verde, blu +- **Normalizzazione**: valori 0.0-1.0 +- **Shading**: ombreggiatura per profondità +- **Ambien/Diffuse**: tipi di illuminazione + +### Rendering +- **Pipeline**: processo dal 3D al 2D +- **Matrici**: trasformazioni matematiche +- **Depth Buffer**: gestione profondità +- **Wireframe**: visualizzazione bordi + +## Applicazione al Nostro Progetto + +Ora che conosci i concetti base, ecco come si applicano: + +1. **Griglia 20×20** = 400 vertici con altezze diverse +2. **Ogni tile** = un quad (4 vertici) in 3D +3. **Vista isometrica** = camera a 45° dall'alto +4. **Colori** = mappati alle altezze (biomi) +5. **Shading** = facce laterali più scure +6. **Wireframe** = linee nere tra le tile +7. **Depth buffer** = gestisce sovrapposizioni + +--- + +**Prossimo Capitolo**: [OpenGL: La Tecnologia di Rendering →](03-opengl.md) + +[← Capitolo Precedente](01-introduzione.md) | [Torna all'indice](README.md) diff --git a/docs/03-opengl.md b/docs/03-opengl.md new file mode 100644 index 0000000..45cab74 --- /dev/null +++ b/docs/03-opengl.md @@ -0,0 +1,533 @@ +# Capitolo 3: OpenGL - La Tecnologia di Rendering + +## Cos'è OpenGL? + +**OpenGL** (Open Graphics Library) è uno standard aperto per il rendering di grafica 2D e 3D. È come un "linguaggio universale" che permette al tuo programma di comunicare con la scheda grafica (GPU). + +### Analogia Semplice + +Immagina OpenGL come un **interprete** tra te e un artista professionista (la GPU): + +``` +Tu: "Voglio un cubo rosso" + ↓ +OpenGL: Traduce in comandi GPU + ↓ +GPU: Disegna il cubo + ↓ +Schermo: Vedi il risultato +``` + +## Perché OpenGL? + +### Vantaggi + +✅ **Cross-platform**: Funziona su Windows, Mac, Linux +✅ **Standard aperto**: Gratis e open source +✅ **Accelerazione hardware**: Usa la potenza della GPU +✅ **Maturo e stabile**: In uso da decenni +✅ **Ben documentato**: Tantissime risorse e tutorial + +### Alternative + +- **DirectX**: Solo Windows (usato nei giochi AAA) +- **Vulkan**: Moderno ma complesso +- **Metal**: Solo Apple +- **WebGL**: OpenGL per browser + +Per progetti educativi e multi-piattaforma, OpenGL è perfetto! + +## Come Funziona OpenGL + +### Macchina a Stati + +OpenGL è una **state machine** (macchina a stati). Una volta impostata, mantiene le configurazioni: + +```python +# Imposta uno stato +glEnable(GL_DEPTH_TEST) # Attiva depth buffer +glLineWidth(5.0) # Linee spesse 5 pixel + +# Tutti i disegni successivi useranno questi stati! +draw_something() +draw_something_else() + +# Cambia stato +glDisable(GL_DEPTH_TEST) # Disattiva depth buffer +glLineWidth(1.0) # Linee sottili +``` + +### Contesto OpenGL + +Prima di usare OpenGL, devi creare un **contesto**: + +```python +import pygame +from pygame.locals import DOUBLEBUF, OPENGL + +# Crea finestra con contesto OpenGL +pygame.display.set_mode((800, 600), DOUBLEBUF | OPENGL) +``` + +Il contesto è l'ambiente in cui OpenGL opera. + +## Pipeline di Rendering + +OpenGL processa la geometria attraverso una pipeline: + +``` +1. VERTICI 2. ASSEMBLAGGIO + ↓ ↓ + Punti 3D Forme geometriche + (x, y, z) (triangoli, quad) + +3. TRASFORMAZIONI 4. RASTERIZZAZIONE + ↓ ↓ + Applica matrici Converte in pixel + (model, view, projection) + +5. FRAGMENT SHADER 6. TEST & BLENDING + ↓ ↓ + Colora ogni pixel Depth test, trasparenza + +7. FRAMEBUFFER + ↓ + Immagine finale sullo schermo +``` + +### Spiegazione Passo per Passo + +**1. Vertici** +```python +# Definisci un quadrato +v1 = (0, 0, 0) +v2 = (1, 0, 0) +v3 = (1, 1, 0) +v4 = (0, 1, 0) +``` + +**2. Assemblaggio** +```python +glBegin(GL_QUADS) # Inizia un quadrilatero + glVertex3f(0, 0, 0) # v1 + glVertex3f(1, 0, 0) # v2 + glVertex3f(1, 1, 0) # v3 + glVertex3f(0, 1, 0) # v4 +glEnd() +``` + +**3. Trasformazioni** +```python +# Model: posiziona oggetto +glTranslatef(x, y, z) + +# View: posiziona camera +gluLookAt(eye_x, eye_y, eye_z, + center_x, center_y, center_z, + up_x, up_y, up_z) + +# Projection: crea prospettiva +gluPerspective(fov, aspect, near, far) +``` + +**4-7**: OpenGL gestisce automaticamente! + +## Primitive Geometriche + +OpenGL può disegnare varie forme base: + +### Punti +```python +glBegin(GL_POINTS) + glVertex3f(0, 0, 0) + glVertex3f(1, 1, 0) +glEnd() + +Risultato: • • +``` + +### Linee +```python +glBegin(GL_LINES) + glVertex3f(0, 0, 0) # Punto 1 linea A + glVertex3f(1, 0, 0) # Punto 2 linea A + glVertex3f(1, 0, 0) # Punto 1 linea B + glVertex3f(1, 1, 0) # Punto 2 linea B +glEnd() + +Risultato: ●──● + │ + │ + ● +``` + +### Triangoli +```python +glBegin(GL_TRIANGLES) + glVertex3f(0, 0, 0) + glVertex3f(1, 0, 0) + glVertex3f(0.5, 1, 0) +glEnd() + +Risultato: ● + ╱ ╲ + ╱ ╲ + ●─────● +``` + +### Quadrilateri (Quads) - Quello che usiamo! +```python +glBegin(GL_QUADS) + glVertex3f(0, 0, 0) + glVertex3f(1, 0, 0) + glVertex3f(1, 1, 0) + glVertex3f(0, 1, 0) +glEnd() + +Risultato: ●─────● + │ │ + │ │ + ●─────● +``` + +## Matrici di Trasformazione + +### Due Modalità + +OpenGL ha due tipi di matrici: + +```python +GL_PROJECTION # Per la proiezione (prospettiva/ortogonale) +GL_MODELVIEW # Per modello e vista (oggetti e camera) +``` + +### Workflow Tipico + +```python +# 1. Setup proiezione +glMatrixMode(GL_PROJECTION) +glLoadIdentity() # Resetta matrice +gluPerspective(45, aspect_ratio, 0.1, 1000.0) + +# 2. Setup vista e modello +glMatrixMode(GL_MODELVIEW) +glLoadIdentity() # Resetta matrice + +# 3. Posiziona camera +gluLookAt(cam_x, cam_y, cam_z, # Posizione camera + 0, 0, 0, # Guarda verso l'origine + 0, 1, 0) # Vettore "su" + +# 4. Disegna oggetti +draw_terrain() +``` + +## Illuminazione in OpenGL + +### Setup Base + +```python +# Abilita illuminazione +glEnable(GL_LIGHTING) +glEnable(GL_LIGHT0) # Attiva la prima luce + +# Colori materiale dai vertici +glEnable(GL_COLOR_MATERIAL) +glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE) +``` + +### Configurare la Luce + +```python +# Posizione luce (x, y, z, w) +# w=0 → luce direzionale (come il sole) +# w=1 → luce posizionale (come una lampada) +glLightfv(GL_LIGHT0, GL_POSITION, [1.0, 1.0, 1.0, 0.0]) + +# Luce ambientale (illuminazione generale) +glLightfv(GL_LIGHT0, GL_AMBIENT, [0.4, 0.4, 0.4, 1.0]) + +# Luce diffusa (illuminazione direzionale) +glLightfv(GL_LIGHT0, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]) +``` + +### Effetto dell'Illuminazione + +``` +Senza illuminazione: Con illuminazione: +┌────┐ ┌────┐ ┌────┐ ┌────┐ +│ │ │ │ │▓▓▓▓│ │░░░░│ ← Lati con +│ │ │ │ │▓▓▓▓│ │░░░░│ ombreggiature +└────┘ └────┘ └────┘ └────┘ diverse + Tutto Profondità + uguale visibile +``` + +## Depth Testing (Test di Profondità) + +### Il Problema + +Senza depth testing, gli oggetti si sovrappongono male: + +``` +Disegnato prima: A Disegnato dopo: B +┌───────┐ ┌───────┐ +│ A │ │ B │ +│ │ + │ │ = ┌───────┐ +└───────┘ ┌───────┐ └───────┘ │ B │ ← B copre A + │ B │ │ │ anche se è + └───────┘ └───────┘ dietro! +``` + +### La Soluzione + +```python +glEnable(GL_DEPTH_TEST) # Abilita depth buffer + +# Ogni frame: +glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) +``` + +Ora OpenGL disegna correttamente tenendo conto della profondità! + +## Blending (Trasparenza) + +Per oggetti semi-trasparenti: + +```python +glEnable(GL_BLEND) +glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + +# Ora puoi usare colori con trasparenza +glColor4f(1.0, 0.0, 0.0, 0.5) # Rosso al 50% +``` + +## Comandi OpenGL Usati nel Progetto + +### Inizializzazione + +```python +# Abilita test profondità +glEnable(GL_DEPTH_TEST) + +# Abilita blending per trasparenze +glEnable(GL_BLEND) +glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + +# Setup illuminazione +glEnable(GL_LIGHTING) +glEnable(GL_LIGHT0) +glEnable(GL_COLOR_MATERIAL) +glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE) +``` + +### Ogni Frame + +```python +# Pulisci schermo e depth buffer +glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + +# Imposta colore di sfondo (cielo blu) +glClearColor(0.53, 0.81, 0.92, 1.0) + +# Setup camera +glMatrixMode(GL_PROJECTION) +glLoadIdentity() +gluPerspective(45, aspect_ratio, 0.1, 5000.0) + +glMatrixMode(GL_MODELVIEW) +glLoadIdentity() +gluLookAt(cam_distance, cam_height, cam_distance, + 0, 0, 0, + 0, 1, 0) +``` + +### Disegno Geometria + +```python +# Disegna faccia superiore tile +glBegin(GL_QUADS) +glColor3f(0.3, 0.6, 0.3) # Verde +glVertex3f(x, y, z) # Vertice 1 +glVertex3f(x+w, y, z) # Vertice 2 +glVertex3f(x+w, y, z+d) # Vertice 3 +glVertex3f(x, y, z+d) # Vertice 4 +glEnd() + +# Disegna bordi wireframe +glColor3f(0.0, 0.0, 0.0) # Nero +glLineWidth(5.0) # Spessore linea +glBegin(GL_LINES) +glVertex3f(x1, y1, z1) +glVertex3f(x2, y2, z2) +glEnd() +``` + +## GLU: OpenGL Utility Library + +### Cos'è GLU? + +Una libreria di funzioni di utilità che semplifica operazioni comuni: + +```python +from OpenGL.GLU import * + +# Invece di calcolare matrici complesse a mano: +gluPerspective(fov, aspect, near, far) +gluLookAt(eye_x, eye_y, eye_z, + center_x, center_y, center_z, + up_x, up_y, up_z) +``` + +### Funzioni GLU Utilizzate + +**gluPerspective** +Crea una matrice di proiezione prospettica: + +```python +gluPerspective( + 45, # Field of View (angolo visuale) + 1.5, # Aspect ratio (larghezza/altezza) + 0.1, # Near clipping plane (minima distanza) + 5000.0 # Far clipping plane (massima distanza) +) +``` + +**gluLookAt** +Posiziona la camera: + +```python +gluLookAt( + 800, 450, 800, # Posizione camera (eye) + 0, 0, 0, # Punto guardato (center) + 0, 1, 0 # Direzione "su" (up) +) +``` + +## Performance e Ottimizzazione + +### Vertex Arrays (Non usati nel progetto, ma utili) + +Invece di: +```python +# Lento: una chiamata per vertice +glBegin(GL_QUADS) +for vertex in vertices: + glVertex3f(vertex.x, vertex.y, vertex.z) +glEnd() +``` + +Potresti usare: +```python +# Veloce: batch di vertici +glEnableClientState(GL_VERTEX_ARRAY) +glVertexPointer(3, GL_FLOAT, 0, vertex_array) +glDrawArrays(GL_QUADS, 0, vertex_count) +``` + +### VBO - Vertex Buffer Objects + +Per progetti più grandi, usa VBO per tenere i dati sulla GPU: + +```python +# Crea buffer sulla GPU +vbo = glGenBuffers(1) +glBindBuffer(GL_ARRAY_BUFFER, vbo) +glBufferData(GL_ARRAY_BUFFER, vertex_data, GL_STATIC_DRAW) +``` + +Il nostro progetto è abbastanza piccolo (400 tile) da non necessitare ottimizzazioni avanzate! + +## Double Buffering + +### Il Problema del Flickering + +Senza double buffering, vedi il disegno in progress: + +``` +Frame 1: ████░░░░ ← Disegno incompleto +Frame 2: ████████ ← Completato +Frame 3: ████░░░░ ← Di nuovo incompleto + ↓ +Risultato: Flicker fastidioso! +``` + +### La Soluzione + +Usa due buffer: +- **Front Buffer**: Quello visibile +- **Back Buffer**: Dove disegni + +```python +# Setup con double buffer +pygame.display.set_mode((width, height), DOUBLEBUF | OPENGL) + +# Ogni frame: +# 1. Disegna sul back buffer +draw_everything() + +# 2. Scambia front e back buffer +pygame.display.flip() # Swap istantaneo! +``` + +Risultato: Animazione fluida! ✨ + +## Riepilogo OpenGL + +### Concetti Chiave + +✅ **State Machine**: Imposti stati globali +✅ **Pipeline**: Vertici → Trasformazioni → Pixel +✅ **Primitive**: Points, Lines, Triangles, Quads +✅ **Matrici**: Projection (come) e ModelView (cosa/dove) +✅ **Illuminazione**: Ambient + Diffuse +✅ **Depth Buffer**: Gestisce sovrapposizioni +✅ **Double Buffering**: Animazioni fluide + +### Comandi Essenziali + +```python +# Setup +glEnable(GL_DEPTH_TEST) +glEnable(GL_LIGHTING) +glEnable(GL_LIGHT0) + +# Ogni frame +glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) +glMatrixMode(GL_PROJECTION) +glLoadIdentity() +gluPerspective(...) +glMatrixMode(GL_MODELVIEW) +glLoadIdentity() +gluLookAt(...) + +# Disegno +glBegin(GL_QUADS) +glColor3f(r, g, b) +glVertex3f(x, y, z) +glEnd() + +# Swap buffer +pygame.display.flip() +``` + +### Flusso Applicazione + +``` +1. Inizializza Pygame + OpenGL +2. Setup stati OpenGL (lighting, depth) +3. Loop principale: + a. Gestisci input + b. Aggiorna stato (camera, ecc.) + c. Pulisci buffer + d. Setup matrici camera + e. Disegna geometria + f. Swap buffer +4. Chiudi applicazione +``` + +--- + +**Prossimo Capitolo**: [Perlin Noise: Generazione Procedurale →](04-perlin-noise.md) + +[← Capitolo Precedente](02-concetti-base.md) | [Torna all'indice](README.md) diff --git a/docs/04-perlin-noise.md b/docs/04-perlin-noise.md new file mode 100644 index 0000000..9820fe9 --- /dev/null +++ b/docs/04-perlin-noise.md @@ -0,0 +1,490 @@ +# Capitolo 4: Perlin Noise - Generazione Procedurale del Terreno + +## Cos'è il Perlin Noise? + +Il **Perlin Noise** è un algoritmo inventato da Ken Perlin nel 1983 per creare texture naturali procedurali. È usato in tantissimi videogiochi e applicazioni grafiche per generare: +- 🏔️ Terreni +- ☁️ Nuvole +- 🌊 Onde dell'oceano +- 🔥 Fuoco e fumo +- 🎨 Texture organiche + +### Perché "Noise" (Rumore)? + +Genera valori "casuali" ma con una caratteristica speciale: sono **coerenti** e **fluidi**. + +## Random vs Perlin Noise + +### Random Puro (Male!) + +```python +import random + +# Genera altezze completamente casuali +for i in range(10): + height = random.random() * 100 + print(height) + +Risultato: 23, 91, 5, 88, 12, 67, 4, 95, 31, 8 + +Terreno: ▂█▁█▁█▁█▃▁ ← Caotico! +``` + +Problema: Troppo casuale, non naturale! + +### Perlin Noise (Bene!) + +```python +import noise + +# Genera altezze con Perlin Noise +for i in range(10): + height = noise.pnoise1(i / 10.0) * 100 + print(height) + +Risultato: 12, 18, 28, 42, 53, 58, 54, 45, 32, 20 + +Terreno: ▂▃▄▆▇█▇▆▄▃ ← Fluido e naturale! +``` + +Vantaggio: Transizioni graduali, come in natura! + +## Come Funziona (Semplificato) + +### 1. Griglia di Gradienti + +Immagina una griglia dove ad ogni punto è assegnata una direzione casuale: + +``` + ↗ → ↘ + + ↑ • ↓ Griglia 3×3 con direzioni + + ↖ ← ↙ +``` + +### 2. Interpolazione + +Per un punto qualsiasi nella griglia, calcola il valore interpolando i 4 punti vicini: + +``` + v1 ●──────● v2 + │ │ + │ P • │ ← Punto P + │ │ + v3 ●──────● v4 + +Valore(P) = interpola(v1, v2, v3, v4) +``` + +### 3. Risultato Fluido + +Il risultato è una superficie continua con variazioni graduali: + +``` +Vista 2D del Perlin Noise: + +▓▓▓▓▓▓▒▒▒▒░░░░░░░░▒▒▒▒▓▓▓▓ +▓▓▓▓▒▒▒▒▒░░░░░░░░░░▒▒▒▒▒▓▓ +▓▓▒▒▒▒▒░░░░░░░░░░░░░▒▒▒▒▒▓ +▒▒▒▒░░░░░░░░░░░░░░░░░░▒▒▒▒ +░░░░░░░░░░░░░░░░░░░░░░░░░░ + +Più scuro = più alto +Più chiaro = più basso +``` + +## Perlin Noise 2D per Terreni + +Nel nostro progetto usiamo **pnoise2** (Perlin Noise bidimensionale): + +```python +import noise + +# Per ogni punto della griglia +for i in range(grid_size): + for j in range(grid_size): + # Calcola altezza + height = noise.pnoise2(i / scale, j / scale) +``` + +### Parametro: Scale (Scala) + +Controlla la "frequenza" delle variazioni: + +**Scale grande** (es. 100.0): +``` +Terreno: ~~~~~~~~ + Colline dolci, variazioni ampie +``` + +**Scale piccola** (es. 5.0): +``` +Terreno: ∧∨∧∨∧∨∧∨ + Colline ripide, variazioni frequenti +``` + +**Nel nostro progetto**: `scale = 8.0` (buon compromesso) + +## Ottave: Aggiungere Dettaglio + +Un singolo livello di Perlin Noise è liscio. Le **ottave** aggiungono dettagli a scale diverse: + +``` +Ottava 1 (base): ~~~~~~~~ + Forma generale + +Ottava 2 (dettaglio): ∧∨∧∨∧∨∧∨ + Piccole colline + +Ottava 3 (dettaglio+): vvvvvvvv + Rugosità fine + +Combinate: ∧~~∨∧~~∨∧~~∨ + Terreno realistico! +``` + +### Come Funziona + +Ogni ottava ha: +- **Frequenza doppia** (variazioni più rapide) +- **Ampiezza ridotta** (impatto minore) + +```python +result = 0 +amplitude = 1.0 +frequency = 1.0 + +for octave in range(num_octaves): + result += noise.pnoise2(x * frequency, y * frequency) * amplitude + amplitude *= persistence # Riduce ampiezza + frequency *= lacunarity # Aumenta frequenza +``` + +## Parametri del Perlin Noise + +### 1. Scale (Scala) + +Quanto "zoomato" è il noise: + +```python +scale = 8.0 # 8 unità = 1 ciclo completo + +Piccola (3.0): ∧∨∧∨∧∨∧∨∧∨ Molto variabile +Media (8.0): ∧~~~∨~~~∧~~~ Bilanciato +Grande (20.0): ∧~~~~~~~∨~~~~ Molto liscio +``` + +### 2. Octaves (Ottave) + +Quanti livelli di dettaglio: + +```python +octaves = 4 + +1 ottava: ~~~~~~~~ Liscio +2 ottave: ∧~∨~∧~∨~ Qualche dettaglio +4 ottave: ∧∨~∧∨~∧∨~ Molto dettagliato +8 ottave: ∧v∨^∧v∨^∧v Iper-dettagliato +``` + +**Nel progetto**: 4 ottave (buon compromesso tra dettaglio e performance) + +### 3. Persistence (Persistenza) + +Quanto ogni ottava influenza il risultato: + +```python +persistence = 0.6 + +Alta (0.8): ∧∨∧∨∧∨∧∨∧∨ Molto "ruggine" +Media (0.6): ∧~∨~∧~∨~∧~ Bilanciato +Bassa (0.3): ∧~~~∨~~~∧~~ Molto liscio +``` + +Formula: `ampiezza_ottava = amplitude * (persistence ^ numero_ottava)` + +### 4. Lacunarity (Lacunarità) + +Quanto aumenta la frequenza per ogni ottava: + +```python +lacunarity = 2.5 + +Bassa (1.5): Ottave simili +Media (2.5): Buona differenza +Alta (4.0): Grande differenza tra ottave +``` + +Formula: `frequenza_ottava = frequency * (lacunarity ^ numero_ottava)` + +### 5. Base (Seed) + +Punto di partenza per la generazione casuale: + +```python +base = 42 # Seed fisso = stesso terreno ogni volta + +base = 0: Terreno A +base = 42: Terreno B (diverso da A) +base = 100: Terreno C (diverso da A e B) +``` + +Cambiando il base, ottieni terreni completamente diversi! + +## Codice nel Progetto + +### Generazione Heightmap + +```python +def generate(self): + total_size = self.grid_size * self.tile_size # 20 + heightmap = np.zeros((total_size, total_size)) # Array 20×20 + + # Parametri Perlin Noise + scale = 8.0 + octaves = 4 + persistence = 0.6 + lacunarity = 2.5 + repeat_x = 1024 + repeat_y = 1024 + base = 42 + + # Per ogni punto della griglia + for i in range(total_size): + for j in range(total_size): + # Calcola valore Perlin Noise + height = noise.pnoise2( + i / scale, # Coordinate X normalizzata + j / scale, # Coordinate Y normalizzata + octaves=octaves, + persistence=persistence, + lacunarity=lacunarity, + repeatx=repeat_x, # Ripeti dopo 1024 unità + repeaty=repeat_y, + base=base # Seed casuale + ) + + # Normalizza (-1,+1) → (0,1) e scala + heightmap[i][j] = (height + 0.5) * 80.0 + + return heightmap +``` + +### Spiegazione Passo per Passo + +**1. Crea array vuoto** +```python +heightmap = np.zeros((20, 20)) +# [[0, 0, 0, ...], +# [0, 0, 0, ...], +# ...] +``` + +**2. Per ogni punto (i, j)** +``` +Punto (0,0) → noise.pnoise2(0/8, 0/8) = -0.2 +Punto (5,3) → noise.pnoise2(5/8, 3/8) = 0.4 +Punto (19,19)→ noise.pnoise2(19/8, 19/8) = -0.1 +``` + +**3. Normalizza e scala** +```python +# pnoise2 restituisce valori tra -1 e +1 +# Aggiungi 0.5 per portare a (0, 1) +# Moltiplica per 80 per range finale (0, 80) + +value = -0.2 +normalized = (-0.2 + 0.5) * 80.0 = 24.0 # Collina media + +value = 0.4 +normalized = (0.4 + 0.5) * 80.0 = 72.0 # Montagna alta + +value = -0.1 +normalized = (-0.1 + 0.5) * 80.0 = 32.0 # Collina +``` + +**4. Risultato finale** +```python +heightmap = [ + [24, 28, 35, 42, ...], + [22, 26, 32, 40, ...], + [18, 23, 30, 38, ...], + ... +] +``` + +## Smoothing (Levigatura) + +Il progetto include una funzione opzionale per rendere il terreno più liscio: + +```python +def _smooth_terrain(self, heightmap): + smoothed = np.copy(heightmap) + kernel_size = 3 # Finestra 3×3 + + for i in range(1, len(heightmap) - 1): + for j in range(1, len(heightmap[0]) - 1): + # Media dei vicini + neighbors = [ + heightmap[i-1][j-1], heightmap[i-1][j], heightmap[i-1][j+1], + heightmap[i][j-1], heightmap[i][j], heightmap[i][j+1], + heightmap[i+1][j-1], heightmap[i+1][j], heightmap[i+1][j+1] + ] + smoothed[i][j] = sum(neighbors) / len(neighbors) + + return smoothed +``` + +### Effetto dello Smoothing + +``` +Prima: Dopo smoothing: +▁▃▇▂▁▅▇▂▁▃ ▁▃▅▄▃▅▆▄▃▄ + Spigoloso Arrotondato +``` + +**Nel progetto**: Disabilitato di default per terreno più drammatico + +## Vantaggi del Perlin Noise + +### ✅ Naturale +I terreni sembrano organici e realistici + +### ✅ Veloce +Calcolo efficiente anche per griglie grandi + +### ✅ Deterministico +Stesso seed = stesso terreno (utile per multiplayer!) + +### ✅ Scalabile +Funziona per mappe piccole (20×20) e enormi (1000×1000) + +### ✅ Configurabile +Tantissimi parametri per personalizzare l'aspetto + +## Varianti e Alternative + +### Simplex Noise +Versione migliorata del Perlin Noise (più veloce, meno artefatti) + +### Worley Noise +Per pattern cellulari (tessere, pelle di giraffa) + +### Fractal Brownian Motion (FBM) +Perlin Noise con ottave (quello che usiamo!) + +### Diamond-Square +Algoritmo alternativo per terreni + +## Esempi di Configurazioni + +### Colline Dolci +```python +scale = 15.0 +octaves = 3 +persistence = 0.4 +lacunarity = 2.0 +height_multiplier = 40.0 +``` + +### Montagne Drammatiche +```python +scale = 5.0 +octaves = 6 +persistence = 0.7 +lacunarity = 3.0 +height_multiplier = 120.0 +``` + +### Pianura con Piccole Ondulazioni +```python +scale = 25.0 +octaves = 2 +persistence = 0.3 +lacunarity = 1.8 +height_multiplier = 20.0 +``` + +### Terreno Alieno/Caotico +```python +scale = 3.0 +octaves = 8 +persistence = 0.8 +lacunarity = 4.0 +height_multiplier = 100.0 +``` + +## Applicazioni Beyond Terreni + +Il Perlin Noise non si usa solo per terreni: + +### Texture Procedurali +```python +# Generare pattern di legno, marmo, ecc. +for x in range(texture_width): + for y in range(texture_height): + value = noise.pnoise2(x/50, y/50) + color = wood_color(value) + texture[x,y] = color +``` + +### Animazioni +```python +# Tempo come terza dimensione +time = frame_count / 60.0 +for x in range(width): + for y in range(height): + value = noise.pnoise3(x/scale, y/scale, time) + # Nuvole che si muovono! +``` + +### Gameplay +```python +# Spawn nemici in posizioni "naturali" +for i in range(num_enemies): + x = i * 10 + y = noise.pnoise1(x/20) * map_height + spawn_enemy(x, y) +``` + +## Riepilogo + +### Concetti Chiave + +✅ **Perlin Noise** genera valori pseudo-casuali coerenti +✅ **Scale** controlla la dimensione delle variazioni +✅ **Octaves** aggiungono livelli di dettaglio +✅ **Persistence** controlla l'influenza di ogni ottava +✅ **Lacunarity** controlla la differenza di frequenza tra ottave +✅ **Seed/Base** determina il terreno generato + +### Formula Completa + +``` +for ogni punto (x, y): + height = 0 + amplitude = 1 + frequency = 1 + + for ogni ottava: + height += pnoise2(x * frequency, y * frequency) * amplitude + amplitude *= persistence + frequency *= lacunarity + + height = (height + 0.5) * height_multiplier +``` + +### Nel Nostro Progetto + +- **400 punti** (20×20 griglia) +- **4 ottave** per buon dettaglio +- **Scale 8.0** per variazioni bilanciate +- **Range 0-80** per altezze finali +- **Seed 42** per terreno consistente + +--- + +**Prossimo Capitolo**: [Architettura del Codice →](05-implementazione.md) + +[← Capitolo Precedente](03-opengl.md) | [Torna all'indice](README.md) diff --git a/docs/05-implementazione.md b/docs/05-implementazione.md new file mode 100644 index 0000000..93c2f99 --- /dev/null +++ b/docs/05-implementazione.md @@ -0,0 +1,499 @@ +# Capitolo 5: Architettura del Codice + +## Panoramica della Struttura + +Il progetto è organizzato in modo modulare con separazione delle responsabilità: + +``` +shader/ +├── config/ +│ └── settings.py # Configurazione centralizzata +├── src/ +│ ├── app.py # Applicazione principale +│ ├── camera/ +│ │ └── camera.py # Sistema camera +│ ├── terrain/ +│ │ └── generator.py # Generazione terreno +│ └── rendering/ +│ └── terrain_renderer.py # Rendering OpenGL +└── main.py # Entry point +``` + +## Principi di Design + +### Separation of Concerns +Ogni modulo ha una responsabilità specifica: +- **Config**: Impostazioni +- **Camera**: Vista 3D +- **Generator**: Creazione terreno +- **Renderer**: Visualizzazione +- **App**: Coordinamento + +### Single Responsibility +Ogni classe fa una cosa e la fa bene. + +### Dependency Injection +Le classi ricevono dipendenze tramite parametri del costruttore. + +## Modulo: config/settings.py + +Contiene tutte le costanti configurabili: + +```python +# Finestra +WINDOW_WIDTH = 1200 +WINDOW_HEIGHT = 800 +FPS = 60 + +# Terreno +TERRAIN = { + 'grid_size': 20, + 'tile_width': 30.0, + 'noise_scale': 8.0, + 'octaves': 4, + ... +} + +# Camera +CAMERA = { + 'initial_distance': 800.0, + 'zoom_speed': 10.0, + ... +} + +# Rendering +RENDERING = { + 'grid_line_width': 5.0, + 'background_color': (0.53, 0.81, 0.92, 1.0), + ... +} + +# Biomi +BIOME_COLORS = {...} +BIOME_THRESHOLDS = {...} +``` + +**Vantaggi**: +- ✅ Tutto in un posto +- ✅ Facile da modificare +- ✅ Nessun valore "magico" nel codice + +## Modulo: src/terrain/generator.py + +### Classe TerrainGenerator + +Responsabilità: Generare heightmap usando Perlin Noise + +```python +class TerrainGenerator: + def __init__(self, config): + # Carica parametri da config + self.grid_size = config['grid_size'] + self.scale = config['noise_scale'] + self.octaves = config['noise_octaves'] + ... + + def generate(self): + """Genera e restituisce heightmap 2D""" + heightmap = np.zeros((self.grid_size, self.grid_size)) + + for i in range(self.grid_size): + for j in range(self.grid_size): + height = noise.pnoise2(...) + heightmap[i][j] = (height + 0.5) * self.height_multiplier + + return heightmap + + def _smooth_terrain(self, heightmap): + """Applica smoothing (opzionale)""" + # Media dei vicini + ... +``` + +**Input**: Configurazione +**Output**: Array 2D con altezze +**Dipendenze**: NumPy, noise library + +## Modulo: src/rendering/terrain_renderer.py + +### Classe TerrainRenderer + +Responsabilità: Disegnare il terreno con OpenGL + +```python +class TerrainRenderer: + def __init__(self, terrain_config, rendering_config, + biome_colors, biome_thresholds): + self.tile_width = terrain_config['tile_width'] + self.colors = biome_colors + self.thresholds = biome_thresholds + ... + self.heightmap = None + + def set_heightmap(self, heightmap): + """Imposta heightmap da renderizzare""" + self.heightmap = heightmap + + def render(self): + """Disegna tutto il terreno""" + self._render_tiles() + self._draw_grid() + + def draw_tile(self, x, z, h1, h2, h3, h4): + """Disegna una singola tile""" + # Faccia superiore + glBegin(GL_QUADS) + glColor3f(*color) + glVertex3f(...) + glEnd() + + # Facce laterali con shading + ... + + def get_color_for_height(self, height): + """Mappa altezza → bioma → colore""" + if height < self.thresholds['water']: + return self.colors['water'] + ... +``` + +**Input**: Heightmap, configurazione +**Output**: Draw calls OpenGL +**Dipendenze**: OpenGL + +## Modulo: src/camera/camera.py + +### Classe Camera + +Responsabilità: Gestire vista 3D e input utente + +```python +class Camera: + def __init__(self, config): + self.distance = config['initial_distance'] + self.height = config['initial_height'] + self.zoom_speed = config['zoom_speed'] + ... + + def handle_input(self, keys): + """Processa input tastiera""" + if keys[pygame.K_UP]: + self.distance -= self.zoom_speed + ... + + def apply(self, aspect_ratio): + """Applica trasformazioni camera""" + self.setup_projection(aspect_ratio) + self.setup_modelview() + + def setup_projection(self, aspect_ratio): + """Imposta matrice proiezione""" + glMatrixMode(GL_PROJECTION) + glLoadIdentity() + gluPerspective(self.fov, aspect_ratio, ...) + + def setup_modelview(self): + """Imposta matrice vista""" + glMatrixMode(GL_MODELVIEW) + glLoadIdentity() + gluLookAt(...) +``` + +**Input**: Tasti premuti +**Output**: Matrici OpenGL +**Dipendenze**: OpenGL, Pygame + +## Modulo: src/app.py + +### Classe IsometricTerrainApp + +Responsabilità: Coordinare tutti i componenti + +```python +class IsometricTerrainApp: + def __init__(self, config): + # Inizializza Pygame e OpenGL + pygame.init() + self.display = (config.WINDOW_WIDTH, config.WINDOW_HEIGHT) + pygame.display.set_mode(self.display, DOUBLEBUF | OPENGL) + + # Crea componenti + self.camera = Camera(config.CAMERA) + self.terrain_generator = TerrainGenerator(config.TERRAIN) + self.renderer = TerrainRenderer(...) + + # Setup OpenGL + self._setup_opengl() + + # Genera terreno + heightmap = self.terrain_generator.generate() + self.renderer.set_heightmap(heightmap) + + self.clock = pygame.time.Clock() + + def _setup_opengl(self): + """Configura stati OpenGL""" + glEnable(GL_DEPTH_TEST) + glEnable(GL_LIGHTING) + ... + + def run(self): + """Loop principale applicazione""" + self.running = True + while self.running: + self.handle_events() + self.update() + self.render() + self.clock.tick(self.config.FPS) + + def handle_events(self): + """Gestisce eventi Pygame""" + for event in pygame.event.get(): + if event.type == pygame.QUIT: + self.running = False + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_r: + self._regenerate_terrain() + + def update(self): + """Aggiorna stato""" + keys = pygame.key.get_pressed() + self.camera.handle_input(keys) + + def render(self): + """Renderizza scena""" + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glClearColor(*self.config.RENDERING['background_color']) + + aspect_ratio = self.display[0] / self.display[1] + self.camera.apply(aspect_ratio) + + self.renderer.render() + + pygame.display.flip() +``` + +## Modulo: main.py + +Entry point dell'applicazione: + +```python +#!/usr/bin/env python3 +import sys +import os + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from config import settings +from src.app import IsometricTerrainApp + +def main(): + try: + app = IsometricTerrainApp(settings) + app.run() + except KeyboardInterrupt: + print("\nExiting...") + sys.exit(0) + +if __name__ == "__main__": + main() +``` + +## Flusso dell'Applicazione + +``` +1. AVVIO + main.py + └─→ Importa settings + └─→ Crea IsometricTerrainApp + +2. INIZIALIZZAZIONE + IsometricTerrainApp.__init__() + ├─→ Crea finestra Pygame/OpenGL + ├─→ Crea Camera + ├─→ Crea TerrainGenerator + │ └─→ Genera heightmap + ├─→ Crea TerrainRenderer + │ └─→ Riceve heightmap + └─→ Setup stati OpenGL + +3. LOOP PRINCIPALE (60 FPS) + app.run() + │ + ├─→ handle_events() + │ └─→ ESC/R/Close window + │ + ├─→ update() + │ └─→ camera.handle_input(keys) + │ └─→ Aggiorna distance/height + │ + ├─→ render() + │ ├─→ Pulisci buffer + │ ├─→ camera.apply() + │ │ ├─→ setup_projection() + │ │ └─→ setup_modelview() + │ ├─→ renderer.render() + │ │ ├─→ Disegna tile + │ │ └─→ Disegna grid + │ └─→ pygame.display.flip() + │ + └─→ clock.tick(60) # Limita a 60 FPS + +4. CHIUSURA + pygame.quit() +``` + +## Pattern di Design Utilizzati + +### 1. Configuration Object +Tutte le impostazioni in un oggetto centralizzato. + +### 2. Dependency Injection +```python +# Le dipendenze sono passate esplicitamente +camera = Camera(config) +renderer = TerrainRenderer(config, colors, thresholds) +``` + +### 3. Single Responsibility Principle +Ogni classe ha un solo motivo per cambiare. + +### 4. Composition Over Inheritance +App compone Camera, Generator, Renderer invece di ereditare. + +### 5. Encapsulation +Dettagli implementativi nascosti (metodi `_private`). + +## Interazioni tra Componenti + +``` +┌─────────────────┐ +│ main.py │ +└────────┬────────┘ + │ crea + ↓ +┌─────────────────────────┐ +│ IsometricTerrainApp │ +├─────────────────────────┤ +│ + camera │←─────┐ +│ + terrain_generator │←─────┼─── Composizione +│ + renderer │←─────┘ +└───────┬─────────────────┘ + │ + ├──→ camera.handle_input(keys) + ├──→ camera.apply(aspect) + ├──→ terrain_generator.generate() + └──→ renderer.render() + +┌────────────┐ heightmap ┌─────────────────┐ +│ Generator │──────────────→│ Renderer │ +└────────────┘ └─────────────────┘ +``` + +## Vantaggi dell'Architettura + +### ✅ Modularità +Ogni componente è indipendente e sostituibile. + +### ✅ Testabilità +Puoi testare Generator senza Renderer o Camera. + +### ✅ Manutenibilità +Modifiche localizzate, nessun effetto a cascata. + +### ✅ Riutilizzabilità +TerrainGenerator può essere usato in altri progetti. + +### ✅ Leggibilità +Codice organizzato logicamente, facile da navigare. + +## Estensione del Progetto + +### Aggiungere un Nuovo Bioma + +**1. Config**: +```python +# config/settings.py +BIOME_COLORS['lava'] = (1.0, 0.3, 0.0) +BIOME_THRESHOLDS['lava'] = 85.0 +``` + +**2. Renderer**: +```python +# src/rendering/terrain_renderer.py +def get_color_for_height(self, height): + ... + elif height < self.thresholds['lava']: + return self.colors['lava'] +``` + +### Aggiungere Rotazione Camera + +**1. Config**: +```python +# config/settings.py +CAMERA = { + ... + 'rotation_angle': 0.0, + 'rotation_speed': 2.0, +} +``` + +**2. Camera**: +```python +# src/camera/camera.py +def handle_input(self, keys): + ... + if keys[pygame.K_q]: + self.rotation_angle += self.rotation_speed + if keys[pygame.K_e]: + self.rotation_angle -= self.rotation_speed +``` + +### Salvare/Caricare Terreni + +**1. Nuovo modulo**: +```python +# src/terrain/serializer.py +class TerrainSerializer: + @staticmethod + def save(heightmap, filename): + np.save(filename, heightmap) + + @staticmethod + def load(filename): + return np.load(filename) +``` + +**2. App**: +```python +# src/app.py +def handle_events(self): + ... + elif event.key == pygame.K_s: + TerrainSerializer.save(self.renderer.heightmap, 'terrain.npy') +``` + +## Riepilogo + +### Struttura +- **config**: Configurazione +- **src/camera**: Sistema vista +- **src/terrain**: Generazione +- **src/rendering**: Visualizzazione +- **src/app**: Coordinamento + +### Componenti Principali +- **TerrainGenerator**: Crea heightmap +- **TerrainRenderer**: Disegna mesh +- **Camera**: Gestisce vista +- **IsometricTerrainApp**: Orchestra tutto + +### Flusso +Init → Loop (Events → Update → Render) → Quit + +--- + +**Prossimo Capitolo**: [Il Sistema di Rendering →](06-rendering.md) + +[← Capitolo Precedente](04-perlin-noise.md) | [Torna all'indice](README.md) diff --git a/docs/06-rendering.md b/docs/06-rendering.md new file mode 100644 index 0000000..44666c2 --- /dev/null +++ b/docs/06-rendering.md @@ -0,0 +1,444 @@ +# Capitolo 6: Il Sistema di Rendering + +## Panoramica + +Il rendering trasforma i dati della heightmap in un'immagine 3D visibile. Questo capitolo spiega come ogni tile viene disegnata e come si crea l'effetto isometrico. + +## Da Heightmap a Mesh 3D + +### Input: Heightmap +```python +heightmap = [ + [10, 12, 15, 18], # Riga 0 + [8, 10, 13, 16], # Riga 1 + [5, 7, 10, 13], # Riga 2 + [3, 5, 8, 11], # Riga 3 +] +``` + +### Output: Geometria 3D +Ogni celle diventa un quad (quadrilatero) in 3D: + +``` +heightmap[i][j] = 10 +heightmap[i+1][j] = 8 +heightmap[i][j+1] = 12 +heightmap[i+1][j+1] = 13 + + v4(j+1)────v3(i+1,j+1) + 12/│ /13 + / │ /│ + / │ / │ + v1(i,j)────v2(i+1,j) + 10 8 +``` + +## Disegno di una Singola Tile + +### 1. Calcolo Vertici + +```python +def draw_tile(self, x, z, h1, h2, h3, h4): + # x, z: posizione in griglia + # h1, h2, h3, h4: altezze dei 4 angoli + + corners = [ + (x, h1, z), # Angolo 1 + (x + tile_width, h2, z), # Angolo 2 + (x + tile_width, h4, z + tile_depth), # Angolo 3 + (x, h3, z + tile_depth) # Angolo 4 + ] +``` + +### 2. Faccia Superiore + +```python +# Calcola colore medio +avg_height = (h1 + h2 + h3 + h4) / 4.0 +color = self.get_color_for_height(avg_height) + +# Disegna quad +glBegin(GL_QUADS) +glColor3f(*color) # Applica colore +for corner in corners: + glVertex3f(*corner) # Definisci vertice +glEnd() +``` + +**Risultato**: Faccia superiore della tile colorata. + +### 3. Facce Laterali + +#### Faccia Destra (X+) + +```python +if h2 > 0.1 or h1 > 0.1: # Solo se c'è altezza + darker_color = tuple(c * 0.7 for c in color) + glBegin(GL_QUADS) + glColor3f(*darker_color) + # Dall'alto al basso + glVertex3f(x + tile_width, h2, z) + glVertex3f(x + tile_width, 0, z) + glVertex3f(x + tile_width, 0, z + tile_depth) + glVertex3f(x + tile_width, h4, z + tile_depth) + glEnd() +``` + +**Shading**: Moltiplicato per 0.7 → più scuro del 30% + +#### Faccia Posteriore (Z+) + +```python +if h3 > 0.1 or h1 > 0.1: + darker_color = tuple(c * 0.8 for c in color) + glBegin(GL_QUADS) + glColor3f(*darker_color) + glVertex3f(x, h3, z + tile_depth) + glVertex3f(x, 0, z + tile_depth) + glVertex3f(x + tile_width, 0, z + tile_depth) + glVertex3f(x + tile_width, h4, z + tile_depth) + glEnd() +``` + +**Shading**: Moltiplicato per 0.8 → più scuro del 20% + +### Visualizzazione Completa + +``` + Faccia superiore (100%) + ┌────────┐ + │░░░░░░░░│ + └────────┘ + │ + Faccia │ Faccia + retro │ destra + (80%) │ (70%) +``` + +## Rendering dell'Intero Terreno + +### Loop Principale + +```python +def render(self): + total_size = self.grid_size # 20 + + # Disegna tutte le tile + for i in range(total_size - 1): + for j in range(total_size - 1): + # Calcola posizione mondo + x = (i - total_size/2) * self.tile_width + z = (j - total_size/2) * self.tile_depth + + # Ottieni altezze degli angoli + h1 = self.heightmap[i][j] + h2 = self.heightmap[i+1][j] + h3 = self.heightmap[i][j+1] + h4 = self.heightmap[i+1][j+1] + + # Disegna tile + self.draw_tile(x, z, h1, h2, h3, h4) + + # Disegna griglia wireframe + self._draw_grid(total_size) +``` + +### Centratura della Griglia + +```python +# Senza centratura: +x = i * tile_width # Tile (0,0) a (0,0), (19,19) a (570,570) + +# Con centratura: +x = (i - total_size/2) * tile_width +# Tile (0,0) a (-300, -300) +# Tile (10,10) a (0, 0) ← Centro +# Tile (19,19) a (270, 270) +``` + +## Mappatura Biomi → Colori + +### Funzione di Mappatura + +```python +def get_color_for_height(self, height): + if height < 10.0: + return (0.2, 0.4, 0.8) # Acqua blu + elif height < 20.0: + return (0.76, 0.7, 0.5) # Sabbia beige + elif height < 30.0: + return (0.2, 0.5, 0.2) # Erba chiara + elif height < 45.0: + return (0.25, 0.6, 0.25) # Erba media + elif height < 60.0: + return (0.3, 0.65, 0.3) # Erba scura + elif height < 70.0: + return (0.5, 0.5, 0.5) # Roccia grigia + else: + return (0.9, 0.9, 0.95) # Neve bianca +``` + +### Gradienti vs Soglie + +Il progetto usa **soglie nette**: + +``` +Height: 0 10 20 30 45 60 70 80 +Color: 🌊 🏖️ 🌿 🌲 🌳 ⛰️ 🏔️ + Blu Beige Verde1 Verde2 Verde3 Grigio Bianco +``` + +Alternative possibili: +- Interpolazione tra colori +- Rumore aggiuntivo per variazioni +- Texture invece di colori solidi + +## Wireframe Grid + +### Perché il Wireframe? + +- ✅ Mostra chiaramente i confini delle tile +- ✅ Stile classico dei giochi isometrici +- ✅ Aiuta a percepire la struttura + +### Implementazione + +```python +def _draw_grid(self, total_size): + glColor3f(0.0, 0.0, 0.0) # Nero + glLineWidth(5.0) # 5 pixel di spessore + glBegin(GL_LINES) + + # Linee lungo l'asse Z + for i in range(total_size): + for j in range(total_size - 1): + x = (i - total_size/2) * self.tile_width + z1 = (j - total_size/2) * self.tile_depth + z2 = ((j+1) - total_size/2) * self.tile_depth + + # Linea da (i,j) a (i,j+1) + glVertex3f(x, self.heightmap[i][j], z1) + glVertex3f(x, self.heightmap[i][j+1], z2) + + # Linee lungo l'asse X + for j in range(total_size): + for i in range(total_size - 1): + x1 = (i - total_size/2) * self.tile_width + x2 = ((i+1) - total_size/2) * self.tile_width + z = (j - total_size/2) * self.tile_depth + + # Linea da (i,j) a (i+1,j) + glVertex3f(x1, self.heightmap[i][j], z) + glVertex3f(x2, self.heightmap[i+1][j], z) + + glEnd() +``` + +### Risultato Visivo + +``` +Senza wireframe: Con wireframe: +███████████████ ┌─┬─┬─┬─┬─┐ +███████████████ ├─┼─┼─┼─┼─┤ +███████████████ ├─┼─┼─┼─┼─┤ +███████████████ ├─┼─┼─┼─┼─┤ +███████████████ └─┴─┴─┴─┴─┘ +``` + +## Ordine di Rendering + +### Problema: Depth Fighting + +Se disegni nell facce nello stesso ordine, possono sovrapporsi male. + +### Soluzione: Depth Buffer + +```python +# All'inizializzazione +glEnable(GL_DEPTH_TEST) + +# Ogni frame +glClear(GL_DEPTH_BUFFER_BIT) +``` + +OpenGL automaticamente: +1. Calcola profondità di ogni pixel +2. Confronta con depth buffer +3. Disegna solo se più vicino alla camera + +### Back-to-Front vs Depth Buffer + +**Vecchio metodo** (painter's algorithm): +``` +1. Ordina oggetti per distanza +2. Disegna da lontano a vicino +``` + +**Metodo moderno** (depth buffer): +``` +1. Disegna in qualsiasi ordine +2. GPU gestisce automaticamente +``` + +Noi usiamo depth buffer → nessun ordinamento necessario! + +## Illuminazione durante il Rendering + +### Setup Globale + +```python +glEnable(GL_LIGHTING) +glEnable(GL_LIGHT0) +glLightfv(GL_LIGHT0, GL_POSITION, [1.0, 1.0, 1.0, 0.0]) +glLightfv(GL_LIGHT0, GL_AMBIENT, [0.4, 0.4, 0.4, 1.0]) +glLightfv(GL_LIGHT0, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]) +``` + +### Color Material + +```python +glEnable(GL_COLOR_MATERIAL) +glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE) +``` + +Questo dice a OpenGL: "Usa i colori dei vertici come materiale" + +### Effetto dell'Illuminazione + +``` +Senza luce: Con luce: +████████████ ▓▓▓▓▓▓▓▓▓▓▓▓ ← Lati luminosi +████████████ → ▒▒▒▒▒▒▒▒▒▒▒▒ ← Lati in ombra +████████████ ░░░░░░░░░░░░ ← Base scura +``` + +## Performance e Ottimizzazione + +### Conteggio Draw Calls + +``` +Tile: 400 (20×20) +Facce per tile: ~3 (top + 2 lati) +Grid lines: ~800 linee +Total draw calls: ~2000/frame +``` + +A 60 FPS: **120,000 draw calls/secondo** + +Questo è gestibile per GPU moderne! + +### Possibili Ottimizzazioni + +**1. Vertex Buffer Objects (VBO)** +```python +# Invece di glBegin/glEnd +vbo = create_vbo_from_heightmap(heightmap) +render_vbo(vbo) # Una sola chiamata! +``` + +**2. Instancing** +```python +# Disegna molte tile identiche in una chiamata +glDrawArraysInstanced(...) +``` + +**3. Frustum Culling** +```python +# Non disegnare tile fuori dal campo visivo +if is_visible(tile, camera): + draw_tile(tile) +``` + +**4. Level of Detail (LOD)** +```python +# Tile lontane = meno dettaglio +if distance_to_camera(tile) > 100: + draw_simple_tile(tile) +else: + draw_detailed_tile(tile) +``` + +Per questo progetto: **non necessarie** (griglia piccola)! + +## Clear e Swap + +### Clear Buffer + +```python +glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) +``` + +Pulisce: +- **Color Buffer**: Pixel sullo schermo +- **Depth Buffer**: Informazioni profondità + +### Background Color + +```python +glClearColor(0.53, 0.81, 0.92, 1.0) # Sky blue +``` + +Quando clearColor viene applicato, lo schermo diventa di questo colore. + +### Swap Buffers + +```python +pygame.display.flip() +``` + +Scambia front e back buffer → mostra il frame renderizzato + +## Pipeline Completa di un Frame + +``` +1. CLEAR + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + ↓ +2. SETUP CAMERA + camera.apply(aspect_ratio) + ├─ Projection matrix + └─ ModelView matrix + ↓ +3. RENDER TILES + for each tile: + ├─ Calcola posizione + ├─ Ottieni altezze + ├─ Calcola colore + ├─ Disegna faccia top + └─ Disegna facce laterali + ↓ +4. RENDER GRID + for each edge: + └─ Disegna linea + ↓ +5. SWAP BUFFERS + pygame.display.flip() + ↓ + FRAME VISIBILE! +``` + +## Riepilogo + +### Concetti Chiave + +- **Heightmap → Mesh**: Array 2D diventa geometria 3D +- **Quad**: Ogni tile = 4 vertici +- **Shading**: Facce laterali più scure +- **Biomi**: Altezza determina colore +- **Wireframe**: Linee nere per visibilità +- **Depth Buffer**: Gestisce sovrapposizioni +- **Double Buffering**: Animazione fluida + +### Flusso Rendering + +1. Clear buffer +2. Setup camera +3. Disegna tile (facce + lati) +4. Disegna grid +5. Swap buffer + +--- + +**Prossimo Capitolo**: [Il Sistema Camera →](07-camera.md) + +[← Capitolo Precedente](05-implementazione.md) | [Torna all'indice](README.md) diff --git a/docs/07-camera.md b/docs/07-camera.md new file mode 100644 index 0000000..509ceb2 --- /dev/null +++ b/docs/07-camera.md @@ -0,0 +1,485 @@ +# Capitolo 7: Il Sistema Camera + +## Cos'è una Camera Virtuale? + +Una camera virtuale è il "punto di vista" da cui osserviamo la scena 3D. È come una videocamera nel mondo virtuale che determina cosa vediamo e da che angolazione. + +## Componenti della Camera + +### Posizione (Eye) +Dove si trova la camera nello spazio 3D: + +```python +camera_position = (distance, height, distance) +# Esempio: (800, 450, 800) +``` + +### Target (Center) +Dove guarda la camera: + +```python +look_at_point = (0, 0, 0) # Centro della scena +``` + +### Up Vector +Quale direzione è "su" per la camera: + +```python +up_vector = (0, 1, 0) # Y+ è "su" +``` + +### Visualizzazione + +``` + Up (0,1,0) + ↑ + │ + │ + Eye ●─────→ Center + (800,450,800) (0,0,0) +``` + +## Vista Isometrica + +### Cosa la Rende Isometrica? + +La camera è posizionata a **45 gradi** sia orizzontalmente che verticalmente: + +``` +Vista dall'alto: + N + ↑ + Camera + W ← ● → E Camera a 45° tra N e E + ↓ + S + +Vista laterale: + ↑ + 45° + Camera ● + ╲ + ╲ 45° + ↘ + Terreno +``` + +### Posizione Isometrica + +```python +# Distanza dalla scena +distance = 800.0 + +# Camera posizionata a (distance, height, distance) +# Questo crea l'angolo di 45° perfetto +camera_pos = (distance, height, distance) +look_at = (0, 0, 0) +``` + +Perché funziona? +``` + Z + ↑ + │ + │ distance + ├──────────● Camera + │ ╱ + │ 45° ╱ + │ ╱ distance + O──────┴──────→ X +``` + +L'angolo tra X e Z è 45° quando le coordinate X e Z sono uguali! + +## Proiezione Prospettica + +### Field of View (FOV) + +L'angolo di visione della camera: + +```python +fov = 45 # gradi + +FOV piccolo (30°): FOV grande (90°): + │ \___ + │ │ ___ + │ │ ___ + │ │ ___ + Camera Camera + Vista stretta Vista ampia + (teleobiettivo) (grandangolo) +``` + +### Clipping Planes + +**Near Plane**: Distanza minima visibile +**Far Plane**: Distanza massima visibile + +```python +near = 0.1 # Oggetti < 0.1 → invisibili +far = 5000.0 # Oggetti > 5000 → invisibili + + Far Plane (5000) + │ + ╱╲ │ + ╱ ╲ │ + ╱ ╲ │ + ╱Camera╲ │ + ╱ ● ╲│ +Near Plane (0.1) +``` + +Oggetti fuori da questo volume non vengono disegnati! + +### Codice Proiezione + +```python +def setup_projection(self, aspect_ratio): + glMatrixMode(GL_PROJECTION) + glLoadIdentity() + gluPerspective( + self.fov, # 45 gradi + aspect_ratio, # width/height (1.5 per 1200×800) + self.near_clip, # 0.1 + self.far_clip # 5000.0 + ) +``` + +## Trasformazione ModelView + +### Cosa Fa + +Imposta la posizione e orientamento della camera. + +### Codice + +```python +def setup_modelview(self): + glMatrixMode(GL_MODELVIEW) + glLoadIdentity() + gluLookAt( + # Posizione camera (eye) + self.distance, self.height, self.distance, + # Punto guardato (center) + 0, 0, 0, + # Direzione "su" (up) + 0, 1, 0 + ) +``` + +### Significato Parametri + +``` +gluLookAt( + eye_x, eye_y, eye_z, # Dove sta la camera + center_x, center_y, center_z, # Dove guarda + up_x, up_y, up_z # Quale direzione è "su" +) +``` + +## Controlli Utente + +### Input Tastiera + +```python +def handle_input(self, keys): + # Zoom in/out + if keys[pygame.K_UP]: + self.distance -= self.zoom_speed + self.distance = max(self.min_distance, self.distance) + + if keys[pygame.K_DOWN]: + self.distance += self.zoom_speed + self.distance = min(self.max_distance, self.distance) + + # Altezza camera + if keys[pygame.K_LEFT]: + self.height += self.height_speed + + if keys[pygame.K_RIGHT]: + self.height -= self.height_speed +``` + +### Effetto Zoom + +``` +Zoom In (UP): Zoom Out (DOWN): + ● ● + │\ │ \ + │ \ │ \ + │ \ │ \ + Distance Distance + (più piccolo) (più grande) + │ │ + Terreno Terreno + (più vicino, (più lontano, + più grande) più piccolo) +``` + +### Effetto Altezza + +``` +Height Up (LEFT): Height Down (RIGHT): + + ● ● + ╱│ ╱│\ + ╱ │ ╱ │ \ + ╱ │ ╱ │ \ +Height Height +(più alto) (più basso) + │ │ +Terreno Terreno +(vista dall'alto) (vista laterale) +``` + +## Limiti della Camera + +### Perché i Limiti? + +Evitare: +- Zoom eccessivo (camera dentro il terreno) +- Zoom troppo distante (terreno invisibile) +- Angolazioni strane + +### Implementazione + +```python +# Limiti zoom +MIN_DISTANCE = 200.0 +MAX_DISTANCE = 2000.0 + +if keys[pygame.K_UP]: + self.distance -= self.zoom_speed + # Blocca al minimo + self.distance = max(MIN_DISTANCE, self.distance) + +if keys[pygame.K_DOWN]: + self.distance += self.zoom_speed + # Blocca al massimo + self.distance = min(MAX_DISTANCE, self.distance) +``` + +### Limiti Altezza + +Nessun limite esplicito sull'altezza, ma potresti aggiungere: + +```python +MIN_HEIGHT = 100.0 +MAX_HEIGHT = 1000.0 + +self.height = max(MIN_HEIGHT, min(MAX_HEIGHT, self.height)) +``` + +## Aspect Ratio + +### Cos'è + +Il rapporto larghezza/altezza della finestra: + +```python +aspect_ratio = width / height +# 1200 / 800 = 1.5 +``` + +### Perché Importante? + +Senza aspect ratio corretto, l'immagine si deforma: + +``` +Aspect corretto (1.5): Aspect sbagliato (1.0): + ┌─────────────┐ ┌────────┐ + │ │ │ │ + │ ●○● │ │ ●○● │ + │ │ │ │ + └─────────────┘ └────────┘ + Proporzioni OK Schiacciato! +``` + +### Utilizzo + +```python +aspect_ratio = self.display[0] / self.display[1] +gluPerspective(fov, aspect_ratio, near, far) +``` + +## Matrici di Trasformazione + +### Matrice Proiezione + +Trasforma coordinate 3D in coordinate schermo 2D: + +``` +Mondo 3D → Matrice Proiezione → Spazio clip → Schermo 2D +``` + +### Matrice ModelView + +Combina trasformazione del modello E della vista: + +``` +ModelView = View × Model + +View: Posizione camera +Model: Trasformazioni oggetto (nel nostro caso, nessuna) +``` + +### Stack Matrici + +OpenGL mantiene uno stack di matrici: + +```python +glMatrixMode(GL_PROJECTION) # Seleziona stack proiezione +glLoadIdentity() # Resetta a identità +gluPerspective(...) # Applica trasformazione + +glMatrixMode(GL_MODELVIEW) # Seleziona stack modelview +glLoadIdentity() # Resetta a identità +gluLookAt(...) # Applica trasformazione +``` + +## Calcoli Interni (Approfondimento) + +### gluLookAt - Cosa Fa + +Crea una matrice che: +1. Trasla il mondo in modo che la camera sia all'origine +2. Ruota il mondo in modo che la camera guardi lungo -Z + +``` +Prima: Dopo gluLookAt: + Mondo Camera all'origine + │ │ + ● Camera O────→ Guarda -Z + ╱ ╱ + ╱ ╱ + Terreno Terreno (traslato/ruotato) +``` + +### gluPerspective - Cosa Fa + +Crea una matrice che: +1. Applica prospettiva (cose lontane = piccole) +2. Mappa il volume visibile in un cubo [-1,+1]³ + +``` + Cubo normalizzato +Frustum visibile [-1,+1] in tutte +(pyramide tronca) le dimensioni + ╱╲ ┌───┐ + ╱ ╲ │ │ + ╱ ╲ → │ │ + ╱ ╲ └───┘ + ╱ Camera ╲ +``` + +## Esempio Completo di Frame + +```python +# 1. Setup proiezione +glMatrixMode(GL_PROJECTION) +glLoadIdentity() +gluPerspective(45, 1.5, 0.1, 5000.0) + +# 2. Setup camera +glMatrixMode(GL_MODELVIEW) +glLoadIdentity() +gluLookAt( + 800, 450, 800, # Camera a (800,450,800) + 0, 0, 0, # Guarda il centro + 0, 1, 0 # Y è "su" +) + +# 3. Disegna geometria +# OpenGL applica automaticamente entrambe le matrici +# a ogni vertice disegnato +glBegin(GL_QUADS) +glVertex3f(x, y, z) # Trasformato da matrici +... +glEnd() +``` + +## Miglioramenti Possibili + +### Rotazione Camera + +```python +def handle_input(self, keys): + if keys[pygame.K_q]: + self.rotation_angle += self.rotation_speed + if keys[pygame.K_e]: + self.rotation_angle -= self.rotation_speed + +def setup_modelview(self): + glMatrixMode(GL_MODELVIEW) + glLoadIdentity() + + # Ruota attorno all'asse Y + glRotatef(self.rotation_angle, 0, 1, 0) + + gluLookAt(...) +``` + +### Camera Libera (First Person) + +```python +# Camera FPS-style +position = [x, y, z] +rotation = [pitch, yaw] + +def move_forward(): + position[0] += cos(yaw) * speed + position[2] += sin(yaw) * speed + +def look_around(mouse_dx, mouse_dy): + yaw += mouse_dx * sensitivity + pitch += mouse_dy * sensitivity +``` + +### Camera Smooth (Interpolazione) + +```python +target_distance = 500 +current_distance = 800 + +# Invece di salto immediato +current_distance = target_distance + +# Usa interpolazione smooth +lerp_speed = 0.1 +current_distance += (target_distance - current_distance) * lerp_speed +``` + +## Riepilogo + +### Componenti Camera + +- **Position**: Dove sta la camera +- **Target**: Dove guarda +- **Up Vector**: Orientamento "su" +- **FOV**: Angolo di visione +- **Clipping**: Near e far plane + +### Controlli + +- **UP/DOWN**: Zoom in/out (distance) +- **LEFT/RIGHT**: Altezza camera (height) +- **Limiti**: Min/max per evitare angoli strani + +### Matrici + +- **Projection**: FOV, aspect, clipping +- **ModelView**: Posizione e orientamento camera + +### Vista Isometrica + +Posizione a **45°** sia orizzontalmente che verticalmente: +```python +camera = (distance, height, distance) +target = (0, 0, 0) +``` + +--- + +**Prossimo Capitolo**: [Personalizzazione e Configurazione →](08-personalizzazione.md) + +[← Capitolo Precedente](06-rendering.md) | [Torna all'indice](README.md) diff --git a/docs/08-personalizzazione.md b/docs/08-personalizzazione.md new file mode 100644 index 0000000..dc9cf93 --- /dev/null +++ b/docs/08-personalizzazione.md @@ -0,0 +1,861 @@ +# Capitolo 8: Personalizzazione e Configurazione + +## File di Configurazione + +Tutto è configurabile tramite `config/settings.py`. Non serve modificare il codice sorgente! + +## Modificare le Dimensioni del Terreno + +### Grid Size (Numero di Tiles) + +```python +TERRAIN = { + 'grid_size': 20, # Griglia 20×20 + ... +} +``` + +#### Effetti + +``` +grid_size = 10: grid_size = 30: +┌────────────┐ ┌────────────────────┐ +│ 10×10 │ │ 30×30 │ +│ tiles │ │ molte tiles │ +│ │ │ │ +└────────────┘ └────────────────────┘ +Meno dettaglio Più dettaglio +Più veloce Più lento +``` + +#### Consigli + +- **10×10**: Test rapidi, PC lenti +- **20×20**: Bilanciato (default) +- **30×30**: Dettaglio alto +- **50×50**: Molto dettagliato, richiede PC potente + +### Dimensione Tile + +```python +TERRAIN = { + 'tile_width': 30, # Larghezza in pixel + 'tile_height': 30, # Altezza in pixel + ... +} +``` + +#### Effetti + +``` +tile_size = 20: tile_size = 40: + Small tiles Big tiles + ╱╲╱╲╱╲ ╱──╲╱──╲ + ╱╲╱╲╱╲╱ ╱────╲──╱ + ╱╲╱╲╱╲╱╲ ╱──────╲╱ +``` + +**Nota**: Se cambi tile_size, adatta anche la camera! + +```python +# tile_size piccole → camera più vicina +CAMERA = { + 'distance': 400, # Invece di 800 + 'height': 200, # Invece di 450 +} +``` + +## Modificare il Terreno + +### Scala Perlin Noise + +```python +TERRAIN = { + 'perlin_scale': 8.0, # IMPORTANTE! + ... +} +``` + +#### Effetti + +``` +scale = 3.0: scale = 15.0: + Grandi formazioni Piccole colline + ╱╲____╱╲ ╱╲╱╲╱╲╱╲ + ╱ ──── ╲ ╱╲╱╲╱╲╱╲╱ + ╱ ╲ ╱╲╱╲╱╲╱╲╱╲ +Montagne ampie Terreno mosso +``` + +**Regola**: scale ≈ grid_size / 2.5 + +- Grid 10×10 → scale = 4.0 +- Grid 20×20 → scale = 8.0 +- Grid 30×30 → scale = 12.0 +- Grid 50×50 → scale = 20.0 + +### Moltiplicatore Altezza + +```python +TERRAIN = { + 'height_multiplier': 80.0, + ... +} +``` + +#### Effetti + +``` +multiplier = 30: multiplier = 150: + Colline basse Montagne alte + ___╱╲___ ╱╲ + ╱ ╲ ╱ ╲ + ╱__________╲ ╱ ╲ + ╱______╲ +``` + +**Consigli**: +- 30-50: Terreno pianeggiante +- 60-80: Colline (default) +- 100-150: Montagne +- 200+: Drammatico, picchi estremi + +### Octaves (Dettaglio) + +```python +TERRAIN = { + 'perlin_octaves': 4, + ... +} +``` + +#### Effetti + +``` +octaves = 1: octaves = 6: + Forma base Molti dettagli + ╱╲ ╱╲╱╲ + ╱ ╲ ╱╲╱╲╱╲ + ╱ ╲ ╱╲╱╲╱╲╱╲ +Liscio Complesso +Veloce Lento +``` + +**Consigli**: +- 1-2: Forme grandi e semplici +- 3-4: Bilanciato (default) +- 5-6: Molto dettagliato +- 7+: Estremo, rallenta + +### Persistence (Influenza Dettagli) + +```python +TERRAIN = { + 'perlin_persistence': 0.6, + ... +} +``` + +#### Effetti + +``` +persistence = 0.3: persistence = 0.8: + Dettagli deboli Dettagli forti + ╱╲____ ╱╲╱╲╱╲ + ╱╲ ╱╲ ╱╲╱╲╱╲╱╲ + ╱ ──── ╲ ╱╲╱╲╱╲╱╲╱ +Forme larghe Texture ruvida +``` + +**Range**: 0.0 - 1.0 +- 0.3-0.4: Liscio +- 0.5-0.6: Bilanciato (default) +- 0.7-0.8: Ruvido + +### Lacunarity (Frequenza Dettagli) + +```python +TERRAIN = { + 'perlin_lacunarity': 2.5, + ... +} +``` + +#### Effetti + +``` +lacunarity = 1.5: lacunarity = 3.0: + Dettagli sparsi Dettagli densi + ╱╲ ╱╲ ╱╲╱╲╱╲ + ╱ ── ╲ ╱╲╱╲╱╲╱ + ╱ ╲ ╱╲╱╲╱╲╱╲ +``` + +**Range**: 1.0 - 4.0 +- 1.5-2.0: Dettagli distanziati +- 2.0-2.5: Bilanciato (default) +- 2.5-3.0: Dettagli fitti + +### Smoothing (Levigatura) + +```python +TERRAIN = { + 'enable_smoothing': False, # Default: disabilitato + 'smoothing_iterations': 2, + 'smoothing_factor': 0.5, + ... +} +``` + +#### Effetti + +``` +Smoothing OFF: Smoothing ON: + ╱╲╱╲ ╱──╲ + ╱╲╱╲╱╲ ╱ ╲ + ╱╲╱╲╱╲╱ ╱ ╲ +Originale Perlin Levigato +``` + +**Nota**: Lo smoothing riduce le caratteristiche uniche del Perlin noise! + +## Configurare la Camera + +### Distanza e Altezza + +```python +CAMERA = { + 'distance': 800, + 'height': 450, + ... +} +``` + +#### Visualizzazione + +``` +Distanza piccola (400): Distanza grande (1200): + Camera Camera + ● ● + ╱│ ╱│ + ╱ │ ╱ │ + ╱ │ ╱ │ + Terreno Terreno + Vista vicina Vista lontana + Dettaglio alto Panoramica +``` + +**Altezza**: Quanto "sopra" il terreno +- Bassa (200): Vista laterale +- Media (450): Vista isometrica (default) +- Alta (800): Vista dall'alto + +### Velocità Controlli + +```python +CAMERA = { + 'zoom_speed': 10.0, # Velocità zoom (UP/DOWN) + 'height_speed': 10.0, # Velocità altezza (LEFT/RIGHT) + ... +} +``` + +**Consigli**: +- 5.0: Controlli lenti, precisi +- 10.0: Bilanciato (default) +- 20.0: Controlli veloci + +### Field of View + +```python +CAMERA = { + 'fov': 45, # Gradi + ... +} +``` + +#### Effetti + +``` +FOV = 30: FOV = 70: + Zoom in Zoom out + │╲ ╲___ + │ ╲ │ ___ + │ ╲ │ ___ + Teleobiettivo Grandangolo +``` + +**Range**: 30-90 +- 30-40: Zoom, distorsione minima +- 45: Bilanciato (default) +- 60-90: Grandangolo, più distorsione + +### Clipping Planes + +```python +CAMERA = { + 'near_clip': 0.1, + 'far_clip': 5000.0, + ... +} +``` + +**near_clip**: Quanto vicino puoi avvicinarti +**far_clip**: Quanto lontano puoi vedere + +``` + Far Plane + │ + ╱╲ │ + ╱ ╲ │ + ╱ ● ╲ │ + Near Plane +``` + +**Nota**: Se oggetti scompaiono, aumenta far_clip! + +### Limiti + +```python +CAMERA = { + 'min_distance': 200, + 'max_distance': 2000, + ... +} +``` + +Previene: +- Zoom troppo vicino (camera dentro terreno) +- Zoom troppo lontano (terreno invisibile) + +## Modificare i Biomi + +### Colori + +```python +BIOME_COLORS = { + 'water': (65, 105, 225), # Blu + 'sand': (238, 214, 175), # Beige + 'grass_low': (34, 139, 34), # Verde scuro + 'grass_mid': (107, 142, 35), # Verde oliva + 'grass_high': (85, 107, 47), # Verde scuro oliva + 'rock': (139, 137, 137), # Grigio + 'snow': (255, 250, 250) # Bianco neve +} +``` + +#### Sperimentare + +```python +# Terreno desertico +BIOME_COLORS = { + 'water': (139, 69, 19), # Marrone (oasi fangose) + 'sand': (244, 164, 96), # Arancione sabbia + 'grass_low': (218, 165, 32), # Oro (dune) + 'grass_mid': (210, 180, 140), # Tan (roccia) + 'grass_high': (160, 82, 45), # Sienna (roccia) + 'rock': (139, 90, 43), # Marrone scuro + 'snow': (255, 228, 181) # Crema (sale?) +} +``` + +```python +# Terreno alieno +BIOME_COLORS = { + 'water': (255, 0, 255), # Magenta + 'sand': (0, 255, 255), # Ciano + 'grass_low': (255, 255, 0), # Giallo + 'grass_mid': (0, 255, 0), # Verde brillante + 'grass_high': (255, 128, 0), # Arancione + 'rock': (128, 0, 255), # Viola + 'snow': (255, 0, 0) # Rosso +} +``` + +### Soglie Biomi + +```python +BIOME_THRESHOLDS = { + 'water': -10, + 'sand': 0, + 'grass_low': 10, + 'grass_mid': 25, + 'grass_high': 40, + 'rock': 55, + 'snow': 70 +} +``` + +#### Logica + +``` +Altezza < -10 → Acqua +-10 ≤ Altezza < 0 → Sabbia +0 ≤ Altezza < 10 → Erba bassa +10 ≤ Altezza < 25 → Erba media +25 ≤ Altezza < 40 → Erba alta +40 ≤ Altezza < 55 → Roccia +55 ≤ Altezza < 70 → Neve +70 ≤ Altezza → Neve +``` + +#### Modificare Distribuzione + +```python +# Più acqua, meno montagne +BIOME_THRESHOLDS = { + 'water': 0, # Era -10 + 'sand': 15, # Era 0 + 'grass_low': 30, # Era 10 + 'grass_mid': 45, # Era 25 + 'grass_high': 60, # Era 40 + 'rock': 80, # Era 55 + 'snow': 100 # Era 70 +} +``` + +``` +Prima (default): Modificato: +███████ Snow ████ Snow +████████ Rock █████ Rock +█████████ Grass High ███████ Grass High +██████████ Grass Mid ████████ Grass Mid +████████████ Grass Low █████████ Grass Low +████ Sand ███████ Sand +██ Water ████████ Water +``` + +### Aggiungere Nuovi Biomi + +```python +# 1. Aggiungi colore +BIOME_COLORS = { + ... + 'lava': (255, 69, 0), # Rosso-arancione + 'ice': (173, 216, 230) # Azzurro ghiaccio +} + +# 2. Aggiungi soglia +BIOME_THRESHOLDS = { + ... + 'lava': -20, # Sotto acqua = lava + 'ice': 85 # Sopra neve = ghiaccio +} + +# 3. Modifica get_color_for_height in terrain_renderer.py +def get_color_for_height(self, height): + if height < BIOME_THRESHOLDS['lava']: + return BIOME_COLORS['lava'] + elif height < BIOME_THRESHOLDS['water']: + return BIOME_COLORS['water'] + # ... altri biomi ... + elif height < BIOME_THRESHOLDS['ice']: + return BIOME_COLORS['snow'] + else: + return BIOME_COLORS['ice'] +``` + +## Rendering + +### Bordi Griglia + +```python +RENDERING = { + 'line_width': 5.0, # Spessore bordi tile + ... +} +``` + +#### Effetti + +``` +line_width = 1.0: line_width = 8.0: + ╱─╲╱─╲ ╱══╲╱══╲ + ╱─╲╱─╲╱ ╱══╲╱══╲╱ +Bordi sottili Bordi spessi +``` + +**Range**: 1.0 - 10.0 + +### Colore Griglia + +```python +RENDERING = { + 'line_color': (0, 0, 0), # Nero (R, G, B) + ... +} +``` + +**Esempi**: +- `(0, 0, 0)`: Nero (default) +- `(255, 255, 255)`: Bianco +- `(128, 128, 128)`: Grigio +- `(255, 0, 0)`: Rosso + +### Illuminazione + +```python +RENDERING = { + 'light_position': (1.0, 1.0, 1.0, 0.0), + 'light_ambient': (0.3, 0.3, 0.3, 1.0), + 'light_diffuse': (0.8, 0.8, 0.8, 1.0), + ... +} +``` + +#### light_position + +``` +(1, 1, 1, 0) → Luce direzionale da alto-destra +(x, y, z, w) + w=0 → direzionale + w=1 → punto luce +``` + +#### light_ambient + +Luce "di base" (ombre non completamente nere): + +``` +ambient = 0.1: ambient = 0.5: + Ombre scure Ombre chiare + ███ ▓▓▓ + ██ ▓▓ +``` + +#### light_diffuse + +Luce "diretta" (quanto sono illuminate le superfici): + +``` +diffuse = 0.5: diffuse = 1.0: + Illuminazione Illuminazione + moderata piena + ▓▓▓ ███ + ▓▓ ██ +``` + +## Preset Configurazioni + +### Montagne Drammatiche + +```python +TERRAIN = { + 'grid_size': 25, + 'perlin_scale': 10.0, + 'height_multiplier': 150.0, + 'perlin_octaves': 5, + 'perlin_persistence': 0.7, + 'perlin_lacunarity': 2.8, +} + +CAMERA = { + 'distance': 1000, + 'height': 600, +} +``` + +### Colline Dolci + +```python +TERRAIN = { + 'grid_size': 20, + 'perlin_scale': 5.0, + 'height_multiplier': 40.0, + 'perlin_octaves': 3, + 'perlin_persistence': 0.4, + 'perlin_lacunarity': 2.0, + 'enable_smoothing': True, + 'smoothing_iterations': 3, +} + +CAMERA = { + 'distance': 700, + 'height': 400, +} +``` + +### Pianure con Dettaglio + +```python +TERRAIN = { + 'grid_size': 30, + 'perlin_scale': 12.0, + 'height_multiplier': 30.0, + 'perlin_octaves': 6, + 'perlin_persistence': 0.6, + 'perlin_lacunarity': 2.5, +} + +CAMERA = { + 'distance': 1200, + 'height': 500, +} +``` + +### Isola Tropicale + +```python +TERRAIN = { + 'grid_size': 20, + 'perlin_scale': 6.0, + 'height_multiplier': 60.0, +} + +BIOME_THRESHOLDS = { + 'water': 5, # Più acqua + 'sand': 12, # Spiagge ampie + 'grass_low': 20, + 'grass_mid': 35, + 'grass_high': 50, + 'rock': 70, + 'snow': 90, +} + +BIOME_COLORS = { + 'water': (0, 119, 190), # Blu oceano + 'sand': (255, 233, 127), # Sabbia dorata + 'grass_low': (0, 155, 119), # Verde tropicale + 'grass_mid': (34, 139, 34), + 'grass_high': (85, 107, 47), + 'rock': (139, 137, 137), + 'snow': (255, 250, 250), +} +``` + +## Funzionalità Avanzate + +### Esportare Heightmap + +Aggiungi a `terrain_renderer.py`: + +```python +def export_heightmap(self, filename): + """Salva heightmap come immagine PNG""" + from PIL import Image + import numpy as np + + # Normalizza altezze 0-255 + heights = self.terrain_generator.heightmap + normalized = ((heights - heights.min()) / + (heights.max() - heights.min()) * 255) + + # Crea immagine + img = Image.fromarray(normalized.astype('uint8')) + img.save(filename) + print(f"Heightmap salvata: {filename}") +``` + +Uso: + +```python +# In app.py, handle_events +if event.type == pygame.KEYDOWN: + if event.key == pygame.K_s: # Premi S per salvare + self.terrain_renderer.export_heightmap('heightmap.png') +``` + +### Salvare/Caricare Seed + +Aggiungi a `terrain_generator.py`: + +```python +def generate_with_seed(self, seed): + """Genera terreno con seed specifico""" + import random + random.seed(seed) + self.seed = seed + self.generate() + +def get_seed(self): + """Ottieni seed corrente""" + return self.seed +``` + +Uso: + +```python +# Genera con seed +generator.generate_with_seed(12345) + +# Salva seed +current_seed = generator.get_seed() +print(f"Seed corrente: {current_seed}") + +# Ricrea stesso terreno +generator.generate_with_seed(current_seed) +``` + +### Animazione (Terreno che Cambia) + +Aggiungi a `app.py`: + +```python +def __init__(self, ...): + ... + self.animation_enabled = False + self.animation_offset = 0.0 + +def update(self): + if self.animation_enabled: + self.animation_offset += 0.01 + # Rigenera con offset temporale + for i in range(grid_size): + for j in range(grid_size): + x = i / scale + y = j / scale + height = noise.pnoise3( + x, y, self.animation_offset, # 3D noise! + octaves=octaves, ... + ) +``` + +### Minimap + +Aggiungi nuovo file `src/rendering/minimap.py`: + +```python +class Minimap: + def __init__(self, terrain_generator, size=200): + self.terrain = terrain_generator + self.size = size + + def render(self, screen_width, screen_height): + """Disegna minimap in angolo""" + glMatrixMode(GL_PROJECTION) + glPushMatrix() + glLoadIdentity() + glOrtho(0, screen_width, 0, screen_height, -1, 1) + + glMatrixMode(GL_MODELVIEW) + glPushMatrix() + glLoadIdentity() + + # Disegna minimap + x = screen_width - self.size - 10 + y = 10 + + # ... rendering heightmap 2D ... + + glPopMatrix() + glMatrixMode(GL_PROJECTION) + glPopMatrix() +``` + +## Troubleshooting + +### Terreno Troppo Piatto + +✅ **Soluzione**: +- Aumenta `height_multiplier` (da 80 a 120) +- Riduci `perlin_scale` (da 8.0 a 5.0) +- Aumenta `perlin_persistence` (da 0.6 a 0.7) + +### Terreno Troppo Caotico + +✅ **Soluzione**: +- Riduci `height_multiplier` (da 80 a 50) +- Aumenta `perlin_scale` (da 8.0 a 12.0) +- Riduci `perlin_octaves` (da 4 a 2) +- Abilita `enable_smoothing` + +### Camera Troppo Lontana + +✅ **Soluzione**: +- Riduci `distance` (da 800 a 500) +- Riduci `height` (da 450 a 300) + +### Bordi Invisibili + +✅ **Soluzione**: +- Aumenta `line_width` (da 5.0 a 8.0) +- Cambia `line_color` per contrasto + +### Performance Basse + +✅ **Soluzione**: +- Riduci `grid_size` (da 20 a 15) +- Riduci `perlin_octaves` (da 4 a 3) +- Disabilita `enable_smoothing` +- Riduci `far_clip` (da 5000 a 3000) + +### Colori Strani + +✅ **Soluzione**: +- Verifica `BIOME_COLORS` (RGB 0-255) +- Verifica `BIOME_THRESHOLDS` (ordine crescente) +- Verifica `height_multiplier` (altezze compatibili con soglie) + +## Checklist Personalizzazione + +Prima di modificare, salva il file originale! + +```bash +cp config/settings.py config/settings.py.backup +``` + +### Cambio Dimensioni + +- [ ] Modifica `grid_size` +- [ ] Adatta `perlin_scale` (≈ grid_size / 2.5) +- [ ] Testa performance +- [ ] Adatta `camera.distance` se necessario + +### Cambio Stile Terreno + +- [ ] Modifica `height_multiplier` +- [ ] Modifica `perlin_octaves` +- [ ] Modifica `perlin_persistence` +- [ ] Testa con R (rigenera) +- [ ] Verifica distribuzione biomi + +### Cambio Colori + +- [ ] Modifica `BIOME_COLORS` +- [ ] Verifica valori RGB (0-255) +- [ ] Testa con diversi terreni +- [ ] Screenshot per confronto + +### Cambio Soglie + +- [ ] Modifica `BIOME_THRESHOLDS` +- [ ] Verifica ordine crescente +- [ ] Testa rigenera più volte +- [ ] Verifica copertura biomi + +## Riepilogo + +### File Principale + +**config/settings.py** contiene tutto! + +### Sezioni Importanti + +1. **TERRAIN**: Dimensioni, Perlin noise +2. **CAMERA**: Posizione, controlli, FOV +3. **RENDERING**: Bordi, colori, illuminazione +4. **BIOME_COLORS**: Colori biomi (RGB) +5. **BIOME_THRESHOLDS**: Soglie altezze + +### Workflow Consigliato + +1. Modifica `config/settings.py` +2. Salva file +3. Esegui `python main.py` +4. Premi `R` per rigenerare +5. Usa frecce per testare camera +6. Ripeti fino a soddisfazione! + +### Backup + +Sempre fare backup prima di grandi modifiche: + +```bash +cp config/settings.py config/settings_backup_YYYYMMDD.py +``` + +--- + +**Congratulazioni!** Ora hai tutte le conoscenze per personalizzare completamente il tuo generatore di terreni isometrici! + +[← Capitolo Precedente](07-camera.md) | [Torna all'indice](README.md) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..11b0049 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,169 @@ +# Generatore di Terreni Isometrici - Guida Completa + +Benvenuto nella documentazione completa del progetto **Generatore di Terreni Isometrici**! + +Questa guida è stata creata per spiegare in modo dettagliato come funziona il progetto, quali tecnologie utilizza e quali algoritmi implementa. È pensata per chi non ha familiarità con OpenGL o con la grafica 3D. + +## 📚 Indice dei Capitoli + +### Parte 1: Introduzione e Concetti Base + +1. **[Introduzione al Progetto](01-introduzione.md)** + - Cos'è questo progetto + - Cosa fa l'applicazione + - A cosa serve + - Risultati visivi + +2. **[Concetti Base di Grafica 3D](02-concetti-base.md)** + - Coordinate 3D (X, Y, Z) + - Vista isometrica vs prospettiva + - Mesh e poligoni + - Colori e shading + - Come funziona il rendering + +### Parte 2: Tecnologie e Algoritmi + +3. **[OpenGL: La Tecnologia di Rendering](03-opengl.md)** + - Cos'è OpenGL + - Come funziona OpenGL + - Pipeline di rendering + - Vertici, facce e geometria + - Illuminazione e materiali + +4. **[Perlin Noise: Generazione Procedurale](04-perlin-noise.md)** + - Cos'è il rumore di Perlin + - Perché usarlo per i terreni + - Come funziona l'algoritmo + - Ottave, persistenza e lacunarità + - Esempi pratici + +### Parte 3: Implementazione + +5. **[Architettura del Codice](05-implementazione.md)** + - Struttura del progetto + - Organizzazione dei moduli + - Flusso dell'applicazione + - Componenti principali + - Interazione tra le parti + +6. **[Il Sistema di Rendering](06-rendering.md)** + - Come vengono disegnate le tile + - Creazione della mesh del terreno + - Mappatura dei colori (biomi) + - Shading e profondità + - Wireframe e bordi + +7. **[Il Sistema Camera](07-camera.md)** + - Posizionamento della camera + - Proiezione prospettica + - Vista isometrica + - Controlli utente + - Trasformazioni matriciali + +### Parte 4: Utilizzo e Personalizzazione + +8. **[Personalizzazione e Configurazione](08-personalizzazione.md)** + - Modificare le dimensioni della griglia + - Cambiare i parametri del terreno + - Aggiungere nuovi biomi + - Modificare i colori + - Creare preset personalizzati + - Estendere il progetto + +## 🎯 Come Usare Questa Guida + +### Per Principianti +Se non hai mai lavorato con grafica 3D o programmazione grafica: +1. Inizia dal **Capitolo 1** (Introduzione) +2. Leggi attentamente i **Capitoli 2 e 3** (Concetti Base e OpenGL) +3. Procedi con il **Capitolo 4** (Perlin Noise) +4. Esplora l'implementazione nei **Capitoli 5-7** +5. Sperimenta con il **Capitolo 8** (Personalizzazione) + +### Per Chi Ha Esperienza +Se conosci già i concetti base di grafica 3D: +1. Dai un'occhiata veloce ai **Capitoli 1-3** +2. Concentrati sul **Capitolo 4** (Perlin Noise) +3. Studia l'architettura nei **Capitoli 5-7** +4. Usa il **Capitolo 8** come riferimento + +### Per Chi Vuole Solo Personalizzare +Se vuoi solo modificare il comportamento del progetto: +1. Leggi il **Capitolo 1** per capire cosa fa +2. Vai direttamente al **Capitolo 8** (Personalizzazione) +3. Consulta gli altri capitoli solo se necessario + +## 🔑 Concetti Chiave Trattati + +Questa guida copre i seguenti argomenti: + +- **Grafica 3D**: Come funziona la rappresentazione tridimensionale +- **Rendering**: Il processo di trasformare dati 3D in immagini 2D +- **OpenGL**: La libreria standard per la grafica 3D +- **Isometria**: Un tipo particolare di proiezione 3D +- **Perlin Noise**: Algoritmo per generare terreni naturali +- **Heightmap**: Mappe di altezza per rappresentare terreni +- **Biomi**: Zone con caratteristiche visive diverse +- **Camera virtuale**: Come controllare il punto di vista +- **Mesh**: Strutture dati per geometria 3D +- **Pipeline di rendering**: Il flusso di elaborazione grafica + +## 📖 Glossario Rapido + +Termini che incontrerai spesso: + +- **Tile**: Una singola tessera quadrata della griglia +- **Heightmap**: Mappa che indica l'altezza in ogni punto +- **Mesh**: Insieme di vertici e facce che formano la geometria +- **Vertice**: Un punto nello spazio 3D +- **Faccia/Quad**: Un poligono (di solito un quadrilatero) +- **Rendering**: Il processo di disegnare la scena +- **Wireframe**: Visualizzazione con solo i bordi +- **Bioma**: Zona del terreno con caratteristiche specifiche +- **Perlin Noise**: Algoritmo per generare valori pseudo-casuali naturali +- **Ottava**: Una iterazione nel calcolo del Perlin Noise +- **Camera**: Il punto di vista virtuale da cui osserviamo la scena + +## 🚀 Prerequisiti + +Per comprendere al meglio questa guida: + +- **Conoscenze base di programmazione** (variabili, funzioni, classi) +- **Matematica di base** (coordinate, moltiplicazione) +- **Nessuna esperienza con grafica 3D richiesta** (tutto viene spiegato) + +## 💡 Obiettivi della Guida + +Dopo aver letto questa documentazione, sarai in grado di: + +1. ✅ Capire come funziona il rendering 3D +2. ✅ Comprendere cos'è e come funziona OpenGL +3. ✅ Capire l'algoritmo Perlin Noise +4. ✅ Modificare e personalizzare il progetto +5. ✅ Estendere il progetto con nuove funzionalità +6. ✅ Applicare questi concetti ad altri progetti + +## 🎨 Esempi Pratici + +Ogni capitolo include: +- Spiegazioni dettagliate con analogie +- Diagrammi e visualizzazioni +- Esempi di codice commentati +- Esercizi e modifiche suggerite + +## 📞 Struttura dei Capitoli + +Ogni capitolo è organizzato così: +- **Introduzione**: Panoramica dell'argomento +- **Concetti**: Spiegazione teorica +- **Implementazione**: Come è realizzato nel codice +- **Esempi**: Casi pratici e visualizzazioni +- **Riepilogo**: Punti chiave da ricordare + +## 🎯 Inizio della Lettura + +Pronto per iniziare? Vai al **[Capitolo 1: Introduzione al Progetto](01-introduzione.md)** → + +--- + +**Nota**: Questa documentazione è stata creata per essere accessibile anche a chi non ha esperienza con OpenGL o grafica 3D. Se trovi sezioni poco chiare o hai suggerimenti, sentiti libero di migliorare la documentazione! diff --git a/main.py b/main.py new file mode 100644 index 0000000..ac1e6ae --- /dev/null +++ b/main.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +""" +Isometric Terrain Generator +Main entry point for the application +""" +import sys +import os + +# Add the project root to the Python path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from config import settings +from src.app import IsometricTerrainApp + + +def main(): + """Main entry point""" + try: + app = IsometricTerrainApp(settings) + app.run() + except KeyboardInterrupt: + print("\nExiting...") + sys.exit(0) + except Exception as e: + print(f"Error: {e}") + import traceback + traceback.print_exc() + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e5c5cc3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +pygame>=2.5.0 +PyOpenGL>=3.1.7 +PyOpenGL-accelerate>=3.1.7 +numpy>=1.24.0 diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..5793f35 --- /dev/null +++ b/src/__init__.py @@ -0,0 +1 @@ +"""Source package""" diff --git a/src/app.py b/src/app.py new file mode 100644 index 0000000..3ce1191 --- /dev/null +++ b/src/app.py @@ -0,0 +1,122 @@ +""" +Main application class that ties everything together +""" +import pygame +from pygame.locals import * +from OpenGL.GL import * +from OpenGL.GLU import * + +from src.camera.camera import Camera +from src.terrain.generator import TerrainGenerator +from src.rendering.terrain_renderer import TerrainRenderer + + +class IsometricTerrainApp: + """Main application class for the isometric terrain generator""" + + def __init__(self, config): + """ + Initialize the application + + Args: + config: Module containing all configuration settings + """ + self.config = config + self.running = False + + # Initialize Pygame and OpenGL + pygame.init() + self.display = (config.WINDOW_WIDTH, config.WINDOW_HEIGHT) + pygame.display.set_mode(self.display, DOUBLEBUF | OPENGL) + pygame.display.set_caption(config.WINDOW_TITLE) + + # Initialize components + self.camera = Camera(config.CAMERA) + self.terrain_generator = TerrainGenerator(config.TERRAIN) + self.renderer = TerrainRenderer( + config.TERRAIN, + config.RENDERING, + config.BIOME_COLORS, + config.BIOME_THRESHOLDS + ) + + # Setup OpenGL + self._setup_opengl() + + # Generate terrain + heightmap = self.terrain_generator.generate() + self.renderer.set_heightmap(heightmap) + + self.clock = pygame.time.Clock() + + def _setup_opengl(self): + """Setup OpenGL rendering settings""" + glEnable(GL_DEPTH_TEST) + glEnable(GL_BLEND) + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + + # Setup lighting + glEnable(GL_LIGHTING) + glEnable(GL_LIGHT0) + glEnable(GL_COLOR_MATERIAL) + glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE) + + glLightfv(GL_LIGHT0, GL_POSITION, self.config.RENDERING['light_position']) + glLightfv(GL_LIGHT0, GL_AMBIENT, self.config.RENDERING['light_ambient']) + glLightfv(GL_LIGHT0, GL_DIFFUSE, self.config.RENDERING['light_diffuse']) + + def handle_events(self): + """Handle pygame events""" + for event in pygame.event.get(): + if event.type == pygame.QUIT: + self.running = False + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + self.running = False + elif event.key == pygame.K_r: + # Regenerate terrain + self._regenerate_terrain() + + def _regenerate_terrain(self): + """Regenerate the terrain with new random seed""" + print("Regenerating terrain...") + heightmap = self.terrain_generator.generate() + self.renderer.set_heightmap(heightmap) + + def update(self): + """Update application state""" + keys = pygame.key.get_pressed() + self.camera.handle_input(keys) + + def render(self): + """Render the scene""" + # Clear screen + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glClearColor(*self.config.RENDERING['background_color']) + + # Setup camera + aspect_ratio = self.display[0] / self.display[1] + self.camera.apply(aspect_ratio) + + # Render terrain + self.renderer.render() + + pygame.display.flip() + + def run(self): + """Main application loop""" + self.running = True + print("Isometric Terrain Generator") + print("Controls:") + print(" UP/DOWN: Zoom in/out") + print(" LEFT/RIGHT: Adjust camera height") + print(" R: Regenerate terrain") + print(" ESC: Exit") + + while self.running: + self.handle_events() + self.update() + self.render() + self.clock.tick(self.config.FPS) + + pygame.quit() diff --git a/src/camera/__init__.py b/src/camera/__init__.py new file mode 100644 index 0000000..157a1b6 --- /dev/null +++ b/src/camera/__init__.py @@ -0,0 +1 @@ +"""Camera package""" diff --git a/src/camera/camera.py b/src/camera/camera.py new file mode 100644 index 0000000..7cfe353 --- /dev/null +++ b/src/camera/camera.py @@ -0,0 +1,86 @@ +""" +Camera class for controlling the isometric view +""" +from OpenGL.GL import * +from OpenGL.GLU import * +import pygame + + +class Camera: + """Handles camera positioning and movement for the isometric view""" + + def __init__(self, config): + """ + Initialize camera with configuration + + Args: + config: Dictionary with camera settings (distance, height, angle, speeds, limits) + """ + self.distance = config['initial_distance'] + self.height = config['initial_height'] + self.angle = config['initial_angle'] + + self.zoom_speed = config['zoom_speed'] + self.height_speed = config['height_speed'] + self.min_distance = config['min_distance'] + self.max_distance = config['max_distance'] + + self.fov = config['fov'] + self.near_clip = config['near_clip'] + self.far_clip = config['far_clip'] + + def handle_input(self, keys): + """ + Handle keyboard input for camera movement + + Args: + keys: Pygame key state array + """ + # Zoom in/out + if keys[pygame.K_UP]: + self.distance -= self.zoom_speed + self.distance = max(self.min_distance, self.distance) + + if keys[pygame.K_DOWN]: + self.distance += self.zoom_speed + self.distance = min(self.max_distance, self.distance) + + # Adjust height + if keys[pygame.K_LEFT]: + self.height += self.height_speed + + if keys[pygame.K_RIGHT]: + self.height -= self.height_speed + + def setup_projection(self, aspect_ratio): + """ + Setup the projection matrix + + Args: + aspect_ratio: Window width / height ratio + """ + glMatrixMode(GL_PROJECTION) + glLoadIdentity() + gluPerspective(self.fov, aspect_ratio, self.near_clip, self.far_clip) + + def setup_modelview(self): + """Setup the modelview matrix for isometric view""" + glMatrixMode(GL_MODELVIEW) + glLoadIdentity() + + # Position camera for isometric view + gluLookAt( + self.distance, self.height, self.distance, # Camera position + 0, 0, 0, # Look at center + 0, 1, 0 # Up vector + ) + + def apply(self, aspect_ratio): + """ + Apply camera transformations + + Args: + aspect_ratio: Window width / height ratio + """ + self.setup_projection(aspect_ratio) + self.setup_modelview() diff --git a/src/rendering/__init__.py b/src/rendering/__init__.py new file mode 100644 index 0000000..27f9417 --- /dev/null +++ b/src/rendering/__init__.py @@ -0,0 +1 @@ +"""Rendering package""" diff --git a/src/rendering/terrain_renderer.py b/src/rendering/terrain_renderer.py new file mode 100644 index 0000000..5563381 --- /dev/null +++ b/src/rendering/terrain_renderer.py @@ -0,0 +1,177 @@ +""" +Terrain rendering with isometric view +""" +from OpenGL.GL import * + + +class TerrainRenderer: + """Handles rendering of the terrain mesh with isometric tiles""" + + def __init__(self, terrain_config, rendering_config, biome_colors, biome_thresholds): + """ + Initialize renderer with configuration + + Args: + terrain_config: Dictionary with terrain settings + rendering_config: Dictionary with rendering settings + biome_colors: Dictionary mapping biome names to RGB colors + biome_thresholds: Dictionary with height thresholds for biomes + """ + self.tile_width = terrain_config['tile_width'] + self.tile_depth = terrain_config['tile_depth'] + self.grid_size = terrain_config['grid_size'] + self.tile_size = terrain_config['tile_size'] + + self.grid_line_width = rendering_config['grid_line_width'] + self.grid_line_color = rendering_config['grid_line_color'] + self.side_face_shading = rendering_config['side_face_shading'] + self.back_face_shading = rendering_config['back_face_shading'] + + self.colors = biome_colors + self.thresholds = biome_thresholds + + self.heightmap = None + + def set_heightmap(self, heightmap): + """ + Set the heightmap to render + + Args: + heightmap: 2D numpy array of height values + """ + self.heightmap = heightmap + + def get_color_for_height(self, height): + """ + Get color based on height (biome mapping) + + Args: + height: Height value + + Returns: + tuple: RGB color values (0-1 range) + """ + if height < self.thresholds['water']: + return self.colors['water'] + elif height < self.thresholds['sand']: + return self.colors['sand'] + elif height < self.thresholds['grass_low']: + return self.colors['grass_low'] + elif height < self.thresholds['grass_mid']: + return self.colors['grass_mid'] + elif height < self.thresholds['grass_high']: + return self.colors['grass_high'] + elif height < self.thresholds['rock']: + return self.colors['rock'] + else: + return self.colors['snow'] + + def draw_tile(self, x, z, height, next_x_height, next_z_height, next_xz_height): + """ + Draw a single isometric tile with proper shading + + Args: + x: X position + z: Z position + height: Height at current position + next_x_height: Height at x+1 position + next_z_height: Height at z+1 position + next_xz_height: Height at x+1, z+1 position + """ + # Define the four corners of the tile + corners = [ + (x, height, z), + (x + self.tile_width, next_x_height, z), + (x + self.tile_width, next_xz_height, z + self.tile_depth), + (x, next_z_height, z + self.tile_depth) + ] + + # Get base color for this height + avg_height = (height + next_x_height + next_z_height + next_xz_height) / 4.0 + base_color = self.get_color_for_height(avg_height) + + # Draw top face + glBegin(GL_QUADS) + glColor3f(*base_color) + for corner in corners: + glVertex3f(*corner) + glEnd() + + # Draw side faces for elevation changes + # Right face + if next_x_height > 0.1 or height > 0.1: + glBegin(GL_QUADS) + darker = tuple(c * self.side_face_shading for c in base_color) + glColor3f(*darker) + glVertex3f(x + self.tile_width, next_x_height, z) + glVertex3f(x + self.tile_width, 0, z) + glVertex3f(x + self.tile_width, 0, z + self.tile_depth) + glVertex3f(x + self.tile_width, next_xz_height, z + self.tile_depth) + glEnd() + + # Back face + if next_z_height > 0.1 or height > 0.1: + glBegin(GL_QUADS) + darker = tuple(c * self.back_face_shading for c in base_color) + glColor3f(*darker) + glVertex3f(x, next_z_height, z + self.tile_depth) + glVertex3f(x, 0, z + self.tile_depth) + glVertex3f(x + self.tile_width, 0, z + self.tile_depth) + glVertex3f(x + self.tile_width, next_xz_height, z + self.tile_depth) + glEnd() + + def render(self): + """Render the entire terrain""" + if self.heightmap is None: + return + + total_size = self.grid_size * self.tile_size + + # Render tiles + for i in range(total_size - 1): + for j in range(total_size - 1): + x = (i - total_size / 2) * self.tile_width + z = (j - total_size / 2) * self.tile_depth + + height = self.heightmap[i][j] + next_x = self.heightmap[i + 1][j] + next_z = self.heightmap[i][j + 1] + next_xz = self.heightmap[i + 1][j + 1] + + self.draw_tile(x, z, height, next_x, next_z, next_xz) + + # Draw wireframe grid + self._draw_grid(total_size) + + def _draw_grid(self, total_size): + """ + Draw wireframe grid over the terrain + + Args: + total_size: Total size of the terrain grid + """ + glColor3f(*self.grid_line_color) + glLineWidth(self.grid_line_width) + glBegin(GL_LINES) + + # Draw lines along Z direction + for i in range(total_size): + for j in range(total_size - 1): + x = (i - total_size / 2) * self.tile_width + z = (j - total_size / 2) * self.tile_depth + z_next = ((j + 1) - total_size / 2) * self.tile_depth + + glVertex3f(x, self.heightmap[i][j], z) + glVertex3f(x, self.heightmap[i][j + 1], z_next) + + # Draw lines along X direction + for j in range(total_size): + for i in range(total_size - 1): + x = (i - total_size / 2) * self.tile_width + x_next = ((i + 1) - total_size / 2) * self.tile_width + z = (j - total_size / 2) * self.tile_depth + + glVertex3f(x, self.heightmap[i][j], z) + glVertex3f(x_next, self.heightmap[i + 1][j], z) + + glEnd() diff --git a/src/terrain/__init__.py b/src/terrain/__init__.py new file mode 100644 index 0000000..a74c801 --- /dev/null +++ b/src/terrain/__init__.py @@ -0,0 +1 @@ +"""Terrain generation package""" diff --git a/src/terrain/generator.py b/src/terrain/generator.py new file mode 100644 index 0000000..5395b91 --- /dev/null +++ b/src/terrain/generator.py @@ -0,0 +1,101 @@ +""" +Terrain generation using Perlin noise +""" +import numpy as np +import noise + + +class TerrainGenerator: + """Generates terrain heightmaps using Perlin noise""" + + def __init__(self, config): + """ + Initialize terrain generator with configuration + + Args: + config: Dictionary with terrain settings + """ + self.grid_size = config['grid_size'] + self.tile_size = config['tile_size'] + self.tile_width = config['tile_width'] + self.tile_depth = config['tile_depth'] + + # Noise parameters + self.scale = config['noise_scale'] + self.octaves = config['noise_octaves'] + self.persistence = config['noise_persistence'] + self.lacunarity = config['noise_lacunarity'] + self.repeat_x = config['noise_repeat_x'] + self.repeat_y = config['noise_repeat_y'] + self.base = config['noise_base'] + + # Height parameters + self.height_multiplier = config['height_multiplier'] + self.enable_smoothing = config['enable_smoothing'] + self.smoothing_kernel_size = config['smoothing_kernel_size'] + + def generate(self): + """ + Generate terrain heightmap using Perlin noise + + Returns: + numpy.ndarray: 2D array of height values + """ + total_size = self.grid_size * self.tile_size + heightmap = np.zeros((total_size, total_size)) + + # Generate height values using Perlin noise + for i in range(total_size): + for j in range(total_size): + height = noise.pnoise2( + i / self.scale, + j / self.scale, + octaves=self.octaves, + persistence=self.persistence, + lacunarity=self.lacunarity, + repeatx=self.repeat_x, + repeaty=self.repeat_y, + base=self.base + ) + + # Normalize and scale height + heightmap[i][j] = (height + 0.5) * self.height_multiplier + + # Apply smoothing if enabled + if self.enable_smoothing: + heightmap = self._smooth_terrain(heightmap) + + return heightmap + + def _smooth_terrain(self, heightmap): + """ + Apply smoothing to create gentler slopes + + Args: + heightmap: 2D array of height values + + Returns: + numpy.ndarray: Smoothed heightmap + """ + smoothed = np.copy(heightmap) + kernel_size = self.smoothing_kernel_size + + for i in range(1, len(heightmap) - 1): + for j in range(1, len(heightmap[0]) - 1): + total = 0 + count = 0 + + for di in range(-kernel_size // 2, kernel_size // 2 + 1): + for dj in range(-kernel_size // 2, kernel_size // 2 + 1): + ni, nj = i + di, j + dj + if 0 <= ni < len(heightmap) and 0 <= nj < len(heightmap[0]): + total += heightmap[ni][nj] + count += 1 + + smoothed[i][j] = total / count + + return smoothed + + def get_total_size(self): + """Get total size of the terrain grid""" + return self.grid_size * self.tile_size