# Capitolo 4: Perlin Noise - Generazione Procedurale del Terreno ## Cos'Γ¨ il Perlin Noise? Il **Perlin Noise** Γ¨ un algoritmo inventato da Ken Perlin nel 1983 per creare texture naturali procedurali. È usato in tantissimi videogiochi e applicazioni grafiche per generare: - πŸ”οΈ Terreni - ☁️ Nuvole - 🌊 Onde dell'oceano - πŸ”₯ Fuoco e fumo - 🎨 Texture organiche ### PerchΓ© "Noise" (Rumore)? Genera valori "casuali" ma con una caratteristica speciale: sono **coerenti** e **fluidi**. ## Random vs Perlin Noise ### Random Puro (Male!) ```python import random # Genera altezze completamente casuali for i in range(10): height = random.random() * 100 print(height) Risultato: 23, 91, 5, 88, 12, 67, 4, 95, 31, 8 Terreno: β–‚β–ˆβ–β–ˆβ–β–ˆβ–β–ˆβ–ƒβ– ← Caotico! ``` Problema: Troppo casuale, non naturale! ### Perlin Noise (Bene!) ```python import noise # Genera altezze con Perlin Noise for i in range(10): height = noise.pnoise1(i / 10.0) * 100 print(height) Risultato: 12, 18, 28, 42, 53, 58, 54, 45, 32, 20 Terreno: β–‚β–ƒβ–„β–†β–‡β–ˆβ–‡β–†β–„β–ƒ ← Fluido e naturale! ``` Vantaggio: Transizioni graduali, come in natura! ## Come Funziona (Semplificato) ### 1. Griglia di Gradienti Immagina una griglia dove ad ogni punto Γ¨ assegnata una direzione casuale: ``` β†— β†’ β†˜ ↑ β€’ ↓ Griglia 3Γ—3 con direzioni β†– ← ↙ ``` ### 2. Interpolazione Per un punto qualsiasi nella griglia, calcola il valore interpolando i 4 punti vicini: ``` v1 ●──────● v2 β”‚ β”‚ β”‚ P β€’ β”‚ ← Punto P β”‚ β”‚ v3 ●──────● v4 Valore(P) = interpola(v1, v2, v3, v4) ``` ### 3. Risultato Fluido Il risultato Γ¨ una superficie continua con variazioni graduali: ``` Vista 2D del Perlin Noise: β–“β–“β–“β–“β–“β–“β–’β–’β–’β–’β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–’β–’β–’β–’β–“β–“β–“β–“ β–“β–“β–“β–“β–’β–’β–’β–’β–’β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–’β–’β–’β–’β–’β–“β–“ β–“β–“β–’β–’β–’β–’β–’β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–’β–’β–’β–’β–’β–“ β–’β–’β–’β–’β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–’β–’β–’β–’ β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ PiΓΉ scuro = piΓΉ alto PiΓΉ chiaro = piΓΉ basso ``` ## Perlin Noise 2D per Terreni Nel nostro progetto usiamo **pnoise2** (Perlin Noise bidimensionale): ```python import noise # Per ogni punto della griglia for i in range(grid_size): for j in range(grid_size): # Calcola altezza height = noise.pnoise2(i / scale, j / scale) ``` ### Parametro: Scale (Scala) Controlla la "frequenza" delle variazioni: **Scale grande** (es. 100.0): ``` Terreno: ~~~~~~~~ Colline dolci, variazioni ampie ``` **Scale piccola** (es. 5.0): ``` Terreno: ∧∨∧∨∧∨∧∨ Colline ripide, variazioni frequenti ``` **Nel nostro progetto**: `scale = 8.0` (buon compromesso) ## Ottave: Aggiungere Dettaglio Un singolo livello di Perlin Noise Γ¨ liscio. Le **ottave** aggiungono dettagli a scale diverse: ``` Ottava 1 (base): ~~~~~~~~ Forma generale Ottava 2 (dettaglio): ∧∨∧∨∧∨∧∨ Piccole colline Ottava 3 (dettaglio+): vvvvvvvv RugositΓ  fine Combinate: ∧~~∨∧~~∨∧~~∨ Terreno realistico! ``` ### Come Funziona Ogni ottava ha: - **Frequenza doppia** (variazioni piΓΉ rapide) - **Ampiezza ridotta** (impatto minore) ```python result = 0 amplitude = 1.0 frequency = 1.0 for octave in range(num_octaves): result += noise.pnoise2(x * frequency, y * frequency) * amplitude amplitude *= persistence # Riduce ampiezza frequency *= lacunarity # Aumenta frequenza ``` ## Parametri del Perlin Noise ### 1. Scale (Scala) Quanto "zoomato" Γ¨ il noise: ```python scale = 8.0 # 8 unitΓ  = 1 ciclo completo Piccola (3.0): ∧∨∧∨∧∨∧∨∧∨ Molto variabile Media (8.0): ∧~~~∨~~~∧~~~ Bilanciato Grande (20.0): ∧~~~~~~~∨~~~~ Molto liscio ``` ### 2. Octaves (Ottave) Quanti livelli di dettaglio: ```python octaves = 4 1 ottava: ~~~~~~~~ Liscio 2 ottave: ∧~∨~∧~∨~ Qualche dettaglio 4 ottave: ∧∨~∧∨~∧∨~ Molto dettagliato 8 ottave: ∧v∨^∧v∨^∧v Iper-dettagliato ``` **Nel progetto**: 4 ottave (buon compromesso tra dettaglio e performance) ### 3. Persistence (Persistenza) Quanto ogni ottava influenza il risultato: ```python persistence = 0.6 Alta (0.8): ∧∨∧∨∧∨∧∨∧∨ Molto "ruggine" Media (0.6): ∧~∨~∧~∨~∧~ Bilanciato Bassa (0.3): ∧~~~∨~~~∧~~ Molto liscio ``` Formula: `ampiezza_ottava = amplitude * (persistence ^ numero_ottava)` ### 4. Lacunarity (LacunaritΓ ) Quanto aumenta la frequenza per ogni ottava: ```python lacunarity = 2.5 Bassa (1.5): Ottave simili Media (2.5): Buona differenza Alta (4.0): Grande differenza tra ottave ``` Formula: `frequenza_ottava = frequency * (lacunarity ^ numero_ottava)` ### 5. Base (Seed) Punto di partenza per la generazione casuale: ```python base = 42 # Seed fisso = stesso terreno ogni volta base = 0: Terreno A base = 42: Terreno B (diverso da A) base = 100: Terreno C (diverso da A e B) ``` Cambiando il base, ottieni terreni completamente diversi! ## Codice nel Progetto ### Generazione Heightmap ```python def generate(self): total_size = self.grid_size * self.tile_size # 20 heightmap = np.zeros((total_size, total_size)) # Array 20Γ—20 # Parametri Perlin Noise scale = 8.0 octaves = 4 persistence = 0.6 lacunarity = 2.5 repeat_x = 1024 repeat_y = 1024 base = 42 # Per ogni punto della griglia for i in range(total_size): for j in range(total_size): # Calcola valore Perlin Noise height = noise.pnoise2( i / scale, # Coordinate X normalizzata j / scale, # Coordinate Y normalizzata octaves=octaves, persistence=persistence, lacunarity=lacunarity, repeatx=repeat_x, # Ripeti dopo 1024 unitΓ  repeaty=repeat_y, base=base # Seed casuale ) # Normalizza (-1,+1) β†’ (0,1) e scala heightmap[i][j] = (height + 0.5) * 80.0 return heightmap ``` ### Spiegazione Passo per Passo **1. Crea array vuoto** ```python heightmap = np.zeros((20, 20)) # [[0, 0, 0, ...], # [0, 0, 0, ...], # ...] ``` **2. Per ogni punto (i, j)** ``` Punto (0,0) β†’ noise.pnoise2(0/8, 0/8) = -0.2 Punto (5,3) β†’ noise.pnoise2(5/8, 3/8) = 0.4 Punto (19,19)β†’ noise.pnoise2(19/8, 19/8) = -0.1 ``` **3. Normalizza e scala** ```python # pnoise2 restituisce valori tra -1 e +1 # Aggiungi 0.5 per portare a (0, 1) # Moltiplica per 80 per range finale (0, 80) value = -0.2 normalized = (-0.2 + 0.5) * 80.0 = 24.0 # Collina media value = 0.4 normalized = (0.4 + 0.5) * 80.0 = 72.0 # Montagna alta value = -0.1 normalized = (-0.1 + 0.5) * 80.0 = 32.0 # Collina ``` **4. Risultato finale** ```python heightmap = [ [24, 28, 35, 42, ...], [22, 26, 32, 40, ...], [18, 23, 30, 38, ...], ... ] ``` ## Smoothing (Levigatura) Il progetto include una funzione opzionale per rendere il terreno piΓΉ liscio: ```python def _smooth_terrain(self, heightmap): smoothed = np.copy(heightmap) kernel_size = 3 # Finestra 3Γ—3 for i in range(1, len(heightmap) - 1): for j in range(1, len(heightmap[0]) - 1): # Media dei vicini neighbors = [ heightmap[i-1][j-1], heightmap[i-1][j], heightmap[i-1][j+1], heightmap[i][j-1], heightmap[i][j], heightmap[i][j+1], heightmap[i+1][j-1], heightmap[i+1][j], heightmap[i+1][j+1] ] smoothed[i][j] = sum(neighbors) / len(neighbors) return smoothed ``` ### Effetto dello Smoothing ``` Prima: Dopo smoothing: ▁▃▇▂▁▅▇▂▁▃ ▁▃▅▄▃▅▆▄▃▄ Spigoloso Arrotondato ``` **Nel progetto**: Disabilitato di default per terreno piΓΉ drammatico ## Vantaggi del Perlin Noise ### βœ… Naturale I terreni sembrano organici e realistici ### βœ… Veloce Calcolo efficiente anche per griglie grandi ### βœ… Deterministico Stesso seed = stesso terreno (utile per multiplayer!) ### βœ… Scalabile Funziona per mappe piccole (20Γ—20) e enormi (1000Γ—1000) ### βœ… Configurabile Tantissimi parametri per personalizzare l'aspetto ## Varianti e Alternative ### Simplex Noise Versione migliorata del Perlin Noise (piΓΉ veloce, meno artefatti) ### Worley Noise Per pattern cellulari (tessere, pelle di giraffa) ### Fractal Brownian Motion (FBM) Perlin Noise con ottave (quello che usiamo!) ### Diamond-Square Algoritmo alternativo per terreni ## Esempi di Configurazioni ### Colline Dolci ```python scale = 15.0 octaves = 3 persistence = 0.4 lacunarity = 2.0 height_multiplier = 40.0 ``` ### Montagne Drammatiche ```python scale = 5.0 octaves = 6 persistence = 0.7 lacunarity = 3.0 height_multiplier = 120.0 ``` ### Pianura con Piccole Ondulazioni ```python scale = 25.0 octaves = 2 persistence = 0.3 lacunarity = 1.8 height_multiplier = 20.0 ``` ### Terreno Alieno/Caotico ```python scale = 3.0 octaves = 8 persistence = 0.8 lacunarity = 4.0 height_multiplier = 100.0 ``` ## Applicazioni Beyond Terreni Il Perlin Noise non si usa solo per terreni: ### Texture Procedurali ```python # Generare pattern di legno, marmo, ecc. for x in range(texture_width): for y in range(texture_height): value = noise.pnoise2(x/50, y/50) color = wood_color(value) texture[x,y] = color ``` ### Animazioni ```python # Tempo come terza dimensione time = frame_count / 60.0 for x in range(width): for y in range(height): value = noise.pnoise3(x/scale, y/scale, time) # Nuvole che si muovono! ``` ### Gameplay ```python # Spawn nemici in posizioni "naturali" for i in range(num_enemies): x = i * 10 y = noise.pnoise1(x/20) * map_height spawn_enemy(x, y) ``` ## Riepilogo ### Concetti Chiave βœ… **Perlin Noise** genera valori pseudo-casuali coerenti βœ… **Scale** controlla la dimensione delle variazioni βœ… **Octaves** aggiungono livelli di dettaglio βœ… **Persistence** controlla l'influenza di ogni ottava βœ… **Lacunarity** controlla la differenza di frequenza tra ottave βœ… **Seed/Base** determina il terreno generato ### Formula Completa ``` for ogni punto (x, y): height = 0 amplitude = 1 frequency = 1 for ogni ottava: height += pnoise2(x * frequency, y * frequency) * amplitude amplitude *= persistence frequency *= lacunarity height = (height + 0.5) * height_multiplier ``` ### Nel Nostro Progetto - **400 punti** (20Γ—20 griglia) - **4 ottave** per buon dettaglio - **Scale 8.0** per variazioni bilanciate - **Range 0-80** per altezze finali - **Seed 42** per terreno consistente --- **Prossimo Capitolo**: [Architettura del Codice β†’](05-implementazione.md) [← Capitolo Precedente](03-opengl.md) | [Torna all'indice](README.md)