Browse Source

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
master v1.1.0
Matteo Benedetto 2 months ago
parent
commit
bce3b0324f
  1. 16
      .github/copilot-instructions.md
  2. 20
      README.md
  3. 17
      config/settings.py
  4. 38
      docs/01-introduzione.md
  5. 94
      docs/06-rendering.md
  6. 8
      src/app.py
  7. 185
      src/rendering/terrain_renderer.py
  8. 10
      src/terrain/generator.py
  9. 120
      test_texture.py

16
.github/copilot-instructions.md

@ -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.

20
README.md

@ -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

17
config/settings.py

@ -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)

38
docs/01-introduzione.md

@ -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

94
docs/06-rendering.md

@ -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
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
```
**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. Faccia Superiore con Texture
```python
# Ottieni texture unica per questo tile
avg_height = (h1 + h2 + h3 + h4) / 4.0
color = self.get_color_for_height(avg_height)
base_color = self.get_color_for_height(avg_height)
texture_id = self.get_texture_for_tile(base_color, grid_i, grid_j)
# Disegna quad
# 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)
glColor3f(*color) # Applica colore
for corner in corners:
glVertex3f(*corner) # Definisci vertice
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
```
**Risultato**: Faccia superiore della tile colorata.
**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.
### 3. Facce Laterali
### 4. Facce Laterali (Uniformi)
#### Faccia Destra (X+)

8
src/app.py

@ -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)
@ -65,6 +65,12 @@ class IsometricTerrainApp:
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"""
for event in pygame.event.get():

185
src/rendering/terrain_renderer.py

@ -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,14 +27,29 @@ 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):
"""
Set the heightmap to render
@ -41,6 +59,127 @@ class TerrainRenderer:
"""
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):
"""
Get color based on height (biome mapping)
@ -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
glBegin(GL_QUADS)
# 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)
for corner in corners:
glVertex3f(*corner)
# Draw top face with texture coordinates
glBegin(GL_QUADS)
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)

10
src/terrain/generator.py

@ -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
test_texture.py

@ -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()
Loading…
Cancel
Save