Source code for simvx.graphics.draw2d_transform

"""2D affine transform stack for Draw2D.

Provides push/pop transform composition used by all drawing primitives to
support rotated, scaled, and translated coordinate spaces, plus a
``screen_space()`` context manager that temporarily disables the active
camera transform for HUDs and overlays.
"""

import math
from contextlib import contextmanager


[docs] class Draw2DTransformMixin: """Mixin providing a 2D affine transform stack for Draw2D.""" # 2D affine transform stack: x' = a*x + b*y + tx, y' = c*x + d*y + ty _xf: tuple = (1.0, 0.0, 0.0, 1.0, 0.0, 0.0) # identity (a, b, c, d, tx, ty) _xf_stack: list = [] _has_xf: bool = False # fast-path: skip transform when identity
[docs] @classmethod def push_transform(cls, a, b, c, d, tx, ty): """Push a 2D affine transform, composing with current.""" cls._xf_stack.append(cls._xf) oa, ob, oc, od, otx, oty = cls._xf cls._xf = ( oa * a + ob * c, oa * b + ob * d, oc * a + od * c, oc * b + od * d, oa * tx + ob * ty + otx, oc * tx + od * ty + oty, ) cls._has_xf = True
[docs] @classmethod def pop_transform(cls): """Pop the last transform, restoring the previous one.""" if cls._xf_stack: cls._xf = cls._xf_stack.pop() cls._has_xf = cls._xf != (1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
[docs] @classmethod def push_identity(cls): """Push current transform and reset to identity (for screen-space drawing).""" cls._xf_stack.append(cls._xf) cls._xf = (1.0, 0.0, 0.0, 1.0, 0.0, 0.0) cls._has_xf = False
[docs] @classmethod @contextmanager def screen_space(cls): """Draw in screen space, bypassing the active Camera2D transform. Pairs with the per-call ``screen_space=True`` kwarg on ``draw_rect`` / ``draw_circle`` / ``draw_line``: use the context manager when several HUD calls share the same scope, the kwarg for a single overlay primitive. with renderer.screen_space(): renderer.draw_rect((10, 10), (100, 20), filled=True, ...) renderer.draw_text("HP", (12, 12), ...) """ cls.push_identity() try: yield finally: cls.pop_transform()
@classmethod def _xf_pt(cls, x, y): """Transform a point. Returns unchanged when no transform is active.""" if not cls._has_xf: return x, y a, b, c, d, tx, ty = cls._xf return a * x + b * y + tx, c * x + d * y + ty @classmethod def _xf_sc(cls): """Uniform scale factor from current transform.""" if not cls._has_xf: return 1.0 a, b, c, d, _, _ = cls._xf return math.sqrt(abs(a * d - b * c))