diff --git a/arcade/application.py b/arcade/application.py index 759e7a7c3..8980638b9 100644 --- a/arcade/application.py +++ b/arcade/application.py @@ -553,6 +553,8 @@ def _dispatch_frame(self, delta_time: float) -> None: # In case the window close in on_update, on_fixed_update or input callbacks if not self.closed: + # Reset the context, camera, and active framebuffer. + self._ctx.reset() self.draw(self._accumulated_draw_time) self._accumulated_draw_time %= self._draw_rate diff --git a/arcade/camera/__init__.py b/arcade/camera/__init__.py index 902e8e402..1ea7e167b 100644 --- a/arcade/camera/__init__.py +++ b/arcade/camera/__init__.py @@ -3,7 +3,7 @@ Providing a multitude of camera's for any need. """ -from arcade.camera.data_types import ( +from .data_types import ( Projection, Projector, CameraData, @@ -11,7 +11,7 @@ PerspectiveProjectionData, ) -from arcade.camera.projection_functions import ( +from .projection_functions import ( generate_view_matrix, generate_orthographic_matrix, generate_perspective_matrix, @@ -21,10 +21,10 @@ unproject_perspective, ) -from arcade.camera.orthographic import OrthographicProjector -from arcade.camera.perspective import PerspectiveProjector - -from arcade.camera.camera_2d import Camera2D +from .viewport import ViewportProjector +from .orthographic import OrthographicProjector +from .perspective import PerspectiveProjector +from .camera_2d import Camera2D import arcade.camera.grips as grips @@ -32,6 +32,7 @@ __all__ = [ "Projection", "Projector", + "ViewportProjector", "CameraData", "generate_view_matrix", "OrthographicProjectionData", diff --git a/arcade/camera/default.py b/arcade/camera/default.py index dcb94d4b9..3b4da0912 100644 --- a/arcade/camera/default.py +++ b/arcade/camera/default.py @@ -1,107 +1,19 @@ from __future__ import annotations -from collections.abc import Generator -from contextlib import contextmanager from typing import TYPE_CHECKING -from pyglet.math import Mat4, Vec2, Vec3 -from typing_extensions import Self +from pyglet.math import Mat4 -from arcade.types import LBWH, Point, Rect -from arcade.window_commands import get_window +from arcade.types import LBWH + +from .viewport import ViewportProjector if TYPE_CHECKING: from arcade.context import ArcadeContext -__all__ = ["ViewportProjector", "DefaultProjector"] - - -class ViewportProjector: - """ - A simple Projector which does not rely on any camera PoDs. - - Does not have a way of moving, rotating, or zooming the camera. - perfect for something like UI or for mapping to an offscreen framebuffer. - - Args: - viewport: The viewport to project to. - context: The window context to bind the camera to. Defaults to the currently active window. - """ - - def __init__( - self, - viewport: Rect | None = None, - *, - context: ArcadeContext | None = None, - ): - self._ctx: ArcadeContext = context or get_window().ctx - self._viewport: Rect = viewport or LBWH(*self._ctx.viewport) - self._projection_matrix: Mat4 = Mat4.orthogonal_projection( - 0.0, self._viewport.width, 0.0, self._viewport.height, -100, 100 - ) - - @property - def viewport(self) -> Rect: - """ - The viewport use to derive projection and view matrix. - """ - return self._viewport - - @viewport.setter - def viewport(self, viewport: Rect) -> None: - self._viewport = viewport - self._projection_matrix = Mat4.orthogonal_projection( - 0, viewport.width, 0, viewport.height, -100, 100 - ) - - def use(self) -> None: - """ - Set the window's projection and view matrix. - Also sets the projector as the windows current camera. - """ - self._ctx.current_camera = self - - self._ctx.viewport = self.viewport.lbwh_int # get the integer 4-tuple LBWH - - self._ctx.view_matrix = Mat4() - self._ctx.projection_matrix = self._projection_matrix - - @contextmanager - def activate(self) -> Generator[Self, None, None]: - """ - The context manager version of the use method. - - usable with the 'with' block. e.g. 'with ViewportProjector.activate() as cam: ...' - """ - previous = self._ctx.current_camera - try: - self.use() - yield self - finally: - previous.use() - - def project(self, world_coordinate: Point) -> Vec2: - """ - Take a Vec2 or Vec3 of coordinates and return the related screen coordinate - """ - x, y, *z = world_coordinate - return Vec2(x, y) - - def unproject(self, screen_coordinate: Point) -> Vec3: - """ - Map the screen pos to screen_coordinates. - - Due to the nature of viewport projector this does not do anything. - """ - x, y, *_z = screen_coordinate - z = 0.0 if not _z else _z[0] - - return Vec3(x, y, z) +__all__ = ("DefaultProjector",) -# As this class is only supposed to be used internally -# I wanted to place an _ in front, but the linting complains -# about it being a protected class. class DefaultProjector(ViewportProjector): """ An extremely limited projector which lacks any kind of control. This is only diff --git a/arcade/camera/viewport.py b/arcade/camera/viewport.py new file mode 100644 index 000000000..c08526310 --- /dev/null +++ b/arcade/camera/viewport.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +from collections.abc import Generator +from contextlib import contextmanager +from typing import TYPE_CHECKING + +from pyglet.math import Mat4, Vec2, Vec3 +from typing_extensions import Self + +from arcade.types import LBWH, Point, Rect +from arcade.window_commands import get_window + +if TYPE_CHECKING: + from arcade.context import ArcadeContext + +__all__ = ("ViewportProjector",) + + +class ViewportProjector: + """ + A simple Projector which does not rely on any camera PoDs. + + Does not have a way of moving, rotating, or zooming the camera. + perfect for something like UI or for mapping to an offscreen framebuffer. + + Args: + viewport: The viewport to project to. + context: The window context to bind the camera to. Defaults to the currently active window. + """ + + def __init__( + self, + viewport: Rect | None = None, + *, + context: ArcadeContext | None = None, + ): + self._ctx: ArcadeContext = context or get_window().ctx + self._viewport: Rect = viewport or LBWH(*self._ctx.viewport) + self._projection_matrix: Mat4 = Mat4.orthogonal_projection( + 0.0, self._viewport.width, 0.0, self._viewport.height, -100, 100 + ) + + @property + def viewport(self) -> Rect: + """ + The viewport use to derive projection and view matrix. + """ + return self._viewport + + @viewport.setter + def viewport(self, viewport: Rect) -> None: + self._viewport = viewport + self._projection_matrix = Mat4.orthogonal_projection( + 0, viewport.width, 0, viewport.height, -100, 100 + ) + + def use(self) -> None: + """ + Set the window's projection and view matrix. + Also sets the projector as the windows current camera. + """ + self._ctx.current_camera = self + + self._ctx.viewport = self.viewport.lbwh_int # get the integer 4-tuple LBWH + + self._ctx.view_matrix = Mat4() + self._ctx.projection_matrix = self._projection_matrix + + @contextmanager + def activate(self) -> Generator[Self, None, None]: + """ + The context manager version of the use method. + + usable with the 'with' block. e.g. 'with ViewportProjector.activate() as cam: ...' + """ + previous = self._ctx.current_camera + try: + self.use() + yield self + finally: + previous.use() + + def project(self, world_coordinate: Point) -> Vec2: + """ + Take a Vec2 or Vec3 of coordinates and return the related screen coordinate + """ + x, y, *z = world_coordinate + return Vec2(x, y) + + def unproject(self, screen_coordinate: Point) -> Vec3: + """ + Map the screen pos to screen_coordinates. + + Due to the nature of viewport projector this does not do anything. + """ + x, y, *_z = screen_coordinate + z = 0.0 if not _z else _z[0] + + return Vec3(x, y, z) diff --git a/arcade/context.py b/arcade/context.py index b727fa179..1f114232e 100644 --- a/arcade/context.py +++ b/arcade/context.py @@ -311,10 +311,7 @@ def reset(self) -> None: self.bind_window_block() # self.active_program = None self.viewport = 0, 0, self.window.width, self.window.height - self.view_matrix = Mat4() - self.projection_matrix = Mat4.orthogonal_projection( - 0, self.window.width, 0, self.window.height, -100, 100 - ) + self._default_camera.use() self.enable_only(self.BLEND) self.blend_func = self.BLEND_DEFAULT self.point_size = 1.0 @@ -378,7 +375,7 @@ def viewport(self) -> tuple[int, int, int, int]: @viewport.setter def viewport(self, value: tuple[int, int, int, int]): self.active_framebuffer.viewport = value - if self._default_camera == self.current_camera: + if self._default_camera is self.current_camera: self._default_camera.use() @property diff --git a/arcade/gl/framebuffer.py b/arcade/gl/framebuffer.py index 65d109161..5fdd050d0 100644 --- a/arcade/gl/framebuffer.py +++ b/arcade/gl/framebuffer.py @@ -225,6 +225,9 @@ def use(self, *, force: bool = False): self._use(force=force) self._ctx.active_framebuffer = self + # This is a hack to ensure the default camera has the correct viewport. + self._ctx.viewport = self.viewport + @abstractmethod def _use(self, *, force: bool = False): """Internal use that do not change the global active framebuffer"""