Skip to content

Commit e554b02

Browse files
authored
Promote sprite depth examples from experimental to main (#2434)
* Fix sprite depth examples to use new-style clock property instead of updating time locally * Fix annoying indent on list at top of comments * Convert sprite_depth_cosine.py and depth_of_field.py to use new-style views style * Use local assignments where appropriate * Move examples * Fix run strings at top of file to reflect new locations * Make the background clear color a Color instance defined at the top of the file * Use WINDOW_TITLE, WINDOW_WIDTH, and WINDOW_HEIGHT constants * Add sprite_deph_cosine and depth_of_field to arcade.examples * Add a screenshots and .rst files for each * Add depth_of_field.py to the examples gallery * Add a screenshot for the page and gallery * Add an example .rst file for it * Add entries in the examples page * Fix issue @alejcas pointed out * Put example tiles in appropriate sections based on feedback * Fix comments and naming conventions for example components per GitHub review * Centralize and document near/far defaults in cameras * Linting
1 parent ce177ba commit e554b02

File tree

11 files changed

+164
-33
lines changed

11 files changed

+164
-33
lines changed

arcade/camera/camera_2d.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from typing_extensions import Self
99

1010
from arcade.camera.data_types import (
11+
DEFAULT_FAR,
12+
DEFAULT_NEAR_ORTHO,
1113
CameraData,
1214
OrthographicProjectionData,
1315
ZeroProjectionDimension,
@@ -90,8 +92,8 @@ def __init__(
9092
up: tuple[float, float] = (0.0, 1.0),
9193
zoom: float = 1.0,
9294
projection: Rect | None = None,
93-
near: float = -100.0,
94-
far: float = 100.0,
95+
near: float = DEFAULT_NEAR_ORTHO,
96+
far: float = DEFAULT_FAR,
9597
*,
9698
scissor: Rect | None = None,
9799
render_target: Framebuffer | None = None,

arcade/camera/data_types.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from __future__ import annotations
88

99
from contextlib import contextmanager
10-
from typing import Generator, Protocol
10+
from typing import Final, Generator, Protocol
1111

1212
from pyglet.math import Vec2, Vec3
1313
from typing_extensions import Self
@@ -16,6 +16,8 @@
1616

1717
__all__ = [
1818
"CameraData",
19+
"DEFAULT_FAR",
20+
"DEFAULT_NEAR_ORTHO",
1921
"OrthographicProjectionData",
2022
"PerspectiveProjectionData",
2123
"Projection",
@@ -25,6 +27,23 @@
2527
"duplicate_camera_data",
2628
]
2729

30+
DEFAULT_NEAR_ORTHO: Final[float] = -100.0
31+
"""The default backward-facing depth cutoff for orthographic rendering.
32+
33+
Unless an orthographic camera is provided a different value, this will be
34+
used as the near cutoff for its point of view.
35+
36+
The :py:class:`~arcade.camera.perspective.PerspectiveProjector` uses
37+
``0.01`` as its default near value to avoid division by zero.
38+
"""
39+
40+
DEFAULT_FAR: Final[float] = 100.0
41+
"""The default forward-facing depth cutoff for all Arcade cameras.
42+
43+
Unless a camera is provided a different value, anything further away than this
44+
value will not be drawn.
45+
"""
46+
2847

2948
class ZeroProjectionDimension(ValueError):
3049
"""A projection's dimensions were zero along at least one axis.

arcade/camera/orthographic.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@
66
from pyglet.math import Mat4, Vec2, Vec3
77
from typing_extensions import Self
88

9-
from arcade.camera.data_types import CameraData, OrthographicProjectionData, Projector
9+
from arcade.camera.data_types import (
10+
DEFAULT_FAR,
11+
DEFAULT_NEAR_ORTHO,
12+
CameraData,
13+
OrthographicProjectionData,
14+
Projector,
15+
)
1016
from arcade.camera.projection_functions import (
1117
generate_orthographic_matrix,
1218
generate_view_matrix,
@@ -78,8 +84,8 @@ def __init__(
7884
0.5 * self._window.width, # Left, Right
7985
-0.5 * self._window.height,
8086
0.5 * self._window.height, # Bottom, Top
81-
-100,
82-
100, # Near, Far
87+
DEFAULT_NEAR_ORTHO,
88+
DEFAULT_FAR, # Near, Far
8389
)
8490

8591
@property

arcade/camera/perspective.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from pyglet.math import Mat4, Vec2, Vec3
88
from typing_extensions import Self
99

10-
from arcade.camera.data_types import CameraData, PerspectiveProjectionData, Projector
10+
from arcade.camera.data_types import DEFAULT_FAR, CameraData, PerspectiveProjectionData, Projector
1111
from arcade.camera.projection_functions import (
1212
generate_perspective_matrix,
1313
generate_view_matrix,
@@ -27,6 +27,12 @@
2727
class PerspectiveProjector(Projector):
2828
"""
2929
The simplest from of a perspective camera.
30+
31+
.. warning:: Near cutoffs for perspective projection must be greater than zero.
32+
33+
This prevents division by zero errors since perspective involves
34+
dividing by distance.
35+
3036
Using ViewData and PerspectiveProjectionData PoDs (Pack of Data)
3137
it generates the correct projection and view matrices. It also
3238
provides methods and a context manager for using the matrices in
@@ -78,7 +84,7 @@ def __init__(
7884
self._window.width / self._window.height, # Aspect
7985
60, # Field of View,
8086
0.01,
81-
100.0, # near, # far
87+
DEFAULT_FAR, # near, # far
8288
)
8389

8490
@property

arcade/experimental/depth_of_field.py renamed to arcade/examples/depth_of_field.py

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
It uses the depth attribute of along with blurring and shaders to
44
roughly approximate depth-based blur effects. The focus bounces
55
back forth automatically between a maximum and minimum depth value
6-
based on time. Adjust the arguments to the App class at the bottom
7-
of the file to change the speed.
6+
based on time. Change the speed and focus via either the constants
7+
at the top of the file or the arguments passed to it at the bottom of
8+
the file.
89
910
This example works by doing the following for each frame:
1011
@@ -17,7 +18,7 @@
1718
both easier and more performant than more accurate blur approaches.
1819
1920
If Python and Arcade are installed, this example can be run from the command line with:
20-
python -m arcade.experimental.examples.array_backed_grid
21+
python -m arcade.examples.depth_of_field
2122
"""
2223

2324
from __future__ import annotations
@@ -30,12 +31,20 @@
3031

3132
from pyglet.graphics import Batch
3233

33-
from arcade import SpriteList, SpriteSolidColor, Text, Window, get_window
34+
import arcade
35+
from arcade import get_window, SpriteList, SpriteSolidColor, Text, Window, View
36+
from arcade.camera.data_types import DEFAULT_NEAR_ORTHO, DEFAULT_FAR
3437
from arcade.color import RED
3538
from arcade.experimental.postprocessing import GaussianBlur
3639
from arcade.gl import NEAREST, Program, Texture2D, geometry
3740
from arcade.types import RGBA255, Color
3841

42+
WINDOW_TITLE = "Depth of Field Example"
43+
44+
WINDOW_WIDTH = 1280
45+
WINDOW_HEIGHT = 720
46+
BACKGROUND_GRAY = Color(155, 155, 155, 255)
47+
3948

4049
class DepthOfField:
4150
"""A depth-of-field effect we can use as a render context manager.
@@ -50,7 +59,7 @@ class DepthOfField:
5059
def __init__(
5160
self,
5261
size: tuple[int, int] | None = None,
53-
clear_color: RGBA255 = (155, 155, 155, 255),
62+
clear_color: RGBA255 = BACKGROUND_GRAY
5463
):
5564
self._geo = geometry.quad_2d_fs()
5665
self._win: Window = get_window()
@@ -171,9 +180,13 @@ def render(self):
171180
self._geo.render(self._render_program)
172181

173182

174-
class App(Window):
183+
class GameView(View):
175184
"""Window subclass to hold sprites and rendering helpers.
176185
186+
To keep the code simpler, this example uses a default camera. That means
187+
that any sprite outside Arcade's default camera near and far render cutoffs
188+
(``-100.0`` to ``100.0``) will not be drawn.
189+
177190
Args:
178191
text_color:
179192
The color of the focus indicator.
@@ -182,13 +195,19 @@ class App(Window):
182195
focus_change_speed:
183196
How fast the focus bounces back and forth
184197
between the ``-focus_range`` and ``focus_range``.
198+
min_sprite_depth:
199+
The minimum sprite depth we'll generate sprites between
200+
max_sprite_depth:
201+
The maximum sprite depth we'll generate sprites between.
185202
"""
186203

187204
def __init__(
188205
self,
189206
text_color: RGBA255 = RED,
190207
focus_range: float = 16.0,
191208
focus_change_speed: float = 0.1,
209+
min_sprite_depth: float = DEFAULT_NEAR_ORTHO,
210+
max_sprite_depth: float = DEFAULT_FAR
192211
):
193212
super().__init__()
194213
self.sprites: SpriteList = SpriteList()
@@ -207,7 +226,7 @@ def __init__(
207226

208227
# Randomize sprite depth, size, and angle, but set color from depth.
209228
for _ in range(100):
210-
depth = uniform(-100, 100)
229+
depth = uniform(min_sprite_depth, max_sprite_depth)
211230
color = Color.from_gray(int(255 * (depth + 100) / 200))
212231
s = SpriteSolidColor(
213232
randint(100, 200),
@@ -223,7 +242,8 @@ def __init__(
223242
self.dof = DepthOfField()
224243

225244
def on_update(self, delta_time: float):
226-
raw_focus = self.focus_range * (cos(pi * self.focus_change_speed * self.time) * 0.5 + 0.5)
245+
time = self.window.time
246+
raw_focus = self.focus_range * (cos(pi * self.focus_change_speed * time) * 0.5 + 0.5)
227247
self.dof.render_program["focus_depth"] = raw_focus / self.focus_range
228248
self.indicator_label.value = f"Focus depth: {raw_focus:.3f} / {self.focus_range}"
229249

@@ -235,10 +255,25 @@ def on_draw(self):
235255
self.sprites.draw(pixelated=True)
236256

237257
# Draw the blurred frame buffer and then the focus display
238-
self.use()
258+
window = self.window
259+
window.use()
239260
self.dof.render()
240261
self._batch.draw()
241262

242263

264+
def main():
265+
# Create a Window to show the view defined above.
266+
window = arcade.Window(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE)
267+
268+
# Create the view
269+
app = GameView()
270+
271+
# Show GameView on screen
272+
window.show_view(app)
273+
274+
# Start the arcade game loop
275+
window.run()
276+
277+
243278
if __name__ == "__main__":
244-
App().run()
279+
main()

arcade/experimental/sprite_depth_cosine.py renamed to arcade/examples/sprite_depth_cosine.py

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
During each update, the depth of each sprite is updated to follow a
77
cosine wave. Afterward, the following is drawn:
88
9-
* All sprites in depth-sorted order
10-
* A white square centered over each sprite along the x-axis, and moving
11-
with the wave along the y-axis
9+
* All sprites in depth-sorted order
10+
* A white square centered over each sprite along the x-axis, and moving
11+
with the wave along the y-axis
1212
1313
If Python and Arcade are installed, this example can be run from the command line with:
14-
python -m arcade.experimental.sprite_depth_cosine
14+
python -m arcade.examples.sprite_depth_cosine
1515
"""
1616

1717
from __future__ import annotations
@@ -23,21 +23,22 @@
2323
import arcade
2424

2525
# All constants are in pixels
26-
WIDTH, HEIGHT = 1280, 720
26+
WINDOW_WIDTH, WINDOW_HEIGHT = 1280, 720
2727

28+
WINDOW_TITLE = "Sprite Depth Testing Example w/ a Cosine Wave"
2829
NUM_SPRITES = 10
2930

3031
SPRITE_X_START = 150
3132
SPRITE_X_STEP = 50
32-
SPRITE_Y = HEIGHT // 2
33+
SPRITE_Y = WINDOW_HEIGHT // 2
3334

3435
DOT_SIZE = 10
3536

3637

37-
class MyGame(arcade.Window):
38+
class GameView(arcade.View):
3839

3940
def __init__(self):
40-
super().__init__(WIDTH, HEIGHT, "Sprite Depth Testing Example w/ a Cosine Wave")
41+
super().__init__()
4142

4243
texture = arcade.load_texture(":resources:images/test_textures/xy_square.png")
4344
self.text_batch = Batch()
@@ -53,7 +54,6 @@ def __init__(self):
5354
)
5455

5556
self.sprite_list = arcade.SpriteList()
56-
self.time = 0.0
5757

5858
for i in range(NUM_SPRITES):
5959
sprite = arcade.Sprite(
@@ -64,9 +64,10 @@ def __init__(self):
6464
def on_draw(self):
6565
self.clear()
6666

67+
ctx = self.window.ctx
6768
if self.use_depth:
68-
# This context manager temporarily enables depth testing
69-
with self.ctx.enabled(self.ctx.DEPTH_TEST):
69+
# This with block temporarily enables depth testing
70+
with ctx.enabled(ctx.DEPTH_TEST):
7071
self.sprite_list.draw()
7172
else:
7273
self.sprite_list.draw()
@@ -88,11 +89,26 @@ def on_key_press(self, symbol: int, modifiers: int):
8889
self.text_use_depth.text = f"SPACE: Toggle depth testing ({self.use_depth})"
8990

9091
def on_update(self, delta_time):
91-
self.time += delta_time
92-
92+
# Using time from the window's clock simplifies the math below
93+
time = self.window.time
9394
for i, sprite in enumerate(self.sprite_list):
94-
sprite.depth = math.cos(self.time + i) * SPRITE_X_STEP
95+
sprite.depth = math.cos(time + i) * SPRITE_X_STEP
96+
97+
98+
def main():
99+
""" Main function """
100+
# Create a window class. This is what actually shows up on screen
101+
window = arcade.Window(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE)
102+
103+
# Create the GameView
104+
game = GameView()
105+
106+
# Show GameView on screen
107+
window.show_view(game)
108+
109+
# Start the arcade game loop
110+
arcade.run()
95111

96112

97113
if __name__ == "__main__":
98-
MyGame().run()
114+
main()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
:orphan:
2+
3+
.. _depth_of_field:
4+
5+
Depth of Field Blur
6+
===================
7+
8+
.. image:: images/depth_of_field.png
9+
:width: 600px
10+
:align: center
11+
:alt: Screenshot of a time-dependent depth of field blur effect.
12+
13+
.. literalinclude:: ../../arcade/examples/depth_of_field.py
14+
:caption: depth_of_field.py
15+
:linenos:
137 KB
Loading
25.4 KB
Loading

0 commit comments

Comments
 (0)