Skip to content

Commit

Permalink
Promote sprite depth examples from experimental to main (#2434)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
pushfoo authored Oct 26, 2024
1 parent ce177ba commit e554b02
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 33 deletions.
6 changes: 4 additions & 2 deletions arcade/camera/camera_2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from typing_extensions import Self

from arcade.camera.data_types import (
DEFAULT_FAR,
DEFAULT_NEAR_ORTHO,
CameraData,
OrthographicProjectionData,
ZeroProjectionDimension,
Expand Down Expand Up @@ -90,8 +92,8 @@ def __init__(
up: tuple[float, float] = (0.0, 1.0),
zoom: float = 1.0,
projection: Rect | None = None,
near: float = -100.0,
far: float = 100.0,
near: float = DEFAULT_NEAR_ORTHO,
far: float = DEFAULT_FAR,
*,
scissor: Rect | None = None,
render_target: Framebuffer | None = None,
Expand Down
21 changes: 20 additions & 1 deletion arcade/camera/data_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from __future__ import annotations

from contextlib import contextmanager
from typing import Generator, Protocol
from typing import Final, Generator, Protocol

from pyglet.math import Vec2, Vec3
from typing_extensions import Self
Expand All @@ -16,6 +16,8 @@

__all__ = [
"CameraData",
"DEFAULT_FAR",
"DEFAULT_NEAR_ORTHO",
"OrthographicProjectionData",
"PerspectiveProjectionData",
"Projection",
Expand All @@ -25,6 +27,23 @@
"duplicate_camera_data",
]

DEFAULT_NEAR_ORTHO: Final[float] = -100.0
"""The default backward-facing depth cutoff for orthographic rendering.
Unless an orthographic camera is provided a different value, this will be
used as the near cutoff for its point of view.
The :py:class:`~arcade.camera.perspective.PerspectiveProjector` uses
``0.01`` as its default near value to avoid division by zero.
"""

DEFAULT_FAR: Final[float] = 100.0
"""The default forward-facing depth cutoff for all Arcade cameras.
Unless a camera is provided a different value, anything further away than this
value will not be drawn.
"""


class ZeroProjectionDimension(ValueError):
"""A projection's dimensions were zero along at least one axis.
Expand Down
12 changes: 9 additions & 3 deletions arcade/camera/orthographic.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
from pyglet.math import Mat4, Vec2, Vec3
from typing_extensions import Self

from arcade.camera.data_types import CameraData, OrthographicProjectionData, Projector
from arcade.camera.data_types import (
DEFAULT_FAR,
DEFAULT_NEAR_ORTHO,
CameraData,
OrthographicProjectionData,
Projector,
)
from arcade.camera.projection_functions import (
generate_orthographic_matrix,
generate_view_matrix,
Expand Down Expand Up @@ -78,8 +84,8 @@ def __init__(
0.5 * self._window.width, # Left, Right
-0.5 * self._window.height,
0.5 * self._window.height, # Bottom, Top
-100,
100, # Near, Far
DEFAULT_NEAR_ORTHO,
DEFAULT_FAR, # Near, Far
)

@property
Expand Down
10 changes: 8 additions & 2 deletions arcade/camera/perspective.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from pyglet.math import Mat4, Vec2, Vec3
from typing_extensions import Self

from arcade.camera.data_types import CameraData, PerspectiveProjectionData, Projector
from arcade.camera.data_types import DEFAULT_FAR, CameraData, PerspectiveProjectionData, Projector
from arcade.camera.projection_functions import (
generate_perspective_matrix,
generate_view_matrix,
Expand All @@ -27,6 +27,12 @@
class PerspectiveProjector(Projector):
"""
The simplest from of a perspective camera.
.. warning:: Near cutoffs for perspective projection must be greater than zero.
This prevents division by zero errors since perspective involves
dividing by distance.
Using ViewData and PerspectiveProjectionData PoDs (Pack of Data)
it generates the correct projection and view matrices. It also
provides methods and a context manager for using the matrices in
Expand Down Expand Up @@ -78,7 +84,7 @@ def __init__(
self._window.width / self._window.height, # Aspect
60, # Field of View,
0.01,
100.0, # near, # far
DEFAULT_FAR, # near, # far
)

@property
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
It uses the depth attribute of along with blurring and shaders to
roughly approximate depth-based blur effects. The focus bounces
back forth automatically between a maximum and minimum depth value
based on time. Adjust the arguments to the App class at the bottom
of the file to change the speed.
based on time. Change the speed and focus via either the constants
at the top of the file or the arguments passed to it at the bottom of
the file.
This example works by doing the following for each frame:
Expand All @@ -17,7 +18,7 @@
both easier and more performant than more accurate blur approaches.
If Python and Arcade are installed, this example can be run from the command line with:
python -m arcade.experimental.examples.array_backed_grid
python -m arcade.examples.depth_of_field
"""

from __future__ import annotations
Expand All @@ -30,12 +31,20 @@

from pyglet.graphics import Batch

from arcade import SpriteList, SpriteSolidColor, Text, Window, get_window
import arcade
from arcade import get_window, SpriteList, SpriteSolidColor, Text, Window, View
from arcade.camera.data_types import DEFAULT_NEAR_ORTHO, DEFAULT_FAR
from arcade.color import RED
from arcade.experimental.postprocessing import GaussianBlur
from arcade.gl import NEAREST, Program, Texture2D, geometry
from arcade.types import RGBA255, Color

WINDOW_TITLE = "Depth of Field Example"

WINDOW_WIDTH = 1280
WINDOW_HEIGHT = 720
BACKGROUND_GRAY = Color(155, 155, 155, 255)


class DepthOfField:
"""A depth-of-field effect we can use as a render context manager.
Expand All @@ -50,7 +59,7 @@ class DepthOfField:
def __init__(
self,
size: tuple[int, int] | None = None,
clear_color: RGBA255 = (155, 155, 155, 255),
clear_color: RGBA255 = BACKGROUND_GRAY
):
self._geo = geometry.quad_2d_fs()
self._win: Window = get_window()
Expand Down Expand Up @@ -171,9 +180,13 @@ def render(self):
self._geo.render(self._render_program)


class App(Window):
class GameView(View):
"""Window subclass to hold sprites and rendering helpers.
To keep the code simpler, this example uses a default camera. That means
that any sprite outside Arcade's default camera near and far render cutoffs
(``-100.0`` to ``100.0``) will not be drawn.
Args:
text_color:
The color of the focus indicator.
Expand All @@ -182,13 +195,19 @@ class App(Window):
focus_change_speed:
How fast the focus bounces back and forth
between the ``-focus_range`` and ``focus_range``.
min_sprite_depth:
The minimum sprite depth we'll generate sprites between
max_sprite_depth:
The maximum sprite depth we'll generate sprites between.
"""

def __init__(
self,
text_color: RGBA255 = RED,
focus_range: float = 16.0,
focus_change_speed: float = 0.1,
min_sprite_depth: float = DEFAULT_NEAR_ORTHO,
max_sprite_depth: float = DEFAULT_FAR
):
super().__init__()
self.sprites: SpriteList = SpriteList()
Expand All @@ -207,7 +226,7 @@ def __init__(

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

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

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

# Draw the blurred frame buffer and then the focus display
self.use()
window = self.window
window.use()
self.dof.render()
self._batch.draw()


def main():
# Create a Window to show the view defined above.
window = arcade.Window(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE)

# Create the view
app = GameView()

# Show GameView on screen
window.show_view(app)

# Start the arcade game loop
window.run()


if __name__ == "__main__":
App().run()
main()
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
During each update, the depth of each sprite is updated to follow a
cosine wave. Afterward, the following is drawn:
* All sprites in depth-sorted order
* A white square centered over each sprite along the x-axis, and moving
with the wave along the y-axis
* All sprites in depth-sorted order
* A white square centered over each sprite along the x-axis, and moving
with the wave along the y-axis
If Python and Arcade are installed, this example can be run from the command line with:
python -m arcade.experimental.sprite_depth_cosine
python -m arcade.examples.sprite_depth_cosine
"""

from __future__ import annotations
Expand All @@ -23,21 +23,22 @@
import arcade

# All constants are in pixels
WIDTH, HEIGHT = 1280, 720
WINDOW_WIDTH, WINDOW_HEIGHT = 1280, 720

WINDOW_TITLE = "Sprite Depth Testing Example w/ a Cosine Wave"
NUM_SPRITES = 10

SPRITE_X_START = 150
SPRITE_X_STEP = 50
SPRITE_Y = HEIGHT // 2
SPRITE_Y = WINDOW_HEIGHT // 2

DOT_SIZE = 10


class MyGame(arcade.Window):
class GameView(arcade.View):

def __init__(self):
super().__init__(WIDTH, HEIGHT, "Sprite Depth Testing Example w/ a Cosine Wave")
super().__init__()

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

self.sprite_list = arcade.SpriteList()
self.time = 0.0

for i in range(NUM_SPRITES):
sprite = arcade.Sprite(
Expand All @@ -64,9 +64,10 @@ def __init__(self):
def on_draw(self):
self.clear()

ctx = self.window.ctx
if self.use_depth:
# This context manager temporarily enables depth testing
with self.ctx.enabled(self.ctx.DEPTH_TEST):
# This with block temporarily enables depth testing
with ctx.enabled(ctx.DEPTH_TEST):
self.sprite_list.draw()
else:
self.sprite_list.draw()
Expand All @@ -88,11 +89,26 @@ def on_key_press(self, symbol: int, modifiers: int):
self.text_use_depth.text = f"SPACE: Toggle depth testing ({self.use_depth})"

def on_update(self, delta_time):
self.time += delta_time

# Using time from the window's clock simplifies the math below
time = self.window.time
for i, sprite in enumerate(self.sprite_list):
sprite.depth = math.cos(self.time + i) * SPRITE_X_STEP
sprite.depth = math.cos(time + i) * SPRITE_X_STEP


def main():
""" Main function """
# Create a window class. This is what actually shows up on screen
window = arcade.Window(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE)

# Create the GameView
game = GameView()

# Show GameView on screen
window.show_view(game)

# Start the arcade game loop
arcade.run()


if __name__ == "__main__":
MyGame().run()
main()
15 changes: 15 additions & 0 deletions doc/example_code/depth_of_field.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
:orphan:

.. _depth_of_field:

Depth of Field Blur
===================

.. image:: images/depth_of_field.png
:width: 600px
:align: center
:alt: Screenshot of a time-dependent depth of field blur effect.

.. literalinclude:: ../../arcade/examples/depth_of_field.py
:caption: depth_of_field.py
:linenos:
Binary file added doc/example_code/images/depth_of_field.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/example_code/images/sprite_depth_cosine.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit e554b02

Please sign in to comment.