Initial release v1.0.0 - Isometric Terrain Generator
- 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 camera
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"""Source package"""
|
||||
+122
@@ -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 @@
|
||||
"""Camera package"""
|
||||
@@ -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 @@
|
||||
"""Rendering package"""
|
||||
@@ -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 @@
|
||||
"""Terrain generation package"""
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user