@@ -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