Source code for simvx.core.math.rect2

"""Rect2 — 2D axis-aligned bounding rectangle."""

from __future__ import annotations

from .types import Vec2

# ============================================================================
# Rect2
# ============================================================================

[docs] class Rect2: """2D axis-aligned bounding rectangle.""" __slots__ = ("x", "y", "width", "height") def __init__(self, x=0.0, y=0.0, width=0.0, height=0.0): self.x = float(x) self.y = float(y) self.width = float(width) self.height = float(height) @property def position(self) -> tuple[float, float]: return (self.x, self.y) @property def size(self) -> tuple[float, float]: return (self.width, self.height) @property def end(self) -> tuple[float, float]: return (self.x + self.width, self.y + self.height) @property def area(self) -> float: return abs(self.width * self.height) @property def center(self) -> tuple[float, float]: return (self.x + self.width / 2, self.y + self.height / 2)
[docs] def contains_point(self, x: float, y: float) -> bool: """Return True if (x, y) lies inside (or on the boundary of) this rect.""" r = self.abs() return r.x <= x <= r.x + r.width and r.y <= y <= r.y + r.height
[docs] def intersects(self, other: Rect2) -> bool: """Return True if this rect overlaps with other.""" a, b = self.abs(), other.abs() return not (a.x + a.width < b.x or b.x + b.width < a.x or a.y + a.height < b.y or b.y + b.height < a.y)
[docs] def intersection(self, other: Rect2) -> Rect2: """Return the overlapping area, or a zero rect if none.""" a, b = self.abs(), other.abs() x1 = max(a.x, b.x) y1 = max(a.y, b.y) x2 = min(a.x + a.width, b.x + b.width) y2 = min(a.y + a.height, b.y + b.height) if x2 < x1 or y2 < y1: return Rect2() return Rect2(x1, y1, x2 - x1, y2 - y1)
[docs] def merge(self, other: Rect2) -> Rect2: """Return the smallest rect enclosing both rects (union).""" a, b = self.abs(), other.abs() x1 = min(a.x, b.x) y1 = min(a.y, b.y) x2 = max(a.x + a.width, b.x + b.width) y2 = max(a.y + a.height, b.y + b.height) return Rect2(x1, y1, x2 - x1, y2 - y1)
[docs] def expand(self, point) -> Rect2: """Return a rect expanded to include the given point (x, y) or Vec2.""" if isinstance(point, Vec2): px, py = point.x, point.y else: px, py = float(point[0]), float(point[1]) r = self.abs() x1 = min(r.x, px) y1 = min(r.y, py) x2 = max(r.x + r.width, px) y2 = max(r.y + r.height, py) return Rect2(x1, y1, x2 - x1, y2 - y1)
[docs] def grow(self, amount: float) -> Rect2: """Return a rect grown by amount on each side.""" return Rect2(self.x - amount, self.y - amount, self.width + 2 * amount, self.height + 2 * amount)
[docs] def abs(self) -> Rect2: """Return a rect with guaranteed positive width/height.""" x, y, w, h = self.x, self.y, self.width, self.height if w < 0: x, w = x + w, -w if h < 0: y, h = y + h, -h return Rect2(x, y, w, h)
[docs] def __eq__(self, other): if isinstance(other, Rect2): return self.x == other.x and self.y == other.y and self.width == other.width and self.height == other.height return NotImplemented
[docs] def __repr__(self): return f"Rect2({self.x:.4g}, {self.y:.4g}, {self.width:.4g}, {self.height:.4g})"