Browse Source
- 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 cameramaster v1.0.0
commit
01799256e7
24 changed files with 5299 additions and 0 deletions
@ -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 |
||||
@ -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,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,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,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,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) |
||||
@ -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,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,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,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,101 @@
|
||||
""" |
||||
Terrain generation using Perlin noise |
||||
""" |
||||
import numpy as np |
||||
import noise |
||||
|
||||
|
||||
class TerrainGenerator: |
||||
"""Generates terrain heightmaps using Perlin noise""" |
||||
|
||||
def __init__(self, config): |
||||
""" |
||||
Initialize terrain generator with configuration |
||||
|
||||
Args: |
||||
config: Dictionary with terrain settings |
||||
""" |
||||
self.grid_size = config['grid_size'] |
||||
self.tile_size = config['tile_size'] |
||||
self.tile_width = config['tile_width'] |
||||
self.tile_depth = config['tile_depth'] |
||||
|
||||
# Noise parameters |
||||
self.scale = config['noise_scale'] |
||||
self.octaves = config['noise_octaves'] |
||||
self.persistence = config['noise_persistence'] |
||||
self.lacunarity = config['noise_lacunarity'] |
||||
self.repeat_x = config['noise_repeat_x'] |
||||
self.repeat_y = config['noise_repeat_y'] |
||||
self.base = config['noise_base'] |
||||
|
||||
# Height parameters |
||||
self.height_multiplier = config['height_multiplier'] |
||||
self.enable_smoothing = config['enable_smoothing'] |
||||
self.smoothing_kernel_size = config['smoothing_kernel_size'] |
||||
|
||||
def generate(self): |
||||
""" |
||||
Generate terrain heightmap using Perlin noise |
||||
|
||||
Returns: |
||||
numpy.ndarray: 2D array of height values |
||||
""" |
||||
total_size = self.grid_size * self.tile_size |
||||
heightmap = np.zeros((total_size, total_size)) |
||||
|
||||
# Generate height values using Perlin noise |
||||
for i in range(total_size): |
||||
for j in range(total_size): |
||||
height = noise.pnoise2( |
||||
i / self.scale, |
||||
j / self.scale, |
||||
octaves=self.octaves, |
||||
persistence=self.persistence, |
||||
lacunarity=self.lacunarity, |
||||
repeatx=self.repeat_x, |
||||
repeaty=self.repeat_y, |
||||
base=self.base |
||||
) |
||||
|
||||
# Normalize and scale height |
||||
heightmap[i][j] = (height + 0.5) * self.height_multiplier |
||||
|
||||
# Apply smoothing if enabled |
||||
if self.enable_smoothing: |
||||
heightmap = self._smooth_terrain(heightmap) |
||||
|
||||
return heightmap |
||||
|
||||
def _smooth_terrain(self, heightmap): |
||||
""" |
||||
Apply smoothing to create gentler slopes |
||||
|
||||
Args: |
||||
heightmap: 2D array of height values |
||||
|
||||
Returns: |
||||
numpy.ndarray: Smoothed heightmap |
||||
""" |
||||
smoothed = np.copy(heightmap) |
||||
kernel_size = self.smoothing_kernel_size |
||||
|
||||
for i in range(1, len(heightmap) - 1): |
||||
for j in range(1, len(heightmap[0]) - 1): |
||||
total = 0 |
||||
count = 0 |
||||
|
||||
for di in range(-kernel_size // 2, kernel_size // 2 + 1): |
||||
for dj in range(-kernel_size // 2, kernel_size // 2 + 1): |
||||
ni, nj = i + di, j + dj |
||||
if 0 <= ni < len(heightmap) and 0 <= nj < len(heightmap[0]): |
||||
total += heightmap[ni][nj] |
||||
count += 1 |
||||
|
||||
smoothed[i][j] = total / count |
||||
|
||||
return smoothed |
||||
|
||||
def get_total_size(self): |
||||
"""Get total size of the terrain grid""" |
||||
return self.grid_size * self.tile_size |
||||
Loading…
Reference in new issue