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
This commit is contained in:
@@ -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
@@ -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
|
||||
@@ -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.
|
||||
@@ -0,0 +1 @@
|
||||
"""Configuration package"""
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
@@ -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!
|
||||
@@ -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()
|
||||
@@ -0,0 +1,4 @@
|
||||
pygame>=2.5.0
|
||||
PyOpenGL>=3.1.7
|
||||
PyOpenGL-accelerate>=3.1.7
|
||||
numpy>=1.24.0
|
||||
@@ -0,0 +1 @@
|
||||
"""Source package"""
|
||||
+122
@@ -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()
|
||||
@@ -0,0 +1 @@
|
||||
"""Camera package"""
|
||||
@@ -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()
|
||||
@@ -0,0 +1 @@
|
||||
"""Rendering package"""
|
||||
@@ -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()
|
||||
@@ -0,0 +1 @@
|
||||
"""Terrain generation package"""
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user