Skip to content

Commit

Permalink
Improved some Camera2D methods, and filled in temporary docs (#2467)
Browse files Browse the repository at this point in the history
* slight correction of Rect.__bool__

* Update `Camera2D.match_...` based on feedback.

* Update Camera.rst with temporary docs pulled from README.md

* finished converting from md to rst

* small doc updates, example updating, and method name changes
  • Loading branch information
DragonMoffon authored Dec 8, 2024
1 parent 33c368d commit e202cfc
Show file tree
Hide file tree
Showing 21 changed files with 194 additions and 92 deletions.
11 changes: 6 additions & 5 deletions arcade/camera/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ world space is equal to one unit in view space

### Projection Matrices
The projection matrix takes the positions of objects in view space and projects them into screen space.
depending on the type of projection matrix how this exactly applies changes. Projection matrices along
depending on the type of projection matrix how this exactly applies changes. Projection matrices alone
do not fully project objects into screen space, instead they transform positions into unit space. This
special coordinate space ranges from -1 to 1 in the x, y, and z axis. Anything within this range will
be transformed into screen space, and everything outside this range is discarded and left undrawn.
you can conceptualise projection matrices as taking a 6 sided 3D prism volume in view space and
you can conceptualise projection matrices as taking a 6 sided 3D volume in view space and
squashing it down into a uniformly sized cube. In every case the closest position projected along the
z-axis is given by the near value, while the furthest is given by the far value.

Expand Down Expand Up @@ -71,19 +71,20 @@ not be drawn.
- `arcade.camera.Projector` is a `Protocol` used internally by arcade
- `Projector.use()` sets the internal projection and view matrices used by Arcade and Pyglet
- `Projector.activate()` is the same as use, but works within a context manager using the `with` syntax
- `Projector.map_screen_to_world_coordinate(screen_coordinate, depth)`
- `Projector.unproject(screen_coordinate)`
provides a way to find the world position of any pixel position on screen.
- `Projector.project(world_coordinate)`
provides a way to find the screen position of any position in the world.
- There are multiple data types which provide the information required to make view and projection matrices
- `camera.CameraData` holds the position, forward, and up vectors along with a zoom value used to create the
view matrix
- `camera.OrthographicProjectionData` holds the left, right, bottom, top, near, far values needed to create a
orthographic projection matrix
- `camera.PerspectiveProjectionData` holds the aspect ratio, field of view, near and far needed to create a
perspective projection matrix.
- both ProjectionData data types also provide a viewport for setting the draw area when using the camera.
- There are three primary `Projectors` in `arcade.camera`
- `arcade.camera.Camera2D` is locked to the x-y plane and is perfect for most use cases within arcade.
- `arcade.camera.OrthographicProjector` can be freely positioned in 3D space, but the scale of objects does not
depend on the distance to the projector
- [not yet implemented ] `arcade.camera.PerspectiveProjector` can be freely position in 3D space,
- `arcade.camera.PerspectiveProjector` can be freely position in 3D space,
and objects look smaller the further from the camera they are
123 changes: 64 additions & 59 deletions arcade/camera/camera_2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,16 +329,17 @@ def equalise(self) -> None:
x, y = self._projection_data.rect.x, self._projection_data.rect.y
self._projection_data.rect = XYWH(x, y, self.viewport_width, self.viewport_height)

def match_screen(
def match_window(
self,
and_projection: bool = True,
and_scissor: bool = True,
and_position: bool = False,
viewport: bool = True,
projection: bool = True,
scissor: bool = True,
position: bool = False,
aspect: float | None = None,
) -> None:
"""
Sets the viewport to the size of the screen.
Should be called when the screen is resized.
Sets the viewport to the size of the window.
Should be called when the window is resized.
Args:
and_projection: Flag whether to also equalize the projection to the viewport.
Expand All @@ -353,96 +354,102 @@ def match_screen(
compared to the height. i.e. for an aspect ratio of ``4:3`` you should
input ``4.0/3.0`` or ``1.33333...``. Cannot be equal to zero.
"""
self.update_viewport(
self.update_values(
self._window.rect,
and_projection=and_projection,
and_scissor=and_scissor,
and_position=and_position,
viewport=viewport,
projection=projection,
scissor=scissor,
position=position,
aspect=aspect,
)

def match_target(
self,
and_projection: bool = True,
and_scissor: bool = True,
and_position: bool = False,
viewport: bool = True,
projection: bool = True,
scissor: bool = True,
position: bool = False,
aspect: float | None = None,
) -> None:
"""
Sets the viewport to the size of the Camera2D's render target.
Args:
and_projection: Flag whether to also equalize the projection to the viewport.
On by default
and_scissor: Flag whether to also equalize the scissor box to the viewport.
On by default
and_position: Flag whether to also center the camera to the viewport.
viewport: Flag whether to equalise the viewport to the area of the render target
projection: Flag whether to equalise the size of the projection to
match the render target
The projection center stays fixed, and the new projection matches only in size.
scissor: Flag whether to update the scissor value.
position: Flag whether to also center the camera to the value.
Off by default
aspect_ratio: The ratio between width and height that the viewport should
be constrained to. If unset then the viewport just matches the window
size. The aspect ratio describes how much larger the width should be
compared to the height. i.e. for an aspect ratio of ``4:3`` you should
aspect_ratio: The ratio between width and height that the value should
be constrained to. i.e. for an aspect ratio of ``4:3`` you should
input ``4.0/3.0`` or ``1.33333...``. Cannot be equal to zero.
If unset then the value will not be updated.
Raises:
ValueError: Will be raised if the Camera2D was has no render target.
"""
if self.render_target is None:
raise ValueError(
"Tried to match a non-exsistant render target. Please use `match_screen` instead"
"Tried to match a non-exsistant render target. Please use `match_window` instead"
)

self.update_viewport(
self.update_values(
LRBT(*self.render_target.viewport),
and_projection=and_projection,
and_scissor=and_scissor,
and_position=and_position,
viewport,
projection,
scissor,
position,
aspect=aspect,
)

def update_viewport(
def update_values(
self,
new_viewport: Rect,
and_projection: bool = True,
and_scissor: bool = True,
and_position: bool = False,
value: Rect,
viewport: bool = True,
projection: bool = True,
scissor: bool = True,
position: bool = False,
aspect: float | None = None,
):
"""
Convienence method for updating the viewport of the camera. To simply change
the viewport you can safely set the projection property.
Convienence method for updating the viewport, projection, position
and a few others with the same value.
Args:
and_projection: Flag whether to also equalize the projection to the viewport.
On by default
and_scissor: Flag whether to also equalize the scissor box to the viewport.
On by default
and_position: Flag whether to also center the camera to the viewport.
value: The rect that the values will be derived from.
viewport: Flag whether to equalise the viewport to the value.
projection: Flag whether to equalise the size of the projection to match the value.
The projection center stays fixed, and the new projection matches only in size.
scissor: Flag whether to update the scissor value.
position: Flag whether to also center the camera to the value.
Off by default
aspect_ratio: The ratio between width and height that the viewport should
be constrained to. If unset then the viewport just matches the window
size. The aspect ratio describes how much larger the width should be
compared to the height. i.e. for an aspect ratio of ``4:3`` you should
aspect_ratio: The ratio between width and height that the value should
be constrained to. i.e. for an aspect ratio of ``4:3`` you should
input ``4.0/3.0`` or ``1.33333...``. Cannot be equal to zero.
If unset then the value will not be updated.
"""
if aspect is not None:
if new_viewport.height * aspect < new_viewport.width:
w = new_viewport.height * aspect
h = new_viewport.height
if value.height * aspect < value.width:
w = value.height * aspect
h = value.height
else:
w = new_viewport.width
h = new_viewport.width / aspect
self.viewport = XYWH(new_viewport.x, new_viewport.y, w, h)
else:
self.viewport = new_viewport
w = value.width
h = value.width / aspect
value = XYWH(value.x, value.y, w, h)

if viewport:
self.viewport = value

if and_projection:
self.equalise()
if projection:
x, y = self._projection_data.rect.x, self._projection_data.rect.y
self._projection_data.rect = XYWH(x, y, value.width, value.height)

if and_scissor and self.scissor:
self.scissor = self.viewport
if scissor and self.scissor:
self.scissor = value

if and_position:
self.position = self.viewport.center
if position:
self.position = value.center

def aabb(self) -> Rect:
"""
Expand Down Expand Up @@ -898,7 +905,6 @@ def top_left(self, new_corner: Point2):
# top_center
@property
def top_center(self) -> Vec2:
# TODO correct
"""Get the top most position the camera can see"""
pos = self.position

Expand All @@ -908,7 +914,6 @@ def top_center(self) -> Vec2:

@top_center.setter
def top_center(self, new_top: Point2):
# TODO correct
ux, uy, *_ = self._camera_data.up
top = self.top

Expand Down
2 changes: 1 addition & 1 deletion arcade/examples/background_blending.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def on_key_release(self, symbol: int, modifiers: int):

def on_resize(self, width: int, height: int):
super().on_resize(width, height)
self.camera.match_screen(and_projection=True)
self.camera.match_window()

# This is to ensure the background covers the entire screen.
self.background_1.size = (width, height)
Expand Down
2 changes: 1 addition & 1 deletion arcade/examples/background_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def on_key_release(self, symbol: int, modifiers: int):

def on_resize(self, width: int, height: int):
super().on_resize(width, height)
self.camera.match_screen(and_projection=True)
self.camera.match_window()


def main():
Expand Down
2 changes: 1 addition & 1 deletion arcade/examples/background_parallax.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def on_key_release(self, symbol: int, modifiers: int):

def on_resize(self, width: int, height: int):
super().on_resize(width, height)
self.camera.match_screen(and_projection=True)
self.camera.match_window()
full_width_size = (width, SCALED_BG_LAYER_HEIGHT_PX)

# We can iterate through a background group,
Expand Down
2 changes: 1 addition & 1 deletion arcade/examples/background_scrolling.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def on_key_release(self, symbol: int, modifiers: int):

def on_resize(self, width: int, height: int):
super().on_resize(width, height)
self.camera.match_screen(and_projection=True)
self.camera.match_window()

# This is to ensure the background covers the entire screen.
self.background.size = (width, height)
Expand Down
2 changes: 1 addition & 1 deletion arcade/examples/background_stationary.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def on_key_release(self, symbol: int, modifiers: int):

def on_resize(self, width: int, height: int):
super().on_resize(width, height)
self.camera.match_screen(and_projection=True)
self.camera.match_window()


def main():
Expand Down
2 changes: 1 addition & 1 deletion arcade/examples/camera_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def setup(self):
def on_resize(self, width, height):
"""Resize window"""
super().on_resize(width, height)
self.camera.match_screen(and_projection=True)
self.camera.match_window()

def on_draw(self):
"""Render the screen."""
Expand Down
2 changes: 1 addition & 1 deletion arcade/examples/gl/custom_sprite.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def __init__(self):

def on_resize(self, width: int, height: int):
super().on_resize(width, height)
self.camera.match_screen(and_position=True)
self.camera.match_window(position=True)

def on_draw(self):
self.clear()
Expand Down
2 changes: 1 addition & 1 deletion arcade/examples/light_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ def on_draw(self):
def on_resize(self, width, height):
""" User resizes the screen. """
super().on_resize(width, height)
self.camera.match_screen()
self.camera.match_window()

# --- Light related ---
# We need to resize the light layer to
Expand Down
4 changes: 2 additions & 2 deletions arcade/examples/minimap_camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,8 @@ def on_resize(self, width: int, height: int):
Handle the user grabbing the edge and resizing the window.
"""
super().on_resize(width, height)
self.camera_sprites.match_screen()
self.camera_gui.match_screen(and_position=True)
self.camera_sprites.match_window()
self.camera_gui.match_window(position=True)
self.camera_minimap.viewport = arcade.LBWH(
width - self.camera_minimap.viewport_width,
height - self.camera_minimap.viewport_height,
Expand Down
4 changes: 2 additions & 2 deletions arcade/examples/minimap_texture.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,8 @@ def on_resize(self, width: int, height: int):
Handle the user grabbing the edge and resizing the window.
"""
super().on_resize(width, height)
self.camera_sprites.match_screen(and_projection=True)
self.camera_gui.match_screen(and_projection=True)
self.camera_sprites.match_window()
self.camera_gui.match_window()


def main():
Expand Down
4 changes: 2 additions & 2 deletions arcade/examples/procedural_caves_cellular.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,8 @@ def on_resize(self, width: int, height: int):
Handle the user grabbing the edge and resizing the window.
"""
super().on_resize(width, height)
self.camera_sprites.match_screen(and_projection=True)
self.camera_gui.match_screen(and_projection=True)
self.camera_sprites.match_window()
self.camera_gui.match_window()

def on_update(self, delta_time):
""" Movement and game logic """
Expand Down
4 changes: 2 additions & 2 deletions arcade/examples/sprite_move_scrolling.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@ def on_resize(self, width: int, height: int):
Handle the user grabbing the edge and resizing the window.
"""
super().on_resize(width, height)
self.camera_sprites.match_screen(and_projection=True)
self.camera_gui.match_screen(and_projection=True)
self.camera_sprites.match_window()
self.camera_gui.match_window()


def main():
Expand Down
4 changes: 2 additions & 2 deletions arcade/examples/sprite_move_scrolling_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,8 @@ def on_resize(self, width: int, height: int):
Handle the user grabbing the edge and resizing the window.
"""
super().on_resize(width, height)
self.camera_sprites.match_screen(and_projection=True)
self.camera_gui.match_screen(and_projection=True, and_position=True)
self.camera_sprites.match_window()
self.camera_gui.match_window(position=True)


def main():
Expand Down
4 changes: 2 additions & 2 deletions arcade/examples/sprite_move_scrolling_shake.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,8 @@ def on_resize(self, width: int, height: int):
Handle the user grabbing the edge and resizing the window.
"""
super().on_resize(width, height)
self.camera_sprites.match_screen(and_projection=True)
self.camera_gui.match_screen(and_projection=True, and_position=True)
self.camera_sprites.match_window()
self.camera_gui.match_window(position=True)


def main():
Expand Down
6 changes: 3 additions & 3 deletions arcade/examples/template_platformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,9 @@ def on_resize(self, width: int, height: int):
""" Resize window """
super().on_resize(width, height)
# Update the cameras to match the new window size
self.camera_sprites.match_screen(and_projection=True)
# The and_position property keeps `0, 0` in the bottom left corner.
self.camera_gui.match_screen(and_projection=True, and_position=True)
self.camera_sprites.match_window()
# The position argument keeps `0, 0` in the bottom left corner.
self.camera_gui.match_window(position=True)


def main():
Expand Down
4 changes: 2 additions & 2 deletions arcade/gui/ui_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,12 +432,12 @@ def on_resize(self, width, height):
"""Resize the UIManager and all of its surfaces."""
# resize ui camera
bottom_left = self.camera.bottom_left
self.camera.match_screen()
self.camera.match_window()
self.camera.bottom_left = bottom_left

# resize render to surface camera
bottom_left = self._render_to_surface_camera.bottom_left
self._render_to_surface_camera.match_screen()
self._render_to_surface_camera.match_window()
self._render_to_surface_camera.bottom_left = bottom_left

scale = self.window.get_pixel_ratio()
Expand Down
Loading

0 comments on commit e202cfc

Please sign in to comment.