from dataclasses import dataclass from typing import Generator from abc import ABC, abstractmethod import pygame as pg from transform import Transform @dataclass class Face: begin: pg.Vector2 end: pg.Vector2 @property def normal(self) -> pg.Vector2: return (self.end-self.begin).rotate(-90).normalize() @dataclass class ColliderContact: __slots__ = ["points", "normal", "penetration"] points: list[pg.Vector2] normal: pg.Vector2 penetration: float @dataclass(frozen=True) class PolygonalHull: _vertices: list[pg.Vector2] def get_vertex_at_index(self, key: int) -> pg.Vector2: return self._vertices[key] def get_face_at_index(self, key: int) -> Face: return Face(self._vertices[key], self._vertices[(key + 1) % len(self._vertices)]) def vertices(self) -> Generator[pg.Vector2, None, None]: for v in self._vertices: yield v def faces(self) -> Generator[Face, None, None]: for i in range(len(self._vertices)): yield self.get_face_at_index(i) def project(self, axis: pg.Vector2) -> tuple[float, float]: projections = [v.dot(axis) for v in self._vertices] return (min(projections), max(projections)) class BaseCollider(ABC): @abstractmethod def moment_of_inertia(self, mass: float) -> float: pass class ConvexCollider(BaseCollider): @abstractmethod def hull(self, transform: Transform) -> PolygonalHull: pass @dataclass class CircleCollider(BaseCollider): radius: float def moment_of_inertia(self, mass: float) -> float: return 0.5 * self.radius ** 2 * mass @dataclass class LineCollider(ConvexCollider): length: float def hull(self, transform: Transform) -> PolygonalHull: return PolygonalHull([ transform.global_position - pg.Vector2(self.length / 2.0, 0).rotate(transform.global_degrees) * transform.global_scale, transform.global_position + pg.Vector2(self.length / 2.0, 0).rotate(transform.global_degrees) * transform.global_scale ]) def moment_of_inertia(self, mass): return 1.0 / 12.0 * mass * self.length**2 @dataclass class RectCollider(ConvexCollider): dimensions: tuple[float, float] @property def width(self): return self.dimensions[0] @property def height(self): return self.dimensions[1] def hull(self, transform: Transform) -> PolygonalHull: return PolygonalHull([ transform.global_position - pg.Vector2(self.width / 2.0, self.height / 2.0).rotate(transform.global_degrees) * transform.global_scale, transform.global_position - pg.Vector2(-self.width / 2.0, self.height / 2.0).rotate(transform.global_degrees) * transform.global_scale, transform.global_position - pg.Vector2(-self.width / 2.0, -self.height / 2.0).rotate(transform.global_degrees) * transform.global_scale, transform.global_position - pg.Vector2(self.width / 2.0, -self.height / 2.0).rotate(transform.global_degrees) * transform.global_scale ]) def moment_of_inertia(self, mass: float) -> float: return (1.0 / 12.0) * mass * (self.width ** 2 + self.height ** 2)