Last active
April 24, 2025 22:11
-
-
Save gary149/4ad09c24b262a782c8688543b7cf1155 to your computer and use it in GitHub Desktop.
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
| import pygame | |
| import sys | |
| import math | |
| import numpy as np | |
| # Initialize pygame | |
| pygame.init() | |
| # Constants | |
| WIDTH, HEIGHT = 800, 600 | |
| FPS = 60 | |
| WHITE = (255, 255, 255) | |
| BLACK = (0, 0, 0) | |
| RED = (255, 0, 0) | |
| BLUE = (0, 0, 255) | |
| # Physics constants | |
| GRAVITY = 0.5 | |
| AIR_RESISTANCE = 0.995 | |
| FRICTION = 0.6 | |
| RESTITUTION = 0.85 | |
| class Ball: | |
| def __init__(self, x, y, radius=15): | |
| self.x = x | |
| self.y = y | |
| self.radius = radius | |
| self.vx = 2 | |
| self.vy = 0 | |
| self.mass = radius * 0.5 | |
| def update(self): | |
| # Apply gravity | |
| self.vy += GRAVITY | |
| # Apply air resistance | |
| self.vx *= AIR_RESISTANCE | |
| self.vy *= AIR_RESISTANCE | |
| # Update position | |
| self.x += self.vx | |
| self.y += self.vy | |
| def draw(self, screen): | |
| pygame.draw.circle(screen, RED, (int(self.x), int(self.y)), self.radius) | |
| class Hexagon: | |
| def __init__(self, center_x, center_y, radius=200): | |
| self.center_x = center_x | |
| self.center_y = center_y | |
| self.radius = radius | |
| self.rotation_speed = 0.01 # radians per frame | |
| self.angle = 0 | |
| self.vertices = [] | |
| self.update_vertices() | |
| def update(self): | |
| self.angle += self.rotation_speed | |
| self.update_vertices() | |
| def update_vertices(self): | |
| self.vertices = [] | |
| for i in range(6): | |
| angle = self.angle + i * (2 * math.pi / 6) | |
| x = self.center_x + self.radius * math.cos(angle) | |
| y = self.center_y + self.radius * math.sin(angle) | |
| self.vertices.append((x, y)) | |
| def draw(self, screen): | |
| pygame.draw.polygon(screen, BLUE, self.vertices, 2) | |
| def check_collision(ball, hexagon): | |
| # Check collision with each edge of the hexagon | |
| for i in range(6): | |
| p1 = hexagon.vertices[i] | |
| p2 = hexagon.vertices[(i + 1) % 6] | |
| # Vector from p1 to p2 | |
| edge_vector = (p2[0] - p1[0], p2[1] - p1[1]) | |
| edge_length = math.sqrt(edge_vector[0] ** 2 + edge_vector[1] ** 2) | |
| # Normalized edge vector | |
| if edge_length > 0: | |
| edge_normal = (edge_vector[0] / edge_length, edge_vector[1] / edge_length) | |
| else: | |
| continue | |
| # Vector from p1 to ball center | |
| to_ball = (ball.x - p1[0], ball.y - p1[1]) | |
| # Project to_ball onto the edge | |
| projection_length = to_ball[0] * edge_normal[0] + to_ball[1] * edge_normal[1] | |
| # Clamp projection to edge length | |
| projection_length = max(0, min(edge_length, projection_length)) | |
| # Find closest point on edge to ball | |
| closest_point = ( | |
| p1[0] + projection_length * edge_normal[0], | |
| p1[1] + projection_length * edge_normal[1], | |
| ) | |
| # Distance from ball to closest point | |
| dx = ball.x - closest_point[0] | |
| dy = ball.y - closest_point[1] | |
| distance = math.sqrt(dx**2 + dy**2) | |
| # Check if collision occurred | |
| if distance <= ball.radius: | |
| # Calculate normal vector (perpendicular to edge) | |
| normal = (-edge_normal[1], edge_normal[0]) | |
| # Make sure normal points toward the ball | |
| dot_product = dx * normal[0] + dy * normal[1] | |
| if dot_product < 0: | |
| normal = (-normal[0], -normal[1]) | |
| # Calculate relative velocity of ball to wall | |
| # For a rotating hexagon, we need to consider the velocity of the wall at the contact point | |
| wall_vx = -hexagon.rotation_speed * (closest_point[1] - hexagon.center_y) | |
| wall_vy = hexagon.rotation_speed * (closest_point[0] - hexagon.center_x) | |
| rel_vx = ball.vx - wall_vx | |
| rel_vy = ball.vy - wall_vy | |
| # Calculate velocity component along the normal | |
| vel_along_normal = rel_vx * normal[0] + rel_vy * normal[1] | |
| # Only resolve collision if objects are moving toward each other | |
| if vel_along_normal < 0: | |
| # Calculate impulse scalar | |
| j = -(1 + RESTITUTION) * vel_along_normal | |
| # Apply impulse to ball's velocity | |
| ball.vx += j * normal[0] | |
| ball.vy += j * normal[1] | |
| # Apply friction to the tangential component | |
| tangent = (-normal[1], normal[0]) | |
| vel_along_tangent = rel_vx * tangent[0] + rel_vy * tangent[1] | |
| ball.vx -= (1 - FRICTION) * vel_along_tangent * tangent[0] | |
| ball.vy -= (1 - FRICTION) * vel_along_tangent * tangent[1] | |
| # Move ball outside of wall to prevent sticking | |
| penetration_depth = ball.radius - distance | |
| ball.x += penetration_depth * normal[0] | |
| ball.y += penetration_depth * normal[1] | |
| return True | |
| return False | |
| def main(): | |
| screen = pygame.display.set_mode((WIDTH, HEIGHT)) | |
| pygame.display.set_caption("Ball Bouncing in a Spinning Hexagon") | |
| clock = pygame.time.Clock() | |
| ball = Ball(WIDTH // 2, HEIGHT // 3) | |
| hexagon = Hexagon(WIDTH // 2, HEIGHT // 2) | |
| running = True | |
| while running: | |
| for event in pygame.event.get(): | |
| if event.type == pygame.QUIT: | |
| running = False | |
| # Update physics | |
| ball.update() | |
| hexagon.update() | |
| # Check for collisions | |
| check_collision(ball, hexagon) | |
| # Draw everything | |
| screen.fill(BLACK) | |
| hexagon.draw(screen) | |
| ball.draw(screen) | |
| pygame.display.flip() | |
| clock.tick(FPS) | |
| 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