feat: add collisions
This commit is contained in:
		
							
								
								
									
										32
									
								
								src/car.py
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								src/car.py
									
									
									
									
									
								
							| @@ -3,6 +3,7 @@ from math import radians | |||||||
| import pygame | import pygame | ||||||
|  |  | ||||||
| from src.camera import Camera | from src.camera import Camera | ||||||
|  | from src.utils import segments_intersect | ||||||
| from src.vec import Vec | from src.vec import Vec | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -13,6 +14,7 @@ class Car: | |||||||
|     COLOR = (230, 150, 80) |     COLOR = (230, 150, 80) | ||||||
|     WIDTH = 0.4 |     WIDTH = 0.4 | ||||||
|     LENGTH = 0.6 |     LENGTH = 0.6 | ||||||
|  |     COLLISION_MARGIN = 0.4 | ||||||
|  |  | ||||||
|     def __init__(self, pos: Vec, direction: Vec) -> None: |     def __init__(self, pos: Vec, direction: Vec) -> None: | ||||||
|         self.pos: Vec = pos |         self.pos: Vec = pos | ||||||
| @@ -22,6 +24,7 @@ class Car: | |||||||
|         self.backward: bool = False |         self.backward: bool = False | ||||||
|         self.left: bool = False |         self.left: bool = False | ||||||
|         self.right: bool = False |         self.right: bool = False | ||||||
|  |         self.colliding: bool = False | ||||||
|  |  | ||||||
|     def update(self): |     def update(self): | ||||||
|         if self.forward: |         if self.forward: | ||||||
| @@ -45,6 +48,8 @@ class Car: | |||||||
|             self.direction = self.direction.rotate(rotate_angle) |             self.direction = self.direction.rotate(rotate_angle) | ||||||
|  |  | ||||||
|         self.speed *= 0.98 |         self.speed *= 0.98 | ||||||
|  |         if abs(self.speed) < 1e-8: | ||||||
|  |             self.speed = 0 | ||||||
|  |  | ||||||
|         self.pos += self.direction * self.speed |         self.pos += self.direction * self.speed | ||||||
|  |  | ||||||
| @@ -62,3 +67,30 @@ class Car: | |||||||
|         p3: Vec = pt - u - v |         p3: Vec = pt - u - v | ||||||
|         p4: Vec = pt + u - v |         p4: Vec = pt + u - v | ||||||
|         return [p1, p2, p3, p4] |         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 | ||||||
|   | |||||||
| @@ -34,6 +34,7 @@ class Game: | |||||||
|         while self.running: |         while self.running: | ||||||
|             self.process_pygame_events() |             self.process_pygame_events() | ||||||
|             self.car.update() |             self.car.update() | ||||||
|  |             self.car.check_collisions(self.track.get_collision_polygons()) | ||||||
|             self.render() |             self.render() | ||||||
|             self.clock.tick(60) |             self.clock.tick(60) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -42,6 +42,18 @@ class Road(TrackObject): | |||||||
|         pygame.draw.lines(surf, (255, 255, 255), True, side1) |         pygame.draw.lines(surf, (255, 255, 255), True, side1) | ||||||
|         pygame.draw.lines(surf, (255, 255, 255), True, side2) |         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: | class RoadPoint: | ||||||
|     def __init__(self, pos: Vec, normal: Vec, width: float) -> None: |     def __init__(self, pos: Vec, normal: Vec, width: float) -> None: | ||||||
|   | |||||||
| @@ -46,3 +46,9 @@ class Track: | |||||||
|     def render(self, surf: pygame.Surface, camera: Camera): |     def render(self, surf: pygame.Surface, camera: Camera): | ||||||
|         for object in self.objects: |         for object in self.objects: | ||||||
|             object.render(surf, camera) |             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 | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import pygame | |||||||
|  |  | ||||||
| import src.objects | import src.objects | ||||||
| from src.camera import Camera | from src.camera import Camera | ||||||
|  | from src.vec import Vec | ||||||
|  |  | ||||||
|  |  | ||||||
| class TrackObjectType(StrEnum): | class TrackObjectType(StrEnum): | ||||||
| @@ -40,3 +41,6 @@ class TrackObject: | |||||||
|  |  | ||||||
|     def render(self, surf: pygame.Surface, camera: Camera): |     def render(self, surf: pygame.Surface, camera: Camera): | ||||||
|         pass |         pass | ||||||
|  |  | ||||||
|  |     def get_collision_polygons(self) -> list[list[Vec]]: | ||||||
|  |         return [] | ||||||
|   | |||||||
							
								
								
									
										29
									
								
								src/utils.py
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								src/utils.py
									
									
									
									
									
								
							| @@ -1,5 +1,34 @@ | |||||||
| import os | import os | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
|  |  | ||||||
|  | from src.vec import Vec | ||||||
|  |  | ||||||
|  |  | ||||||
| ROOT = Path(os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))) | 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 | ||||||
|   | |||||||
| @@ -61,3 +61,8 @@ class Vec: | |||||||
|             cos(angle) * self.x - sin(angle) * self.y, |             cos(angle) * self.x - sin(angle) * self.y, | ||||||
|             sin(angle) * self.x + cos(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) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user