43 lines
1.7 KiB
Python
43 lines
1.7 KiB
Python
from __future__ import annotations
|
|
|
|
|
|
class OrthoCamera:
|
|
"""Top-down orthographic camera; world space is tile units with y increasing downward."""
|
|
|
|
def __init__(self, viewport_w: int, viewport_h: int, tiles_visible_y: float = 12.0):
|
|
self.x = 0.0
|
|
self.y = 0.0
|
|
self.viewport_w = viewport_w
|
|
self.viewport_h = viewport_h
|
|
self.tiles_visible_y = tiles_visible_y
|
|
|
|
def set_target(self, x: float, y: float) -> None:
|
|
self.x, self.y = x, y
|
|
|
|
def resize(self, viewport_w: int, viewport_h: int) -> None:
|
|
self.viewport_w, self.viewport_h = viewport_w, viewport_h
|
|
|
|
def _world_bounds(self) -> tuple[float, float, float, float]:
|
|
half_h = self.tiles_visible_y / 2
|
|
aspect = self.viewport_w / max(self.viewport_h, 1)
|
|
half_w = half_h * aspect
|
|
return self.x - half_w, self.x + half_w, self.y - half_h, self.y + half_h
|
|
|
|
def scale_offset(self) -> tuple[tuple[float, float], tuple[float, float]]:
|
|
"""Returns (scale, offset) such that ndc = world * scale + offset."""
|
|
left, right, top, bottom = self._world_bounds()
|
|
|
|
scale_x = 2.0 / (right - left)
|
|
scale_y = -2.0 / (bottom - top)
|
|
offset_x = -left * scale_x - 1.0
|
|
offset_y = 1.0 - top * scale_y
|
|
|
|
return (scale_x, scale_y), (offset_x, offset_y)
|
|
|
|
def screen_to_world(self, px: float, py: float) -> tuple[float, float]:
|
|
"""Unprojects a canvas logical-pixel position (0,0 top-left) into world tile coords."""
|
|
left, right, top, bottom = self._world_bounds()
|
|
wx = left + (px / max(self.viewport_w, 1)) * (right - left)
|
|
wy = top + (py / max(self.viewport_h, 1)) * (bottom - top)
|
|
return wx, wy
|