Skip to content

Commit 69fb9bc

Browse files
committed
Almost finished chapter 10
1 parent a39aa83 commit 69fb9bc

30 files changed

+2777
-280
lines changed

Diff for: 07-points.rst

+2
Original file line numberDiff line numberDiff line change
@@ -483,3 +483,5 @@ the distance to the center of each disc/sphere. The goal of this exercise is
483483
thus to adapt this method to render a Voronoi diagram as shown on the right.
484484

485485
Solution: `voronoi.py <code/chapter-07/voronoi.py>`_
486+
487+
----

Diff for: 08-markers.rst

+2
Original file line numberDiff line numberDiff line change
@@ -585,3 +585,5 @@ demo. As an exercise, have a look at this `wonderful demo
585585
signed distance field functions with light and shadows.
586586

587587
Simply gorgeous...
588+
589+
----

Diff for: 09-lines.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ contours, grids, etc. Lines and segments are among the most simple geometrical
1313
objects. And yet, they can become quite complex if we consider line thickness,
1414
cap, joint and pattern such as dotted, dashed, etc. In the end, rendering lines
1515
with perfect quality is a lot of work as you'll read below. But it's worth the
16-
effort as illustrated in the teaser image above. This cames from an interactive
16+
effort as illustrated in the teaser image above. This comes from an interactive
1717
demo of glumpy (See the `spiral demo
1818
<https://github.com/glumpy/glumpy/blob/master/examples/spiral.py>`_).
1919

@@ -845,3 +845,4 @@ illustrated on the figure on the right?
845845
Solution:
846846
`linestrip-varying-thickness.py <code/chapter-09/linestrip-varying-thickness.py>`_
847847

848+
----

Diff for: 10-polygons.rst

+299-3
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,315 @@
1+
12
Rendering polygons
23
===============================================================================
34

45
.. contents:: .
56
:local:
67
:depth: 2
78
:class: toc chapter-10
8-
9+
10+
Polygons are an important topic for scientific visualization because they can
11+
be used to display bars, histograms, charts, filled plots, etc. Displaying
12+
polygons using OpenGL is really fast, provided we have the proper
13+
triangulation. The teaser image comes from the `tiger demo
14+
<https://github.com/glumpy/glumpy/blob/master/examples/tiger.py>`_ of glumpy.
15+
916

1017
Triangulation
1118
-------------------------------------------------------------------------------
1219

13-
Odd-fill rule
20+
In order to draw a polygon, we need to triangulate it, i.e., we have to
21+
decompose it into a sum of non overlapping triangles. To do that, we have to
22+
consider whether the polygon is convex or concave:
23+
24+
.. image:: images/chapter-10/polygons.png
25+
:width: 100%
26+
27+
To know if a given polygon is concave or convex, it is rather easy. Convex
28+
polygons have all their diagonals contained inside, while it is not true for
29+
concave polygons, i.e. you can find two summits such that when you connect
30+
them, the segment is outside the polygon. Another test is to find a straight
31+
line that cross a concave polygon at more than two points as shown on the
32+
figure above with the red lines.
33+
34+
35+
Convex polygons
36+
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
37+
38+
For convex polygons, we have to consider two cases:
39+
40+
1. points are ordered and describe the contour of the polygon
41+
2. points are unordered and spread randomly onto the 2d plane
42+
43+
For the second case, we can use scipy to compute the convex hull of the points
44+
such as to be in the first case situation:
45+
46+
.. code:: python
47+
48+
import numpy as np
49+
import scipy.spatial
50+
51+
P = np.random.uniform(-1.0, 1.0, (100,2))
52+
P = P[scipy.spatial.ConvexHull(P).vertices]
53+
54+
From this ordered set of vertices describing the contour, it is now easy to
55+
render the polygon using the `gl.GL_TRIANGLE_FAN` primitives:
56+
57+
.. code:: python
58+
59+
@window.event
60+
def on_draw(dt):
61+
window.clear()
62+
polygon.draw(gl.GL_TRIANGLE_FAN)
63+
64+
You can see on the figures below that it is better to use only the convex hull
65+
points to compute the triangulation. You can also check that all other points
66+
are actually inside the polygon area.
67+
68+
69+
.. figure:: images/chapter-10/convex-polygon-point.png
70+
:figwidth: 30%
71+
:figclass: left
72+
73+
Figure
74+
75+
A cloud of random points. Convex hull points have been highlighted.
76+
See `convex-polygon-point.py <code/chapter-10/convex-polygon-point.py>`_
77+
78+
.. figure:: images/chapter-10/convex-polygon.png
79+
:figwidth: 30%
80+
:figclass: left
81+
82+
Figure
83+
84+
A Delaunay triangulation with a lof of useless triangles.
85+
See `convex-polygon.py <code/chapter-10/convex-polygon.py>`_
86+
87+
88+
.. figure:: images/chapter-10/convex-polygon-fan.png
89+
:figwidth: 30%
90+
:figclass: left
91+
92+
Figure
93+
94+
A triangulation restricted to points belonging to the convex hull.
95+
See `convex-polygon-fan.py <code/chapter-10/convex-polygon-fan.py>`_
96+
97+
98+
Concave polygons
99+
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
100+
101+
For concave polygons, we could consider the two aforementionned cases where
102+
points are either ordered and describe the contour of the polygon or points are
103+
unordered and spread randomly onto the 2d plane. However, for the latter case,
104+
things become more difficult because the solution is not unique as shown on the
105+
figure below.
106+
107+
.. figure:: images/chapter-10/concave-hull.png
108+
:figwidth: 100%
109+
110+
Figure
111+
112+
The concave hull (or `alpha shape
113+
<https://en.wikipedia.org/wiki/Alpha_shape>`_) of a set of points is not
114+
unique. Images by `Martin Laloux
115+
<http://www.portailsig.org/content/sur-la-creation-des-enveloppes-concaves-concave-hull-et-les-divers-moyens-d-y-parvenir-forme>`_.
116+
117+
This is the reason why we'll restrict ourselves to the first case, that is, we
118+
have a set or ordered points describing the contour of a concave polygon. But
119+
even in such simple case, triangulation is not obvious and we'll thus need a
120+
dedicated library. We'll use the `triangles <http://dzhelil.info/triangle/>`_
121+
library but there are others:
122+
123+
.. figure:: images/chapter-10/firefox.png
124+
:figwidth: 30%
125+
:figclass: right
126+
127+
Figure
128+
129+
The firefox logo, tesselated (Bézier curves converted to segments) and
130+
triangulated. See `firefox.py <code/chapter-10/firefox.py>`_
131+
132+
.. code:: python
133+
134+
def triangulate(vertices):
135+
n = len(vertices)
136+
segments = (np.repeat(np.arange(n+1),2)[1:-1]) % n
137+
T = triangle.triangulate({'vertices': vertices,
138+
'segments': segments}, "p")
139+
return T["vertices"], T["triangles"]
140+
141+
142+
143+
On the image on the right, we've parsed (see `svg.py
144+
<code/chapter-10/svg.py>`_) the firefox icon SVG path and tesselated the Bézier
145+
curves into line segments. Then we have triangulated the resulting path and
146+
obtained the displayed triangulation using `gl.GL_TRIANGLES`. See `firefox.py
147+
<code/chapter-10/firefox.py>`_
148+
149+
150+
Fill rule
151+
-------------------------------------------------------------------------------
152+
153+
The fill-rule property is used to specify how to paint the different parts of a
154+
shape. As explained in the `SVG specification`_, *for a simple,
155+
non-intersecting path, it is intuitively clear what region lies "inside";
156+
however, for a more complex path, such as a path that intersects itself or
157+
where one subpath encloses another, the interpretation of "inside" is not so
158+
obvious. The fill-rule property provides two options for how the inside of a
159+
shape is determined: non-zero and even-odd.*
160+
161+
.. figure:: images/chapter-10/fillrule-nonzero.png
162+
:figwidth: 45%
163+
:figclass: left
164+
165+
Figure
166+
167+
From the `SVG Specification`_: *The nonzero fill rule determines the
168+
"insideness" of a point on the canvas by drawing a ray from that point to
169+
infinity in any direction and then examining the places where a segment of
170+
the shape crosses the ray.*
171+
172+
173+
.. figure:: images/chapter-10/fillrule-evenodd.png
174+
:figwidth: 45%
175+
:figclass: left
176+
177+
Figure
178+
179+
From the `SVG Specification`_: *The evenodd fill rule determines the
180+
"insideness" of a point on the canvas by drawing a ray from that point to
181+
infinity in any direction and counting the number of path segments from the
182+
given shape that the ray crosses.*
183+
184+
----
185+
186+
To enforce the fill-rule property, we'll need to use the `stencil buffer
187+
<https://www.khronos.org/opengl/wiki/Stencil_Test>`_ that allows to have
188+
per-sample operation and test performed after the fragment shader
189+
stage. Depending on the `stencil function
190+
<https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glStencilFunc.xhtml>`_
191+
and `stencil operation
192+
<https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glStencilOp.xhtml>`_
193+
we'll define, we can control precisely how a shape is rendered. But first, we
194+
need to tell OpenGL we'll be using a stencil buffer. In glumpy, the default is
195+
to have no stencil buffer, that is, the default bit depth of the stencil buffer
196+
is zero. To activate it, we thus simply need to specify some non-zero stencil
197+
bit depth (e.g. 8 for 256 possible values):
198+
199+
.. code:: python
200+
201+
config = app.configuration.Configuration()
202+
config.stencil_size = 8
203+
window = app.Window(config=config, width=512, height=512)
204+
205+
@window.event
206+
def on_init():
207+
gl.glEnable(gl.GL_STENCIL_TEST)
208+
209+
Note that we also need to activate the stencil test in the `on_init` window event.
210+
211+
.. _SVG Specification: https://www.w3.org/TR/SVG/painting.html
212+
213+
214+
Non-zero fill rule
215+
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
216+
217+
The non-zero fill rule implementation is easy because it corresponds to the
218+
default triangulation we've just seen above and no extra work is necessary.
219+
220+
221+
Odd-even fill rule
222+
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
223+
224+
In order to enforce the odd-even fill rule, we need to use a 2-pass
225+
rendering. The first pass will write to the stencil buffer according to the
226+
operation we define and the second pass will read the stencil buffer in order
227+
to decide if a fragment need to be painted or not. For the first pass, we thus
228+
disable depth and color writing and we instruct OpenGL to increment stencil
229+
value if a shape is drawn clockwise (CW) and to decrement it for counter clock
230+
wise shapes (CCW):
231+
232+
.. code:: python
233+
234+
# Disable color and depth writing
235+
gl.glColorMask(gl.GL_FALSE, gl.GL_FALSE, gl.GL_FALSE, gl.GL_FALSE)
236+
gl.glDepthMask(gl.GL_FALSE)
237+
238+
# Always write to stencil
239+
gl.glStencilFunc(gl.GL_ALWAYS, 0, 0)
240+
241+
# Increment value for CW shape
242+
gl.glStencilOpSeparate(gl.GL_FRONT, gl.GL_KEEP, gl.GL_KEEP, gl.GL_INCR)
243+
244+
# Decrement value for CCW shape
245+
gl.glStencilOpSeparate(gl.GL_BACK, gl.GL_KEEP, gl.GL_KEEP, gl.GL_DECR)
246+
247+
248+
Once the stencil buffer has been written, we can use the stored value to decide
249+
for the condition to be tested for writing to the render buffer. Using the
250+
`glStencilFunc` function, we can express virtually any condition we want:
251+
252+
=============== ================================================
253+
`glStencilFunc` `(func, ref, mask)`
254+
=============== ================================================
255+
`GL_NEVER` Always fails
256+
--------------- ------------------------------------------------
257+
`GL_LESS` Passes if ( ref & mask ) < ( stencil & mask )
258+
--------------- ------------------------------------------------
259+
`GL_LEQUAL` Passes if ( ref & mask ) <= ( stencil & mask )
260+
--------------- ------------------------------------------------
261+
`GL_GREATER` Passes if ( ref & mask ) > ( stencil & mask )
262+
--------------- ------------------------------------------------
263+
`GL_GEQUAL` Passes if ( ref & mask ) >= ( stencil & mask )
264+
--------------- ------------------------------------------------
265+
`GL_EQUAL` Passes if ( ref & mask ) = ( stencil & mask )
266+
--------------- ------------------------------------------------
267+
`GL_NOTEQUAL` Passes if ( ref & mask ) != ( stencil & mask )
268+
--------------- ------------------------------------------------
269+
`GL_ALWAYS` Always passes
270+
=============== ================================================
271+
272+
273+
274+
.. figure:: images/chapter-10/winding.png
275+
:figwidth: 30%
276+
:figclass: right
277+
278+
Figure
279+
280+
Odd-even fill rule using the stencil buffer.
281+
See `winding.py <code/chapter-10/winding.py>`_
282+
283+
For the actual odd-even fill rule, we only need to test for the last bit in the
284+
stencil buffer:
285+
286+
.. code:: python
287+
288+
# Enable color and depth writing
289+
gl.glColorMask(gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE)
290+
gl.glDepthMask(gl.GL_TRUE)
291+
292+
# Actual stencil test
293+
# Odd-even
294+
gl.glStencilFunc(gl.GL_EQUAL, 0x01, 0x1)
295+
296+
# Non zero
297+
# gl.glStencilFunc(gl.GL_NOTEQUAL, 0x00, 0xff)
298+
299+
# Positive
300+
# gl.glStencilFunc(gl.GL_LESS, 0x0, 0xff)
301+
302+
# Stencil operation (for both CW and CCW shapes)
303+
gl.glStencilOp(gl.GL_KEEP, gl.GL_KEEP, gl.GL_KEEP)
304+
305+
306+
Gradient and pattern
307+
-------------------------------------------------------------------------------
308+
309+
Antialiasing
14310
-------------------------------------------------------------------------------
15311

16-
Holes
312+
Exercises
17313
-------------------------------------------------------------------------------
18314

19315

Diff for: book.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ body {
9292
}
9393

9494
/* Buy the book !!! */
95-
#id62 {
95+
#id65 {
9696
padding-top: .25em;
9797
padding-bottom: .25em;
9898
color: #ffffff;

0 commit comments

Comments
 (0)