overhaul to the collision system... should have maybe made more commits.
This commit is contained in:
218
main.py
218
main.py
@@ -1,176 +1,90 @@
|
||||
import pygame as pg
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from itertools import combinations
|
||||
from collections import namedtuple
|
||||
from random import random
|
||||
from math import pi
|
||||
from rigidbody import *
|
||||
from collider import LineCollider, CircleCollider
|
||||
|
||||
GLOBAL_GRAVITY=250
|
||||
DIMENSIONS = namedtuple("Dimensions", ["WIDTH", "HEIGHT"])(500,500)
|
||||
|
||||
@dataclass
|
||||
class ColliderContact:
|
||||
__slots__ = ["point", "normal", "penetration"]
|
||||
point: pg.Vector2
|
||||
normal: pg.Vector2
|
||||
penetration: float
|
||||
class Ball:
|
||||
|
||||
@dataclass
|
||||
class RigidBody:
|
||||
position: pg.Vector2
|
||||
collider: "BaseCollider"
|
||||
velocity: pg.Vector2
|
||||
mass: float = 1.0
|
||||
restitution: float = 0.5
|
||||
|
||||
@property
|
||||
def inv_mass(self):
|
||||
return 0.0 if self.mass == 0 else 1.0 / self.mass
|
||||
|
||||
class BaseCollider(ABC):
|
||||
|
||||
def collide(self, other: "BaseCollider", this_position: pg.Vector2, other_position: pg.Vector2) -> ColliderContact | None:
|
||||
if isinstance(other, CircleCollider):
|
||||
return self.collide_circle(other, this_position, other_position)
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def collide_circle(self, other: "CircleCollider", this_position: pg.Vector2, other_position: pg.Vector2) -> ColliderContact | None:
|
||||
pass
|
||||
|
||||
class CircleCollider(BaseCollider):
|
||||
|
||||
def __init__(self, radius: float):
|
||||
def __init__(self, transform: Transform, radius: float):
|
||||
self.transform = transform
|
||||
self.radius = radius
|
||||
|
||||
def collide_circle(self, other: "CircleCollider", this_position: pg.Vector2, other_position: pg.Vector2) -> ColliderContact | None:
|
||||
delta = this_position - other_position
|
||||
dist = delta.length()
|
||||
radii = self.radius + other.radius
|
||||
if dist >= radii or dist == 0:
|
||||
return None
|
||||
normal = delta.normalize()
|
||||
return ColliderContact(
|
||||
point=other_position + normal * other.radius,
|
||||
normal=normal,
|
||||
penetration=radii - dist,
|
||||
)
|
||||
|
||||
class PhysicsSystem:
|
||||
|
||||
def __init__(self):
|
||||
self.bodies: list[RigidBody] = []
|
||||
self.gravity = pg.Vector2(0,GLOBAL_GRAVITY)
|
||||
|
||||
def add_body(self, body: RigidBody) -> None:
|
||||
self.bodies.append(body)
|
||||
|
||||
def update(self, dt: float):
|
||||
g = self.gravity * dt
|
||||
for body in self.bodies:
|
||||
body.velocity += g
|
||||
body.position += dt * body.velocity
|
||||
self.resolve_bounds(body)
|
||||
|
||||
for a, b in combinations(self.bodies, 2):
|
||||
if collision := a.collider.collide(b.collider, a.position, b.position):
|
||||
self.resolve_collision(a, b, collision)
|
||||
|
||||
def resolve_collision(self, a: RigidBody, b: RigidBody, collision: ColliderContact) -> None:
|
||||
SLACK=0.4
|
||||
correction = collision.penetration / (a.inv_mass + b.inv_mass) * SLACK * collision.normal
|
||||
a.position += correction
|
||||
b.position -= correction
|
||||
|
||||
v_rel = a.velocity - b.velocity
|
||||
restitution = a.restitution * b.restitution
|
||||
impulse = (-(1 + restitution) * (collision.normal.dot(v_rel))) / (a.inv_mass + b.inv_mass)
|
||||
a.velocity += collision.normal*impulse*a.inv_mass
|
||||
b.velocity -= collision.normal*impulse*b.inv_mass
|
||||
|
||||
def resolve_bounds(self, body: RigidBody) -> None:
|
||||
r = body.collider.radius
|
||||
|
||||
if body.position.x - r < 0:
|
||||
body.position.x = r
|
||||
body.velocity.x = abs(body.velocity.x) * body.restitution
|
||||
elif body.position.x + r > DIMENSIONS.WIDTH:
|
||||
body.position.x = DIMENSIONS.WIDTH - r
|
||||
body.velocity.x = -abs(body.velocity.x) * body.restitution
|
||||
|
||||
if body.position.y - r < 0:
|
||||
body.position.y = r
|
||||
body.velocity.y = abs(body.velocity.y) * body.restitution
|
||||
elif body.position.y + r > DIMENSIONS.HEIGHT:
|
||||
body.position.y = DIMENSIONS.HEIGHT - r
|
||||
body.velocity.y = -abs(body.velocity.y) * body.restitution
|
||||
|
||||
class Circle:
|
||||
|
||||
def __init__(self, radius: float, body: RigidBody, sprite: pg.Surface):
|
||||
self.radius = radius
|
||||
self.body = body
|
||||
self.sprite = sprite
|
||||
self.surface = pg.Surface((2*self.radius, 2*self.radius), pg.SRCALPHA)
|
||||
self.surface.fill((255,255,255,0))
|
||||
pg.draw.circle(
|
||||
self.surface,
|
||||
color=(0,255,0,255),
|
||||
center=(self.radius,self.radius),
|
||||
radius=self.radius
|
||||
)
|
||||
|
||||
def draw(self, screen: pg.Surface):
|
||||
screen.blit(
|
||||
self.sprite,
|
||||
(
|
||||
self.body.position.x - self.radius,
|
||||
self.body.position.y - self.radius
|
||||
)
|
||||
)
|
||||
|
||||
class World:
|
||||
|
||||
def __init__(self, screen: pg.Surface):
|
||||
self.physics = PhysicsSystem()
|
||||
self.screen = screen
|
||||
self.entities = []
|
||||
|
||||
def update(self, dt) -> None:
|
||||
self.physics.update(dt)
|
||||
|
||||
def draw(self) -> None:
|
||||
for entity in self.entities:
|
||||
entity.draw(self.screen)
|
||||
surface = pg.transform.rotate(self.surface, self.transform.global_degrees)
|
||||
screen.blit(surface, self.transform.global_position - pg.Vector2(self.radius, self.radius))
|
||||
|
||||
def spawn_circle(self, radius: float, position: pg.Vector2, velocity: pg.Vector2 | None = None, restitution:float = 0.5, mass: float = 1.0) -> None:
|
||||
if velocity is None:
|
||||
velocity = pg.Vector2(0,0)
|
||||
collider = CircleCollider(radius)
|
||||
body = RigidBody(position=position, collider=collider, velocity=velocity, restitution=restitution,mass=mass)
|
||||
self.physics.add_body(body)
|
||||
sprite = pg.Surface((2*radius, 2*radius),pg.SRCALPHA)
|
||||
sprite.fill((255,255,255,0))
|
||||
pg.draw.circle(
|
||||
sprite,
|
||||
color=(0,255,0,255),
|
||||
center=(radius, radius),
|
||||
radius=radius
|
||||
class Square:
|
||||
|
||||
def __init__(self, transform: Transform, side: float, color=(255,0,0,255)):
|
||||
self.transform = transform
|
||||
self.side = side
|
||||
self.surface = pg.Surface((side, side), pg.SRCALPHA)
|
||||
self.surface.fill((255,255,255,0))
|
||||
pg.draw.rect(
|
||||
self.surface,
|
||||
color=color,
|
||||
rect=pg.Rect(0, 0, self.side, self.side)
|
||||
)
|
||||
self.entities.append(Circle(radius,body,sprite))
|
||||
|
||||
def draw(self, screen: pg.Surface):
|
||||
|
||||
surface = pg.transform.rotate(self.surface, self.transform.global_degrees)
|
||||
screen.blit(surface, self.transform.global_position - pg.Vector2(self.side / 2.0, self.side / 2.0))
|
||||
|
||||
def main():
|
||||
running=True
|
||||
pg.init()
|
||||
clock=pg.time.Clock()
|
||||
screen = pg.display.set_mode(size=DIMENSIONS)
|
||||
world=World(screen)
|
||||
screen = pg.display.set_mode((500,500))
|
||||
|
||||
physics = PhysicsSystem()
|
||||
|
||||
ball_transform = Transform(position=pg.Vector2(250,250), rotation=pi/8.0)
|
||||
square_transform = Transform(position=pg.Vector2(250, 100))
|
||||
|
||||
ball = Square(ball_transform, 20)
|
||||
|
||||
ball2 = Square(square_transform, 20, color=(0,255,0,255))
|
||||
|
||||
physics.add_body(
|
||||
RigidBody(
|
||||
ball_transform,
|
||||
RectCollider((20,20)),
|
||||
velocity=pg.Vector2(0,-400),
|
||||
restitution=1.0,
|
||||
)
|
||||
)
|
||||
|
||||
physics.add_body(
|
||||
RigidBody(
|
||||
square_transform,
|
||||
RectCollider((20,20)),
|
||||
pg.Vector2(0,0),
|
||||
restitution=1.0
|
||||
)
|
||||
)
|
||||
|
||||
while running:
|
||||
|
||||
dt = clock.tick(144) / 1000
|
||||
screen.fill((0,0,0,0))
|
||||
world.update(dt)
|
||||
world.draw()
|
||||
|
||||
physics.update(dt)
|
||||
ball.draw(screen)
|
||||
ball2.draw(screen)
|
||||
pg.display.flip()
|
||||
for event in pg.event.get():
|
||||
if event.type == pg.MOUSEBUTTONDOWN:
|
||||
world.spawn_circle(
|
||||
10,
|
||||
pg.Vector2(pg.mouse.get_pos()),
|
||||
restitution=random()
|
||||
)
|
||||
|
||||
if event.type == pg.QUIT:
|
||||
running = False
|
||||
|
||||
|
||||
Reference in New Issue
Block a user