diff --git a/arcade/camera/camera_2d.py b/arcade/camera/camera_2d.py index 3b59283ce9..30a37374ff 100644 --- a/arcade/camera/camera_2d.py +++ b/arcade/camera/camera_2d.py @@ -2,6 +2,8 @@ from math import degrees, radians, atan2, cos, sin from contextlib import contextmanager +from typing_extensions import Self + from arcade.camera.orthographic import OrthographicProjector from arcade.camera.data_types import CameraData, OrthographicProjectionData, Projector from arcade.gl import Framebuffer @@ -59,21 +61,21 @@ def __init__(self, *, window: Optional["Window"] = None): self._window: "Window" = window or get_window() self.render_target: Framebuffer = render_target or self._window.ctx.screen - - half_width = self._window.width / 2 - half_height = self._window.height / 2 + width, height = self.render_target.size + half_width = width / 2 + half_height = height / 2 self._data = camera_data or CameraData( - (half_width, half_height, 0.0), # position - (0.0, 1.0, 0.0), # up vector - (0.0, 0.0, -1.0), # forward vector - 1.0 # zoom + position=(half_width, half_height, 0.0), + up=(0.0, 1.0, 0.0), + forward=(0.0, 0.0, -1.0), + zoom=1.0 ) self._projection: OrthographicProjectionData = projection_data or OrthographicProjectionData( - -half_width, half_width, # Left and Right. - -half_height, half_height, # Bottom and Top. - 0.0, 100.0, # Near and Far. - (0, 0, self._window.width, self._window.height) # Viewport + left=-half_width, right=half_width, + bottom=-half_height, top=half_height, + near=0.0, far=100.0, + viewport=(0, 0, width, height) ) self._ortho_projector: OrthographicProjector = OrthographicProjector( @@ -82,8 +84,9 @@ def __init__(self, *, projection=self._projection ) - @staticmethod + @classmethod def from_raw_data( + cls, viewport: Optional[Tuple[int, int, int, int]] = None, position: Optional[Tuple[float, float]] = None, up: Tuple[float, float] = (0.0, 1.0), @@ -94,7 +97,7 @@ def from_raw_data( *, render_target: Optional[Framebuffer] = None, window: Optional["Window"] = None - ): + ) -> Self: """ Create a Camera2D without first defining CameraData or an OrthographicProjectionData object. @@ -114,31 +117,32 @@ def from_raw_data( Defaults to the currently active window. """ window = window or get_window() - - half_width = window.width / 2 - half_height = window.height / 2 + render_target = render_target or window.ctx.screen + width, height = render_target.size + half_width = width / 2 + half_height = height / 2 _pos = position or (half_width, half_height) - _data: CameraData = CameraData( - (_pos[0], _pos[1], 0.0), # position - (up[0], up[1], 0.0), # up vector - (0.0, 0.0, -1.0), # forward vector - zoom # zoom + _data = CameraData( + position=(_pos[0], _pos[1], 0.0), + up=(up[0], up[1], 0.0), + forward=(0.0, 0.0, -1.0), + zoom=zoom ) left, right, bottom, top = projection or (-half_width, half_width, -half_height, half_height) _projection: OrthographicProjectionData = OrthographicProjectionData( - left, right, # Left and Right. - top, bottom, # Bottom and Top. - near or 0.0, far or 100.0, # Near and Far. - viewport or (0, 0, window.width, window.height) # Viewport + left=left, right=right, + top=top, bottom=bottom, + near=near or 0.0, far=far or 100.0, + viewport=viewport or (0, 0, width, height) ) - return Camera2D( + return cls( camera_data=_data, projection_data=_projection, window=window, - render_target=(render_target or window.ctx.screen) + render_target=render_target ) @property @@ -497,9 +501,11 @@ def projection_far(self, _far: float) -> None: @property def viewport(self) -> Tuple[int, int, int, int]: - """ - The pixel area that will be drawn to while the camera is active. - (left, right, bottom, top) + """Get/set pixels of the ``render_target`` drawn to when active. + + The pixel area is defined as integer pixel coordinates starting + from the bottom left of ``self.render_target``. They are ordered + as ``(left, bottom, width, height)``. """ return self._projection.viewport diff --git a/tests/unit/camera/test_camera2d.py b/tests/unit/camera/test_camera2d.py new file mode 100644 index 0000000000..404c98055a --- /dev/null +++ b/tests/unit/camera/test_camera2d.py @@ -0,0 +1,55 @@ +import pytest as pytest + +from arcade import Window +from arcade.camera import Camera2D + + +def test_camera2d_from_raw_data_inheritance_safety(window: Window): + class MyCamera2D(Camera2D): + ... + + subclassed = MyCamera2D.from_raw_data(zoom=10.0) + assert isinstance(subclassed, MyCamera2D) + + +RENDER_TARGET_SIZES = [ + (800, 600), # Normal window size + (1280, 720), # Bigger + (16, 16) # Tiny +] + + +@pytest.mark.parametrize("width, height", RENDER_TARGET_SIZES) +def test_camera2d_init_uses_render_target_size(window: Window, width, height): + + size = (width, height) + texture = window.ctx.texture(size, components=4) + framebuffer = window.ctx.framebuffer(color_attachments=[texture]) + + ortho_camera = Camera2D(render_target=framebuffer) + assert ortho_camera.viewport_width == width + assert ortho_camera.viewport_height == height + + assert ortho_camera.viewport == (0, 0, width, height) + assert ortho_camera.viewport_left == 0 + assert ortho_camera.viewport_right == width + assert ortho_camera.viewport_bottom == 0 + assert ortho_camera.viewport_top == height + + +@pytest.mark.parametrize("width, height", RENDER_TARGET_SIZES) +def test_camera2d_from_raw_data_uses_render_target_size(window: Window, width, height): + + size = (width, height) + texture = window.ctx.texture(size, components=4) + framebuffer = window.ctx.framebuffer(color_attachments=[texture]) + + ortho_camera = Camera2D.from_raw_data(render_target=framebuffer) + assert ortho_camera.viewport_width == width + assert ortho_camera.viewport_height == height + + assert ortho_camera.viewport == (0, 0, width, height) + assert ortho_camera.viewport_left == 0 + assert ortho_camera.viewport_right == width + assert ortho_camera.viewport_bottom == 0 + assert ortho_camera.viewport_top == height diff --git a/tests/unit/camera/test_orthographic_camera.py b/tests/unit/camera/test_orthographic_projector.py similarity index 99% rename from tests/unit/camera/test_orthographic_camera.py rename to tests/unit/camera/test_orthographic_projector.py index 3ccf8e9684..de38b3643d 100644 --- a/tests/unit/camera/test_orthographic_camera.py +++ b/tests/unit/camera/test_orthographic_projector.py @@ -5,7 +5,6 @@ def test_orthographic_projector_use(window: Window): # Given - from pyglet.math import Mat4 ortho_camera = camera.OrthographicProjector() view_matrix = ortho_camera._generate_view_matrix() @@ -184,4 +183,4 @@ def test_orthographic_projector_map_coordinates_zoom(window: Window, width, heig ortho_camera.map_screen_to_world_coordinate(mouse_pos_b) == pytest.approx(((100 - half_width)*4.0, (100 - half_height)*4.0, 0.0)) - ) \ No newline at end of file + )