Compare commits
3 Commits
8989341714
...
91e93759e8
| Author | SHA1 | Date | |
|---|---|---|---|
|
91e93759e8
|
|||
|
04ac674982
|
|||
|
8ca15eaa78
|
56
src/car.py
56
src/car.py
@@ -1,9 +1,10 @@
|
||||
from math import radians
|
||||
from typing import Optional
|
||||
|
||||
import pygame
|
||||
|
||||
from src.camera import Camera
|
||||
from src.utils import segments_intersect
|
||||
from src.utils import get_segments_intersection, segments_intersect
|
||||
from src.vec import Vec
|
||||
|
||||
|
||||
@@ -12,14 +13,17 @@ sign = lambda x: 0 if x == 0 else (-1 if x < 0 else 1)
|
||||
|
||||
class Car:
|
||||
MAX_SPEED = 5
|
||||
MAX_BACK_SPEED = -2
|
||||
MAX_BACK_SPEED = -3
|
||||
ROTATE_SPEED = 1
|
||||
COLOR = (230, 150, 80)
|
||||
WIDTH = 0.4
|
||||
LENGTH = 0.6
|
||||
COLLISION_MARGIN = 0.4
|
||||
ACCELERATION = 2
|
||||
FRICTION = 3
|
||||
FRICTION = 2.5
|
||||
N_RAYS = 15
|
||||
RAYS_FOV = 180
|
||||
RAYS_MAX_DIST = 100
|
||||
|
||||
def __init__(self, pos: Vec, direction: Vec) -> None:
|
||||
self.pos: Vec = pos
|
||||
@@ -31,6 +35,9 @@ class Car:
|
||||
self.right: bool = False
|
||||
self.colliding: bool = False
|
||||
|
||||
self.rays: list[float] = [0] * self.N_RAYS
|
||||
self.rays_end: list[Vec] = [Vec() for _ in range(self.N_RAYS)]
|
||||
|
||||
def update(self, dt: float):
|
||||
if self.forward:
|
||||
self.speed += self.ACCELERATION * dt
|
||||
@@ -53,14 +60,21 @@ class Car:
|
||||
self.direction = self.direction.rotate(rotate_angle)
|
||||
|
||||
if not self.forward and not self.backward:
|
||||
fn = max if self.speed >= 0 else min
|
||||
self.speed -= sign(self.speed) * self.FRICTION * dt
|
||||
self.speed = fn(0, self.speed)
|
||||
|
||||
if abs(self.speed) < 1e-4:
|
||||
self.speed = 0
|
||||
|
||||
self.pos += self.direction * self.speed * dt
|
||||
|
||||
def render(self, surf: pygame.Surface, camera: Camera):
|
||||
def render(self, surf: pygame.Surface, camera: Camera, show_raycasts: bool = False):
|
||||
if show_raycasts:
|
||||
pos: Vec = camera.world2screen(self.pos)
|
||||
for p in self.rays_end:
|
||||
pygame.draw.line(surf, (255, 0, 0), pos, camera.world2screen(p), 2)
|
||||
|
||||
pts: list[Vec] = self.get_corners()
|
||||
pts = [camera.world2screen(p) for p in pts]
|
||||
pygame.draw.polygon(surf, self.COLOR, pts)
|
||||
@@ -76,6 +90,8 @@ class Car:
|
||||
return [p1, p2, p3, p4]
|
||||
|
||||
def check_collisions(self, polygons: list[list[Vec]]):
|
||||
self.cast_rays(polygons)
|
||||
|
||||
self.colliding = False
|
||||
corners: list[Vec] = self.get_corners()
|
||||
sides: list[tuple[Vec, Vec]] = [
|
||||
@@ -101,3 +117,35 @@ class Car:
|
||||
self.speed = 0
|
||||
self.pos = self.pos + n * (self.COLLISION_MARGIN - dist)
|
||||
return
|
||||
|
||||
def cast_rays(self, polygons: list[list[Vec]]):
|
||||
for i in range(self.N_RAYS):
|
||||
angle: float = radians((i / (self.N_RAYS - 1) - 0.5) * self.RAYS_FOV)
|
||||
p: Optional[Vec] = self.cast_ray(angle, polygons)
|
||||
self.rays[i] = self.RAYS_MAX_DIST if p is None else (p - self.pos).mag()
|
||||
self.rays_end[i] = self.pos if p is None else p
|
||||
|
||||
def cast_ray(self, angle: float, polygons: list[list[Vec]]) -> Optional[Vec]:
|
||||
v: Vec = self.direction.normalized.rotate(angle)
|
||||
|
||||
segments: list[tuple[Vec, Vec]] = []
|
||||
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]
|
||||
segments.append((pt1, pt2))
|
||||
|
||||
p1: Vec = self.pos
|
||||
p2: Vec = p1 + v * self.RAYS_MAX_DIST
|
||||
dist: float = self.RAYS_MAX_DIST
|
||||
closest: Optional[Vec] = None
|
||||
|
||||
for q1, q2 in segments:
|
||||
p: Optional[Vec] = get_segments_intersection(p1, p2, q1, q2)
|
||||
if p is not None:
|
||||
d: float = (p - p1).mag()
|
||||
if d < dist:
|
||||
dist = d
|
||||
closest = p
|
||||
return closest
|
||||
|
||||
21
src/game.py
21
src/game.py
@@ -31,6 +31,7 @@ class Game:
|
||||
)
|
||||
self.show_fps: bool = True
|
||||
self.show_speed: bool = True
|
||||
self.show_raycasts: bool = True
|
||||
|
||||
def mainloop(self):
|
||||
while self.running:
|
||||
@@ -38,13 +39,11 @@ class Game:
|
||||
self.process_pygame_events()
|
||||
self.car.update(dt)
|
||||
self.car.check_collisions(self.track.get_collision_polygons())
|
||||
self.update_camera()
|
||||
self.render()
|
||||
self.clock.tick(60)
|
||||
|
||||
def process_pygame_events(self):
|
||||
self.camera.set_pos(self.car.pos)
|
||||
self.camera.set_direction(self.car.direction)
|
||||
self.camera.set_size(Vec(*self.win.get_size()))
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
self.quit()
|
||||
@@ -58,13 +57,18 @@ class Game:
|
||||
elif event.type == pygame.KEYUP:
|
||||
self.on_key_up(event)
|
||||
|
||||
def update_camera(self):
|
||||
self.camera.set_pos(self.car.pos)
|
||||
self.camera.set_direction(self.car.direction)
|
||||
self.camera.set_size(Vec(*self.win.get_size()))
|
||||
|
||||
def quit(self):
|
||||
self.running = False
|
||||
|
||||
def render(self):
|
||||
self.win.fill(self.BACKGROUND_COLOR)
|
||||
self.track.render(self.win, self.camera)
|
||||
self.car.render(self.win, self.camera)
|
||||
self.car.render(self.win, self.camera, self.show_raycasts)
|
||||
if self.show_fps:
|
||||
self.render_fps()
|
||||
if self.show_speed:
|
||||
@@ -85,6 +89,10 @@ class Game:
|
||||
self.show_fps = not self.show_fps
|
||||
elif event.key == pygame.K_v:
|
||||
self.show_speed = not self.show_speed
|
||||
elif event.key == pygame.K_c:
|
||||
self.show_raycasts = not self.show_raycasts
|
||||
elif event.key == pygame.K_r:
|
||||
self.reset()
|
||||
|
||||
def on_key_up(self, event: pygame.event.Event):
|
||||
if event.key == pygame.K_w:
|
||||
@@ -125,3 +133,8 @@ class Game:
|
||||
pts2.append((ox + r2 * dx, oy + r2 * dy))
|
||||
|
||||
pygame.draw.polygon(self.win, (200, 200, 200), pts1 + pts2[::-1])
|
||||
|
||||
def reset(self):
|
||||
self.car.pos = self.track.start_pos
|
||||
self.car.direction = self.track.start_dir
|
||||
self.car.speed = 0
|
||||
|
||||
@@ -10,9 +10,14 @@ from src.vec import Vec
|
||||
class Road(TrackObject):
|
||||
type = TrackObjectType.Road
|
||||
|
||||
STRIP_LENGTH = 0.5
|
||||
STRIP_GAP = 0.5
|
||||
|
||||
def __init__(self, pts: list[RoadPoint]) -> None:
|
||||
super().__init__()
|
||||
self.pts: list[RoadPoint] = pts
|
||||
self.strips: list[tuple[Vec, Vec]] = []
|
||||
self.compute_strips()
|
||||
|
||||
@classmethod
|
||||
def load(cls, data: dict) -> Road:
|
||||
@@ -40,6 +45,15 @@ class Road(TrackObject):
|
||||
pygame.draw.lines(surf, (255, 255, 255), True, side1)
|
||||
pygame.draw.lines(surf, (255, 255, 255), True, side2)
|
||||
|
||||
for p1, p2 in self.strips:
|
||||
pygame.draw.line(
|
||||
surf,
|
||||
(255, 255, 255),
|
||||
camera.world2screen(p1),
|
||||
camera.world2screen(p2),
|
||||
6,
|
||||
)
|
||||
|
||||
def get_collision_polygons(self) -> list[list[Vec]]:
|
||||
side1: list[Vec] = []
|
||||
side2: list[Vec] = []
|
||||
@@ -49,9 +63,50 @@ class Road(TrackObject):
|
||||
p3: Vec = p1 - pt.normal * pt.width
|
||||
side1.append(p2)
|
||||
side2.append(p3)
|
||||
|
||||
return [side1, side2]
|
||||
|
||||
def compute_strips(self):
|
||||
n: int = len(self.pts)
|
||||
vecs: list[Vec] = [
|
||||
self.pts[(i + 1) % n].pos - pt.pos for i, pt in enumerate(self.pts)
|
||||
]
|
||||
lengths: list[float] = [v.mag() for v in vecs]
|
||||
cum_sums: list[float] = [0]
|
||||
for l in lengths:
|
||||
cum_sums.append(cum_sums[-1] + l)
|
||||
self.strips = []
|
||||
total_length: float = sum(lengths)
|
||||
|
||||
def get_pt(length: float) -> tuple[int, float]:
|
||||
length %= total_length
|
||||
for i, cs in list(enumerate(cum_sums))[::-1]:
|
||||
if cs <= length:
|
||||
return (i, (length - cs) / lengths[i])
|
||||
raise ValueError()
|
||||
|
||||
l0: float = 0
|
||||
while l0 < total_length:
|
||||
l1: float = l0 + self.STRIP_LENGTH
|
||||
i0, t0 = get_pt(l0)
|
||||
i1, t1 = get_pt(l1)
|
||||
p0: Vec = self.pts[i0].pos + vecs[i0] * t0
|
||||
p1: Vec = self.pts[i1].pos + vecs[i1] * t1
|
||||
if i0 == i1:
|
||||
self.strips.append((p0, p1))
|
||||
elif (i0 + 1) % n == i1:
|
||||
pm: Vec = self.pts[i1].pos
|
||||
self.strips.append((p0, pm))
|
||||
self.strips.append((pm, p1))
|
||||
else:
|
||||
self.strips.append((p0, self.pts[(i0 + 1) % n].pos))
|
||||
i = (i0 + 1) % n
|
||||
while i != i1:
|
||||
i2 = (i + 1) % n
|
||||
self.strips.append((self.pts[i].pos, self.pts[i2].pos))
|
||||
i = i2
|
||||
self.strips.append((self.pts[i1].pos, p1))
|
||||
l0 = l1 + self.STRIP_GAP
|
||||
|
||||
|
||||
class RoadPoint:
|
||||
def __init__(self, pos: Vec, normal: Vec, width: float) -> None:
|
||||
|
||||
29
src/utils.py
29
src/utils.py
@@ -1,9 +1,9 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from src.vec import Vec
|
||||
|
||||
|
||||
ROOT = Path(os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)))
|
||||
|
||||
|
||||
@@ -32,3 +32,30 @@ def segments_intersect(a1: Vec, a2: Vec, b1: Vec, b2: Vec) -> bool:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_segments_intersection(a1: Vec, a2: Vec, b1: Vec, b2: Vec) -> Optional[Vec]:
|
||||
da: Vec = a2 - a1
|
||||
db: Vec = b2 - b1
|
||||
dp: Vec = a1 - b1
|
||||
dap: Vec = da.perp
|
||||
denom: float = dap.dot(db)
|
||||
|
||||
if abs(denom) < 1e-9:
|
||||
o1: float = da.cross(-dp)
|
||||
if abs(o1) < 1e-9:
|
||||
for p in [b1, b2]:
|
||||
if p.within(a1, a2):
|
||||
return p
|
||||
for p in [a1, a2]:
|
||||
if p.within(b1, b2):
|
||||
return p
|
||||
return None
|
||||
return None
|
||||
|
||||
num: float = dap.dot(dp)
|
||||
t: float = num / denom
|
||||
intersection: Vec = b1 + db * t
|
||||
if intersection.within(a1, a2) and intersection.within(b1, b2):
|
||||
return intersection
|
||||
return None
|
||||
|
||||
@@ -24,6 +24,9 @@ class Vec:
|
||||
def __truediv__(self, value: float) -> Vec:
|
||||
return Vec(self.x / value, self.y / value)
|
||||
|
||||
def __neg__(self) -> Vec:
|
||||
return Vec(-self.x, -self.y)
|
||||
|
||||
def dot(self, other: Vec) -> float:
|
||||
return self.x * other.x + self.y * other.y
|
||||
|
||||
|
||||
Reference in New Issue
Block a user