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 math import radians | ||||||
|  | from typing import Optional | ||||||
|  |  | ||||||
| import pygame | import pygame | ||||||
|  |  | ||||||
| from src.camera import Camera | 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 | 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: | class Car: | ||||||
|     MAX_SPEED = 5 |     MAX_SPEED = 5 | ||||||
|     MAX_BACK_SPEED = -2 |     MAX_BACK_SPEED = -3 | ||||||
|     ROTATE_SPEED = 1 |     ROTATE_SPEED = 1 | ||||||
|     COLOR = (230, 150, 80) |     COLOR = (230, 150, 80) | ||||||
|     WIDTH = 0.4 |     WIDTH = 0.4 | ||||||
|     LENGTH = 0.6 |     LENGTH = 0.6 | ||||||
|     COLLISION_MARGIN = 0.4 |     COLLISION_MARGIN = 0.4 | ||||||
|     ACCELERATION = 2 |     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: |     def __init__(self, pos: Vec, direction: Vec) -> None: | ||||||
|         self.pos: Vec = pos |         self.pos: Vec = pos | ||||||
| @@ -31,6 +35,9 @@ class Car: | |||||||
|         self.right: bool = False |         self.right: bool = False | ||||||
|         self.colliding: 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): |     def update(self, dt: float): | ||||||
|         if self.forward: |         if self.forward: | ||||||
|             self.speed += self.ACCELERATION * dt |             self.speed += self.ACCELERATION * dt | ||||||
| @@ -53,14 +60,21 @@ class Car: | |||||||
|             self.direction = self.direction.rotate(rotate_angle) |             self.direction = self.direction.rotate(rotate_angle) | ||||||
|  |  | ||||||
|         if not self.forward and not self.backward: |         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 -= sign(self.speed) * self.FRICTION * dt | ||||||
|  |             self.speed = fn(0, self.speed) | ||||||
|  |  | ||||||
|         if abs(self.speed) < 1e-4: |         if abs(self.speed) < 1e-4: | ||||||
|             self.speed = 0 |             self.speed = 0 | ||||||
|  |  | ||||||
|         self.pos += self.direction * self.speed * dt |         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: list[Vec] = self.get_corners() | ||||||
|         pts = [camera.world2screen(p) for p in pts] |         pts = [camera.world2screen(p) for p in pts] | ||||||
|         pygame.draw.polygon(surf, self.COLOR, pts) |         pygame.draw.polygon(surf, self.COLOR, pts) | ||||||
| @@ -76,6 +90,8 @@ class Car: | |||||||
|         return [p1, p2, p3, p4] |         return [p1, p2, p3, p4] | ||||||
|  |  | ||||||
|     def check_collisions(self, polygons: list[list[Vec]]): |     def check_collisions(self, polygons: list[list[Vec]]): | ||||||
|  |         self.cast_rays(polygons) | ||||||
|  |  | ||||||
|         self.colliding = False |         self.colliding = False | ||||||
|         corners: list[Vec] = self.get_corners() |         corners: list[Vec] = self.get_corners() | ||||||
|         sides: list[tuple[Vec, Vec]] = [ |         sides: list[tuple[Vec, Vec]] = [ | ||||||
| @@ -101,3 +117,35 @@ class Car: | |||||||
|                         self.speed = 0 |                         self.speed = 0 | ||||||
|                         self.pos = self.pos + n * (self.COLLISION_MARGIN - dist) |                         self.pos = self.pos + n * (self.COLLISION_MARGIN - dist) | ||||||
|                         return |                         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_fps: bool = True | ||||||
|         self.show_speed: bool = True |         self.show_speed: bool = True | ||||||
|  |         self.show_raycasts: bool = True | ||||||
|  |  | ||||||
|     def mainloop(self): |     def mainloop(self): | ||||||
|         while self.running: |         while self.running: | ||||||
| @@ -38,13 +39,11 @@ class Game: | |||||||
|             self.process_pygame_events() |             self.process_pygame_events() | ||||||
|             self.car.update(dt) |             self.car.update(dt) | ||||||
|             self.car.check_collisions(self.track.get_collision_polygons()) |             self.car.check_collisions(self.track.get_collision_polygons()) | ||||||
|  |             self.update_camera() | ||||||
|             self.render() |             self.render() | ||||||
|             self.clock.tick(60) |             self.clock.tick(60) | ||||||
|  |  | ||||||
|     def process_pygame_events(self): |     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(): |         for event in pygame.event.get(): | ||||||
|             if event.type == pygame.QUIT: |             if event.type == pygame.QUIT: | ||||||
|                 self.quit() |                 self.quit() | ||||||
| @@ -58,13 +57,18 @@ class Game: | |||||||
|             elif event.type == pygame.KEYUP: |             elif event.type == pygame.KEYUP: | ||||||
|                 self.on_key_up(event) |                 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): |     def quit(self): | ||||||
|         self.running = False |         self.running = False | ||||||
|  |  | ||||||
|     def render(self): |     def render(self): | ||||||
|         self.win.fill(self.BACKGROUND_COLOR) |         self.win.fill(self.BACKGROUND_COLOR) | ||||||
|         self.track.render(self.win, self.camera) |         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: |         if self.show_fps: | ||||||
|             self.render_fps() |             self.render_fps() | ||||||
|         if self.show_speed: |         if self.show_speed: | ||||||
| @@ -85,6 +89,10 @@ class Game: | |||||||
|             self.show_fps = not self.show_fps |             self.show_fps = not self.show_fps | ||||||
|         elif event.key == pygame.K_v: |         elif event.key == pygame.K_v: | ||||||
|             self.show_speed = not self.show_speed |             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): |     def on_key_up(self, event: pygame.event.Event): | ||||||
|         if event.key == pygame.K_w: |         if event.key == pygame.K_w: | ||||||
| @@ -125,3 +133,8 @@ class Game: | |||||||
|             pts2.append((ox + r2 * dx, oy + r2 * dy)) |             pts2.append((ox + r2 * dx, oy + r2 * dy)) | ||||||
|  |  | ||||||
|         pygame.draw.polygon(self.win, (200, 200, 200), pts1 + pts2[::-1]) |         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): | class Road(TrackObject): | ||||||
|     type = TrackObjectType.Road |     type = TrackObjectType.Road | ||||||
|  |  | ||||||
|  |     STRIP_LENGTH = 0.5 | ||||||
|  |     STRIP_GAP = 0.5 | ||||||
|  |  | ||||||
|     def __init__(self, pts: list[RoadPoint]) -> None: |     def __init__(self, pts: list[RoadPoint]) -> None: | ||||||
|         super().__init__() |         super().__init__() | ||||||
|         self.pts: list[RoadPoint] = pts |         self.pts: list[RoadPoint] = pts | ||||||
|  |         self.strips: list[tuple[Vec, Vec]] = [] | ||||||
|  |         self.compute_strips() | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def load(cls, data: dict) -> Road: |     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, side1) | ||||||
|         pygame.draw.lines(surf, (255, 255, 255), True, side2) |         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]]: |     def get_collision_polygons(self) -> list[list[Vec]]: | ||||||
|         side1: list[Vec] = [] |         side1: list[Vec] = [] | ||||||
|         side2: list[Vec] = [] |         side2: list[Vec] = [] | ||||||
| @@ -49,9 +63,50 @@ class Road(TrackObject): | |||||||
|             p3: Vec = p1 - pt.normal * pt.width |             p3: Vec = p1 - pt.normal * pt.width | ||||||
|             side1.append(p2) |             side1.append(p2) | ||||||
|             side2.append(p3) |             side2.append(p3) | ||||||
|  |  | ||||||
|         return [side1, side2] |         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: | class RoadPoint: | ||||||
|     def __init__(self, pos: Vec, normal: Vec, width: float) -> None: |     def __init__(self, pos: Vec, normal: Vec, width: float) -> None: | ||||||
|   | |||||||
							
								
								
									
										29
									
								
								src/utils.py
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								src/utils.py
									
									
									
									
									
								
							| @@ -1,9 +1,9 @@ | |||||||
| import os | import os | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
|  | from typing import Optional | ||||||
|  |  | ||||||
| from src.vec import Vec | 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))) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -32,3 +32,30 @@ def segments_intersect(a1: Vec, a2: Vec, b1: Vec, b2: Vec) -> bool: | |||||||
|         return True |         return True | ||||||
|  |  | ||||||
|     return False |     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: |     def __truediv__(self, value: float) -> Vec: | ||||||
|         return Vec(self.x / value, self.y / value) |         return Vec(self.x / value, self.y / value) | ||||||
|  |  | ||||||
|  |     def __neg__(self) -> Vec: | ||||||
|  |         return Vec(-self.x, -self.y) | ||||||
|  |  | ||||||
|     def dot(self, other: Vec) -> float: |     def dot(self, other: Vec) -> float: | ||||||
|         return self.x * other.x + self.y * other.y |         return self.x * other.x + self.y * other.y | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user