From dde22479b69ddd380e901a8e17783d0033577d79 Mon Sep 17 00:00:00 2001 From: OusmBlueNinja <89956790+OusmBlueNinja@users.noreply.github.com> Date: Mon, 7 Apr 2025 10:03:21 -0500 Subject: [PATCH] Tree And Grass Simulations... --- pygame-grass/main.py | 252 ++++++++++++++++++++++++++++++++++++++++ pygame-imgui/example.py | 0 pygame-imgui/pygui.py | 0 3 files changed, 252 insertions(+) create mode 100644 pygame-grass/main.py create mode 100644 pygame-imgui/example.py create mode 100644 pygame-imgui/pygui.py diff --git a/pygame-grass/main.py b/pygame-grass/main.py new file mode 100644 index 0000000..0722b0f --- /dev/null +++ b/pygame-grass/main.py @@ -0,0 +1,252 @@ +import pygame +import sys +import math +import random + +# ------------------------------- +# Simulation Parameters +# ------------------------------- +WIDTH, HEIGHT = 800, 600 +grass_count = 3000 # Thousands of blades for a lush field + +# Initial wind parameters +wind_amplitude = 20 # How far things sway (pixels) +wind_speed = 2.0 # Oscillation frequency + +# Debug mode flag (toggle with F3) +debug_mode = True + +# ------------------------------- +# Pygame Setup +# ------------------------------- +pygame.init() +screen = pygame.display.set_mode((WIDTH, HEIGHT)) +pygame.display.set_caption("Optimized Grass, Tree & Collision Simulation") +clock = pygame.time.Clock() +font = pygame.font.SysFont("Arial", 18) + +# ------------------------------- +# Collision System +# ------------------------------- +class GrassCollider: + """ + Base collider class. Entities that affect grass or leaves should extend this class. + Collision is defined as a circle. The collider's movement is independent. + """ + def __init__(self, pos, radius, repulsion_strength=5): + self.pos = list(pos) # stored as [x, y] so it can be updated independently + self.radius = radius + self.repulsion_strength = repulsion_strength + + def collide_point(self, x, y): + dx = x - self.pos[0] + dy = y - self.pos[1] + return (dx * dx + dy * dy) < (self.radius * self.radius) + +# A sample moving entity: a bouncing ball that acts as a collider. +class Ball(GrassCollider): + def __init__(self, pos, radius, repulsion_strength=30): + super().__init__(pos, radius, repulsion_strength) + self.velocity = [3, 2] + + def update(self): + # Update position and bounce off window edges. + self.pos[0] += self.velocity[0] + self.pos[1] += self.velocity[1] + if self.pos[0] - self.radius < 0 or self.pos[0] + self.radius > WIDTH: + self.velocity[0] = -self.velocity[0] + if self.pos[1] - self.radius < 0 or self.pos[1] + self.radius > HEIGHT: + self.velocity[1] = -self.velocity[1] + + def draw(self, surface): + pygame.draw.circle(surface, (255, 0, 0), (int(self.pos[0]), int(self.pos[1])), self.radius) + +# ------------------------------- +# Grass Blade Class +# ------------------------------- +class GrassBlade: + def __init__(self, x, base_y, length, phase, tilt): + self.x = x + self.base_y = base_y + self.length = length + self.phase = phase # For varied motion per blade + self.tilt = tilt # Base tilt for a natural look + + def draw(self, surface, t, wind_amp, wind_spd, colliders): + segments = 10 # Number of segments along the blade + points = [] + sin_arg = t * wind_spd # Precompute common sine argument for efficiency + for i in range(segments + 1): + progress = i / segments + # Base (static) position along a slightly tilted vertical line. + base_x = self.x + progress * self.length * math.sin(self.tilt) + base_y = self.base_y - progress * self.length * math.cos(self.tilt) + # Wind displacement increases with height. + displacement = wind_amp * progress * math.sin(sin_arg + self.phase) + x_pos = base_x + displacement + y_pos = base_y + # For each collider, push the grass blade away if it's too close. + for collider in colliders: + if collider.collide_point(x_pos, y_pos): + dx = x_pos - collider.pos[0] + dy = y_pos - collider.pos[1] + dist = math.hypot(dx, dy) + if dist > 0: + repulsion = collider.repulsion_strength / dist + x_pos += repulsion * (dx / dist) + y_pos += repulsion * (dy / dist) + points.append((x_pos, y_pos)) + pygame.draw.aalines(surface, (34, 139, 34), False, points) + +# Initialize a list of grass blades. +grass_blades = [] +def init_grass(): + for _ in range(grass_count): + x = random.uniform(0, WIDTH) + # Grass base near the bottom. + base_y = HEIGHT - random.uniform(0, 20) + length = random.uniform(50, 100) + phase = random.uniform(0, 2 * math.pi) + tilt = random.uniform(-0.1, 0.1) + grass_blades.append(GrassBlade(x, base_y, length, phase, tilt)) + +init_grass() + +def draw_grass(surface, t, wind_amp, wind_spd, colliders): + for blade in grass_blades: + blade.draw(surface, t, wind_amp, wind_spd, colliders) + +# ------------------------------- +# Tree and Leaves Drawing +# ------------------------------- +# Global leaf counter for debug display. +leaf_count = 0 + +def draw_leaf(surface, x, y, colliders): + global leaf_count + # Adjust leaf position if a collider is nearby. + for collider in colliders: + if collider.collide_point(x, y): + dx = x - collider.pos[0] + dy = y - collider.pos[1] + dist = math.hypot(dx, dy) + if dist > 0: + repulsion = collider.repulsion_strength / dist + x += repulsion * (dx / dist) + y += repulsion * (dy / dist) + pygame.draw.circle(surface, (50, 205, 50), (int(x), int(y)), 5) + leaf_count += 1 + +def draw_tree(surface, x, y, length, angle, level, t, wind_amp, wind_spd, colliders): + if level == 0 or length < 2: + return + + # Wind-induced sway for the branch. + sway = math.radians(wind_amp * 0.3 * math.sin(t * wind_spd + level)) + new_angle = angle + sway + + # Calculate branch endpoint. + end_x = x + length * math.sin(new_angle) + end_y = y - length * math.cos(new_angle) + + # Apply collision repulsion at the branch endpoint. + for collider in colliders: + if collider.collide_point(end_x, end_y): + dx = end_x - collider.pos[0] + dy = end_y - collider.pos[1] + dist = math.hypot(dx, dy) + if dist > 0: + repulsion = collider.repulsion_strength / dist + end_x += repulsion * (dx / dist) + end_y += repulsion * (dy / dist) + + # Draw the branch. + thickness = max(1, level) + branch_color = (139, 69, 19) if level > 2 else (34, 139, 34) + pygame.draw.line(surface, branch_color, (x, y), (end_x, end_y), thickness) + + # Draw a leaf at lower recursion levels. + if level <= 2: + draw_leaf(surface, end_x, end_y, colliders) + + # Recursively draw left and right branches. + new_length = length * 0.7 + branch_angle = math.radians(30) + draw_tree(surface, end_x, end_y, new_length, new_angle - branch_angle, level - 1, t, wind_amp, wind_spd, colliders) + draw_tree(surface, end_x, end_y, new_length, new_angle + branch_angle, level - 1, t, wind_amp, wind_spd, colliders) + +# ------------------------------- +# Create Colliders +# ------------------------------- +colliders = [] +# Add a bouncing ball as a collider. +ball = Ball([400, 300], 20, repulsion_strength=30) +colliders.append(ball) + +# ------------------------------- +# Main Loop +# ------------------------------- +running = True +start_time = pygame.time.get_ticks() / 1000.0 + +while running: + t = pygame.time.get_ticks() / 1000.0 - start_time + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + elif event.type == pygame.KEYDOWN: + # Adjust wind parameters. + if event.key == pygame.K_UP: + wind_amplitude += 5 + elif event.key == pygame.K_DOWN: + wind_amplitude = max(0, wind_amplitude - 5) + elif event.key == pygame.K_RIGHT: + wind_speed += 0.5 + elif event.key == pygame.K_LEFT: + wind_speed = max(0.5, wind_speed - 0.5) + elif event.key == pygame.K_F3: + debug_mode = not debug_mode # Toggle debug overlay + + # Update colliders (the collider itself is not pushed by grass/leaves). + ball.update() + + # Draw background. + screen.fill((135, 206, 235)) + ground_rect = pygame.Rect(0, int(HEIGHT * 0.8), WIDTH, int(HEIGHT * 0.2)) + pygame.draw.rect(screen, (85, 107, 47), ground_rect) + + # Draw grass blades. + draw_grass(screen, t, wind_amplitude, wind_speed, colliders) + + # Reset leaf counter and draw the fractal tree. + leaf_count = 0 + tree_base_x = int(WIDTH * 0.2) + tree_base_y = int(HEIGHT * 0.8) + tree_length = 100 + tree_angle = 0 # vertical + tree_levels = 7 + draw_tree(screen, tree_base_x, tree_base_y, tree_length, tree_angle, tree_levels, t, wind_amplitude, wind_speed, colliders) + + # Draw the collider (ball). + ball.draw(screen) + + # Debug Menu overlay. + if debug_mode: + debug_texts = [ + f"FPS: {clock.get_fps():.1f}", + f"Grass Blades: {len(grass_blades)}", + f"Leaves: {leaf_count}", + f"Colliders: {len(colliders)}", + f"Wind Amplitude: {wind_amplitude}", + f"Wind Speed: {wind_speed}" + ] + for i, text in enumerate(debug_texts): + text_surface = font.render(text, True, (0, 0, 0)) + screen.blit(text_surface, (10, 10 + i * 20)) + + pygame.display.flip() + clock.tick(60) + +pygame.quit() +sys.exit() diff --git a/pygame-imgui/example.py b/pygame-imgui/example.py new file mode 100644 index 0000000..e69de29 diff --git a/pygame-imgui/pygui.py b/pygame-imgui/pygui.py new file mode 100644 index 0000000..e69de29