From 699acd1602844d4290d8c12e874433db5cf1d01a Mon Sep 17 00:00:00 2001 From: Matt Low Date: Fri, 13 Nov 2020 18:46:31 +0400 Subject: [PATCH] Initial commit --- .gitignore | 138 +++++++++++++++++++++++++++++++++++++++++ geom.py | 24 +++++++ geom/__init__.py | 34 ++++++++++ geom/colors.py | 9 +++ geom/demos/aabb.py | 85 +++++++++++++++++++++++++ geom/demos/vec_proj.py | 29 +++++++++ geom/geom/rect.py | 55 ++++++++++++++++ geom/math.py | 28 +++++++++ geom/screen.py | 40 ++++++++++++ geom/text.py | 10 +++ requirements.txt | 1 + 11 files changed, 453 insertions(+) create mode 100644 .gitignore create mode 100755 geom.py create mode 100644 geom/__init__.py create mode 100644 geom/colors.py create mode 100644 geom/demos/aabb.py create mode 100644 geom/demos/vec_proj.py create mode 100644 geom/geom/rect.py create mode 100644 geom/math.py create mode 100644 geom/screen.py create mode 100644 geom/text.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a81c8ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,138 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ diff --git a/geom.py b/geom.py new file mode 100755 index 0000000..11d5213 --- /dev/null +++ b/geom.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +import geom +import pygame +import sys + +def main(): + pygame.init() + geom.init() + + clock = pygame.time.Clock() + elapsed = 0 + while geom.loop(): + clock.tick(60) + #elapsed += clock.get_time() + #if elapsed >= 1000: + # print(f"FPS: {clock.get_fps()}") + # elapsed = 0 + + pygame.quit() + sys.exit() + + +if __name__ == "__main__": + main() diff --git a/geom/__init__.py b/geom/__init__.py new file mode 100644 index 0000000..bd3a4d9 --- /dev/null +++ b/geom/__init__.py @@ -0,0 +1,34 @@ +import pygame +from pygame.locals import * +from . import text + +WIDTH = 1280 +HEIGHT = 720 + +surface = None +screen = None + + +def init(): + pygame.display.set_caption("Geom Demo") + pygame.mouse.set_visible(False) + global surface + global screen + + surface = pygame.display.set_mode((WIDTH, HEIGHT)) + text.init() + + from .screen import Screen + screen = Screen() + + +def loop(): + for event in pygame.event.get(QUIT): + return False + + surface.fill((0, 0, 0)) + + screen.render(surface) + + pygame.display.update() + return True diff --git a/geom/colors.py b/geom/colors.py new file mode 100644 index 0000000..9744597 --- /dev/null +++ b/geom/colors.py @@ -0,0 +1,9 @@ +RED = (255, 0, 0, 255) +YELLOW = (255, 255, 0, 255) +GREEN = (0, 255, 0, 255) +CYAN = (0, 255, 255, 255) +BLUE = (0, 0, 255, 255) +VIOLET = (255, 0, 255, 255) + +WHITE = (255, 255, 255, 255) +BLACK = (0, 0, 0, 255) diff --git a/geom/demos/aabb.py b/geom/demos/aabb.py new file mode 100644 index 0000000..223d547 --- /dev/null +++ b/geom/demos/aabb.py @@ -0,0 +1,85 @@ +from pygame import draw, mouse +from .. import text +from ..geom.rect import Rect +from .. import WIDTH, HEIGHT, text +from ..colors import * +from ..math import * + +class AABBDistance: + + title = "AABB Distance" + + def render(self, surface): + rect_a = Rect( + WIDTH / 2 - (WIDTH / 8), + HEIGHT / 2 - (HEIGHT / 8), + WIDTH / 4, + HEIGHT / 4, + ) + rect_b = Rect( + *mouse.get_pos(), + WIDTH / 5, + HEIGHT / 5 + ) + + # cx, cy = center + dist_x = abs(rect_b.cx - rect_a.cx) + dist_y = abs(rect_b.cy - rect_a.cy) + + gap_x = dist_x - rect_a.halfwidth - rect_b.halfwidth + gap_y = dist_y - rect_a.halfheight - rect_b.halfheight + + distance = 0 + + if gap_x < 0 and gap_y < 0: + distance = max(gap_x, gap_y) + else: + if gap_x < 0: + distance = gap_y + elif gap_y < 0: + distance = gap_x + else: + distance = length(gap_x, gap_y) + + text.draw(surface, (10, HEIGHT - 20), WHITE, f"Distance: {distance}") + rect_a.draw(surface, YELLOW, 4) + rect_b.draw(surface, WHITE, 4) + draw.line(surface, RED if distance < 0 else GREEN, + rect_a.get_center(), rect_b.get_center(), 2) + +class PointAABBDistance: + + title = "Point-AABB Distance" + + def render(self, surface): + rect_a = Rect( + WIDTH / 2 - (WIDTH / 8), + HEIGHT / 2 - (HEIGHT / 8), + WIDTH / 4, + HEIGHT / 4, + ) + cx, cy = mouse.get_pos() + + # cx, cy = point + dist_x = abs(cx - rect_a.cx) + dist_y = abs(cy - rect_a.cy) + + gap_x = dist_x - rect_a.halfwidth + gap_y = dist_y - rect_a.halfheight + + distance = 0 + + if gap_x < 0 and gap_y < 0: + distance = max(gap_x, gap_y) + else: + if gap_x < 0: + distance = gap_y + elif gap_y < 0: + distance = gap_x + else: + distance = length(gap_x, gap_y) + + text.draw(surface, (10, HEIGHT - 20), WHITE, f"Distance: {distance}") + rect_a.draw(surface, YELLOW, 4) + draw.line(surface, RED if distance < 0 else GREEN, + rect_a.get_center(), (cx, cy), 2) diff --git a/geom/demos/vec_proj.py b/geom/demos/vec_proj.py new file mode 100644 index 0000000..f4eadeb --- /dev/null +++ b/geom/demos/vec_proj.py @@ -0,0 +1,29 @@ +from pygame import draw, mouse +from .. import HEIGHT, WIDTH +from ..colors import * +from ..math import * + +class VecProj: + + title = "Vector Projection" + + def render(self, surface): + x1 = WIDTH / 4 + y1 = HEIGHT / 2 + 100 + x2 = 0.75 * WIDTH + y2 = HEIGHT / 2 + + cx, cy = mouse.get_pos() + + lx, ly = x2 - x1, y2 - y1 # line vector + llen = length(lx, ly) + lnx, lny = lx / llen, ly / llen # line normal + lcx, lcy = cx - x1, cy - y1 # line start - cursor vector + + proj = dot(lcx, lcy, lx, ly) / llen + + draw.line( + surface, GREEN, (cx, cy), add(x1, y1, *scale(lnx, lny, proj)), 5 + ) + draw.line(surface, WHITE, (cx, cy), (x1, y1), 5) + draw.line(surface, YELLOW, (x1, y1), (x2, y2), 5) diff --git a/geom/geom/rect.py b/geom/geom/rect.py new file mode 100644 index 0000000..a392f6f --- /dev/null +++ b/geom/geom/rect.py @@ -0,0 +1,55 @@ +from pygame import draw, gfxdraw +from pygame.rect import Rect as pRect + +class Rect: + + def __init__(self, x, y, width, height): + self.x = x + self.y = y + self.height = height + self.width = width + self.update() + + def update(self): + self.min_x = self.x + self.min_y = self.y + self.max_x = self.x + self.width + self.max_y = self.y + self.height + + self.min = (self.min_x, self.min_y) + self.max = (self.max_x, self.max_y) + + self.halfwidth = self.width / 2 + self.halfheight = self.height / 2 + + self.cx = self.x + self.width / 2 + self.cy = self.y + self.height / 2 + + self.vertices = [ + (self.x, self.y), + (self.x + self.width, self.y), + (self.x + self.width, self.y + self.height), + (self.x, self.y + self.height) + ] + self.segments = [ + (self.vertices[0], self.vertices[1]), + (self.vertices[1], self.vertices[2]), + (self.vertices[2], self.vertices[3]), + (self.vertices[3], self.vertices[0]), + ] + + def get_center(self): + return (self.cx, self.cy) + + def get_center_px(self): + return (int(self.cx), int(self.cy)) + + def contains(self, x, y): + return (x >= self.min_x and x <= self.max_x + and y >= self.min_y and y <= self.max_y) + + def draw(self, surface, colour, width): + #gfxdraw.pixel(surface, *self.get_center_px(), colour) + draw.rect(surface, colour, + pRect(self.x, self.y, self.width, self.height), + width) diff --git a/geom/math.py b/geom/math.py new file mode 100644 index 0000000..98a2bee --- /dev/null +++ b/geom/math.py @@ -0,0 +1,28 @@ +import math + +ZERO = (0, 0) + +def dot(x1, y1, x2, y2): + return (x1 * x2) + (y1 * y2) + +def length(x, y): + return math.sqrt((x * x) + (y * y)) + +def normalize(x, y): + _len = length(x, y) + return x / _len, y / _len + +def scale(x, y, factor): + return x * factor, y * factor + +def add(x1, y1, x2, y2): + return x1 + x2, y1 + y2 + +def sub(x1, y1, x2, y2): + return x1 - x2, y1 - y2 + +def vmin(x1, y1, x2, y2): + return min(x1, x2), min(y1, y2) + +def vmax(x1, y1, x2, y2): + return max(x1, x2), max(y1, y2) diff --git a/geom/screen.py b/geom/screen.py new file mode 100644 index 0000000..d56c299 --- /dev/null +++ b/geom/screen.py @@ -0,0 +1,40 @@ +import pygame +from .demos import aabb, vec_proj +from . import text, HEIGHT +from .colors import * + +class Screen: + + def __init__(self): + self.demos = [ + vec_proj.VecProj(), + aabb.AABBDistance(), + aabb.PointAABBDistance(), + ] + self.demo = 0 + + def handle_input(self): + for event in pygame.event.get(pygame.KEYDOWN): + if event.key == pygame.K_DOWN: + self.demo += 1 + if self.demo >= len(self.demos): + self.demo = 0 + elif event.key == pygame.K_UP: + self.demo -= 1 + if self.demo < 0: + self.demo = len(self.demos) - 1 + + def draw_demo_list(self, surface): + y_offset = 10 + for i in range(len(self.demos)): + text.draw(surface, + (10, y_offset + i * 20), + CYAN if self.demo == i else WHITE, + self.demos[i].title) + + def render(self, surface): + self.handle_input() + + self.demos[self.demo].render(surface) + + self.draw_demo_list(surface) diff --git a/geom/text.py b/geom/text.py new file mode 100644 index 0000000..735bb35 --- /dev/null +++ b/geom/text.py @@ -0,0 +1,10 @@ +from pygame import freetype + +FONT = None + +def init(): + global FONT + FONT = freetype.SysFont('Arial', 16) + +def draw(surface, position, color, text): + FONT.render_to(surface, position, text, color) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..88d1df8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +pygame==2.0.0