Skip to content

Commit 980dd0e

Browse files
committed
Docs: Geometry and cleanup
1 parent 788bb63 commit 980dd0e

File tree

9 files changed

+294
-94
lines changed

9 files changed

+294
-94
lines changed

docs/source/controls.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ Basic Keyboard Controls
66
^^^^^^^^^^^^^^^^^^^^^^^
77

88
- ``ESC`` to exit
9-
- ``SPACE`` to pause the current time
10-
- ``X`` for taking a screenshot (see settings)
9+
- ``SPACE`` to pause the current time (tells the configured timer to pause)
10+
- ``X`` for taking a screenshot (output path is configurable in settings)
1111

1212
Camera Controls
1313
^^^^^^^^^^^^^^^

docs/source/effectmanagers.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,22 @@ An effect manager is responsible of:
88
- Knowing what effect should be drawn based in some internal state
99
- Reading keyboard events if this is needed (optional)
1010

11+
You are fairly free to do what you want. Having control over
12+
effect instantiation also means you can make multiple instances
13+
of the same effect and assign different resources to them.
14+
15+
The most important part in the end is how you implement ``draw()``.
16+
17+
Some sane or insane examples to get started:
18+
19+
- Simply hard code what should run at what time or state
20+
- A manger that cycles what effect is active based on a next/previous key
21+
- Cycle effects based on a duration property you assign to them
22+
- Load some external timer data describing what effect should run at what time. This can
23+
easily be done with rocket (we are planning to make a manager for this)
24+
- You could just put all your draw code in the manager and not use effects
25+
- Treat the manager as the main loop of a simple game
26+
1127
This is an example of the default ``SingleEffectManager``.
1228

1329
.. code-block:: python

docs/source/effects.rst

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22
Effects
33
=======
44

5-
In order to draw something to the screen using this framework you need to make one or
6-
multiple effects. what these effects are doing is entirely up to you. Some like to
5+
In order to actually draw something to the screen you need to make one or
6+
multiple effects. What these effects are doing is entirely up to you. Some like to
77
put everything into one effect and switch what they draw by flipping some internal
88
states, but this is probably not practical for more complex things.
99

10-
An effect is a class with references to resources and a method for drawing.
11-
An effect is an independent python package of specific format.
10+
An effect is a class with references to resources such as shaders, geometry, fbos and textures
11+
and a method for drawing. An effect is an independent python package of specific format.
1212

1313
The Effect Package
1414
^^^^^^^^^^^^^^^^^^
1515

16-
The effect package should have the following structure (assuming our effect is named "cube".
16+
The effect package should have the following structure (assuming our effect is named "cube").
1717

1818
.. code-block:: bash
1919
@@ -26,20 +26,29 @@ The effect package should have the following structure (assuming our effect is n
2626
   └── cube
2727
└── ...
2828
29-
The ``effect.py`` module is where the draw logic for the effect resides. Directories at the
30-
same level are for resources for the effect. Notice that the resource directories contains
31-
another directory with the name of the effect. This is because these folders are added to
32-
a virtual directory (for each resource type) so we should place it in a directory to
33-
reduce the change of name collisions. Two effects with the texture ``texture.png`` in
34-
the root of their local ``textures/`` directory will cause the first effect to
29+
The ``effect.py`` module is the actual code for the effect. Directories at the
30+
same level are for local resources for the effect.
31+
32+
.. Note:: Notice that the resource directories contains another sub-directory with
33+
the same name as the effect. This is because these folders are added to
34+
a **virtual directory** (for each resource type), so we should place it in a directory to
35+
reduce the chance of a name collisions.
36+
37+
.. Note:: Two effects with the texture name ``texture.png`` in
38+
the root of their local ``textures/`` directory will cause a name collision were
39+
the texture from the first registered effect will be used in both effects.
40+
This can be used to override resources intentionally.
41+
42+
We can also decide not to have any effect-local resources and configure
43+
a project-global resource directory. More about this in :doc:`settings`.
3544

3645
Registry
3746
^^^^^^^^
3847

39-
For an effect to be recognised by the system it has to be registered
48+
For an effect to be recognised by the system, it has to be registered
4049
in the ``EFFECTS`` tuple/list in your settings module.
4150
Simply add the full python path to the package. If our cube example
42-
above resides inside a ``myproject`` project package we need to add
51+
above is located inside a ``myproject`` project package we need to add
4352
the string ``myproject.cube``. See :doc:`settings`.
4453

4554
You can always run a single effect by using the ``runeffect`` command.
@@ -48,7 +57,7 @@ You can always run a single effect by using the ``runeffect`` command.
4857
4958
./manage.py runeffect myproject.cube
5059
51-
If you have multiple effects you need to crate or use an existing :doc:`effectmanagers`
60+
If you have multiple effects, you need to crate or use an existing :doc:`effectmanagers`
5261
that will decide what effect would be active at what time or state.
5362

5463
Resources
@@ -64,24 +73,24 @@ resource finders in :doc:`settings`.
6473
The Effect Module
6574
^^^^^^^^^^^^^^^^^
6675

67-
The effect module in an effect package needs to be named ``effect.py`` and
68-
reside in the root of the package. It can only contain a single effect
76+
The effect module needs to be named ``effect.py`` and
77+
located in the root of the effect package. It can only contain a single effect
6978
class. The name of the class doesn't matter right now, but we are
7079
considering allowing multiple effects in the future, so giving it
71-
at least a descriptive name of that it represents is a good idea.
80+
at least a descriptive name is a good idea.
7281

7382
There are two important methods in an effect:
83+
7484
- ``__init__()``
75-
- draw()
85+
- ``draw()``
7686

77-
The **initializer** is called before resources are loaded. This is so the
78-
effects can register the what resources they need. The resource
87+
The **initializer** is called before resources are loaded. This way the
88+
effects can register the resources they need. The resource
7989
managers will return an empty object that will be populated when
8090
loading starts.
8191

82-
The **draw** method is called by the framework or from a your custom
83-
:doc:`effectmanagers` ever frame, or at least every frame the manager
84-
decides the effect should be active at least.
92+
The ``draw`` method is called by the configured `EffectManager`` (see :doc:`effectmanagers`)
93+
ever frame, or at least every frame the manager decides the effect should be active.
8594

8695
The standard effect example:
8796

@@ -121,20 +130,24 @@ The standard effect example:
121130
122131
The parameters in the draw effect is:
123132

124-
- ``time``: The current time reported by our configured Timer in seconds.
133+
- ``time``: The current time reported by our configured ``Timer`` in seconds.
125134
- ``frametime``: The time a frame is expected to take in seconds.
126-
This is useful when you cannot use ``time``. Should be avoided.
135+
This is useful when you cannot use time. Should be avoided.
127136
- ``target`` is the target FBO of the effect
128137

129-
Time can potentially move at any speed or direction so it's good practice
138+
Time can potentially move at any speed or direction, so it's good practice
130139
to make sure the effect can run when time is moving in any direction.
131140

132141
The ``bind_target`` decorator is useful when you want to ensure
133142
that an FBO passed to the effect is bound on entry and released on exit.
134-
By default a fake FBO is passed in representing the window framebuffer.
143+
By default a fake FBO is passed in representing the window frame buffer.
135144
EffectManagers can be used to pass in your own FBOs or another effect
136145
can call ``draw(..)`` requesting the result to end up in the FBO it passes in
137-
and then use this FBO as a texture on a cube.
146+
and then use this FBO as a texture on a cube or do post processing.
147+
148+
As we can see in the example, the ``Effect`` base class have a couple
149+
of convenient methods for doing basic matrix math, but generally you
150+
are expected do to these calculations yourself.
138151

139152
Effect Base Class
140153
^^^^^^^^^^^^^^^^^

docs/source/geometry.rst

Lines changed: 147 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,156 @@
22
Geometry
33
========
44

5-
Write stuff here.
5+
The ``demosys.opengl.geometry`` module currently provides some simple functions to generate VAOs.
66

7-
The geometry Module
8-
^^^^^^^^^^^^^^^^^^^
7+
- Quad: Full screen quads for drawing offscreen buffers
8+
- Cube: Cube with normals, uvs and texture coordinates
9+
- Plane: A plane with a dimension and resolution
10+
- Points: Random points in 3D
911

10-
Write stuff here.
12+
.. Nore:: We definitely need more here. Please make pull requests or make an issue on github.
1113

1214
Creating Custom Geometry
1315
^^^^^^^^^^^^^^^^^^^^^^^^
1416

15-
Write stuff here.
17+
To efficiently generate geometry in Python we must avoid as much memory allocation as possible.
18+
As mentioned in other sections we use PyOpenGL's VBO class that takes numpy arrays.
19+
We also use pyrr for vector and matrix math and representation.
20+
21+
.. Note:: This is a "best practices" guide to efficiently generate geometry
22+
with python code that will scale well even for large amounts of data.
23+
This was benchmarked generating various vertex formats with 1M vertices.
24+
For fairly small data sizes doesn't matter that much.
25+
26+
The naive way to generate geometry would probably look something like this:
27+
28+
.. code-block:: python
29+
30+
from OpenGL import GL
31+
from OpenGL.arrays.vbo import VBO
32+
import numpy
33+
from pyrr import Vector3
34+
35+
def random_points(count):
36+
points = []
37+
for p in range(count):
38+
# Let's pretend we calculated random values for x, y, z
39+
points.append(Vector3([x, y, x]))
40+
41+
# Create VBO enforcing float32 values with numpy
42+
points_vbo = VBO(numpy.array(points, dtype=numpy.float32))
43+
44+
vao = VAO("random_points", mode=GL.GL_POINTS)
45+
vao.map_array_buffer(GL.GL_FLOAT, points_vbo)
46+
vao.map_buffer(points_vbo, "in_position", 3))
47+
vao.build()
48+
return vao
49+
50+
This works perfectly fine, but we allocate a new list for every iteration
51+
and pyrr internally creates a numpy array of this. The ``points`` list will also
52+
have to dynamically expand. This exponentially more ugly as the ``count``
53+
value increases.
54+
55+
We move on to version 2:
56+
57+
.. code-block:: python
58+
59+
def random_points(count):
60+
# Pre-allocate a list containing zeros of length count
61+
# We multiply by 3 to make room for a x, y and z value
62+
points = [0] * count * 3
63+
# Loop count time incrementing by 3 every frame
64+
for p in range(0, count * 3, 3):
65+
# Let's pretend we calculated random values for x, y, z
66+
points[p] = x
67+
points[p + 1] = y
68+
points[p + 2] = z
69+
70+
points_vbo = VBO(numpy.array(points, dtype=numpy.float32))
71+
72+
This version is orders of magnitude faster because we don't allocate memory
73+
in the loop. It has one glaring flaw though. It's not a very pleasant read
74+
even for such simple task, and it will not get any better if we add more complexity.
75+
76+
Let's move on to version 3:
77+
78+
.. code-block:: python
79+
80+
def random_points(count):
81+
def generate():
82+
for p in range(count):
83+
# Let's pretend we calculated random values for x, y, z
84+
yield x
85+
yield y
86+
yield z
87+
88+
points_vbo = VBO(numpy.fromiter(generate(), count=count * 3, dtype=numpy.float32)
89+
90+
Using generators in python like this is much cleaner way. We also take advantage
91+
of numpy's ``fromiter()`` that basically slurps up all the numbers we emit with
92+
yield into its internal buffers. By also telling numpy what the final size of the
93+
buffer will be using the ``count`` parameter, it will pre-allocate this not having
94+
to dynamically increase it's internal buffer.
95+
96+
Generators are extremely simple and powerful. If things get complex we can easily
97+
split things up in several functions because Pythons ``yield from`` can forward
98+
generators.
99+
100+
Imagine generating a single VBO with interleaved position, normal and uv data:
101+
102+
.. code-block:: python
103+
104+
def generate_stuff(count):
105+
# Returns a distorted position of x, y, z
106+
def pos(x, y, z):
107+
# Calculate..
108+
yield x
109+
yield y
110+
yield x
111+
112+
def normal(x, y, z):
113+
# Calculate
114+
yield x
115+
yield y
116+
yield z
117+
118+
def uv(x, y, x):
119+
# Calculate
120+
yield u
121+
yield v
122+
123+
def generate(count):
124+
for i in range(count):
125+
# resolve current x, y, z pos
126+
yield from pos(x, y, z)
127+
yield from normal(x, y, z)
128+
yield from uv(x, y, z)
129+
130+
interleaved_vbo = VBO(numpy.fromiter(generate(), count=count * 3, dtype=numpy.float32)
131+
132+
133+
The geometry Module
134+
^^^^^^^^^^^^^^^^^^^
135+
136+
.. automodule:: demosys.opengl.geometry.cube
137+
:members:
138+
:undoc-members:
139+
:show-inheritance:
140+
141+
142+
.. automodule:: demosys.opengl.geometry.plane
143+
:members:
144+
:undoc-members:
145+
:show-inheritance:
146+
147+
148+
.. automodule:: demosys.opengl.geometry.points
149+
:members:
150+
:undoc-members:
151+
:show-inheritance:
152+
153+
154+
.. automodule:: demosys.opengl.geometry.quad
155+
:members:
156+
:undoc-members:
157+
:show-inheritance:

0 commit comments

Comments
 (0)