From 990fd2dd0e1d09463d02bcd9fe5fd88a6e941179 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sun, 23 Aug 2026 19:55:02 +0200 Subject: [PATCH] feat(sim): geometry primitives and collision math --- game/sim/__init__.py | 14 ++++ game/sim/geometry.py | 156 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 game/sim/__init__.py create mode 100644 game/sim/geometry.py diff --git a/game/sim/__init__.py b/game/sim/__init__.py new file mode 100644 index 0000000..b80be2a --- /dev/null +++ b/game/sim/__init__.py @@ -0,0 +1,14 @@ +from .geometry import Vec2, Aabb +from .world import World +from .inputCommand import InputCommand +from .level import Level, loadLevel, saveLevelJson + +__all__ = [ + "Vec2", + "Aabb", + "World", + "InputCommand", + "Level", + "loadLevel", + "saveLevelJson", +] diff --git a/game/sim/geometry.py b/game/sim/geometry.py new file mode 100644 index 0000000..14fa32c --- /dev/null +++ b/game/sim/geometry.py @@ -0,0 +1,156 @@ +import math + + +class Vec2: + __slots__ = ("x", "y") + + def __init__(self, x=0.0, y=0.0): + self.x = float(x) + self.y = float(y) + + def copy(self): + return Vec2(self.x, self.y) + + def set(self, x, y): + self.x = float(x) + self.y = float(y) + return self + + def add(self, other): + return Vec2(self.x + other.x, self.y + other.y) + + def sub(self, other): + return Vec2(self.x - other.x, self.y - other.y) + + def scale(self, factor): + return Vec2(self.x * factor, self.y * factor) + + def length(self): + return math.hypot(self.x, self.y) + + def lengthSq(self): + return self.x * self.x + self.y * self.y + + def normalized(self): + mag = self.length() + if mag == 0.0: + return Vec2(0.0, 0.0) + return Vec2(self.x / mag, self.y / mag) + + def distanceTo(self, other): + return math.hypot(self.x - other.x, self.y - other.y) + + def asTuple(self): + return (self.x, self.y) + + def __repr__(self): + return "Vec2(%.3f, %.3f)" % (self.x, self.y) + + +class Aabb: + __slots__ = ("x", "y", "w", "h") + + def __init__(self, x, y, w, h): + self.x = float(x) + self.y = float(y) + self.w = float(w) + self.h = float(h) + + @property + def left(self): + return self.x + + @property + def right(self): + return self.x + self.w + + @property + def top(self): + return self.y + + @property + def bottom(self): + return self.y + self.h + + @property + def centerX(self): + return self.x + self.w * 0.5 + + @property + def centerY(self): + return self.y + self.h * 0.5 + + @property + def center(self): + return Vec2(self.centerX, self.centerY) + + def copy(self): + return Aabb(self.x, self.y, self.w, self.h) + + def moved(self, dx, dy): + return Aabb(self.x + dx, self.y + dy, self.w, self.h) + + def intersects(self, other): + return ( + self.left < other.right + and self.right > other.left + and self.top < other.bottom + and self.bottom > other.top + ) + + def touches(self, other): + return ( + self.left <= other.right + and self.right >= other.left + and self.top <= other.bottom + and self.bottom >= other.top + ) + + def containsPoint(self, px, py): + return self.left <= px <= self.right and self.top <= py <= self.bottom + + def overlapArea(self, other): + overlapX = min(self.right, other.right) - max(self.left, other.left) + overlapY = min(self.bottom, other.bottom) - max(self.top, other.top) + if overlapX <= 0.0 or overlapY <= 0.0: + return 0.0 + return overlapX * overlapY + + def inflated(self, marginX, marginY): + return Aabb( + self.x - marginX, + self.y - marginY, + self.w + marginX * 2.0, + self.h + marginY * 2.0, + ) + + def asTuple(self): + return (self.x, self.y, self.w, self.h) + + def asIntRect(self): + return (int(self.x), int(self.y), int(self.w), int(self.h)) + + def __repr__(self): + return "Aabb(%.2f, %.2f, %.2f, %.2f)" % (self.x, self.y, self.w, self.h) + + +def clamp(value, low, high): + if value < low: + return low + if value > high: + return high + return value + + +def sign(value): + if value > 0: + return 1 + if value < 0: + return -1 + return 0 + + +def approach(current, target, delta): + if current < target: + return min(current + delta, target) + return max(current - delta, target)