Last active
December 21, 2025 20:20
-
-
Save jeanCarloMachado/47fca792c1b6e6359804e9ee5d6db8b1 to your computer and use it in GitHub Desktop.
3d world simulator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| 3D World Simulator - An impressive 3D world with mountains, buildings, and exploration | |
| Controls: | |
| - WASD: Move forward/left/backward/right | |
| - Q/E: Move down/up | |
| - Mouse: Look around | |
| - Shift: Sprint | |
| - ESC: Exit | |
| """ | |
| import pygame | |
| from pygame.locals import * | |
| from OpenGL.GL import * | |
| from OpenGL.GLU import * | |
| import math | |
| import sys | |
| import random | |
| import time | |
| class Camera: | |
| """First-person camera""" | |
| def __init__(self): | |
| self.pos = [0.0, 15.0, 45.0] # Start at good viewing height | |
| self.rot = [0.0, 0.0] # pitch, yaw - look straight ahead | |
| self.speed = 20.0 | |
| self.sensitivity = 0.15 # Mouse sensitivity | |
| def mouse_move(self, dx, dy): | |
| self.rot[1] += dx * self.sensitivity | |
| self.rot[0] += dy * self.sensitivity | |
| self.rot[0] = max(-89, min(89, self.rot[0])) | |
| def move(self, forward=0, right=0, up=0): | |
| # Convert yaw to radians - note we use negative because of OpenGL camera | |
| yaw_rad = math.radians(-self.rot[1]) | |
| if forward: | |
| # Forward/backward along view direction | |
| self.pos[0] += forward * math.sin(yaw_rad) | |
| self.pos[2] += forward * math.cos(yaw_rad) | |
| if right: | |
| # Right/left perpendicular to view (90 degrees from forward) | |
| self.pos[0] += right * math.sin(yaw_rad + math.pi/2) | |
| self.pos[2] += right * math.cos(yaw_rad + math.pi/2) | |
| if up: | |
| # Up/down (vertical) | |
| self.pos[1] += up | |
| def update(self): | |
| glRotatef(-self.rot[0], 1, 0, 0) | |
| glRotatef(-self.rot[1], 0, 1, 0) | |
| glTranslatef(-self.pos[0], -self.pos[1], -self.pos[2]) | |
| def draw_cube(size=1.0): | |
| """Draw a simple cube""" | |
| s = size / 2 | |
| glBegin(GL_QUADS) | |
| # Front | |
| glNormal3f(0, 0, 1) | |
| glVertex3f(-s, -s, s) | |
| glVertex3f(s, -s, s) | |
| glVertex3f(s, s, s) | |
| glVertex3f(-s, s, s) | |
| # Back | |
| glNormal3f(0, 0, -1) | |
| glVertex3f(-s, -s, -s) | |
| glVertex3f(-s, s, -s) | |
| glVertex3f(s, s, -s) | |
| glVertex3f(s, -s, -s) | |
| # Top | |
| glNormal3f(0, 1, 0) | |
| glVertex3f(-s, s, -s) | |
| glVertex3f(-s, s, s) | |
| glVertex3f(s, s, s) | |
| glVertex3f(s, s, -s) | |
| # Bottom | |
| glNormal3f(0, -1, 0) | |
| glVertex3f(-s, -s, -s) | |
| glVertex3f(s, -s, -s) | |
| glVertex3f(s, -s, s) | |
| glVertex3f(-s, -s, s) | |
| # Right | |
| glNormal3f(1, 0, 0) | |
| glVertex3f(s, -s, -s) | |
| glVertex3f(s, s, -s) | |
| glVertex3f(s, s, s) | |
| glVertex3f(s, -s, s) | |
| # Left | |
| glNormal3f(-1, 0, 0) | |
| glVertex3f(-s, -s, -s) | |
| glVertex3f(-s, -s, s) | |
| glVertex3f(-s, s, s) | |
| glVertex3f(-s, s, -s) | |
| glEnd() | |
| def draw_pyramid(size=1.0): | |
| """Draw a pyramid""" | |
| s = size / 2 | |
| h = size * 1.5 | |
| glBegin(GL_TRIANGLES) | |
| # Front | |
| glNormal3f(0, 0.7, 0.7) | |
| glVertex3f(0, h, 0) | |
| glVertex3f(-s, 0, s) | |
| glVertex3f(s, 0, s) | |
| # Right | |
| glNormal3f(0.7, 0.7, 0) | |
| glVertex3f(0, h, 0) | |
| glVertex3f(s, 0, s) | |
| glVertex3f(s, 0, -s) | |
| # Back | |
| glNormal3f(0, 0.7, -0.7) | |
| glVertex3f(0, h, 0) | |
| glVertex3f(s, 0, -s) | |
| glVertex3f(-s, 0, -s) | |
| # Left | |
| glNormal3f(-0.7, 0.7, 0) | |
| glVertex3f(0, h, 0) | |
| glVertex3f(-s, 0, -s) | |
| glVertex3f(-s, 0, s) | |
| glEnd() | |
| # Base | |
| glBegin(GL_QUADS) | |
| glNormal3f(0, -1, 0) | |
| glVertex3f(-s, 0, -s) | |
| glVertex3f(s, 0, -s) | |
| glVertex3f(s, 0, s) | |
| glVertex3f(-s, 0, s) | |
| glEnd() | |
| def draw_building(width, height, depth): | |
| """Draw a building""" | |
| glPushMatrix() | |
| glScalef(width, height, depth) | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| def draw_tree(x, z, color=None): | |
| """Draw a colorful tree""" | |
| glPushMatrix() | |
| glTranslatef(x, 0, z) | |
| # Trunk | |
| glColor3f(0.4, 0.25, 0.1) | |
| glPushMatrix() | |
| glTranslatef(0, 1.5, 0) | |
| glScalef(0.3, 3, 0.3) | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| # Colorful leaves | |
| if color: | |
| glColor3f(*color) | |
| else: | |
| glColor3f(0.1, 0.6, 0.1) | |
| glTranslatef(0, 4, 0) | |
| draw_pyramid(2.5) | |
| glPopMatrix() | |
| def draw_bird(x, y, z, phase): | |
| """Draw a simple flying bird""" | |
| glPushMatrix() | |
| glTranslatef(x, y, z) | |
| # Body | |
| glColor3f(0.2, 0.2, 0.3) | |
| glScalef(0.5, 0.3, 0.8) | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| # Wings flapping | |
| wing_angle = math.sin(phase) * 45 | |
| # Left wing | |
| glPushMatrix() | |
| glTranslatef(x - 0.4, y, z) | |
| glRotatef(wing_angle, 0, 0, 1) | |
| glColor3f(0.3, 0.3, 0.4) | |
| glScalef(1.2, 0.1, 0.6) | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| # Right wing | |
| glPushMatrix() | |
| glTranslatef(x + 0.4, y, z) | |
| glRotatef(-wing_angle, 0, 0, 1) | |
| glColor3f(0.3, 0.3, 0.4) | |
| glScalef(1.2, 0.1, 0.6) | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| def draw_cloud(x, y, z, size=1.0): | |
| """Draw a fluffy cloud""" | |
| glColor3f(1.0, 1.0, 1.0) | |
| # Multiple spheres to make fluffy cloud | |
| cloud_parts = [ | |
| (0, 0, 0, 1.0), | |
| (-0.7, 0, 0, 0.8), | |
| (0.7, 0, 0, 0.8), | |
| (-0.4, 0.3, 0, 0.6), | |
| (0.4, 0.3, 0, 0.6), | |
| (0, 0.4, 0, 0.5), | |
| ] | |
| for dx, dy, dz, scale in cloud_parts: | |
| glPushMatrix() | |
| glTranslatef(x + dx * size, y + dy * size, z + dz * size) | |
| glScalef(scale * size, scale * size * 0.7, scale * size) | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| def draw_person(x, y, z, rot, color): | |
| """Draw a simple person""" | |
| glPushMatrix() | |
| glTranslatef(x, y, z) | |
| glRotatef(rot, 0, 1, 0) | |
| # Head | |
| glColor3f(0.9, 0.7, 0.6) | |
| glPushMatrix() | |
| glTranslatef(0, 1.5, 0) | |
| glScalef(0.4, 0.4, 0.4) | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| # Body | |
| glColor3f(*color) | |
| glPushMatrix() | |
| glTranslatef(0, 0.8, 0) | |
| glScalef(0.5, 0.8, 0.3) | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| # Legs | |
| glColor3f(0.2, 0.2, 0.4) | |
| for offset in [-0.15, 0.15]: | |
| glPushMatrix() | |
| glTranslatef(offset, 0.2, 0) | |
| glScalef(0.15, 0.4, 0.15) | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| glPopMatrix() | |
| def draw_river(): | |
| """Draw a winding river""" | |
| # River is blue and flows across the terrain | |
| glColor3f(0.2, 0.5, 0.9) | |
| glBegin(GL_QUADS) | |
| segments = 50 | |
| for i in range(segments): | |
| z = -60 + i * 2.5 | |
| # River winds left and right | |
| center_x = math.sin(z * 0.1) * 15 | |
| width = 6 | |
| z_next = z + 2.5 | |
| center_x_next = math.sin(z_next * 0.1) * 15 | |
| # River quad | |
| glNormal3f(0, 1, 0) | |
| glVertex3f(center_x - width, 0.1, z) | |
| glVertex3f(center_x + width, 0.1, z) | |
| glVertex3f(center_x_next + width, 0.1, z_next) | |
| glVertex3f(center_x_next - width, 0.1, z_next) | |
| glEnd() | |
| def draw_mountain(x, z, height, base_size): | |
| """Draw a mountain grounded at y=0""" | |
| # Mountain base - starts from ground | |
| glColor3f(0.5, 0.5, 0.55) | |
| glPushMatrix() | |
| glTranslatef(x, 0, z) | |
| glScalef(base_size, height, base_size) | |
| draw_pyramid(1.0) | |
| glPopMatrix() | |
| # Snow peak | |
| glColor3f(0.95, 0.95, 1.0) | |
| glPushMatrix() | |
| glTranslatef(x, height * 0.7, z) | |
| glScalef(base_size * 0.4, height * 0.4, base_size * 0.4) | |
| draw_pyramid(1.0) | |
| glPopMatrix() | |
| def draw_sun(x, y, z): | |
| """Draw a bright sun""" | |
| glColor3f(1.0, 1.0, 0.3) # Bright yellow | |
| # Sun center | |
| glPushMatrix() | |
| glTranslatef(x, y, z) | |
| glScalef(4, 4, 4) | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| # Sun rays | |
| num_rays = 12 | |
| for i in range(num_rays): | |
| angle = i * (360 / num_rays) | |
| rad = math.radians(angle) | |
| ray_x = x + math.cos(rad) * 6 | |
| ray_y = y + math.sin(rad) * 6 | |
| glColor3f(1.0, 0.9, 0.2) | |
| glPushMatrix() | |
| glTranslatef(ray_x, ray_y, z) | |
| glRotatef(angle, 0, 0, 1) | |
| glScalef(2, 0.5, 0.5) | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| def draw_moon(x, y, z): | |
| """Draw a bright moon""" | |
| # Make moon brighter and more visible | |
| glColor3f(1.0, 1.0, 0.95) # Bright white/cream | |
| # Moon body - larger | |
| glPushMatrix() | |
| glTranslatef(x, y, z) | |
| glScalef(5, 5, 5) # Bigger moon | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| # Craters (darker spots) | |
| glColor3f(0.8, 0.8, 0.85) | |
| crater_positions = [ | |
| (0.8, 0.8, 0), | |
| (-1.2, -0.5, 0), | |
| (0.5, -1.0, 0), | |
| (-0.6, 1.0, 0), | |
| ] | |
| for cx, cy, cz in crater_positions: | |
| glPushMatrix() | |
| glTranslatef(x + cx, y + cy, z + cz + 0.5) | |
| glScalef(0.8, 0.8, 0.2) | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| # Moon glow effect (semi-transparent halo) | |
| glColor4f(1.0, 1.0, 0.9, 0.3) | |
| glPushMatrix() | |
| glTranslatef(x, y, z) | |
| glScalef(7, 7, 7) | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| def draw_stars(time_of_day): | |
| """Draw twinkling stars at night""" | |
| # Only visible at night | |
| if 0.2 < time_of_day < 0.8: | |
| return | |
| random.seed(42) # Consistent star positions | |
| num_stars = 100 | |
| glDisable(GL_LIGHTING) | |
| glPointSize(2.0) | |
| glBegin(GL_POINTS) | |
| for i in range(num_stars): | |
| x = random.uniform(-100, 100) | |
| y = random.uniform(20, 80) | |
| z = random.uniform(-120, -80) | |
| # Twinkle effect | |
| brightness = 0.8 + 0.2 * math.sin(time_of_day * 20 + i) | |
| glColor3f(brightness, brightness, brightness) | |
| glVertex3f(x, y, z) | |
| glEnd() | |
| glEnable(GL_LIGHTING) | |
| def draw_flower(x, z, color): | |
| """Draw a simple flower""" | |
| # Stem | |
| glColor3f(0.2, 0.6, 0.2) | |
| glPushMatrix() | |
| glTranslatef(x, 0.3, z) | |
| glScalef(0.05, 0.6, 0.05) | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| # Flower petals (5 small cubes around center) | |
| glColor3f(*color) | |
| petal_positions = [ | |
| (0, 0), | |
| (0.15, 0), | |
| (-0.15, 0), | |
| (0, 0.15), | |
| (0, -0.15), | |
| ] | |
| for px, pz in petal_positions: | |
| glPushMatrix() | |
| glTranslatef(x + px, 0.7, z + pz) | |
| glScalef(0.15, 0.15, 0.15) | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| def draw_castle(): | |
| """Draw a medieval castle""" | |
| # Main keep | |
| glColor3f(0.6, 0.6, 0.65) | |
| glPushMatrix() | |
| glTranslatef(0, 0, 0) | |
| draw_building(12, 18, 12) | |
| glPopMatrix() | |
| # Four corner towers | |
| tower_positions = [ | |
| (-7, -7), (7, -7), (-7, 7), (7, 7) | |
| ] | |
| for tx, tz in tower_positions: | |
| # Tower body | |
| glColor3f(0.55, 0.55, 0.6) | |
| glPushMatrix() | |
| glTranslatef(tx, 12, tz) | |
| glScalef(2.5, 24, 2.5) | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| # Crenellations (battlements) | |
| for i in range(4): | |
| angle = i * 90 | |
| rad = math.radians(angle) | |
| bx = tx + math.cos(rad) * 1.5 | |
| bz = tz + math.sin(rad) * 1.5 | |
| glColor3f(0.5, 0.5, 0.55) | |
| glPushMatrix() | |
| glTranslatef(bx, 25, bz) | |
| glScalef(0.8, 2, 0.8) | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| # Tower roof | |
| glColor3f(0.7, 0.2, 0.2) | |
| glPushMatrix() | |
| glTranslatef(tx, 26, tz) | |
| draw_pyramid(3) | |
| glPopMatrix() | |
| # Main gate | |
| glColor3f(0.3, 0.2, 0.1) | |
| glPushMatrix() | |
| glTranslatef(0, 3, 6.5) | |
| glScalef(3, 5, 0.5) | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| # Windows | |
| window_positions = [ | |
| (-3, 10, 6.5), (3, 10, 6.5), | |
| (-3, 14, 6.5), (3, 14, 6.5), | |
| ] | |
| glColor3f(0.3, 0.3, 0.5) | |
| for wx, wy, wz in window_positions: | |
| glPushMatrix() | |
| glTranslatef(wx, wy, wz) | |
| glScalef(1, 1.5, 0.3) | |
| draw_cube(1.0) | |
| glPopMatrix() | |
| def draw_ground(): | |
| """Draw ground with grass texture pattern""" | |
| size = 150 | |
| grid = 40 # More detail | |
| step = size / grid | |
| glBegin(GL_QUADS) | |
| for i in range(grid): | |
| for j in range(grid): | |
| x = -size/2 + i * step | |
| z = -size/2 + j * step | |
| # Gentle rolling hills | |
| h1 = math.sin(x * 0.15) * math.cos(z * 0.15) * 3 | |
| h2 = math.sin((x + step) * 0.15) * math.cos(z * 0.15) * 3 | |
| h3 = math.sin((x + step) * 0.15) * math.cos((z + step) * 0.15) * 3 | |
| h4 = math.sin(x * 0.15) * math.cos((z + step) * 0.15) * 3 | |
| # Grass texture - vary green shades for texture | |
| base_seed = int((x * 10 + z * 10) * 1000) % 100 | |
| if base_seed < 70: | |
| glColor3f(0.3, 0.7, 0.3) # Dark grass | |
| elif base_seed < 85: | |
| glColor3f(0.4, 0.8, 0.3) # Medium grass | |
| elif base_seed < 95: | |
| glColor3f(0.5, 0.85, 0.4) # Light grass | |
| else: | |
| glColor3f(0.6, 0.9, 0.5) # Very light grass | |
| glNormal3f(0, 1, 0) | |
| glVertex3f(x, h1, z) | |
| glVertex3f(x + step, h2, z) | |
| glVertex3f(x + step, h3, z + step) | |
| glVertex3f(x, h4, z + step) | |
| glEnd() | |
| def draw_compass(): | |
| """Draw a small compass/direction indicator""" | |
| # Draw at origin for reference | |
| # North (positive Z) - Red line | |
| glColor3f(1.0, 0.0, 0.0) | |
| glBegin(GL_LINES) | |
| glVertex3f(0, 0.5, 0) | |
| glVertex3f(0, 0.5, 10) | |
| glEnd() | |
| # East (positive X) - Green line | |
| glColor3f(0.0, 1.0, 0.0) | |
| glBegin(GL_LINES) | |
| glVertex3f(0, 0.5, 0) | |
| glVertex3f(10, 0.5, 0) | |
| glEnd() | |
| # Up (positive Y) - Blue line | |
| glColor3f(0.0, 0.0, 1.0) | |
| glBegin(GL_LINES) | |
| glVertex3f(0, 0, 0) | |
| glVertex3f(0, 10, 0) | |
| glEnd() | |
| # Center marker | |
| glColor3f(1.0, 1.0, 0.0) | |
| glPushMatrix() | |
| glTranslatef(0, 0.5, 0) | |
| glScalef(1, 1, 1) | |
| draw_cube(1) | |
| glPopMatrix() | |
| class DayNightCycle: | |
| """Manages day/night cycle and weather""" | |
| def __init__(self): | |
| self.time_of_day = 0.35 # Start mid-morning (0.25=sunrise, 0.5=noon, 0.75=sunset) | |
| self.cycle_speed = 0.02 # How fast day progresses (full cycle = 50 seconds) | |
| self.is_raining = False | |
| self.rain_timer = 0 | |
| self.next_rain_check = random.uniform(10, 30) # Random time until next rain check | |
| def update(self, dt): | |
| """Update day/night cycle""" | |
| self.time_of_day += self.cycle_speed * dt | |
| if self.time_of_day >= 1.0: | |
| self.time_of_day -= 1.0 | |
| # Random rain events | |
| self.rain_timer += dt | |
| if self.rain_timer >= self.next_rain_check: | |
| # Random chance to start/stop rain | |
| if random.random() < 0.3: # 30% chance to change rain state | |
| self.is_raining = not self.is_raining | |
| self.rain_timer = 0 | |
| self.next_rain_check = random.uniform(15, 40) | |
| def get_sun_position(self): | |
| """Calculate sun position based on time of day""" | |
| # Sun rises at 0.25, sets at 0.75 | |
| # Move in an arc across the sky from east to west | |
| angle = (self.time_of_day - 0.25) * math.pi # 0 to pi (sunrise to sunset) | |
| # Sun moves perpendicular to player starting direction | |
| x = math.cos(angle) * 120 # Move along X axis (east-west) | |
| y = math.sin(angle) * 80 + 10 # Arc height (higher) | |
| z = -70 # Far away from player start | |
| return x, max(y, -20), z # Don't go too far below | |
| def get_moon_position(self): | |
| """Calculate moon position (opposite of sun)""" | |
| angle = (self.time_of_day + 0.5) * 2 * math.pi # Opposite of sun | |
| # Moon moves opposite to sun | |
| moon_angle = (self.time_of_day + 0.5 - 0.25) * math.pi | |
| x = math.cos(moon_angle) * 120 | |
| y = math.sin(moon_angle) * 80 + 10 | |
| z = -70 | |
| return x, max(y, -20), z | |
| def is_daytime(self): | |
| """Check if it's daytime""" | |
| return 0.2 < self.time_of_day < 0.8 | |
| def get_sky_color(self): | |
| """Get sky color based on time of day""" | |
| if self.is_raining: | |
| return (0.4, 0.5, 0.6) | |
| # Dawn (0.2 - 0.3) | |
| if 0.2 < self.time_of_day < 0.3: | |
| t = (self.time_of_day - 0.2) * 10 | |
| r = 0.5 + t * 0.1 | |
| g = 0.5 + t * 0.3 | |
| b = 0.7 + t * 0.3 | |
| return (r, g, b) | |
| # Day (0.3 - 0.7) | |
| elif 0.3 <= self.time_of_day <= 0.7: | |
| return (0.6, 0.8, 1.0) | |
| # Dusk (0.7 - 0.8) | |
| elif 0.7 < self.time_of_day < 0.8: | |
| t = (self.time_of_day - 0.7) * 10 | |
| r = 0.6 + t * 0.3 | |
| g = 0.8 - t * 0.5 | |
| b = 1.0 - t * 0.6 | |
| return (r, g, b) | |
| # Night (0.8 - 1.0 and 0.0 - 0.2) | |
| else: | |
| return (0.1, 0.1, 0.2) | |
| def get_ambient_light(self): | |
| """Get ambient light level""" | |
| if self.is_daytime(): | |
| return 0.5 | |
| else: | |
| return 0.2 | |
| class RainSystem: | |
| """Particle system for rain""" | |
| def __init__(self, num_drops=300): | |
| self.drops = [] | |
| self.num_drops = num_drops | |
| self.reset_drops() | |
| def reset_drops(self): | |
| """Initialize rain drops""" | |
| self.drops = [] | |
| for _ in range(self.num_drops): | |
| self.drops.append({ | |
| 'x': random.uniform(-80, 80), | |
| 'y': random.uniform(10, 60), | |
| 'z': random.uniform(-80, 80), | |
| 'speed': random.uniform(30, 50), | |
| }) | |
| def update(self, dt): | |
| """Update rain drop positions""" | |
| for drop in self.drops: | |
| drop['y'] -= drop['speed'] * dt | |
| # Reset to top when hit ground | |
| if drop['y'] < 0: | |
| drop['y'] = random.uniform(50, 60) | |
| drop['x'] = random.uniform(-80, 80) | |
| drop['z'] = random.uniform(-80, 80) | |
| drop['speed'] = random.uniform(30, 50) | |
| def draw(self): | |
| """Draw rain drops""" | |
| glColor4f(0.7, 0.7, 0.9, 0.6) # Slightly transparent blue-gray | |
| glBegin(GL_LINES) | |
| for drop in self.drops: | |
| x, y, z = drop['x'], drop['y'], drop['z'] | |
| # Draw as small vertical lines | |
| glVertex3f(x, y, z) | |
| glVertex3f(x, y - 1.5, z) # Line length | |
| glEnd() | |
| def draw_world(t, rain_system=None, day_night=None): | |
| """Draw all world objects with animation time t""" | |
| if day_night: | |
| # Draw stars at night | |
| draw_stars(day_night.time_of_day) | |
| # Draw sun or moon based on time | |
| if day_night.is_daytime(): | |
| sun_x, sun_y, sun_z = day_night.get_sun_position() | |
| if sun_y > 0: # Only draw if above horizon | |
| draw_sun(sun_x, sun_y, sun_z) | |
| else: | |
| moon_x, moon_y, moon_z = day_night.get_moon_position() | |
| if moon_y > 0: # Only draw if above horizon | |
| draw_moon(moon_x, moon_y, moon_z) | |
| # Draw compass at origin for navigation reference | |
| draw_compass() | |
| # Ground with grass texture | |
| draw_ground() | |
| # River flowing through the land | |
| draw_river() | |
| # Flowers scattered around | |
| random.seed(100) # Different seed than trees | |
| flower_colors = [ | |
| (1.0, 0.2, 0.3), # Red | |
| (1.0, 0.5, 0.0), # Orange | |
| (1.0, 1.0, 0.2), # Yellow | |
| (1.0, 0.3, 0.8), # Pink | |
| (0.6, 0.2, 0.8), # Purple | |
| (0.2, 0.5, 1.0), # Blue | |
| (1.0, 1.0, 1.0), # White | |
| ] | |
| # Lots of flowers in open areas | |
| for i in range(150): | |
| x = random.uniform(-60, 60) | |
| z = random.uniform(-60, 60) | |
| # Don't place on river or too close to buildings | |
| river_center = math.sin(z * 0.1) * 15 | |
| if abs(x - river_center) > 8: # Not on river | |
| color = flower_colors[i % len(flower_colors)] | |
| draw_flower(x, z, color) | |
| # Draw rain if available | |
| if rain_system: | |
| rain_system.draw() | |
| # Mountains in the background | |
| mountain_positions = [ | |
| (-60, -50, 35, 20), # left back | |
| (-50, -60, 40, 22), # left back | |
| (60, -50, 38, 21), # right back | |
| (50, -60, 42, 24), # right back | |
| (0, -70, 45, 26), # center back (tallest) | |
| (30, -55, 32, 18), # right mid | |
| (-30, -55, 34, 19), # left mid | |
| ] | |
| for mx, mz, height, base in mountain_positions: | |
| draw_mountain(mx, mz, height, base) | |
| # Castle on a hill | |
| glPushMatrix() | |
| glTranslatef(-45, 8, 30) | |
| draw_castle() | |
| glPopMatrix() | |
| # Colorful houses scattered around | |
| houses = [ | |
| ((-15, -15), (1.0, 0.3, 0.3), 6), # Red house | |
| ((15, -15), (0.3, 0.3, 1.0), 5), # Blue house | |
| ((-15, 15), (1.0, 0.8, 0.2), 7), # Yellow house | |
| ((15, 15), (0.8, 0.3, 1.0), 5), # Purple house | |
| ((25, 5), (1.0, 0.5, 0.2), 8), # Orange house | |
| ((-25, 5), (0.5, 1.0, 0.8), 5), # Mint house | |
| ] | |
| for (x, z), color, height in houses: | |
| glColor3f(*color) | |
| glPushMatrix() | |
| glTranslatef(x, height/2, z) | |
| draw_building(4, height, 4) | |
| glPopMatrix() | |
| # Roof | |
| glColor3f(color[0] * 0.7, color[1] * 0.7, color[2] * 0.7) | |
| glPushMatrix() | |
| glTranslatef(x, height + 1, z) | |
| draw_pyramid(3.5) | |
| glPopMatrix() | |
| # Green trees (many more!) | |
| random.seed(42) | |
| green_color = (0.1, 0.7, 0.1) | |
| # Forest on left side | |
| for i in range(40): | |
| x = random.uniform(-60, -30) | |
| z = random.uniform(-40, 40) | |
| draw_tree(x, z, green_color) | |
| # Forest on right side | |
| for i in range(40): | |
| x = random.uniform(30, 60) | |
| z = random.uniform(-40, 40) | |
| draw_tree(x, z, green_color) | |
| # Trees near castle | |
| for i in range(20): | |
| angle = random.uniform(0, 360) | |
| rad = math.radians(angle) | |
| distance = random.uniform(15, 25) | |
| x = -45 + math.cos(rad) * distance | |
| z = 30 + math.sin(rad) * distance | |
| draw_tree(x, z, green_color) | |
| # Colorful rainbow trees (fewer, more special) | |
| tree_colors = [ | |
| (1.0, 0.3, 0.5), # Pink | |
| (0.5, 1.0, 0.3), # Lime | |
| (0.3, 0.5, 1.0), # Sky blue | |
| (1.0, 0.8, 0.2), # Yellow | |
| (0.8, 0.3, 1.0), # Purple | |
| ] | |
| for i in range(15): | |
| x = random.uniform(-25, 25) | |
| z = random.uniform(-25, 25) | |
| # Avoid house areas | |
| if abs(x) > 10 or abs(z) > 10: | |
| color = tree_colors[i % len(tree_colors)] | |
| draw_tree(x, z, color) | |
| # People walking around | |
| person_paths = [ | |
| # Path 1: Walking in a circle | |
| (0, lambda t: math.cos(t * 0.5) * 20, lambda t: math.sin(t * 0.5) * 20, (0.8, 0.3, 0.3)), | |
| # Path 2: Walking along path | |
| (1, lambda t: (t * 2) % 40 - 20, lambda t: 10, (0.3, 0.8, 0.3)), | |
| # Path 3: Walking other direction | |
| (2, lambda t: 15, lambda t: -(t * 2.5) % 40 + 20, (0.3, 0.3, 0.8)), | |
| # Path 4: Walking near castle | |
| (3, lambda t: -45 + math.cos(t * 0.4) * 12, lambda t: 30 + math.sin(t * 0.4) * 12, (0.8, 0.8, 0.3)), | |
| # Path 5: Walking by river | |
| (4, lambda t: math.sin((t * 2) * 0.1) * 15, lambda t: (t * 2) % 40 - 20, (0.8, 0.3, 0.8)), | |
| # Path 6: Random walker | |
| (5, lambda t: math.sin(t * 0.3) * 15, lambda t: math.cos(t * 0.4) * 15, (0.5, 0.8, 0.8)), | |
| ] | |
| for person_id, x_func, z_func, color in person_paths: | |
| x = x_func(t) | |
| z = z_func(t) | |
| # Calculate rotation based on movement direction | |
| dx = x_func(t + 0.1) - x | |
| dz = z_func(t + 0.1) - z | |
| rot = math.degrees(math.atan2(dx, dz)) | |
| draw_person(x, 0, z, rot, color) | |
| # Colorful cubes floating around (fewer) | |
| for i in range(5): | |
| angle = i * 72 | |
| rad = math.radians(angle) | |
| x = math.cos(rad) * 30 | |
| z = math.sin(rad) * 30 | |
| y = 8 + math.sin(t * 0.5 + i) * 2 | |
| color = [ | |
| (math.sin(i * 0.7) + 1) / 2, | |
| (math.cos(i * 0.9) + 1) / 2, | |
| (math.sin(i * 1.1) + 1) / 2, | |
| ] | |
| glColor3f(*color) | |
| glPushMatrix() | |
| glTranslatef(x, y, z) | |
| glRotatef(t * 30 + i * 45, 1, 1, 0) | |
| draw_cube(2) | |
| glPopMatrix() | |
| # Animated birds flying around | |
| for i in range(12): | |
| angle = (t * 0.3 + i * 30) % 360 | |
| rad = math.radians(angle) | |
| radius = 40 + i * 3 | |
| x = math.cos(rad) * radius | |
| z = math.sin(rad) * radius | |
| y = 20 + math.sin(t + i) * 5 | |
| phase = t * 5 + i | |
| draw_bird(x, y, z, phase) | |
| # Floating clouds | |
| cloud_positions = [ | |
| (30, 35, -20, 4), | |
| (-40, 40, -30, 5), | |
| (20, 38, 40, 3.5), | |
| (-30, 42, 30, 4.5), | |
| (50, 45, 10, 3), | |
| (-50, 37, -10, 4), | |
| (0, 50, -50, 5), | |
| (10, 43, -35, 4.2), | |
| ] | |
| for i, (base_x, base_y, base_z, size) in enumerate(cloud_positions): | |
| # Clouds drift slowly | |
| x = base_x + math.sin(t * 0.1 + i) * 10 | |
| y = base_y + math.cos(t * 0.15 + i) * 3 | |
| z = base_z + math.cos(t * 0.1 + i) * 10 | |
| draw_cloud(x, y, z, size) | |
| def main(): | |
| """Main function""" | |
| # Initialize Pygame | |
| pygame.init() | |
| width, height = 1400, 900 | |
| try: | |
| pygame.display.set_mode((width, height), DOUBLEBUF | OPENGL) | |
| pygame.display.set_caption("โ๏ธ๐ Magical 3D World - Day/Night Cycle, Random Rain & More! โญ") | |
| except pygame.error as e: | |
| print(f"\nโ ๏ธ Display Error: {e}") | |
| print("Please run from a terminal with display access") | |
| return | |
| # OpenGL setup | |
| glEnable(GL_DEPTH_TEST) | |
| glEnable(GL_LIGHTING) | |
| glEnable(GL_LIGHT0) | |
| glEnable(GL_COLOR_MATERIAL) | |
| glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE) | |
| # Enable blending for transparency (rain) | |
| glEnable(GL_BLEND) | |
| glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) | |
| # Sun-like lighting | |
| glLightfv(GL_LIGHT0, GL_POSITION, [1, 2, 1, 0]) | |
| glLightfv(GL_LIGHT0, GL_AMBIENT, [0.5, 0.5, 0.55, 1]) | |
| glLightfv(GL_LIGHT0, GL_DIFFUSE, [1.0, 0.95, 0.8, 1]) | |
| # Fog for atmosphere | |
| glEnable(GL_FOG) | |
| glFogi(GL_FOG_MODE, GL_LINEAR) | |
| glFogfv(GL_FOG_COLOR, [0.6, 0.75, 0.95, 1.0]) | |
| glFogf(GL_FOG_START, 60.0) | |
| glFogf(GL_FOG_END, 180.0) | |
| # Perspective | |
| glMatrixMode(GL_PROJECTION) | |
| gluPerspective(75, width / height, 0.5, 250.0) | |
| glMatrixMode(GL_MODELVIEW) | |
| # Hide and grab cursor | |
| pygame.mouse.set_visible(False) | |
| pygame.event.set_grab(True) | |
| pygame.mouse.set_pos(width // 2, height // 2) | |
| camera = Camera() | |
| clock = pygame.time.Clock() | |
| rain_system = RainSystem(num_drops=300) | |
| day_night = DayNightCycle() | |
| manual_rain_control = False # If True, user controls rain; if False, automatic | |
| print("\n" + "="*75) | |
| print(" โ๏ธ๐ MAGICAL 3D WORLD - Day/Night Cycle & Random Rain ๐ง๏ธโญ") | |
| print("="*75) | |
| print("\n๐ FEATURES:") | |
| print(" โข โ๏ธ๐ Full day/night cycle (50 second cycle)") | |
| print(" โข โ๏ธ Sun moves across the sky in realistic arc (rises/sets)") | |
| print(" โข ๐ BRIGHT MOON with craters and glow at night!") | |
| print(" โข โญ 100 twinkling stars at night") | |
| print(" โข โฐ Quick time controls: N=Night, M=Noon") | |
| print(" โข ๐ Dynamic sky colors (dawn, day, dusk, night)") | |
| print(" โข ๐ง๏ธ Random rain events at any time!") | |
| print(" โข ๐ธ 150 colorful flowers (red, orange, yellow, pink, purple, blue, white)") | |
| print(" โข ๐ฟ Textured grass (varied green shades)") | |
| print(" โข ๐ฐ Medieval castle with 4 towers on a hill") | |
| print(" โข ๐๏ธ 7 grounded mountains in the background") | |
| print(" โข ๐ Winding river flowing through the land") | |
| print(" โข ๐ฒ 100+ green trees (dense forests on both sides)") | |
| print(" โข ๐ณ 15 rainbow trees (pink, lime, blue, yellow, purple)") | |
| print(" โข ๐ถ 6 people walking on different paths") | |
| print(" โข ๐ก 6 colorful houses with roofs") | |
| print(" โข ๐ฆ 12 animated birds flying in circles") | |
| print(" โข โ๏ธ 8 floating clouds drifting in the sky") | |
| print(" โข ๐ฆ 5 spinning magical cubes") | |
| print("\n๐ฎ CONTROLS:") | |
| print(" W/S or โ/โ - Move forward/backward") | |
| print(" A/D or โ/โ - Move left/right") | |
| print(" Q/E - Move down/up") | |
| print(" Shift - Sprint (3x faster)") | |
| print(" Mouse - Look around (360ยฐ)") | |
| print(" N - Jump to NIGHT (see the moon!) ๐") | |
| print(" M - Jump to NOON (midday) โ๏ธ") | |
| print(" R - Toggle manual rain") | |
| print(" -/+ - Mouse sensitivity") | |
| print(" ESC - Exit") | |
| print("\n๐ก TIPS:") | |
| print(" โข Press N to jump to NIGHT and see the MOON immediately! ๐") | |
| print(" โข Press M to jump back to NOON โ๏ธ") | |
| print(" โข Moon is BRIGHT with craters and a glow - look up at night!") | |
| print(" โข 100 twinkling stars appear at night") | |
| print(" โข Full day/night cycle takes 50 seconds") | |
| print(" โข Sun rises east to west in realistic arc") | |
| print(" โข Rain happens RANDOMLY - any time of day or night!") | |
| print(" โข Sky colors: Dawn (orange) โ Day (blue) โ Dusk (pink) โ Night (dark)") | |
| print(" โข Console shows: โ๏ธ=day, ๐=night, ๐ง๏ธ=raining") | |
| print(" โข Fly high (E) to watch sun/moon move across the sky!") | |
| print("="*70 + "\n") | |
| running = True | |
| frame = 0 | |
| start_time = time.time() | |
| while running: | |
| dt = clock.tick(60) / 1000.0 | |
| frame += 1 | |
| t = time.time() - start_time # Animation time | |
| # Events | |
| for event in pygame.event.get(): | |
| if event.type == QUIT: | |
| running = False | |
| elif event.type == KEYDOWN: | |
| if event.key == K_ESCAPE: | |
| running = False | |
| elif event.key == K_MINUS or event.key == K_KP_MINUS: | |
| camera.sensitivity = max(0.05, camera.sensitivity - 0.05) | |
| print(f"๐ฏ Mouse sensitivity: {camera.sensitivity:.2f}") | |
| elif event.key == K_EQUALS or event.key == K_KP_PLUS: | |
| camera.sensitivity = min(0.5, camera.sensitivity + 0.05) | |
| print(f"๐ฏ Mouse sensitivity: {camera.sensitivity:.2f}") | |
| elif event.key == K_r: | |
| manual_rain_control = not manual_rain_control | |
| if manual_rain_control: | |
| day_night.is_raining = not day_night.is_raining | |
| print(f"๐ง๏ธ Manual rain control: {'ON' if day_night.is_raining else 'OFF'}") | |
| else: | |
| print(f"๐ง๏ธ Automatic rain mode (random)") | |
| elif event.key == K_n: | |
| # Skip to night to see the moon | |
| day_night.time_of_day = 0.0 # Midnight | |
| hour = int(day_night.time_of_day * 24) | |
| print(f"๐ Jumped to night! Time: {hour:02d}:00 - Look for the moon!") | |
| elif event.key == K_m: | |
| # Skip to noon | |
| day_night.time_of_day = 0.5 # Noon | |
| hour = int(day_night.time_of_day * 24) | |
| print(f"โ๏ธ Jumped to noon! Time: {hour:02d}:00") | |
| # Mouse look - smooth and responsive | |
| mouse_x, mouse_y = pygame.mouse.get_pos() | |
| center_x, center_y = width // 2, height // 2 | |
| dx = mouse_x - center_x | |
| dy = mouse_y - center_y | |
| # Apply mouse movement with deadzone for tiny jitters | |
| if abs(dx) > 0 or abs(dy) > 0: | |
| camera.mouse_move(dx, dy) | |
| pygame.mouse.set_pos(center_x, center_y) | |
| # Keyboard movement | |
| keys = pygame.key.get_pressed() | |
| speed = camera.speed * dt | |
| if keys[K_LSHIFT] or keys[K_RSHIFT]: | |
| speed *= 3 | |
| # WASD or Arrow keys for movement | |
| forward = (keys[K_w] or keys[K_UP]) - (keys[K_s] or keys[K_DOWN]) | |
| right = (keys[K_d] or keys[K_RIGHT]) - (keys[K_a] or keys[K_LEFT]) | |
| up = keys[K_e] - keys[K_q] | |
| camera.move(forward * speed, right * speed, up * speed) | |
| # Update day/night cycle | |
| if not manual_rain_control: | |
| day_night.update(dt) | |
| # Update rain | |
| if day_night.is_raining: | |
| rain_system.update(dt) | |
| # Debug info every 2 seconds | |
| if frame % 120 == 0: | |
| x, y, z = camera.pos | |
| yaw_dir = "N" if -45 < camera.rot[1] <= 45 else "E" if 45 < camera.rot[1] <= 135 else "S" if 135 < camera.rot[1] <= 225 or camera.rot[1] <= -135 else "W" | |
| # Time display | |
| hour = int(day_night.time_of_day * 24) | |
| time_icon = "โ๏ธ" if day_night.is_daytime() else "๐" | |
| rain_status = "๐ง๏ธ" if day_night.is_raining else "" | |
| print(f"๐ Pos: ({x:5.1f}, {y:5.1f}, {z:5.1f}) | {yaw_dir} | {time_icon} {hour:02d}:00 {rain_status}") | |
| # Render | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) | |
| # Sky color based on time of day | |
| sky_r, sky_g, sky_b = day_night.get_sky_color() | |
| glClearColor(sky_r, sky_g, sky_b, 1.0) | |
| # Update ambient lighting based on time | |
| ambient = day_night.get_ambient_light() | |
| glLightfv(GL_LIGHT0, GL_AMBIENT, [ambient, ambient, ambient + 0.1, 1]) | |
| glLoadIdentity() | |
| camera.update() | |
| # Draw sky gradient during daytime (not raining) | |
| if day_night.is_daytime() and not day_night.is_raining: | |
| # Disable depth test and lighting for sky | |
| glDisable(GL_DEPTH_TEST) | |
| glDisable(GL_LIGHTING) | |
| # Draw sky gradient quad far away | |
| glBegin(GL_QUADS) | |
| # Time-based colors | |
| if 0.2 < day_night.time_of_day < 0.3: # Dawn | |
| top_color = (0.4, 0.5, 0.8) | |
| horizon_color = (1.0, 0.5, 0.3) | |
| elif 0.7 < day_night.time_of_day < 0.8: # Dusk | |
| top_color = (0.4, 0.4, 0.7) | |
| horizon_color = (1.0, 0.4, 0.2) | |
| else: # Day | |
| top_color = (0.3, 0.5, 0.9) | |
| horizon_color = (1.0, 0.7, 0.5) | |
| # Top of sky | |
| glColor3f(*top_color) | |
| glVertex3f(-200, 80, -150) | |
| glVertex3f(200, 80, -150) | |
| # Horizon | |
| glColor3f(*horizon_color) | |
| glVertex3f(200, -10, -150) | |
| glVertex3f(-200, -10, -150) | |
| glEnd() | |
| glEnable(GL_DEPTH_TEST) | |
| glEnable(GL_LIGHTING) | |
| draw_world(t, rain_system if day_night.is_raining else None, day_night=day_night) | |
| pygame.display.flip() | |
| pygame.quit() | |
| sys.exit() | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment