Compare commits
	
		
			10 Commits
		
	
	
		
			12319fc1ab
			...
			09f70223b8
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 09f70223b8 | |||
| 45ed1c85c8 | |||
| 2b20582b87 | |||
| 6805e69509 | |||
| 9c5f39b669 | |||
| adb25e6ef6 | |||
| 6276f97cce | |||
| da8c64624f | |||
| e154a1fde9 | |||
| eb10933f4b | 
							
								
								
									
										
											BIN
										
									
								
								assets/fonts/Ubuntu-M.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/fonts/Ubuntu-M.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								assets/fonts/Ubuntu-R.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/fonts/Ubuntu-R.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								main.py
									
									
									
									
									
								
							| @@ -4,6 +4,7 @@ from src.game import Game | |||||||
| def main(): | def main(): | ||||||
|     print("Welcome to Rally Racer !") |     print("Welcome to Rally Racer !") | ||||||
|     game: Game = Game() |     game: Game = Game() | ||||||
|  |     game.mainloop() | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|   | |||||||
							
								
								
									
										43
									
								
								src/camera.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/camera.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | from src.vec import Vec | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Camera: | ||||||
|  |     UNIT_RATIO = 150 | ||||||
|  |  | ||||||
|  |     def __init__(self) -> None: | ||||||
|  |         self.pos: Vec = Vec() | ||||||
|  |         self.up: Vec = Vec(0, -1) | ||||||
|  |         self.size: Vec = Vec(600, 600) | ||||||
|  |         self.zoom: float = 1 | ||||||
|  |  | ||||||
|  |     def set_pos(self, pos: Vec): | ||||||
|  |         self.pos = pos | ||||||
|  |  | ||||||
|  |     def set_direction(self, up: Vec): | ||||||
|  |         self.up = up.normalized | ||||||
|  |  | ||||||
|  |     def set_size(self, size: Vec): | ||||||
|  |         self.size = size | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def center(self) -> Vec: | ||||||
|  |         return self.size / 2 | ||||||
|  |  | ||||||
|  |     def screen2world(self, screen_pos: Vec) -> Vec: | ||||||
|  |         delta: Vec = screen_pos - self.center | ||||||
|  |         delta /= self.zoom * self.UNIT_RATIO | ||||||
|  |         dx: float = delta.x | ||||||
|  |         dy: float = delta.y | ||||||
|  |  | ||||||
|  |         v1: Vec = self.up.perp * dx | ||||||
|  |         v2: Vec = self.up * dy | ||||||
|  |  | ||||||
|  |         return self.pos + v1 + v2 | ||||||
|  |  | ||||||
|  |     def world2screen(self, world_pos: Vec) -> Vec: | ||||||
|  |         delta: Vec = world_pos - self.pos | ||||||
|  |         dy: float = -delta.dot(self.up) | ||||||
|  |         dx: float = delta.dot(self.up.perp) | ||||||
|  |         screen_delta: Vec = Vec(dx, dy) * self.zoom * self.UNIT_RATIO | ||||||
|  |         screen_pos: Vec = self.center + screen_delta | ||||||
|  |         return screen_pos | ||||||
							
								
								
									
										96
									
								
								src/car.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/car.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | |||||||
|  | from math import radians | ||||||
|  |  | ||||||
|  | import pygame | ||||||
|  |  | ||||||
|  | from src.camera import Camera | ||||||
|  | from src.utils import segments_intersect | ||||||
|  | from src.vec import Vec | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Car: | ||||||
|  |     MAX_SPEED = 0.05 | ||||||
|  |     MAX_BACK_SPEED = -0.025 | ||||||
|  |     ROTATE_SPEED = radians(1) | ||||||
|  |     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 | ||||||
|  |         self.direction: Vec = direction | ||||||
|  |         self.speed: float = 0 | ||||||
|  |         self.forward: bool = False | ||||||
|  |         self.backward: bool = False | ||||||
|  |         self.left: bool = False | ||||||
|  |         self.right: bool = False | ||||||
|  |         self.colliding: bool = False | ||||||
|  |  | ||||||
|  |     def update(self): | ||||||
|  |         if self.forward: | ||||||
|  |             self.speed += 0.001 | ||||||
|  |             self.speed = min(self.MAX_SPEED, self.speed) | ||||||
|  |  | ||||||
|  |         if self.backward: | ||||||
|  |             self.speed -= 0.002 | ||||||
|  |             self.speed = max(self.MAX_BACK_SPEED, self.speed) | ||||||
|  |  | ||||||
|  |         rotate_angle: float = 0 | ||||||
|  |         if self.left: | ||||||
|  |             rotate_angle -= self.ROTATE_SPEED | ||||||
|  |         if self.right: | ||||||
|  |             rotate_angle += self.ROTATE_SPEED | ||||||
|  |  | ||||||
|  |         # if self.backward: | ||||||
|  |         #    rotate_angle *= -1 | ||||||
|  |  | ||||||
|  |         if rotate_angle != 0: | ||||||
|  |             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 | ||||||
|  |  | ||||||
|  |     def render(self, surf: pygame.Surface, camera: Camera): | ||||||
|  |         pts: list[Vec] = self.get_corners() | ||||||
|  |         pts = [camera.world2screen(p) for p in pts] | ||||||
|  |         pygame.draw.polygon(surf, self.COLOR, pts) | ||||||
|  |  | ||||||
|  |     def get_corners(self) -> list[Vec]: | ||||||
|  |         u: Vec = self.direction * self.LENGTH / 2 | ||||||
|  |         v: Vec = self.direction.perp * self.WIDTH / 2 | ||||||
|  |         pt: Vec = self.pos | ||||||
|  |         p1: Vec = pt + u + v | ||||||
|  |         p2: Vec = pt - u + v | ||||||
|  |         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 | ||||||
							
								
								
									
										89
									
								
								src/game.py
									
									
									
									
									
								
							
							
						
						
									
										89
									
								
								src/game.py
									
									
									
									
									
								
							| @@ -1,9 +1,94 @@ | |||||||
| import pygame | import pygame | ||||||
|  |  | ||||||
|  | from src.camera import Camera | ||||||
|  | from src.car import Car | ||||||
|  | from src.track import Track | ||||||
|  | from src.utils import ROOT | ||||||
|  | from src.vec import Vec | ||||||
|  |  | ||||||
|  |  | ||||||
| class Game: | class Game: | ||||||
|     DEFAULT_SIZE = (1280, 720) |     DEFAULT_SIZE = (1280, 720) | ||||||
|  |     BACKGROUND_COLOR = (80, 80, 80) | ||||||
|  |     MAX_FPS = 60 | ||||||
|  |     FPS_COLOR = (255, 0, 0) | ||||||
|  |  | ||||||
|     def __init__(self): |     def __init__(self) -> None: | ||||||
|         pygame.init() |         pygame.init() | ||||||
|         self.win: pygame.Surface = pygame.display.set_mode(self.DEFAULT_SIZE) |         self.win: pygame.Surface = pygame.display.set_mode( | ||||||
|  |             self.DEFAULT_SIZE, pygame.RESIZABLE | ||||||
|  |         ) | ||||||
|  |         pygame.display.set_caption("Rally Racer") | ||||||
|  |         self.running: bool = True | ||||||
|  |         self.track: Track = Track.load("simple") | ||||||
|  |         self.car: Car = Car(self.track.start_pos, self.track.start_dir) | ||||||
|  |         self.camera: Camera = Camera() | ||||||
|  |  | ||||||
|  |         self.clock: pygame.time.Clock = pygame.time.Clock() | ||||||
|  |         self.font: pygame.font.Font = pygame.font.Font( | ||||||
|  |             str(ROOT / "assets" / "fonts" / "Ubuntu-M.ttf"), 20 | ||||||
|  |         ) | ||||||
|  |         self.show_fps: bool = True | ||||||
|  |  | ||||||
|  |     def mainloop(self): | ||||||
|  |         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) | ||||||
|  |  | ||||||
|  |     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() | ||||||
|  |             elif event.type == pygame.VIDEORESIZE: | ||||||
|  |                 self.camera.set_size(Vec(event.w, event.h)) | ||||||
|  |             elif event.type == pygame.KEYDOWN: | ||||||
|  |                 if event.key == pygame.K_ESCAPE: | ||||||
|  |                     self.quit() | ||||||
|  |                 else: | ||||||
|  |                     self.on_key_down(event) | ||||||
|  |             elif event.type == pygame.KEYUP: | ||||||
|  |                 self.on_key_up(event) | ||||||
|  |  | ||||||
|  |     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) | ||||||
|  |         if self.show_fps: | ||||||
|  |             self.render_fps() | ||||||
|  |  | ||||||
|  |         pygame.display.flip() | ||||||
|  |  | ||||||
|  |     def on_key_down(self, event: pygame.event.Event): | ||||||
|  |         if event.key == pygame.K_w: | ||||||
|  |             self.car.forward = True | ||||||
|  |         elif event.key == pygame.K_s: | ||||||
|  |             self.car.backward = True | ||||||
|  |         elif event.key == pygame.K_a: | ||||||
|  |             self.car.left = True | ||||||
|  |         elif event.key == pygame.K_d: | ||||||
|  |             self.car.right = True | ||||||
|  |  | ||||||
|  |     def on_key_up(self, event: pygame.event.Event): | ||||||
|  |         if event.key == pygame.K_w: | ||||||
|  |             self.car.forward = False | ||||||
|  |         elif event.key == pygame.K_s: | ||||||
|  |             self.car.backward = False | ||||||
|  |         elif event.key == pygame.K_a: | ||||||
|  |             self.car.left = False | ||||||
|  |         elif event.key == pygame.K_d: | ||||||
|  |             self.car.right = False | ||||||
|  |  | ||||||
|  |     def render_fps(self): | ||||||
|  |         txt: pygame.Surface = self.font.render( | ||||||
|  |             f"{self.clock.get_fps():.1f}", True, self.FPS_COLOR | ||||||
|  |         ) | ||||||
|  |         self.win.blit(txt, (self.win.get_width() - txt.get_width(), 0)) | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								src/objects/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/objects/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										66
									
								
								src/objects/road.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/objects/road.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | |||||||
|  | from __future__ import annotations | ||||||
|  |  | ||||||
|  | import pygame | ||||||
|  |  | ||||||
|  | from src.camera import Camera | ||||||
|  | from src.track_object import TrackObject, TrackObjectType | ||||||
|  | from src.vec import Vec | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Road(TrackObject): | ||||||
|  |     type = TrackObjectType.Road | ||||||
|  |  | ||||||
|  |     def __init__(self, pts: list[RoadPoint]) -> None: | ||||||
|  |         super().__init__() | ||||||
|  |         self.pts: list[RoadPoint] = pts | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def load(cls, data: dict) -> Road: | ||||||
|  |         return Road([RoadPoint.load(pt) for pt in data["pts"]]) | ||||||
|  |  | ||||||
|  |     def render(self, surf: pygame.Surface, camera: Camera): | ||||||
|  |         side1: list[Vec] = [] | ||||||
|  |         side2: list[Vec] = [] | ||||||
|  |  | ||||||
|  |         for i, pt in enumerate(self.pts): | ||||||
|  |             p1: Vec = pt.pos | ||||||
|  |             p2: Vec = p1 + pt.normal * pt.width | ||||||
|  |             p3: Vec = p1 - pt.normal * pt.width | ||||||
|  |             side1.append(camera.world2screen(p2)) | ||||||
|  |             side2.append(camera.world2screen(p3)) | ||||||
|  |             col: tuple[float, float, float] = (i * 10 + 150, 100, 100) | ||||||
|  |             pygame.draw.circle(surf, col, camera.world2screen(p1), 5) | ||||||
|  |  | ||||||
|  |         n: int = len(self.pts) | ||||||
|  |         for i in range(n): | ||||||
|  |             pygame.draw.polygon( | ||||||
|  |                 surf, | ||||||
|  |                 (100, 100, 100), | ||||||
|  |                 [side1[i], side1[(i + 1) % n], side2[(i + 1) % n], side2[i]], | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         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: | ||||||
|  |         self.pos: Vec = pos | ||||||
|  |         self.normal: Vec = normal.normalized | ||||||
|  |         self.width: float = width | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def load(data: list[float]) -> RoadPoint: | ||||||
|  |         return RoadPoint(Vec(data[0], data[1]), Vec(data[2], data[3]), data[4]) | ||||||
							
								
								
									
										54
									
								
								src/track.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/track.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | from __future__ import annotations | ||||||
|  |  | ||||||
|  | import json | ||||||
|  |  | ||||||
|  | import pygame | ||||||
|  |  | ||||||
|  | from src.camera import Camera | ||||||
|  | from src.track_object import TrackObject | ||||||
|  | from src.utils import ROOT | ||||||
|  | from src.vec import Vec | ||||||
|  |  | ||||||
|  | TrackObject.init() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Track: | ||||||
|  |     TRACKS_DIRECTORY = ROOT / "assets" / "tracks" | ||||||
|  |  | ||||||
|  |     def __init__(self, id: str, name: str, start_pos: Vec, start_dir: Vec) -> None: | ||||||
|  |         self.id: str = id | ||||||
|  |         self.name: str = name | ||||||
|  |         self.start_pos: Vec = start_pos | ||||||
|  |         self.start_dir: Vec = start_dir | ||||||
|  |         self.objects: list[TrackObject] = [] | ||||||
|  |         self.load_objects() | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def load(name: str) -> Track: | ||||||
|  |         with open(Track.TRACKS_DIRECTORY / name / "meta.json", "r") as f: | ||||||
|  |             meta: dict = json.load(f) | ||||||
|  |  | ||||||
|  |         return Track( | ||||||
|  |             name, | ||||||
|  |             meta["name"], | ||||||
|  |             Vec(*meta["start"]["pos"]), | ||||||
|  |             Vec(*meta["start"]["direction"]), | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def load_objects(self): | ||||||
|  |         with open(Track.TRACKS_DIRECTORY / self.id / "track.json", "r") as f: | ||||||
|  |             data: list = json.load(f) | ||||||
|  |  | ||||||
|  |         self.objects = [] | ||||||
|  |         for obj_data in data: | ||||||
|  |             self.objects.append(TrackObject.load(obj_data)) | ||||||
|  |  | ||||||
|  |     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 | ||||||
							
								
								
									
										46
									
								
								src/track_object.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/track_object.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | import importlib | ||||||
|  | import pkgutil | ||||||
|  | from enum import StrEnum | ||||||
|  | from typing import Optional, Self | ||||||
|  |  | ||||||
|  | import pygame | ||||||
|  |  | ||||||
|  | import src.objects | ||||||
|  | from src.camera import Camera | ||||||
|  | from src.vec import Vec | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TrackObjectType(StrEnum): | ||||||
|  |     Road = "road" | ||||||
|  |  | ||||||
|  |     Unknown = "unknown" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TrackObject: | ||||||
|  |     REGISTRY = {} | ||||||
|  |     type: TrackObjectType = TrackObjectType.Unknown | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def init(): | ||||||
|  |         package = src.objects | ||||||
|  |         for _, modname, _ in pkgutil.walk_packages( | ||||||
|  |             package.__path__, package.__name__ + "." | ||||||
|  |         ): | ||||||
|  |             importlib.import_module(modname) | ||||||
|  |  | ||||||
|  |     def __init_subclass__(cls, **kwargs) -> None: | ||||||
|  |         super().__init_subclass__(**kwargs) | ||||||
|  |         TrackObject.REGISTRY[cls.type] = cls | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def load(cls, data: dict) -> Self: | ||||||
|  |         obj_type: Optional[TrackObjectType] = data.get("type") | ||||||
|  |         if obj_type not in cls.REGISTRY: | ||||||
|  |             raise ValueError(f"Unknown object tyoe: {obj_type}") | ||||||
|  |         return cls.REGISTRY[obj_type].load(data) | ||||||
|  |  | ||||||
|  |     def render(self, surf: pygame.Surface, camera: Camera): | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     def get_collision_polygons(self) -> list[list[Vec]]: | ||||||
|  |         return [] | ||||||
							
								
								
									
										34
									
								
								src/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/utils.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +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 | ||||||
							
								
								
									
										13
									
								
								src/vec.py
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								src/vec.py
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| from __future__ import annotations | from __future__ import annotations | ||||||
|  |  | ||||||
| from math import sqrt | from math import cos, sin, sqrt | ||||||
|  |  | ||||||
|  |  | ||||||
| class Vec: | class Vec: | ||||||
| @@ -55,3 +55,14 @@ class Vec: | |||||||
|  |  | ||||||
|     def __repr__(self) -> str: |     def __repr__(self) -> str: | ||||||
|         return f"Vec({self.x}, {self.y})" |         return f"Vec({self.x}, {self.y})" | ||||||
|  |  | ||||||
|  |     def rotate(self, angle: float) -> Vec: | ||||||
|  |         return 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) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user