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