Add procedural pixelated textures for tiles
- Implement 16x16 procedural textures using Perlin noise (RCT-style) - Each tile has unique texture based on grid position and biome - GL_NEAREST filtering for retro pixelated look - Random seeds for both heightmap and textures on regeneration (R key) - Disable lighting on top faces to show pure texture colors - Uniform brown color for side faces with shading - Update documentation (README, docs/) with texture features - Update copilot instructions with communication rules
This commit is contained in:
@@ -1,5 +1,21 @@
|
||||
# Copilot Instructions for GLTerrain Project
|
||||
|
||||
## CRITICAL COMMUNICATION RULES
|
||||
|
||||
**NEVER claim success without proof:**
|
||||
1. Don't say "FATTO!", "PERFETTO!", "Done!" unless you have verified the code works
|
||||
2. Don't start responses with exclamations like "PERFETTO!", "Ottimo!", "Fantastico!", "Eccellente!" - they feel disingenuous
|
||||
3. Be direct and honest - just explain what you did clearly
|
||||
4. Let the user verify results before celebrating
|
||||
|
||||
**ALWAYS:**
|
||||
- Test before claiming success
|
||||
- Be honest about uncertainty
|
||||
- Search web/documentation if unsure
|
||||
- Wait for user confirmation
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -30,9 +30,11 @@ shader/
|
||||
## Features
|
||||
|
||||
- **20×20 grid** of 30px×30px isometric tiles
|
||||
- **Procedural terrain generation** using Perlin noise
|
||||
- **Procedural terrain generation** using Perlin noise with random seeds
|
||||
- **Procedural texture decorations** - Each tile has unique pixelated patterns (RCT-style)
|
||||
- **Multiple biomes** based on elevation (water, sand, grass, rock, snow)
|
||||
- **Real-time camera controls**
|
||||
- **Dynamic regeneration** - Press R for new terrain and textures
|
||||
- **Configurable settings** via `config/settings.py`
|
||||
|
||||
## Installation
|
||||
@@ -88,6 +90,11 @@ All settings can be modified in `config/settings.py`:
|
||||
- `grid_line_width`: Thickness of tile borders
|
||||
- `light_position`: Light source position
|
||||
- `light_ambient/light_diffuse`: Lighting properties
|
||||
- `side_face_color`: Uniform color for lateral tile faces
|
||||
- `texture_enabled`: Enable/disable procedural textures (default: true)
|
||||
- `texture_detail_scale`: Controls texture pattern size
|
||||
- `texture_variation`: Amount of color variation (0.0-1.0)
|
||||
- `texture_spots_threshold`: Controls dark spot frequency
|
||||
|
||||
### Biome Configuration
|
||||
- `BIOME_COLORS`: RGB colors for each biome type
|
||||
@@ -115,9 +122,11 @@ The code is organized into logical modules:
|
||||
|
||||
4. **Rendering** (`src/rendering/terrain_renderer.py`)
|
||||
- OpenGL rendering of terrain mesh
|
||||
- **Procedural texture generation** - 16×16 pixelated textures per tile
|
||||
- Biome coloring based on elevation
|
||||
- Wireframe grid overlay
|
||||
- Shaded side faces for depth
|
||||
- Texture caching for performance
|
||||
|
||||
5. **Application** (`src/app.py`)
|
||||
- Main application loop
|
||||
@@ -138,6 +147,15 @@ 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
|
||||
- Each generation uses a random seed for unique terrains
|
||||
|
||||
### Customizing Textures
|
||||
|
||||
Edit texture parameters in `RENDERING` section:
|
||||
- `texture_variation`: Increase for more dramatic color changes
|
||||
- `texture_detail_scale`: Lower values = larger patterns
|
||||
- `texture_spots_threshold`: Lower values = more dark spots
|
||||
- Textures are 16×16 pixels with GL_NEAREST filtering for retro look
|
||||
|
||||
### Adding Camera Features
|
||||
|
||||
|
||||
+13
-4
@@ -22,7 +22,7 @@ TERRAIN = {
|
||||
'noise_lacunarity': 2.5, # Higher = more frequency increase per octave
|
||||
'noise_repeat_x': 1024,
|
||||
'noise_repeat_y': 1024,
|
||||
'noise_base': 42,
|
||||
'noise_base': 1988,
|
||||
|
||||
# Height scaling
|
||||
'height_multiplier': 80.0, # Multiplier for height variation
|
||||
@@ -61,9 +61,18 @@ RENDERING = {
|
||||
'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,
|
||||
# Side faces color (uniform terrain color)
|
||||
'side_face_color': (0.55, 0.45, 0.35), # Brown terrain color
|
||||
'side_face_shading': 0.7, # Right face shading multiplier
|
||||
'back_face_shading': 0.85, # Back face shading multiplier
|
||||
|
||||
# Procedural texture settings for top faces
|
||||
'texture_enabled': True,
|
||||
'texture_detail_scale': 8.0, # Higher = more detail noise
|
||||
'texture_variation': 0.25, # Amount of color variation (0.0-1.0)
|
||||
'texture_pattern_scale': 2.0, # Scale for pattern details
|
||||
'texture_spots_scale': 15.0, # Scale for random spots/patches
|
||||
'texture_spots_threshold': 0.6, # Threshold for spot appearance
|
||||
}
|
||||
|
||||
# Biome colors (RCT style)
|
||||
|
||||
+23
-15
@@ -11,8 +11,9 @@ Quando avvii il programma, vedrai:
|
||||
1. **Una griglia di terreno 3D** composta da 20×20 tessere (chiamate "tile")
|
||||
2. **Variazioni di altezza** che creano colline, valli e montagne
|
||||
3. **Diversi colori** che rappresentano biomi (acqua, sabbia, erba, roccia, neve)
|
||||
4. **Linee nere** che delimitano ogni tessera, creando un effetto griglia
|
||||
5. **Una vista dall'alto** con un angolo di 45 gradi (vista isometrica)
|
||||
4. **Texture procedurali pixelate** - Ogni tile ha decorazioni uniche in stile retro
|
||||
5. **Linee nere** che delimitano ogni tessera, creando un effetto griglia
|
||||
6. **Una vista dall'alto** con un angolo di 45 gradi (vista isometrica)
|
||||
|
||||
### Interattività
|
||||
|
||||
@@ -74,23 +75,24 @@ Altezza 70+ → Bianco (Neve) 🏔️
|
||||
|
||||
### 3. Rendering 3D
|
||||
|
||||
Il computer disegna ogni tessera come un quadrato in 3D:
|
||||
Il computer disegna ogni tessera come un quadrato in 3D con texture procedurale:
|
||||
|
||||
```
|
||||
Vista dall'alto: Vista isometrica:
|
||||
Vista dall'alto: Vista isometrica con texture:
|
||||
|
||||
┌──┬──┐ ◇──◇
|
||||
│ │ │ ╱│ ╱│
|
||||
├──┼──┤ → ◇──◇ │
|
||||
│ │ │ │ ╱│ ╱
|
||||
┌──┬──┐ ◇──◇ (con pattern 16×16 pixel)
|
||||
│ │ │ ╱│ ╱│ (macchie scure, variazioni)
|
||||
├──┼──┤ → ◇──◇ │ (ogni tile unica)
|
||||
│ │ │ │ ╱│ ╱ (stile retro pixelato)
|
||||
└──┴──┘ ◇──◇
|
||||
```
|
||||
|
||||
Ogni quadrato (quad) è disegnato con:
|
||||
- Una **faccia superiore** (quella che vediamo)
|
||||
- **Facce laterali** (per mostrare l'altezza)
|
||||
- **Colore** basato sull'altezza
|
||||
- **Ombreggiatura** per dare profondità
|
||||
- Una **faccia superiore** con texture procedurale 16×16 pixel
|
||||
- **Facce laterali** uniformi marroni per l'altezza
|
||||
- **Colore base** determinato dal bioma
|
||||
- **Pattern unici** generati con Perlin noise per ogni tile
|
||||
- **Ombreggiatura** sulle facce laterali per profondità
|
||||
|
||||
## Risultati Visivi
|
||||
|
||||
@@ -161,7 +163,14 @@ shader/
|
||||
## Caratteristiche Principali
|
||||
|
||||
### ✅ Generazione Procedurale
|
||||
Il terreno è generato automaticamente usando algoritmi matematici. Ogni volta che premi R, ottieni un paesaggio completamente diverso!
|
||||
Il terreno è generato automaticamente usando algoritmi matematici. Ogni volta che premi R, ottieni un paesaggio completamente diverso con texture uniche!
|
||||
|
||||
### ✅ Texture Procedurali Pixelate
|
||||
Ogni tile ha una texture 16×16 pixel generata proceduralmente con:
|
||||
- Pattern casuali unici per ogni tile
|
||||
- Macchie scure e variazioni di colore
|
||||
- Stile retro pixelato (GL_NEAREST filtering)
|
||||
- Contestuali al bioma (erba, sabbia, roccia, ecc.)
|
||||
|
||||
### ✅ Configurabilità
|
||||
Tutto è personalizzabile tramite il file `config/settings.py`:
|
||||
@@ -211,10 +220,9 @@ Il progetto base ha alcune limitazioni:
|
||||
- ❌ Non c'è salvataggio/caricamento delle mappe
|
||||
- ❌ Non puoi modificare il terreno interattivamente
|
||||
- ❌ La griglia è fissa a 20×20 (modificabile nel config)
|
||||
- ❌ Non ci sono texture, solo colori solidi
|
||||
- ❌ Nessun sistema di gameplay
|
||||
|
||||
Queste sono tutte cose che puoi aggiungere estendendo il progetto!
|
||||
Le texture procedurali sono completamente implementate! ✅
|
||||
|
||||
## Prossimi Passi
|
||||
|
||||
|
||||
+88
-14
@@ -38,9 +38,10 @@ heightmap[i+1][j+1] = 13
|
||||
### 1. Calcolo Vertici
|
||||
|
||||
```python
|
||||
def draw_tile(self, x, z, h1, h2, h3, h4):
|
||||
def draw_tile(self, x, z, h1, h2, h3, h4, grid_i, grid_j):
|
||||
# x, z: posizione in griglia
|
||||
# h1, h2, h3, h4: altezze dei 4 angoli
|
||||
# grid_i, grid_j: indici griglia per texture unica
|
||||
|
||||
corners = [
|
||||
(x, h1, z), # Angolo 1
|
||||
@@ -50,24 +51,97 @@ def draw_tile(self, x, z, h1, h2, h3, h4):
|
||||
]
|
||||
```
|
||||
|
||||
### 2. Faccia Superiore
|
||||
### 2. Generazione Texture Procedurale
|
||||
|
||||
Prima di disegnare, ogni tile genera una texture OpenGL unica:
|
||||
|
||||
```python
|
||||
# Calcola colore medio
|
||||
avg_height = (h1 + h2 + h3 + h4) / 4.0
|
||||
color = self.get_color_for_height(avg_height)
|
||||
|
||||
# Disegna quad
|
||||
glBegin(GL_QUADS)
|
||||
glColor3f(*color) # Applica colore
|
||||
for corner in corners:
|
||||
glVertex3f(*corner) # Definisci vertice
|
||||
glEnd()
|
||||
def generate_procedural_texture(self, base_color, grid_i, grid_j, texture_size=16):
|
||||
# Crea array 16×16 pixel
|
||||
texture_data = np.zeros((16, 16, 3), dtype=np.uint8)
|
||||
|
||||
# Offset unico per questo tile
|
||||
tile_offset_x = grid_i * 1000.0
|
||||
tile_offset_y = grid_j * 1000.0
|
||||
|
||||
for i in range(16):
|
||||
for j in range(16):
|
||||
# Coordinate noise con offset tile
|
||||
i_coord = float(i) / 16 * scale + tile_offset_x
|
||||
j_coord = float(j) / 16 * scale + tile_offset_y
|
||||
|
||||
# 3 layer di Perlin noise
|
||||
detail = noise.pnoise2(i_coord, j_coord, octaves=3)
|
||||
pattern = noise.pnoise2(i_coord * 0.5, j_coord * 0.5, octaves=2)
|
||||
spots = noise.pnoise2(i_coord * 1.5, j_coord * 1.5, octaves=1)
|
||||
|
||||
# Combina e applica al colore base
|
||||
variation = (detail * 0.4 + pattern * 0.6) * 0.25
|
||||
spot_factor = 0.75 if spots > 0.6 else 1.0
|
||||
|
||||
final_color = (base_color + variation) * spot_factor
|
||||
texture_data[i, j] = final_color * 255
|
||||
|
||||
# Crea texture OpenGL
|
||||
texture_id = glGenTextures(1)
|
||||
glBindTexture(GL_TEXTURE_2D, texture_id)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) # Pixelato!
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, texture_data)
|
||||
|
||||
return texture_id
|
||||
```
|
||||
|
||||
**Risultato**: Faccia superiore della tile colorata.
|
||||
**Caratteristiche**:
|
||||
- 16×16 pixel per effetto retro
|
||||
- GL_NEAREST = nessuna interpolazione → pixel netti
|
||||
- 3 layer Perlin noise per variazioni naturali
|
||||
- Offset unico (grid_i, grid_j) = ogni tile diversa
|
||||
- Macchie scure dove `spots > 0.6`
|
||||
|
||||
### 3. Facce Laterali
|
||||
### 3. Faccia Superiore con Texture
|
||||
|
||||
```python
|
||||
# Ottieni texture unica per questo tile
|
||||
avg_height = (h1 + h2 + h3 + h4) / 4.0
|
||||
base_color = self.get_color_for_height(avg_height)
|
||||
texture_id = self.get_texture_for_tile(base_color, grid_i, grid_j)
|
||||
|
||||
# Abilita texturing
|
||||
glEnable(GL_TEXTURE_2D)
|
||||
glBindTexture(GL_TEXTURE_2D, texture_id)
|
||||
glDisable(GL_LIGHTING) # Mostra colori puri texture
|
||||
glColor3f(1.0, 1.0, 1.0) # Bianco = non tinta la texture
|
||||
|
||||
# Disegna quad con coordinate texture
|
||||
glBegin(GL_QUADS)
|
||||
glTexCoord2f(0.0, 0.0) # Angolo texture
|
||||
glVertex3f(*corners[0])
|
||||
glTexCoord2f(1.0, 0.0)
|
||||
glVertex3f(*corners[1])
|
||||
glTexCoord2f(1.0, 1.0)
|
||||
glVertex3f(*corners[2])
|
||||
glTexCoord2f(0.0, 1.0)
|
||||
glVertex3f(*corners[3])
|
||||
glEnd()
|
||||
|
||||
glDisable(GL_TEXTURE_2D)
|
||||
glEnable(GL_LIGHTING) # Riabilita per facce laterali
|
||||
```
|
||||
|
||||
**Coordinate Texture (UV)**:
|
||||
```
|
||||
(0,0)────(1,0)
|
||||
│ 16×16 │
|
||||
│ pixel │
|
||||
(0,1)────(1,1)
|
||||
```
|
||||
|
||||
Ogni angolo del quad mappa un angolo della texture 16×16.
|
||||
|
||||
**Risultato**: Faccia superiore con pattern procedurale pixelato unico.
|
||||
|
||||
### 4. Facce Laterali (Uniformi)
|
||||
|
||||
#### Faccia Destra (X+)
|
||||
|
||||
|
||||
+7
-1
@@ -55,7 +55,7 @@ class IsometricTerrainApp:
|
||||
glEnable(GL_BLEND)
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
||||
|
||||
# Setup lighting
|
||||
# Setup lighting (sunset lighting)
|
||||
glEnable(GL_LIGHTING)
|
||||
glEnable(GL_LIGHT0)
|
||||
glEnable(GL_COLOR_MATERIAL)
|
||||
@@ -64,6 +64,12 @@ class IsometricTerrainApp:
|
||||
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'])
|
||||
|
||||
# Add specular component for sunset highlights
|
||||
if 'light_specular' in self.config.RENDERING:
|
||||
glLightfv(GL_LIGHT0, GL_SPECULAR, self.config.RENDERING['light_specular'])
|
||||
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, [0.3, 0.3, 0.3, 1.0])
|
||||
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 10.0)
|
||||
|
||||
def handle_events(self):
|
||||
"""Handle pygame events"""
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"""
|
||||
Terrain rendering with isometric view
|
||||
Terrain rendering with isometric view and procedural textures
|
||||
"""
|
||||
from OpenGL.GL import *
|
||||
import random
|
||||
import noise
|
||||
import numpy as np
|
||||
|
||||
|
||||
class TerrainRenderer:
|
||||
@@ -24,13 +27,28 @@ class TerrainRenderer:
|
||||
|
||||
self.grid_line_width = rendering_config['grid_line_width']
|
||||
self.grid_line_color = rendering_config['grid_line_color']
|
||||
self.side_face_color = rendering_config['side_face_color']
|
||||
self.side_face_shading = rendering_config['side_face_shading']
|
||||
self.back_face_shading = rendering_config['back_face_shading']
|
||||
|
||||
# Texture settings
|
||||
self.texture_enabled = rendering_config.get('texture_enabled', True)
|
||||
self.texture_detail_scale = rendering_config.get('texture_detail_scale', 8.0)
|
||||
self.texture_variation = rendering_config.get('texture_variation', 0.25)
|
||||
self.texture_pattern_scale = rendering_config.get('texture_pattern_scale', 2.0)
|
||||
self.texture_spots_scale = rendering_config.get('texture_spots_scale', 15.0)
|
||||
self.texture_spots_threshold = rendering_config.get('texture_spots_threshold', 0.6)
|
||||
|
||||
self.colors = biome_colors
|
||||
self.thresholds = biome_thresholds
|
||||
|
||||
self.heightmap = None
|
||||
|
||||
# Initialize random seed for consistent patterns per session
|
||||
self.texture_seed = random.randint(0, 10000)
|
||||
|
||||
# Texture cache: one GL texture per tile (grid_i, grid_j, biome)
|
||||
self.tile_textures = {}
|
||||
|
||||
def set_heightmap(self, heightmap):
|
||||
"""
|
||||
@@ -40,6 +58,127 @@ class TerrainRenderer:
|
||||
heightmap: 2D numpy array of height values
|
||||
"""
|
||||
self.heightmap = heightmap
|
||||
|
||||
# Regenerate texture seed and clear cache for new terrain
|
||||
self.texture_seed = random.randint(0, 10000)
|
||||
|
||||
# Delete old textures from GPU memory
|
||||
for texture_id in self.tile_textures.values():
|
||||
glDeleteTextures([texture_id])
|
||||
|
||||
# Clear texture cache
|
||||
self.tile_textures = {}
|
||||
|
||||
print(f"New texture seed: {self.texture_seed}")
|
||||
|
||||
def generate_procedural_texture(self, base_color, grid_i, grid_j, texture_size=16):
|
||||
"""
|
||||
Generate procedural texture EXACTLY like test_texture.py but unique per tile
|
||||
Pixelated style (16×16) like old games
|
||||
|
||||
Args:
|
||||
base_color: Base RGB color tuple (0-1 range)
|
||||
grid_i: Grid index i (for variation)
|
||||
grid_j: Grid index j (for variation)
|
||||
texture_size: Size of texture in pixels (16×16 for retro look)
|
||||
|
||||
Returns:
|
||||
int: OpenGL texture ID
|
||||
"""
|
||||
# Create texture array (same as test_texture.py)
|
||||
texture_data = np.zeros((texture_size, texture_size, 3), dtype=np.uint8)
|
||||
|
||||
# Unique offset per tile for variation
|
||||
tile_offset_x = grid_i * 1000.0
|
||||
tile_offset_y = grid_j * 1000.0
|
||||
|
||||
for i in range(texture_size):
|
||||
for j in range(texture_size):
|
||||
# Same exact algorithm as test_texture.py, but with tile-specific offset
|
||||
i_coord = float(i) / texture_size * self.texture_detail_scale + tile_offset_x
|
||||
j_coord = float(j) / texture_size * self.texture_detail_scale + tile_offset_y
|
||||
|
||||
# Fine detail noise
|
||||
detail_noise = noise.pnoise2(
|
||||
i_coord + self.texture_seed * 0.01,
|
||||
j_coord + self.texture_seed * 0.01,
|
||||
octaves=3,
|
||||
persistence=0.5,
|
||||
lacunarity=2.0
|
||||
)
|
||||
|
||||
# Medium pattern noise
|
||||
pattern_noise = noise.pnoise2(
|
||||
i_coord * 0.5 + 100,
|
||||
j_coord * 0.5 + 100,
|
||||
octaves=2,
|
||||
persistence=0.6
|
||||
)
|
||||
|
||||
# Random spots
|
||||
spots_noise = noise.pnoise2(
|
||||
i_coord * 1.5 + 200,
|
||||
j_coord * 1.5 + 200,
|
||||
octaves=1
|
||||
)
|
||||
|
||||
# Spot darkening
|
||||
spot_factor = 1.0
|
||||
if spots_noise > self.texture_spots_threshold:
|
||||
spot_factor = 0.75
|
||||
elif spots_noise > self.texture_spots_threshold + 0.2:
|
||||
spot_factor = 0.6
|
||||
|
||||
# Combine layers
|
||||
variation = (detail_noise * 0.4 + pattern_noise * 0.6) * self.texture_variation
|
||||
|
||||
# Apply to base color
|
||||
textured_color = tuple(
|
||||
max(0.0, min(1.0, (c + variation) * spot_factor))
|
||||
for c in base_color
|
||||
)
|
||||
|
||||
# Convert to 0-255 range
|
||||
texture_data[i, j] = [int(c * 255) for c in textured_color]
|
||||
|
||||
# Create OpenGL texture
|
||||
texture_id = glGenTextures(1)
|
||||
glBindTexture(GL_TEXTURE_2D, texture_id)
|
||||
|
||||
# Set texture parameters for pixelated look (no filtering!)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) # No interpolation
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) # Sharp pixels
|
||||
|
||||
# Upload texture data
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D, 0, GL_RGB,
|
||||
texture_size, texture_size, 0,
|
||||
GL_RGB, GL_UNSIGNED_BYTE, texture_data
|
||||
)
|
||||
|
||||
return texture_id
|
||||
|
||||
def get_texture_for_tile(self, base_color, grid_i, grid_j):
|
||||
"""
|
||||
Get or create unique texture for this specific tile
|
||||
|
||||
Args:
|
||||
base_color: Base RGB color tuple
|
||||
grid_i: Grid index i
|
||||
grid_j: Grid index j
|
||||
|
||||
Returns:
|
||||
int: OpenGL texture ID
|
||||
"""
|
||||
# Use (i, j, color, seed) as unique key - seed ensures new textures on regeneration
|
||||
tile_key = (grid_i, grid_j, tuple(base_color), self.texture_seed)
|
||||
|
||||
if tile_key not in self.tile_textures:
|
||||
self.tile_textures[tile_key] = self.generate_procedural_texture(base_color, grid_i, grid_j)
|
||||
|
||||
return self.tile_textures[tile_key]
|
||||
|
||||
def get_color_for_height(self, height):
|
||||
"""
|
||||
@@ -66,17 +205,19 @@ class TerrainRenderer:
|
||||
else:
|
||||
return self.colors['snow']
|
||||
|
||||
def draw_tile(self, x, z, height, next_x_height, next_z_height, next_xz_height):
|
||||
def draw_tile(self, x, z, height, next_x_height, next_z_height, next_xz_height, grid_i, grid_j):
|
||||
"""
|
||||
Draw a single isometric tile with proper shading
|
||||
|
||||
Args:
|
||||
x: X position
|
||||
z: Z position
|
||||
x: X position in world coordinates
|
||||
z: Z position in world coordinates
|
||||
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
|
||||
grid_i: Grid index i (for texture)
|
||||
grid_j: Grid index j (for texture)
|
||||
"""
|
||||
# Define the four corners of the tile
|
||||
corners = [
|
||||
@@ -90,18 +231,40 @@ class TerrainRenderer:
|
||||
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
|
||||
# Enable texturing and get unique procedural texture for this tile
|
||||
if self.texture_enabled:
|
||||
texture_id = self.get_texture_for_tile(base_color, grid_i, grid_j)
|
||||
glEnable(GL_TEXTURE_2D)
|
||||
glBindTexture(GL_TEXTURE_2D, texture_id)
|
||||
|
||||
# Disable lighting to show pure texture colors
|
||||
glDisable(GL_LIGHTING)
|
||||
glColor3f(1.0, 1.0, 1.0) # White to not tint texture
|
||||
else:
|
||||
glColor3f(*base_color)
|
||||
|
||||
# Draw top face with texture coordinates
|
||||
glBegin(GL_QUADS)
|
||||
glColor3f(*base_color)
|
||||
for corner in corners:
|
||||
glVertex3f(*corner)
|
||||
glTexCoord2f(0.0, 0.0)
|
||||
glVertex3f(*corners[0])
|
||||
glTexCoord2f(1.0, 0.0)
|
||||
glVertex3f(*corners[1])
|
||||
glTexCoord2f(1.0, 1.0)
|
||||
glVertex3f(*corners[2])
|
||||
glTexCoord2f(0.0, 1.0)
|
||||
glVertex3f(*corners[3])
|
||||
glEnd()
|
||||
|
||||
# Disable texturing and re-enable lighting for side faces
|
||||
if self.texture_enabled:
|
||||
glDisable(GL_TEXTURE_2D)
|
||||
glEnable(GL_LIGHTING)
|
||||
|
||||
# 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)
|
||||
darker = tuple(c * self.side_face_shading for c in self.side_face_color)
|
||||
glColor3f(*darker)
|
||||
glVertex3f(x + self.tile_width, next_x_height, z)
|
||||
glVertex3f(x + self.tile_width, 0, z)
|
||||
@@ -112,7 +275,7 @@ class TerrainRenderer:
|
||||
# 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)
|
||||
darker = tuple(c * self.back_face_shading for c in self.side_face_color)
|
||||
glColor3f(*darker)
|
||||
glVertex3f(x, next_z_height, z + self.tile_depth)
|
||||
glVertex3f(x, 0, z + self.tile_depth)
|
||||
@@ -138,7 +301,7 @@ class TerrainRenderer:
|
||||
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)
|
||||
self.draw_tile(x, z, height, next_x, next_z, next_xz, i, j)
|
||||
|
||||
# Draw wireframe grid
|
||||
self._draw_grid(total_size)
|
||||
|
||||
@@ -3,6 +3,7 @@ Terrain generation using Perlin noise
|
||||
"""
|
||||
import numpy as np
|
||||
import noise
|
||||
import random
|
||||
|
||||
|
||||
class TerrainGenerator:
|
||||
@@ -36,11 +37,14 @@ class TerrainGenerator:
|
||||
|
||||
def generate(self):
|
||||
"""
|
||||
Generate terrain heightmap using Perlin noise
|
||||
Generate terrain heightmap using Perlin noise with random seed
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: 2D array of height values
|
||||
"""
|
||||
# Generate random base offset for different terrain each time
|
||||
random_base = random.randint(0, 10000)
|
||||
|
||||
total_size = self.grid_size * self.tile_size
|
||||
heightmap = np.zeros((total_size, total_size))
|
||||
|
||||
@@ -55,12 +59,14 @@ class TerrainGenerator:
|
||||
lacunarity=self.lacunarity,
|
||||
repeatx=self.repeat_x,
|
||||
repeaty=self.repeat_y,
|
||||
base=self.base
|
||||
base=random_base # Use random base instead of config base
|
||||
)
|
||||
|
||||
# Normalize and scale height
|
||||
heightmap[i][j] = (height + 0.5) * self.height_multiplier
|
||||
|
||||
print(f"New heightmap seed: {random_base}")
|
||||
|
||||
# Apply smoothing if enabled
|
||||
if self.enable_smoothing:
|
||||
heightmap = self._smooth_terrain(heightmap)
|
||||
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
"""
|
||||
Test script to generate and visualize procedural textures for terrain tiles
|
||||
"""
|
||||
import numpy as np
|
||||
import noise
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.colors import ListedColormap
|
||||
|
||||
# Configuration
|
||||
GRID_SIZE = 20
|
||||
SEED = 42
|
||||
|
||||
# Base biome colors (RGB 0-1)
|
||||
BIOME_COLORS = {
|
||||
'grass': (0.25, 0.6, 0.25),
|
||||
'sand': (0.76, 0.7, 0.5),
|
||||
'water': (0.2, 0.4, 0.8),
|
||||
}
|
||||
|
||||
def generate_texture(grid_size, base_color, seed):
|
||||
"""
|
||||
Generate procedural texture for a tile grid
|
||||
|
||||
Args:
|
||||
grid_size: Size of the grid (grid_size x grid_size)
|
||||
base_color: Base RGB color tuple (0-1 range)
|
||||
seed: Random seed
|
||||
|
||||
Returns:
|
||||
numpy array of shape (grid_size, grid_size, 3) with RGB values
|
||||
"""
|
||||
texture = np.zeros((grid_size, grid_size, 3))
|
||||
|
||||
for i in range(grid_size):
|
||||
for j in range(grid_size):
|
||||
# Use grid coordinates for noise sampling
|
||||
i_coord = float(i) * 0.1
|
||||
j_coord = float(j) * 0.1
|
||||
|
||||
# Fine detail noise (subtle variations)
|
||||
detail_noise = noise.pnoise2(
|
||||
i_coord + seed * 0.01,
|
||||
j_coord + seed * 0.01,
|
||||
octaves=3,
|
||||
persistence=0.5,
|
||||
lacunarity=2.0
|
||||
)
|
||||
|
||||
# Medium pattern noise (patches/clusters)
|
||||
pattern_noise = noise.pnoise2(
|
||||
i_coord * 0.5 + 100,
|
||||
j_coord * 0.5 + 100,
|
||||
octaves=2,
|
||||
persistence=0.6
|
||||
)
|
||||
|
||||
# Random spots (like grass tufts or darker patches)
|
||||
spots_noise = noise.pnoise2(
|
||||
i_coord * 1.5 + 200,
|
||||
j_coord * 1.5 + 200,
|
||||
octaves=1
|
||||
)
|
||||
|
||||
# Create darker spots where noise is high
|
||||
spot_factor = 1.0
|
||||
if spots_noise > 0.3: # Lower threshold for more spots
|
||||
spot_factor = 0.75 # Darker patch
|
||||
elif spots_noise > 0.5:
|
||||
spot_factor = 0.6 # Even darker
|
||||
|
||||
# Combine all noise layers
|
||||
variation = (detail_noise * 0.4 + pattern_noise * 0.6) * 0.25
|
||||
|
||||
# Apply to color with spot darkening
|
||||
textured_color = tuple(
|
||||
max(0.0, min(1.0, (c + variation) * spot_factor))
|
||||
for c in base_color
|
||||
)
|
||||
|
||||
texture[i, j] = textured_color
|
||||
|
||||
return texture
|
||||
|
||||
def main():
|
||||
"""Generate and display textures for different biomes"""
|
||||
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
|
||||
fig.suptitle('Procedural Texture Generation - RCT Style', fontsize=16)
|
||||
|
||||
biomes = [
|
||||
('Grass', BIOME_COLORS['grass']),
|
||||
('Sand', BIOME_COLORS['sand']),
|
||||
('Water', BIOME_COLORS['water']),
|
||||
]
|
||||
|
||||
for idx, (name, base_color) in enumerate(biomes):
|
||||
# Generate texture with variation
|
||||
texture = generate_texture(GRID_SIZE, base_color, SEED)
|
||||
|
||||
# Show textured version
|
||||
ax1 = axes[0, idx]
|
||||
ax1.imshow(texture)
|
||||
ax1.set_title(f'{name} - Textured')
|
||||
ax1.axis('off')
|
||||
|
||||
# Show base color for comparison
|
||||
base_texture = np.zeros((GRID_SIZE, GRID_SIZE, 3))
|
||||
base_texture[:, :] = base_color
|
||||
|
||||
ax2 = axes[1, idx]
|
||||
ax2.imshow(base_texture)
|
||||
ax2.set_title(f'{name} - Base Color')
|
||||
ax2.axis('off')
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig('texture_test.png', dpi=150)
|
||||
print("✓ Texture generated and saved as 'texture_test.png'")
|
||||
plt.show()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user