Browse Source

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
master v1.0.0
Matteo Benedetto 2 months ago
commit
01799256e7
  1. 314
      .github/copilot-instructions.md
  2. 34
      .gitignore
  3. 151
      README.md
  4. 1
      config/__init__.py
  5. 89
      config/settings.py
  6. 243
      docs/01-introduzione.md
  7. 460
      docs/02-concetti-base.md
  8. 533
      docs/03-opengl.md
  9. 490
      docs/04-perlin-noise.md
  10. 499
      docs/05-implementazione.md
  11. 444
      docs/06-rendering.md
  12. 485
      docs/07-camera.md
  13. 861
      docs/08-personalizzazione.md
  14. 169
      docs/README.md
  15. 32
      main.py
  16. 4
      requirements.txt
  17. 1
      src/__init__.py
  18. 122
      src/app.py
  19. 1
      src/camera/__init__.py
  20. 86
      src/camera/camera.py
  21. 1
      src/rendering/__init__.py
  22. 177
      src/rendering/terrain_renderer.py
  23. 1
      src/terrain/__init__.py
  24. 101
      src/terrain/generator.py

314
.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 <script_name.py>
```
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

34
.gitignore vendored

@ -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

151
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.

1
config/__init__.py

@ -0,0 +1 @@
"""Configuration package"""

89
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
}

243
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)

460
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)

533
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)

490
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)

499
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)

444
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)

485
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)

861
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)

169
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!

32
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()

4
requirements.txt

@ -0,0 +1,4 @@
pygame>=2.5.0
PyOpenGL>=3.1.7
PyOpenGL-accelerate>=3.1.7
numpy>=1.24.0

1
src/__init__.py

@ -0,0 +1 @@
"""Source package"""

122
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()

1
src/camera/__init__.py

@ -0,0 +1 @@
"""Camera package"""

86
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()

1
src/rendering/__init__.py

@ -0,0 +1 @@
"""Rendering package"""

177
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()

1
src/terrain/__init__.py

@ -0,0 +1 @@
"""Terrain generation package"""

101
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
Loading…
Cancel
Save