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