Skip to content

Commit 3965847

Browse files
committed
improve camera init position logic and add aspect argument to init
1 parent 7ae4a01 commit 3965847

1 file changed

Lines changed: 37 additions & 9 deletions

File tree

arcade/camera/camera_2d.py

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ class Camera2D:
6060
If the viewport is not 1:1 with the projection then positions in world space
6161
won't match pixels on screen.
6262
position:
63-
The 2D position of the camera in the XY plane.
63+
The 2D position of the camera.
64+
65+
This is in world space, so the same as :py:class:`Sprite` and draw commands.
66+
The default projection is a :py:func:`XYWH` rect positioned at (0, 0) so the
67+
position of the camera is the center of the viewport.
6468
up:
6569
A 2D vector which describes which direction is up
6670
(defines the +Y-axis of the camera space).
@@ -75,6 +79,11 @@ class Camera2D:
7579
The near clipping plane of the camera.
7680
far:
7781
The far clipping plane of the camera.
82+
aspect: The ratio between width and height that the viewport should
83+
be constrained to. If unset then the viewport just matches the given
84+
size. The aspect ratio describes how much larger the width should be
85+
compared to the height. i.e. for an aspect ratio of ``4:3`` you should
86+
input ``4.0/3.0`` or ``1.33333...``. Cannot be equal to zero.
7887
scissor:
7988
A ``Rect`` which will crop the camera's output to this area on screen.
8089
Unlike the viewport this has no influence on the visuals rendered with
@@ -96,6 +105,7 @@ def __init__(
96105
near: float = DEFAULT_NEAR_ORTHO,
97106
far: float = DEFAULT_FAR,
98107
*,
108+
aspect: float | None = None,
99109
scissor: Rect | None = None,
100110
render_target: Framebuffer | None = None,
101111
window: Window | None = None,
@@ -111,7 +121,19 @@ def __init__(
111121
# but we need to have some form of default size.
112122
render_target = render_target or self._window.ctx.screen
113123
viewport = viewport or LBWH(*render_target.viewport)
114-
width, height = viewport.size
124+
125+
if aspect is None:
126+
width, height = viewport.size
127+
elif aspect == 0.0:
128+
raise ZeroProjectionDimension(
129+
"aspect ratio is 0 which will cause invalid viewport dimensions."
130+
)
131+
elif viewport.height * aspect < viewport.width:
132+
width = viewport.height * aspect
133+
height = viewport.height
134+
else:
135+
width = viewport.width
136+
height = viewport.width / aspect
115137
half_width = width / 2
116138
half_height = height / 2
117139

@@ -136,8 +158,10 @@ def __init__(
136158
f"projection depth is 0 due to equal {near=} and {far=} values"
137159
)
138160

139-
pos_x = position[0] if position is not None else half_width
140-
pos_y = position[1] if position is not None else half_height
161+
# By using -left and -bottom this ensures that (0.0, 0.0) is always
162+
# in the bottom left corner of the viewport
163+
pos_x = position[0] if position is not None else -left
164+
pos_y = position[1] if position is not None else -bottom
141165
self._camera_data = CameraData(
142166
position=(pos_x, pos_y, 0.0),
143167
up=(up[0], up[1], 0.0),
@@ -350,7 +374,7 @@ def match_window(
350374
scissor: Flag whether to also equalize the scissor box to the viewport.
351375
On by default
352376
position: Flag whether to position the camera so that (0.0, 0.0) is in
353-
the bottom-left
377+
the bottom-left of the viewport
354378
aspect: The ratio between width and height that the viewport should
355379
be constrained to. If unset then the viewport just matches the window
356380
size. The aspect ratio describes how much larger the width should be
@@ -384,7 +408,7 @@ def match_target(
384408
The projection center stays fixed, and the new projection matches only in size.
385409
scissor: Flag whether to update the scissor value.
386410
position: Flag whether to position the camera so that (0.0, 0.0) is in
387-
the bottom-left
411+
the bottom-left of the viewport
388412
aspect: The ratio between width and height that the value should
389413
be constrained to. i.e. for an aspect ratio of ``4:3`` you should
390414
input ``4.0/3.0`` or ``1.33333...``. Cannot be equal to zero.
@@ -426,7 +450,7 @@ def update_values(
426450
The projection center stays fixed, and the new projection matches only in size.
427451
scissor: Flag whether to update the scissor value.
428452
position: Flag whether to position the camera so that (0.0, 0.0) is in
429-
the bottom-left
453+
the bottom-left of the viewport
430454
aspect: The ratio between width and height that the value should
431455
be constrained to. i.e. for an aspect ratio of ``4:3`` you should
432456
input ``4.0/3.0`` or ``1.33333...``. Cannot be equal to zero.
@@ -452,7 +476,11 @@ def update_values(
452476
self.scissor = value
453477

454478
if position:
455-
self.position = Vec2(-self._projection_data.left, -self._projection_data.bottom)
479+
self._camera_data.position = (
480+
-self._projection_data.left,
481+
-self._projection_data.bottom,
482+
self._camera_data.position[2]
483+
)
456484

457485
def aabb(self) -> Rect:
458486
"""
@@ -647,7 +675,7 @@ def position(self) -> Vec2:
647675
648676
This is in world space, so the same as :py:class:`Sprite` and draw commands.
649677
The default projection is a :py:func:`XYWH` rect positioned at (0, 0) so the
650-
position of the camera is the center of the screen.
678+
position of the camera is the center of the viewport.
651679
"""
652680
return Vec2(self._camera_data.position[0], self._camera_data.position[1])
653681

0 commit comments

Comments
 (0)