|
4 | 4 | wide usage throughout Arcade's camera code. |
5 | 5 | """ |
6 | 6 | from __future__ import annotations |
7 | | -from typing import Protocol, Tuple, Iterator, Optional |
| 7 | +from typing import Protocol, Tuple, Iterator, Optional, Generator |
8 | 8 | from contextlib import contextmanager |
9 | 9 |
|
| 10 | +from typing_extensions import Self |
10 | 11 | from pyglet.math import Vec3 |
11 | 12 |
|
12 | 13 |
|
@@ -184,18 +185,110 @@ def __repr__(self): |
184 | 185 |
|
185 | 186 |
|
186 | 187 | class Projection(Protocol): |
| 188 | + """Matches the data universal in Arcade's projection data objects. |
| 189 | +
|
| 190 | + There are multiple types of projections used in games, but all the |
| 191 | + common ones share key features. This :py:class:`~typing.Protocol`: |
| 192 | +
|
| 193 | + #. Defines those shared elements |
| 194 | + #. Annotates these in code for both humans and automated type |
| 195 | + checkers |
| 196 | +
|
| 197 | + The specific implementations which match it are used inside of |
| 198 | + implementations of Arcade's :py:class:`.Projector` behavior. All |
| 199 | + of these projectors rely on a ``viewport`` as well as ``near`` and |
| 200 | + ``far`` values. |
| 201 | +
|
| 202 | + The ``viewport`` is measured in screen pixels. By default, the |
| 203 | + conventions for this are the same as the rest of Arcade and |
| 204 | + OpenGL: |
| 205 | +
|
| 206 | + * X is measured rightward from left of the screen |
| 207 | + * Y is measured up from the bottom of the screen |
| 208 | +
|
| 209 | + Although the ``near`` and ``far`` values are describe the cutoffs |
| 210 | + for what the camera sees in world space, the exact meaning differs |
| 211 | + between projection type. |
| 212 | +
|
| 213 | + .. list-table:: |
| 214 | + :header-rows: 1 |
| 215 | +
|
| 216 | + * - Common Projection Type |
| 217 | + - Meaning of ``near`` & ``far`` |
| 218 | +
|
| 219 | + * - Simple Orthographic |
| 220 | + - The Z position in world space |
| 221 | +
|
| 222 | + * - Perspective & Isometric |
| 223 | + - Where the rear and front clipping planes sit along a |
| 224 | + camera's :py:attr:`.CameraData.forward` vector. |
| 225 | +
|
| 226 | + """ |
187 | 227 | viewport: Tuple[int, int, int, int] |
188 | 228 | near: float |
189 | 229 | far: float |
190 | 230 |
|
191 | 231 |
|
192 | 232 | class Projector(Protocol): |
| 233 | + """Projects from world coordinates to viewport pixel coordinates. |
| 234 | +
|
| 235 | + Projectors also support converting in the opposite direction from |
| 236 | + screen pixel coordinates to world space coordinates. |
| 237 | +
|
| 238 | + The two key spatial methods which do this are: |
| 239 | +
|
| 240 | + .. list-table:: |
| 241 | + :header-rows: 1 |
| 242 | + * - Method |
| 243 | + - Action |
| 244 | +
|
| 245 | + * - :py:meth:`.project` |
| 246 | + - Turn world coordinates into pixel coordinates relative |
| 247 | + to the origin (bottom left by default). |
| 248 | +
|
| 249 | + * - :py:meth:`.unproject` |
| 250 | + - Convert screen pixel coordinates into world space. |
| 251 | +
|
| 252 | + .. note: Every :py:class:`.Camera` is also a kind of projector. |
| 253 | +
|
| 254 | + The other required methods are for helping manage which camera is |
| 255 | + currently used to draw. |
| 256 | +
|
| 257 | + """ |
193 | 258 |
|
194 | 259 | def use(self) -> None: |
| 260 | + """Set the GL context to use this projector and its settings. |
| 261 | +
|
| 262 | + .. warning:: You may be looking for:py:meth:`.activate`! |
| 263 | +
|
| 264 | + This method only sets rendering state for a given |
| 265 | + projector. Since it doesn't restore any afterward, |
| 266 | + it's easy to misuse in ways which can cause bugs |
| 267 | + or temporarily break a game's rendering until |
| 268 | + relaunch. For reliable, automatic clean-up see |
| 269 | + the :py:meth:`.activate` method instead. |
| 270 | +
|
| 271 | + If you are implementing your own custom projector, this method |
| 272 | + should only: |
| 273 | +
|
| 274 | + #. Set the Arcade :py:class:`~arcade.Window`'s |
| 275 | + :py:attr:`~arcade.Window.current_camera` to this object |
| 276 | + #. Calculate any required view and projection matrices |
| 277 | + #. Set any resulting values on the current |
| 278 | + :py:class:`~arcade.context.ArcadeContext`, including the: |
| 279 | +
|
| 280 | + * :py:attr:`~arcade.context.ArcadeContext.viewport` |
| 281 | + * :py:attr:`~arcade.context.ArcadeContext.view_matrix` |
| 282 | + * :py:attr:`~arcade.context.ArcadeContext.projection_matrix` |
| 283 | +
|
| 284 | + This method should **never** handle cleanup. That is the |
| 285 | + responsibility of :py:attr:`.activate`. |
| 286 | +
|
| 287 | + """ |
195 | 288 | ... |
196 | 289 |
|
197 | 290 | @contextmanager |
198 | | - def activate(self) -> Iterator[Projector]: |
| 291 | + def activate(self) -> Generator[Self, None, None]: |
199 | 292 | ... |
200 | 293 |
|
201 | 294 | def map_screen_to_world_coordinate( |
|
0 commit comments