From 09f70223b83aef0519c619e34b9692123679e819 Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Sat, 18 Oct 2025 21:02:24 +0200 Subject: [PATCH] feat: add collisions --- src/car.py | 32 ++++++++++++++++++++++++++++++++ src/game.py | 1 + src/objects/road.py | 12 ++++++++++++ src/track.py | 6 ++++++ src/track_object.py | 4 ++++ src/utils.py | 29 +++++++++++++++++++++++++++++ src/vec.py | 5 +++++ 7 files changed, 89 insertions(+) diff --git a/src/car.py b/src/car.py index eda8634..79e8a67 100644 --- a/src/car.py +++ b/src/car.py @@ -3,6 +3,7 @@ from math import radians import pygame from src.camera import Camera +from src.utils import segments_intersect from src.vec import Vec @@ -13,6 +14,7 @@ class Car: COLOR = (230, 150, 80) WIDTH = 0.4 LENGTH = 0.6 + COLLISION_MARGIN = 0.4 def __init__(self, pos: Vec, direction: Vec) -> None: self.pos: Vec = pos @@ -22,6 +24,7 @@ class Car: self.backward: bool = False self.left: bool = False self.right: bool = False + self.colliding: bool = False def update(self): if self.forward: @@ -45,6 +48,8 @@ class Car: self.direction = self.direction.rotate(rotate_angle) self.speed *= 0.98 + if abs(self.speed) < 1e-8: + self.speed = 0 self.pos += self.direction * self.speed @@ -62,3 +67,30 @@ class Car: p3: Vec = pt - u - v p4: Vec = pt + u - v return [p1, p2, p3, p4] + + def check_collisions(self, polygons: list[list[Vec]]): + self.colliding = False + corners: list[Vec] = self.get_corners() + sides: list[tuple[Vec, Vec]] = [ + (corners[i], corners[(i + 1) % 4]) for i in range(4) + ] + + for polygon in polygons: + n_pts: int = len(polygon) + for i in range(n_pts): + pt1: Vec = polygon[i] + pt2: Vec = polygon[(i + 1) % n_pts] + d: Vec = pt2 - pt1 + + for s1, s2 in sides: + if segments_intersect(s1, s2, pt1, pt2): + self.colliding = True + self.direction = d.normalized + n: Vec = self.direction.perp + dist: float = (self.pos - pt1).dot(n) + if dist < 0: + n *= -1 + dist = -dist + self.speed = 0 + self.pos = self.pos + n * (self.COLLISION_MARGIN - dist) + return diff --git a/src/game.py b/src/game.py index 8afffaa..330d4ad 100644 --- a/src/game.py +++ b/src/game.py @@ -34,6 +34,7 @@ class Game: while self.running: self.process_pygame_events() self.car.update() + self.car.check_collisions(self.track.get_collision_polygons()) self.render() self.clock.tick(60) diff --git a/src/objects/road.py b/src/objects/road.py index 03e1ee8..3b199a0 100644 --- a/src/objects/road.py +++ b/src/objects/road.py @@ -42,6 +42,18 @@ class Road(TrackObject): pygame.draw.lines(surf, (255, 255, 255), True, side1) pygame.draw.lines(surf, (255, 255, 255), True, side2) + def get_collision_polygons(self) -> list[list[Vec]]: + side1: list[Vec] = [] + side2: list[Vec] = [] + for pt in self.pts: + p1: Vec = pt.pos + p2: Vec = p1 + pt.normal * pt.width + p3: Vec = p1 - pt.normal * pt.width + side1.append(p2) + side2.append(p3) + + return [side1, side2] + class RoadPoint: def __init__(self, pos: Vec, normal: Vec, width: float) -> None: diff --git a/src/track.py b/src/track.py index eed9f09..ecbd114 100644 --- a/src/track.py +++ b/src/track.py @@ -46,3 +46,9 @@ class Track: def render(self, surf: pygame.Surface, camera: Camera): for object in self.objects: object.render(surf, camera) + + def get_collision_polygons(self) -> list[list[Vec]]: + polygons: list[list[Vec]] = [] + for obj in self.objects: + polygons.extend(obj.get_collision_polygons()) + return polygons diff --git a/src/track_object.py b/src/track_object.py index abbf82e..4d318e9 100644 --- a/src/track_object.py +++ b/src/track_object.py @@ -7,6 +7,7 @@ import pygame import src.objects from src.camera import Camera +from src.vec import Vec class TrackObjectType(StrEnum): @@ -40,3 +41,6 @@ class TrackObject: def render(self, surf: pygame.Surface, camera: Camera): pass + + def get_collision_polygons(self) -> list[list[Vec]]: + return [] diff --git a/src/utils.py b/src/utils.py index 4b58a5c..541a901 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,5 +1,34 @@ import os from pathlib import Path +from src.vec import Vec + ROOT = Path(os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))) + + +def orientation(a: Vec, b: Vec, c: Vec) -> float: + return (b - a).cross(c - a) + + +def segments_intersect(a1: Vec, a2: Vec, b1: Vec, b2: Vec) -> bool: + o1 = orientation(a1, a2, b1) + o2 = orientation(a1, a2, b2) + o3 = orientation(b1, b2, a1) + o4 = orientation(b1, b2, a2) + + # General case: segments straddle each other + if (o1 * o2 < 0) and (o3 * o4 < 0): + return True + + # Special cases: Collinear overlaps + if o1 == 0 and b1.within(a1, a2): + return True + if o2 == 0 and b2.within(a1, a2): + return True + if o3 == 0 and a1.within(b1, b2): + return True + if o4 == 0 and a2.within(b1, b2): + return True + + return False diff --git a/src/vec.py b/src/vec.py index 52945b1..dd3e278 100644 --- a/src/vec.py +++ b/src/vec.py @@ -61,3 +61,8 @@ class Vec: cos(angle) * self.x - sin(angle) * self.y, sin(angle) * self.x + cos(angle) * self.y, ) + + def within(self, p1: Vec, p2: Vec) -> bool: + x1, x2 = min(p1.x, p2.x), max(p1.x, p2.x) + y1, y2 = min(p1.y, p2.y), max(p1.y, p2.y) + return (x1 <= self.x <= x2) and (y1 <= self.y <= y2)