Tree And Grass Simulations...
This commit is contained in:
parent
58962e52fc
commit
dde22479b6
252
pygame-grass/main.py
Normal file
252
pygame-grass/main.py
Normal file
@ -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()
|
0
pygame-imgui/example.py
Normal file
0
pygame-imgui/example.py
Normal file
0
pygame-imgui/pygui.py
Normal file
0
pygame-imgui/pygui.py
Normal file
Loading…
Reference in New Issue
Block a user