Jason McKesson avatar Jason McKesson committed cb1415a

Moved Gimbal Lock and transformation spaces from Tutorial 6 to the new Tutorial 8.

Comments (0)

Files changed (10)

Documents/Positioning/Tutorial 06.xml

                 rotate about two completely different points.</para>
         </section>
         <section>
-            <title>Transformation Spaces</title>
-            <para>Our scaling transform is interesting, but it does have certain flaws. The most
-                important problem is that it causes a scale based on the X, Y and Z axes of the
-                initial coordinate system. So what do you do if you want to scale something along
-                different axes?</para>
-            <para>This is quite simple, really: you use successive transforms. First, you transform
-                into a space where the <quote>different axes</quote>
-                <emphasis>are</emphasis> the X, Y and Z axes. Then you do your scaling transform.
-                After that, you transform back to the original space.</para>
-            <para>If the different axes represent an orientation change, you can use a rotation
-                matrix. Simply rotate, apply the scale, and rotate back. The last part is done by
-                generating the rotation matrix using the same axis, but negating the angle.</para>
-            <para>Transforming into a space that is convenient for another transform, applying that
-                transform, and then undoing the first transform is a general-purpose operation. The
-                matrix that results from this is a transformation in a different space. In the
-                scaling case, we want to do the scaling in a different space, so we apply a rotation
-                matrix to get into that space, then undo the rotation after applying the
-                scale.</para>
-            <para>The general form of this sequence is as follows. Suppose you have a transformation
-                matrix T, which operates on points in a space called F. We have some positions in
-                the space P. What we want is to create a matrix that applies T's transformation
-                operation, except that it needs to operate on points in the space of P. Given a
-                matrix M that transforms from P space to F space, that matrix is <inlineequation>
-                    <mathphrase>M<superscript>-1</superscript>TM</mathphrase>
-                </inlineequation>.</para>
-            <para>The matrix M<superscript>-1</superscript> is the <glossterm>inverse
-                    matrix</glossterm> of M. The symbol <superscript>-1</superscript> does not
-                (strictly) mean to raise the matrix to the -1 power. It means to invert it.</para>
-            <para>The inverse of a matrix M is the matrix N such that <inlineequation>
-                    <mathphrase>MN = I</mathphrase>
-                </inlineequation>, where I is the identity matrix. This can be analogized to the
-                scalar multiplicative inverse (ie: reciprocal). The scalar multiplicative inverse of
-                X is the number Y such that <inlineequation>
-                    <mathphrase>XY = 1</mathphrase>
-                </inlineequation>.</para>
-            <para>In the case of the scalar inverse, this is very easy to solve for: <inlineequation>
-                    <mathphrase>Y = 1/X</mathphrase>
-                </inlineequation>. Even in this case, there are values of X for which there is no
-                multiplicative inverse. OK, there's <emphasis>one</emphasis> such value of X:
-                0.</para>
-            <para>The case of the inverse matrix is much more complicated. Just as with the scalar
-                inverse, there are matrices that have no inverse. Unlike the scalar case, there are
-                a <emphasis>lot</emphasis> of matrices with no inverse. Also, computing the inverse
-                matrix is a <emphasis>lot</emphasis> more complicated than simply taking the
-                reciprocal of a value.</para>
-            <para>Most common transformation matrices do have an inverse. And for the basic
-                transformation matrices, the inverse matrix is very easy to compute. For a pure
-                rotation matrix, simply compute a new rotation matrix by negating the angle that the
-                old one was generated with. For a translation matrix, negate the origin value in the
-                matrix. For a scale matrix, take the reciprocal of the scale along each axis.</para>
-            <para>To take the inverse of a sequence of matrices, you can take the inverse of each of
-                the component matrices. But you have to do the matrix multiplication in
-                    <emphasis>reverse</emphasis> order. So if we have <inlineequation>
-                    <mathphrase>M = TRS</mathphrase>
-                </inlineequation>, then M<superscript>-1</superscript> is <inlineequation>
-                    <mathphrase>M<superscript>-1</superscript> =
-                            S<superscript>-1</superscript>R<superscript>-1</superscript>T<superscript>-1</superscript></mathphrase>
-                </inlineequation>.</para>
-        </section>
-        <section>
             <title>Hierarchical Models</title>
             <para>In more complex scenes, it is often desirable to specify the transform of one
                 model relative to the model space transform of another model. This is useful if you
         </section>
     </section>
     <section>
-        <?dbhtml filename="Tut06 Gimbal Lock.html" ?>
-        <title>Gimbal Lock</title>
-        <para>Remember back when we said that n rotation matrix isn't a rotation matrix at all, that
-            it is an orientation matrix? We also said that forgetting this can come back to bite
-            you. Well, here's likely the most common way.</para>
-        <para>Normally, when dealing with orienting an object like a plane or spaceship in 3D space,
-            you want to orient it based on 3 rotations about the 3 axes. The obvious way to do this
-            is with a series of 3 rotations. This means that the program stores 3 angles, and you
-            generate a rotation matrix by creating 3 rotation matrices based on these angles and
-            concatenating them. Indeed, the 3 angles often have special names, based on common
-            flight terminology: yaw, pitch, and roll.</para>
-        <para>Pitch is the rotation that raises or lowers the front of the object. Yaw is the
-            rotation that turns the object left and right. Roll is the rotation that spins it around
-            the direction of motion. These terms neatly duck the question of what the axes
-            technically are; they are defined in terms of semantic axes (front, left, direction of
-            motion, etc), rather than a specific model space. So in one model space, roll might be a
-            rotation about the X axis, but in another, it might be a rotation about the Z
-            axis.</para>
-        <para>One of the first problems you will note is that the order you apply these rotations
-            matter. As stated earlier, a rotation matrix is an <emphasis>orientation</emphasis>
-            matrix. Each transform defines a new coordinate system, and the next transform is based
-            on an object in the <emphasis>new</emphasis> space. For example, if we apply the roll
-            first, we have now changed what the axis for the subsequent yaw is.</para>
-        <para>You can use any order that you like, so long as you understand what these angles
-            mean. If you apply the roll first, your yaw and pitch must be in terms of the new roll
-            coordinate system, and not the original coordinate system. That is, a change that is
-            intended to only affect the roll of the final space may need yaw or pitch changes to
-            allow it to have the same apparent orientation (except for the new roll, of
-            course).</para>
-        <para>But there is a far more insidious problem lurking in here. And this problem happens
-            anytime you compute a final rotation from a series of 3 rotations about axes
-            perpendicular to each other.</para>
-        <para>The tutorial project <phrase role="propername">Gimbal Lock</phrase> illustrates this
-            problem. Because the problem was first diagnosed with a physical device called a <link
-                xlink:href="http://en.wikipedia.org/wiki/Gimbal">gimbal</link>, the problem has
-            become known as <glossterm>gimbal lock.</glossterm></para>
-        <para>A gimbal is a pivoted support that provides the ability to rotate in one axis. A
-            gimbal can be mounted within another gimbal. The Gimbal Lock project has a set of 3
-            square gimbals, each with a pivot axis that is perpendicular to the other two. This
-            effectively mimics the common yaw/pitch/roll angle setup.</para>
-        <figure>
-            <title>Gimbal Lock Project</title>
-            <mediaobject>
-                <imageobject>
-                    <imagedata fileref="Three%20Gimbal%20Array.png"/>
-                </imageobject>
-            </mediaobject>
-        </figure>
-        <para>You can control the orientation of each gimbal separately. The <keycap>w</keycap> and
-                <keycap>s</keycap> keys control the blue gimbal, the <keycap>a</keycap> and
-                <keycap>d</keycap> keys control the green gimbal, and the <keycap>q</keycap> and
-                <keycap>e</keycap> keys control the red gimbal.</para>
-        <para>With these three gimbals, you can cause the innermost gimbal (the red one) to have any
-            arbitrary orientation. That isn't the problem. The problem happens when two of the
-            gimbals are parallel with one another:</para>
-        <figure>
-            <title>Parallel Gimbals</title>
-            <mediaobject>
-                <imageobject>
-                    <imagedata fileref="In%20Gimbal%20Lock.png"/>
-                </imageobject>
-            </mediaobject>
-        </figure>
-        <para>Recall that the purpose of the three gimbals is to be able to adjust one of the three
-            angles and orient the object in a particular direction. In a flight-simulation game, the
-            player would have controls that would change their yaw, pitch, and roll. However, look
-            at this picture.</para>
-        <para>The player's theoretical ship is pointed in the direction of the center gimbal's
-            plane. Given the controls you have here, can you cause the center gimbal to rotate
-            around the axis that it is facing? No. You can't even do this somewhat; you can only
-            rotate it in two directions. But we have three gimbals, which means we should have three
-            axes of rotation. Why can't we rotate the red gimbal in the Z (forward) axis?</para>
-        <para>Because the outer and inner gimbals are now rotating about the <emphasis>same
-                axis</emphasis>. Which means you really only have two gimbals to manipulate in order
-            to orient the red gimbal. And 3D orientation cannot be fully controlled with only 2
-            axial rotations, with only 2 gimbals.</para>
-        <para>When gimbals are in such a position, you have what is known as <glossterm>gimbal
-                lock</glossterm>; you have locked one of the gimbals to another, and now both cause
-            the same effect.</para>
-        <para>How do you fix this problem? There are several possible solutions to the
-            problem.</para>
-        <para>Perhaps the most optimal solution is to simply not use gimbals. After all, if you
-            don't have gimbals, you can't gimbal lock. Instead of storing the orientation as a
-            series of rotations, store the orientation as an <emphasis>orientation.</emphasis> That
-            is, maintain the current orientation as a matrix. When you need to modify the
-            orientation, you apply a transformation to this matrix, storing the result as the new
-            current orientation.</para>
-        <para>This means that every yaw, pitch, and roll applied to the current orientation will be
-            relative to that current orientation. Which is usually exactly what you want. If the
-            user applies a positive yaw, you want that yaw to rotate them relative to where they are
-            current pointing.</para>
-        <para>A downside of this approach, besides the size penalty of having to store a 4x4 matrix
-            rather than 3 floating-point angles, is that floating-point math can lead to errors. If
-            you keep accumulating successive transformations of an object, once every 1/30th of a
-            second for a period of several minutes or hours, these floating-point errors start
-            accumulating. Eventually, the orientation stops being a pure rotation and starts
-            incorporating scale and skewing characteristics.</para>
-        <para>The solution here is to re-orthonormalize the matrix after applying each transform. A
-            transform (space) is said to be <glossterm>orthonormal</glossterm> if the basis vectors
-            are of unit length (no scale) and each axis is perpendicular to all of the
-            others.</para>
-        <para>Unfortunately, re-orthonormalizing a matrix is not a simple operation. You could try
-            to normalize each of the axis vectors with typical vector normalization, but that
-            wouldn't ensure that the matrix was orthonormal. It would remove scaling, but the axes
-            wouldn't be guaranteed to be perpendicular.</para>
-        <para>The proper solution is to use <link
-                xlink:href="http://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation">unit
-                quaternions</link> to store orientations, which can be orthonormalized much more
-            easily. This is beyond the scope of this tutorial, but the Wikipedia article on the
-            subject is a good place to start.</para>
-        <section>
-            <title>Complex Mesh Space</title>
-            <para>The way this tutorial renders each gimbal serves as an object lesson in the use of
-                mesh space. The gimbals are composed of 6 pieces, each of which ultimately derives
-                from a simple 3D cube who's sides are of unit length. The cube is scaled and
-                translated to form the rectangular prisms used to build each the gimbal.</para>
-            <para>The <function>DrawGimbal</function> function takes a matrix stack, an axis (one of
-                the three kinds of gimbals), a size for the gimbal, and a color. The width and
-                height of a gimbal are the same, so this code only takes a single size.</para>
-            <para>There is ultimately only one gimbal <quote>mesh</quote>: the difference between
-                them is what their base orientation is. That is computed in this function:</para>
-            <example>
-                <title>DrawGimbal Function</title>
-                <programlisting language="cpp">void DrawGimbal(MatrixStack &amp;currMatrix, GimbalAxis eAxis, float fSize, glm::vec4 baseColor)
-{
-    currMatrix.Push();
-    
-    switch(eAxis)
-    {
-    case GIMBAL_X_AXIS:
-        break;
-    case GIMBAL_Y_AXIS:
-        currMatrix.RotateZ(90.0f);
-        currMatrix.RotateX(90.0f);
-        break;
-    case GIMBAL_Z_AXIS:
-        currMatrix.RotateY(90.0f);
-        currMatrix.RotateX(90.0f);
-        break;
-    }
-    
-    DrawBaseGimbal(currMatrix, fSize, baseColor);
-    currMatrix.Pop();
-}</programlisting>
-            </example>
-            <para>The rotation matrices change the orientation of the gimbal to its neutral
-                position. These matrices are chosen to allow the full array of 3 gimbals to connect
-                properly.</para>
-            <para>The <function>DrawBaseGimbal</function> function performs the basic computations
-                necessary to draw the pieces. It delegates the actual drawing to other
-                functions.</para>
-            <example>
-                <title>DrawBaseGimbal Function</title>
-                <programlisting language="cpp">void DrawBaseGimbal(MatrixStack &amp;currMatrix, float fSize, glm::vec4 baseColor)
-{
-    //A Gimbal can only be 4 units in size or more.
-    assert(fSize > 4.0f);
-    
-    glUseProgram(theProgram);
-    //Set the base color for this object.
-    glUniform4fv(baseColorUnif, 1, glm::value_ptr(baseColor));
-    glBindVertexArray(vao);
-    
-    float fGimbalSidesOffset = (fSize / 2.0f) - 1.5f;
-    float fGimbalSidesScale = fSize - 2.0f;
-    
-    DrawGimbalSides(currMatrix, fGimbalSidesOffset, fGimbalSidesScale);
-    
-    float fGimbalAttachOffset = (fSize / 2.0f) - 0.5f;
-    
-    DrawGimbalAttachments(currMatrix, fGimbalAttachOffset);
-    
-    glBindVertexArray(0);
-    glUseProgram(0);
-}
-</programlisting>
-            </example>
-            <para>The color is stored into a uniform in the program. The fragment shader uses this
-                color along with the interpolated per-vertex color to compute the final output
-                color.</para>
-            <example>
-                <title>ColorMultUniform Fragment Shader</title>
-                <programlisting language="glsl">#version 330
-
-smooth in vec4 theColor;
-uniform vec4 baseColor;
-
-out vec4 outputColor;
-
-void main()
-{
-    outputColor = theColor * baseColor;
-}</programlisting>
-            </example>
-            <para>The interpolated per-vertex color is multiplied with the base color. The colors
-                from the mesh are not really colors in this case; they serve only to darken certain
-                faces. The front and back faces use full white (1.0, 1.0, 1.0, 1.0); multiplying
-                this by <varname>baseColor</varname> will simply return
-                <varname>baseColor</varname>. Thus the front and back faces of the object are the
-                intended color. The top and bottom sides use (0.75, 0.75, 0.75, 1.0), which serves
-                to make <varname>baseColor</varname> closer to zero, but not by a lot. The left and
-                right sides use a smaller value (0.5, 0.5, 0.5, 1.0), which makes the resulting
-                color even smaller.</para>
-            <para>The <function>DrawBaseGimbal</function> function computes the length and
-                positional offset for each of the main pieces of the ring of the gimbal. Since the
-                gimbal is square, it only needs a single length and offset. The
-                    <function>DrawGimbalSides</function> draws the gimbal's square ring.</para>
-            <para>The two attachment points are rendered with ; the sizes for these are fixed, so
-                they don't vary with the size of the gimbal. The <emphasis>location</emphasis> of
-                these do vary, so <function>DrawBaseGimbal</function> must compute that
-                offset.</para>
-            <para>All of this code uses multiple spaces in multiple different places. The most
-                important thing that it shows is how each layer in the hierarchy of functions
-                doesn't care what the space in the matrix stack currently is. DrawBaseGimbal draws
-                the gimbal in a certain space; it is up to whatever happens to be on the matrix
-                stack as to how it gets transformed into the final space.</para>
-            <para>Furthermore, the lower parts of the code don't care about how the gimbal drawing
-                gets done. The <function>display</function> function that ultimately calls
-                    <function>DrawGimbal</function> doesn't care if it is drawing multiple meshes,
-                or a single scaled mesh, or whatever. It simply says to draw the Y-axis gimbal of a
-                certain size in a certain color at a transform given by the matrix stack. Similarly,
-                    <function>DrawGimbal</function> only expects <function>DrawBaseGimbal</function>
-                to draw a X-oriented gimbal; that this requires 6 rendering calls and some matrix
-                stack work is not something <function>DrawGimbal</function> is concerned
-                with.</para>
-            <para>This is the ideal for dealing with complex objects that are positioned via various
-                transforms. The higher level code should be insulated from the fact that low level
-                code may be performing complex transformations, and low-level code should be
-                insulated from the specific uses of that particular object. Look at how simple the
-                display function is:</para>
-            <example>
-                <title>Gimbal Lock's Display Function</title>
-                <programlisting language="cpp">void display()
-{
-    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
-    glClearDepth(1.0f);
-    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-    
-    MatrixStack currMatrix;
-    currMatrix.Translate(glm::vec3(0.0f, 0.0f, -60.0f));
-    currMatrix.RotateX(g_angles.fAngleX);
-    DrawGimbal(currMatrix, GIMBAL_X_AXIS, 30.0f, glm::vec4(0.4f, 0.4f, 1.0f, 1.0f));
-    currMatrix.RotateY(g_angles.fAngleY);
-    DrawGimbal(currMatrix, GIMBAL_Y_AXIS, 26.0f, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f));
-    currMatrix.RotateZ(g_angles.fAngleZ);
-    DrawGimbal(currMatrix, GIMBAL_Z_AXIS, 22.0f, glm::vec4(1.0f, 0.3f, 0.3f, 1.0f));
-    
-    glutSwapBuffers();
-    glutPostRedisplay();
-}</programlisting>
-            </example>
-            <para>It performs a translation, so that the gimbal is positioned in front of the
-                camera. Then it performs the 3 successive rotations, drawing each gimbal after each
-                one. So long as <function>DrawGimbal</function> is doing the job that
-                    <function>display</function> expects, everything works out fine.</para>
-        </section>
-    </section>
-    <section>
         <?dbhtml filename="Tut06 In Review.html" ?>
         <title>In Review</title>
         <para>In this tutorial, you have learned the following:</para>
                         uses.</para>
                 </glossdef>
             </glossentry>
-            <glossentry>
-                <glossterm>gimbal lock</glossterm>
-                <glossdef>
-                    <para>When applying 3 or more successive rotations about axes that are
-                        orthogonal to each other, gimbal lock occurs when a degree of rotational
-                        freedom is lost due to two or more axes that cause the same effective
-                        rotation.</para>
-                </glossdef>
-            </glossentry>
-            <glossentry>
-                <glossterm>orthonormal</glossterm>
-                <glossdef>
-                    <para>A transform has the property of being orthonormal if the three basis axes
-                        are all orthogonal and the three axes are normal (have a length of
-                        1).</para>
-                </glossdef>
-            </glossentry>
         </glosslist>
     </section>
 </chapter>

Documents/Positioning/Tutorial 07.xml

     gl_Position = cameraToClipMatrix * temp;
 }</programlisting>
             </example>
-            <note>
+            <sidebar>
                 <title>Mismatched Attributes and Programs</title>
                 <para>You may be wondering what happens if there is a mis-match between the
-                    attributes provided by a VAO and the vertex shader inputs. For example, if we
+                    attributes provided by a VAO and the vertex shader inputs. For example, we could
                     use the position-only vertex shader, which takes attribute 0) with a mesh that
                     provides attributes 0 and 1, with 0 being the position and 1 being the
                     color.</para>
                     as it can be. If the reverse is true, if the VAO doesn't provide enough
                     components of the vector, then the unfilled values are always filled in from the
                     (0, 0, 0, 1) vector.</para>
-            </note>
+            </sidebar>
         </section>
         <section>
             <title>Camera of the World</title>

Documents/Positioning/Tutorial 08.xml

 <?oxygen SCHSchema="http://docbook.org/xml/5.0/rng/docbookxi.rng"?>
 <chapter xmlns="http://docbook.org/ns/docbook" xmlns:xi="http://www.w3.org/2001/XInclude"
     xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0">
-    <?dbhtml filename="Tutorial 07.html" ?>
+    <?dbhtml filename="Tutorial 08.html" ?>
     <title>Getting Oriented</title>
-    <para>G</para>
+    <para>In this tutorial, we will investigate specific problems with regard to orienting
+        objects.</para>
     <section>
-        <?dbhtml filename="Tut07 In Review.html" ?>
+        <?dbhtml filename="Tut08 Gimbal Lock.html"?>
+        <title>Gimbal Lock</title>
+        <para>Remember back when we said that n rotation matrix isn't a rotation matrix at all, that
+            it is an orientation matrix? We also said that forgetting this can come back to bite
+            you. Well, here's likely the most common way.</para>
+        <para>Normally, when dealing with orienting an object like a plane or spaceship in 3D space,
+            you want to orient it based on 3 rotations about the 3 axes. The obvious way to do this
+            is with a series of 3 rotations. This means that the program stores 3 angles, and you
+            generate a rotation matrix by creating 3 rotation matrices based on these angles and
+            concatenating them. Indeed, the 3 angles often have special names, based on common
+            flight terminology: yaw, pitch, and roll.</para>
+        <para>Pitch is the rotation that raises or lowers the front of the object. Yaw is the
+            rotation that turns the object left and right. Roll is the rotation that spins it around
+            the direction of motion. These terms neatly duck the question of what the axes
+            technically are; they are defined in terms of semantic axes (front, left, direction of
+            motion, etc), rather than a specific model space. So in one model space, roll might be a
+            rotation about the X axis, but in another, it might be a rotation about the Z
+            axis.</para>
+        <para>One of the first problems you will note is that the order you apply these rotations
+            matter. As previously stated, a rotation matrix is an <emphasis>orientation</emphasis>
+            transform. Each transform defines a new coordinate system, and the next transform is
+            based on an object in the <emphasis>new</emphasis> space. For example, if we apply the
+            roll first, we have now changed what the axis for the subsequent yaw is.</para>
+        <para>You can use any order that you like, so long as you understand what these angles mean.
+            If you apply the roll first, your yaw and pitch must be in terms of the new roll
+            coordinate system, and not the original coordinate system. That is, a change that is
+            intended to only affect the roll of the final space may need yaw or pitch changes to
+            allow it to have the same apparent orientation (except for the new roll, of
+            course).</para>
+        <para>But there is a far more insidious problem lurking in here. And this problem happens
+            anytime you compute a final rotation from a series of 3 rotations about axes
+            perpendicular to each other.</para>
+        <para>The tutorial project <phrase role="propername">Gimbal Lock</phrase> illustrates this
+            problem. Because the problem was first diagnosed with a physical device called a <link
+                xlink:href="http://en.wikipedia.org/wiki/Gimbal">gimbal</link>, the problem has
+            become known as <glossterm>gimbal lock.</glossterm></para>
+        <para>A gimbal is a pivoted support that provides the ability to rotate in one axis. A
+            gimbal can be mounted within another gimbal. The Gimbal Lock project has a set of 3
+            square gimbals, each with a pivot axis that is perpendicular to the other two. This
+            effectively mimics the common yaw/pitch/roll angle setup.</para>
+        <figure>
+            <title>Gimbal Lock Project</title>
+            <mediaobject>
+                <imageobject>
+                    <imagedata fileref="Three%20Gimbal%20Array.png"/>
+                </imageobject>
+            </mediaobject>
+        </figure>
+        <para>You can control the orientation of each gimbal separately. The <keycap>w</keycap> and
+                <keycap>s</keycap> keys control the blue gimbal, the <keycap>a</keycap> and
+                <keycap>d</keycap> keys control the green gimbal, and the <keycap>q</keycap> and
+                <keycap>e</keycap> keys control the red gimbal.</para>
+        <para>With these three gimbals, you can cause the innermost gimbal (the red one) to have any
+            arbitrary orientation. That isn't the problem. The problem happens when two of the
+            gimbals are parallel with one another:</para>
+        <figure>
+            <title>Parallel Gimbals</title>
+            <mediaobject>
+                <imageobject>
+                    <imagedata fileref="In%20Gimbal%20Lock.png"/>
+                </imageobject>
+            </mediaobject>
+        </figure>
+        <para>Recall that the purpose of the three gimbals is to be able to adjust one of the three
+            angles and orient the object in a particular direction. In a flight-simulation game, the
+            player would have controls that would change their yaw, pitch, and roll. However, look
+            at this picture.</para>
+        <para>The player's theoretical ship is pointed in the direction of the center gimbal's
+            plane. Given the controls you have here, can you cause the center gimbal to rotate
+            around the axis that it is facing? No. You can't even do this somewhat; you can only
+            rotate it in two directions. But we have three gimbals, which means we should have three
+            axes of rotation. Why can't we rotate the red gimbal in the Z (forward) axis?</para>
+        <para>Because the outer and inner gimbals are now rotating about the <emphasis>same
+                axis</emphasis>. Which means you really only have two gimbals to manipulate in order
+            to orient the red gimbal. And 3D orientation cannot be fully controlled with only 2
+            axial rotations, with only 2 gimbals.</para>
+        <para>When gimbals are in such a position, you have what is known as <glossterm>gimbal
+                lock</glossterm>; you have locked one of the gimbals to another, and now both cause
+            the same effect.</para>
+        <para>How do you fix this problem? There are several possible solutions to the
+            problem.</para>
+        <para>Perhaps the most optimal solution is to simply not use gimbals. After all, if you
+            don't have gimbals, you can't gimbal lock. Instead of storing the orientation as a
+            series of rotations, store the orientation as an <emphasis>orientation.</emphasis> That
+            is, maintain the current orientation as a matrix. When you need to modify the
+            orientation, you apply a transformation to this matrix, storing the result as the new
+            current orientation.</para>
+        <para>This means that every yaw, pitch, and roll applied to the current orientation will be
+            relative to that current orientation. Which is usually exactly what you want. If the
+            user applies a positive yaw, you want that yaw to rotate them relative to where they are
+            current pointing.</para>
+        <para>A downside of this approach, besides the size penalty of having to store a 4x4 matrix
+            rather than 3 floating-point angles, is that floating-point math can lead to errors. If
+            you keep accumulating successive transformations of an object, once every 1/30th of a
+            second for a period of several minutes or hours, these floating-point errors start
+            accumulating. Eventually, the orientation stops being a pure rotation and starts
+            incorporating scale and skewing characteristics.</para>
+        <para>The solution here is to re-orthonormalize the matrix after applying each transform. A
+            transform (space) is said to be <glossterm>orthonormal</glossterm> if the basis vectors
+            are of unit length (no scale) and each axis is perpendicular to all of the
+            others.</para>
+        <para>Unfortunately, re-orthonormalizing a matrix is not a simple operation. You could try
+            to normalize each of the axis vectors with typical vector normalization, but that
+            wouldn't ensure that the matrix was orthonormal. It would remove scaling, but the axes
+            wouldn't be guaranteed to be perpendicular.</para>
+        <para>The proper solution is to use <link
+                xlink:href="http://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation">unit
+                quaternions</link> to store orientations, which can be orthonormalized much more
+            easily. This is beyond the scope of this tutorial, but the Wikipedia article on the
+            subject is a good place to start.</para>
+        <section>
+            <title>Complex Mesh Space</title>
+            <para>The way this tutorial renders each gimbal serves as an object lesson in the use of
+                mesh space. The gimbals are composed of 6 pieces, each of which ultimately derives
+                from a simple 3D cube who's sides are of unit length. The cube is scaled and
+                translated to form the rectangular prisms used to build each the gimbal.</para>
+            <para>The <function>DrawGimbal</function> function takes a matrix stack, an axis (one of
+                the three kinds of gimbals), a size for the gimbal, and a color. The width and
+                height of a gimbal are the same, so this code only takes a single size.</para>
+            <para>There is ultimately only one gimbal <quote>mesh</quote>: the difference between
+                them is what their base orientation is. That is computed in this function:</para>
+            <example>
+                <title>DrawGimbal Function</title>
+                <programlisting language="cpp">void DrawGimbal(MatrixStack &amp;currMatrix, GimbalAxis eAxis, float fSize, glm::vec4 baseColor)
+{
+    currMatrix.Push();
+    
+    switch(eAxis)
+    {
+    case GIMBAL_X_AXIS:
+        break;
+    case GIMBAL_Y_AXIS:
+        currMatrix.RotateZ(90.0f);
+        currMatrix.RotateX(90.0f);
+        break;
+    case GIMBAL_Z_AXIS:
+        currMatrix.RotateY(90.0f);
+        currMatrix.RotateX(90.0f);
+        break;
+    }
+    
+    DrawBaseGimbal(currMatrix, fSize, baseColor);
+    currMatrix.Pop();
+}</programlisting>
+            </example>
+            <para>The rotation matrices change the orientation of the gimbal to its neutral
+                position. These matrices are chosen to allow the full array of 3 gimbals to connect
+                properly.</para>
+            <para>The <function>DrawBaseGimbal</function> function performs the basic computations
+                necessary to draw the pieces. It delegates the actual drawing to other
+                functions.</para>
+            <example>
+                <title>DrawBaseGimbal Function</title>
+                <programlisting language="cpp">void DrawBaseGimbal(MatrixStack &amp;currMatrix, float fSize, glm::vec4 baseColor)
+{
+    //A Gimbal can only be 4 units in size or more.
+    assert(fSize > 4.0f);
+    
+    glUseProgram(theProgram);
+    //Set the base color for this object.
+    glUniform4fv(baseColorUnif, 1, glm::value_ptr(baseColor));
+    glBindVertexArray(vao);
+    
+    float fGimbalSidesOffset = (fSize / 2.0f) - 1.5f;
+    float fGimbalSidesScale = fSize - 2.0f;
+    
+    DrawGimbalSides(currMatrix, fGimbalSidesOffset, fGimbalSidesScale);
+    
+    float fGimbalAttachOffset = (fSize / 2.0f) - 0.5f;
+    
+    DrawGimbalAttachments(currMatrix, fGimbalAttachOffset);
+    
+    glBindVertexArray(0);
+    glUseProgram(0);
+}
+</programlisting>
+            </example>
+            <para>The color is stored into a uniform in the program. The fragment shader uses this
+                color along with the interpolated per-vertex color to compute the final output
+                color.</para>
+            <example>
+                <title>ColorMultUniform Fragment Shader</title>
+                <programlisting language="glsl">#version 330
+
+smooth in vec4 theColor;
+uniform vec4 baseColor;
+
+out vec4 outputColor;
+
+void main()
+{
+    outputColor = theColor * baseColor;
+}</programlisting>
+            </example>
+            <para>The interpolated per-vertex color is multiplied with the base color. The colors
+                from the mesh are not really colors in this case; they serve only to darken certain
+                faces. The front and back faces use full white (1.0, 1.0, 1.0, 1.0); multiplying
+                this by <varname>baseColor</varname> will simply return
+                <varname>baseColor</varname>. Thus the front and back faces of the object are the
+                intended color. The top and bottom sides use (0.75, 0.75, 0.75, 1.0), which serves
+                to make <varname>baseColor</varname> closer to zero, but not by a lot. The left and
+                right sides use a smaller value (0.5, 0.5, 0.5, 1.0), which makes the resulting
+                color even smaller.</para>
+            <para>The <function>DrawBaseGimbal</function> function computes the length and
+                positional offset for each of the main pieces of the ring of the gimbal. Since the
+                gimbal is square, it only needs a single length and offset. The
+                    <function>DrawGimbalSides</function> draws the gimbal's square ring.</para>
+            <para>The two attachment points are rendered with ; the sizes for these are fixed, so
+                they don't vary with the size of the gimbal. The <emphasis>location</emphasis> of
+                these do vary, so <function>DrawBaseGimbal</function> must compute that
+                offset.</para>
+            <para>All of this code uses multiple spaces in multiple different places. The most
+                important thing that it shows is how each layer in the hierarchy of functions
+                doesn't care what the space in the matrix stack currently is. DrawBaseGimbal draws
+                the gimbal in a certain space; it is up to whatever happens to be on the matrix
+                stack as to how it gets transformed into the final space.</para>
+            <para>Furthermore, the lower parts of the code don't care about how the gimbal drawing
+                gets done. The <function>display</function> function that ultimately calls
+                    <function>DrawGimbal</function> doesn't care if it is drawing multiple meshes,
+                or a single scaled mesh, or whatever. It simply says to draw the Y-axis gimbal of a
+                certain size in a certain color at a transform given by the matrix stack. Similarly,
+                    <function>DrawGimbal</function> only expects <function>DrawBaseGimbal</function>
+                to draw a X-oriented gimbal; that this requires 6 rendering calls and some matrix
+                stack work is not something <function>DrawGimbal</function> is concerned
+                with.</para>
+            <para>This is the ideal for dealing with complex objects that are positioned via various
+                transforms. The higher level code should be insulated from the fact that low level
+                code may be performing complex transformations, and low-level code should be
+                insulated from the specific uses of that particular object. Look at how simple the
+                display function is:</para>
+            <example>
+                <title>Gimbal Lock's Display Function</title>
+                <programlisting language="cpp">void display()
+{
+    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+    glClearDepth(1.0f);
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+    
+    MatrixStack currMatrix;
+    currMatrix.Translate(glm::vec3(0.0f, 0.0f, -60.0f));
+    currMatrix.RotateX(g_angles.fAngleX);
+    DrawGimbal(currMatrix, GIMBAL_X_AXIS, 30.0f, glm::vec4(0.4f, 0.4f, 1.0f, 1.0f));
+    currMatrix.RotateY(g_angles.fAngleY);
+    DrawGimbal(currMatrix, GIMBAL_Y_AXIS, 26.0f, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f));
+    currMatrix.RotateZ(g_angles.fAngleZ);
+    DrawGimbal(currMatrix, GIMBAL_Z_AXIS, 22.0f, glm::vec4(1.0f, 0.3f, 0.3f, 1.0f));
+    
+    glutSwapBuffers();
+    glutPostRedisplay();
+}</programlisting>
+            </example>
+            <para>It performs a translation, so that the gimbal is positioned in front of the
+                camera. Then it performs the 3 successive rotations, drawing each gimbal after each
+                one. So long as <function>DrawGimbal</function> is doing the job that
+                    <function>display</function> expects, everything works out fine.</para>
+        </section>
+    </section>
+    <section>
+        <title>Quaternions</title>
+        <para/>
+    </section>
+    <section>
+        <title>Camera-Relative Orientation</title>
+        <para/>
+        <section>
+            <title>Transformation Spaces</title>
+            <para>Our scaling transform is interesting, but it does have certain flaws. The most
+                important problem is that it causes a scale based on the X, Y and Z axes of the
+                initial coordinate system. So what do you do if you want to scale something along
+                different axes?</para>
+            <para>This is quite simple, really: you use successive transforms. First, you transform
+                into a space where the <quote>different axes</quote>
+                <emphasis>are</emphasis> the X, Y and Z axes. Then you do your scaling transform.
+                After that, you transform back to the original space.</para>
+            <para>If the different axes represent an orientation change, you can use a rotation
+                matrix. Simply rotate, apply the scale, and rotate back. The last part is done by
+                generating the rotation matrix using the same axis, but negating the angle.</para>
+            <para>Transforming into a space that is convenient for another transform, applying that
+                transform, and then undoing the first transform is a general-purpose operation. The
+                matrix that results from this is a transformation in a different space. In the
+                scaling case, we want to do the scaling in a different space, so we apply a rotation
+                matrix to get into that space, then undo the rotation after applying the
+                scale.</para>
+            <para>The general form of this sequence is as follows. Suppose you have a transformation
+                matrix T, which operates on points in a space called F. We have some positions in
+                the space P. What we want is to create a matrix that applies T's transformation
+                operation, except that it needs to operate on points in the space of P. Given a
+                matrix M that transforms from P space to F space, that matrix is <inlineequation>
+                    <mathphrase>M<superscript>-1</superscript>TM</mathphrase>
+                </inlineequation>.</para>
+            <para>The matrix M<superscript>-1</superscript> is the <glossterm>inverse
+                    matrix</glossterm> of M. The symbol <superscript>-1</superscript> does not
+                (strictly) mean to raise the matrix to the -1 power. It means to invert it.</para>
+            <para>The inverse of a matrix M is the matrix N such that <inlineequation>
+                    <mathphrase>MN = I</mathphrase>
+                </inlineequation>, where I is the identity matrix. This can be analogized to the
+                scalar multiplicative inverse (ie: reciprocal). The scalar multiplicative inverse of
+                X is the number Y such that <inlineequation>
+                    <mathphrase>XY = 1</mathphrase>
+                </inlineequation>.</para>
+            <para>In the case of the scalar inverse, this is very easy to solve for: <inlineequation>
+                    <mathphrase>Y = 1/X</mathphrase>
+                </inlineequation>. Even in this case, there are values of X for which there is no
+                multiplicative inverse. OK, there's <emphasis>one</emphasis> such value of X:
+                0.</para>
+            <para>The case of the inverse matrix is much more complicated. Just as with the scalar
+                inverse, there are matrices that have no inverse. Unlike the scalar case, there are
+                a <emphasis>lot</emphasis> of matrices with no inverse. Also, computing the inverse
+                matrix is a <emphasis>lot</emphasis> more complicated than simply taking the
+                reciprocal of a value.</para>
+            <para>Most common transformation matrices do have an inverse. And for the basic
+                transformation matrices, the inverse matrix is very easy to compute. For a pure
+                rotation matrix, simply compute a new rotation matrix by negating the angle that the
+                old one was generated with. For a translation matrix, negate the origin value in the
+                matrix. For a scale matrix, take the reciprocal of the scale along each axis.</para>
+            <para>To take the inverse of a sequence of matrices, you can take the inverse of each of
+                the component matrices. But you have to do the matrix multiplication in
+                    <emphasis>reverse</emphasis> order. So if we have <inlineequation>
+                    <mathphrase>M = TRS</mathphrase>
+                </inlineequation>, then M<superscript>-1</superscript> is <inlineequation>
+                    <mathphrase>M<superscript>-1</superscript> =
+                            S<superscript>-1</superscript>R<superscript>-1</superscript>T<superscript>-1</superscript></mathphrase>
+                </inlineequation>.</para>
+        </section>
+    </section>
+    
+    <section>
+        <?dbhtml filename="Tut08 In Review.html" ?>
         <title>In Review</title>
         <para>In this tutorial, you have learned the following:</para>
+        <itemizedlist>
+            <listitem>
+                <para>A fixed sequence of successive rotations can prevent other rotations from
+                    contributing to the object's orientation.</para>
+            </listitem>
+            <listitem>
+                <para>Quaternions are 4-dimensional vectors that can encode an orientation. They can
+                    be used for successively applying small rotations to an orientation. Matrices
+                    fail at this because of the difficulty of orthonormalizing them to avoid
+                    floating-point error accumulation.</para>
+            </listitem>
+            <listitem>
+                <para>Quaternions work almost identically to matrices, in so far as they specify
+                    orientations. They can be constructed directly from an angle/axis
+                    rotation.</para>
+            </listitem>
+            <listitem>
+                <para>One can transform a matrix, or a quaternion with another matrix or quaternion,
+                    such that the resulting transform is specified in a different space. This is
+                    useful for applying rotations to an object's orientation that are in
+                    camera-space,, while the object's orientation remains a model-to-world
+                    transform.</para>
+            </listitem>
+        </itemizedlist>
     </section>
     <section>
         <?dbhtml filename="Tut08 Glossary.html" ?>
         <title>Glossary</title>
         <glosslist>
+            <glossentry>
+                <glossterm>gimbal lock</glossterm>
+                <glossdef>
+                    <para>When applying 3 or more successive rotations about axes that are
+                        orthogonal to each other, gimbal lock occurs when a degree of rotational
+                        freedom is lost due to two or more axes that cause the same effective
+                        rotation.</para>
+                </glossdef>
+            </glossentry>
+            <glossentry>
+                <glossterm>orthonormal</glossterm>
+                <glossdef>
+                    <para>A transform has the property of being orthonormal if the three basis axes
+                        are all orthogonal and the three axes are normal (have a length of
+                        1).</para>
+                </glossdef>
+            </glossentry>
         </glosslist>
     </section>
 </chapter>

Tut 06 Objects in Motion/GimbalLock.cpp

-#include <string>
-#include <vector>
-#include <stack>
-#include <math.h>
-#include <glloader/gl_3_2_comp.h>
-#include <GL/freeglut.h>
-#include "../framework/framework.h"
-#include <glm/glm.hpp>
-#include <glm/gtc/type_ptr.hpp>
-
-#define ARRAY_COUNT( array ) (sizeof( array ) / (sizeof( array[0] ) * (sizeof( array ) != sizeof(void*) || sizeof( array[0] ) <= sizeof(void*))))
-
-GLuint theProgram;
-GLuint positionAttrib;
-GLuint colorAttrib;
-
-GLuint modelToCameraMatrixUnif;
-GLuint cameraToClipMatrixUnif;
-GLuint baseColorUnif;
-
-glm::mat4 cameraToClipMatrix(0.0f);
-
-float CalcFrustumScale(float fFovDeg)
-{
-	const float degToRad = 3.14159f * 2.0f / 360.0f;
-	float fFovRad = fFovDeg * degToRad;
-	return 1.0f / tan(fFovRad / 2.0f);
-}
-
-const float fFrustumScale = CalcFrustumScale(45.0f);
-
-void InitializeProgram()
-{
-	std::vector<GLuint> shaderList;
-
-	shaderList.push_back(Framework::LoadShader(GL_VERTEX_SHADER, "PosColorLocalTransform.vert"));
-	shaderList.push_back(Framework::LoadShader(GL_FRAGMENT_SHADER, "ColorMultUniform.frag"));
-
-	theProgram = Framework::CreateProgram(shaderList);
-
-	positionAttrib = glGetAttribLocation(theProgram, "position");
-	colorAttrib = glGetAttribLocation(theProgram, "color");
-
-	modelToCameraMatrixUnif = glGetUniformLocation(theProgram, "modelToCameraMatrix");
-	cameraToClipMatrixUnif = glGetUniformLocation(theProgram, "cameraToClipMatrix");
-	baseColorUnif = glGetUniformLocation(theProgram, "baseColor");
-
-	float fzNear = 1.0f; float fzFar = 100.0f;
-
-	cameraToClipMatrix[0].x = fFrustumScale;
-	cameraToClipMatrix[1].y = fFrustumScale;
-	cameraToClipMatrix[2].z = (fzFar + fzNear) / (fzNear - fzFar);
-	cameraToClipMatrix[2].w = -1.0f;
-	cameraToClipMatrix[3].z = (2 * fzFar * fzNear) / (fzNear - fzFar);
-
-	glUseProgram(theProgram);
-	glUniformMatrix4fv(cameraToClipMatrixUnif, 1, GL_FALSE, glm::value_ptr(cameraToClipMatrix));
-	glUseProgram(0);
-}
-
-const int numberOfVertices = 24;
-
-#define FULL_COLOR 1.0f, 1.0f, 1.0f, 1.0f
-#define LIGHT_COLOR 0.75f, 0.75f, 0.75f, 1.0f
-#define MID_COLOR 0.5f, 0.5f, 0.5f, 1.0f
-#define DARK_COLOR 	0.3f, 0.3f, 0.3f, 1.0f
-
-
-const float vertexData[] =
-{
-	//Front
-	+0.5f, +0.5f, +0.5f,
-	+0.5f, -0.5f, +0.5f,
-	-0.5f, -0.5f, +0.5f,
-	-0.5f, +0.5f, +0.5f,
-
-	//Top
-	+0.5f, +0.5f, +0.5f,
-	-0.5f, +0.5f, +0.5f,
-	-0.5f, +0.5f, -0.5f,
-	+0.5f, +0.5f, -0.5f,
-
-	//Left
-	+0.5f, +0.5f, +0.5f,
-	+0.5f, +0.5f, -0.5f,
-	+0.5f, -0.5f, -0.5f,
-	+0.5f, -0.5f, +0.5f,
-
-	//Back
-	+0.5f, +0.5f, -0.5f,
-	-0.5f, +0.5f, -0.5f,
-	-0.5f, -0.5f, -0.5f,
-	+0.5f, -0.5f, -0.5f,
-
-	//Bottom
-	+0.5f, -0.5f, +0.5f,
-	+0.5f, -0.5f, -0.5f,
-	-0.5f, -0.5f, -0.5f,
-	-0.5f, -0.5f, +0.5f,
-
-	//Right
-	-0.5f, +0.5f, +0.5f,
-	-0.5f, -0.5f, +0.5f,
-	-0.5f, -0.5f, -0.5f,
-	-0.5f, +0.5f, -0.5f,
-
-
-	FULL_COLOR,
-	FULL_COLOR,
-	FULL_COLOR,
-	FULL_COLOR,
-
-	LIGHT_COLOR,
-	LIGHT_COLOR,
-	LIGHT_COLOR,
-	LIGHT_COLOR,
-
-	MID_COLOR,
-	MID_COLOR,
-	MID_COLOR,
-	MID_COLOR,
-
-	FULL_COLOR,
-	FULL_COLOR,
-	FULL_COLOR,
-	FULL_COLOR,
-
-	LIGHT_COLOR,
-	LIGHT_COLOR,
-	LIGHT_COLOR,
-	LIGHT_COLOR,
-
-	MID_COLOR,
-	MID_COLOR,
-	MID_COLOR,
-	MID_COLOR,
-};
-
-const GLshort indexData[] =
-{
-	0, 1, 2,
-	2, 3, 0,
-
-	4, 5, 6,
-	6, 7, 4,
-
-	8, 9, 10,
-	10, 11, 8,
-
-	12, 13, 14,
-	14, 15, 12,
-
-	16, 17, 18,
-	18, 19, 16,
-
-	20, 21, 22,
-	22, 23, 20,
-};
-
-GLuint vertexBufferObject;
-GLuint indexBufferObject;
-GLuint vao;
-
-void InitializeVAO()
-{
-	glGenBuffers(1, &vertexBufferObject);
-
-	glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObject);
-	glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
-	glBindBuffer(GL_ARRAY_BUFFER, 0);
-
-	glGenBuffers(1, &indexBufferObject);
-
-	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferObject);
-	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexData), indexData, GL_STATIC_DRAW);
-	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
-
-	glGenVertexArrays(1, &vao);
-	glBindVertexArray(vao);
-
-	size_t colorDataOffset = sizeof(float) * 3 * numberOfVertices;
-	glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObject);
-	glEnableVertexAttribArray(positionAttrib);
-	glEnableVertexAttribArray(colorAttrib);
-	glVertexAttribPointer(positionAttrib, 3, GL_FLOAT, GL_FALSE, 0, 0);
-	glVertexAttribPointer(colorAttrib, 4, GL_FLOAT, GL_FALSE, 0, (void*)colorDataOffset);
-	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferObject);
-
-	glBindVertexArray(0);
-}
-
-inline float DegToRad(float fAngDeg)
-{
-	const float fDegToRad = 3.14159f * 2.0f / 360.0f;
-	return fAngDeg * fDegToRad;
-}
-
-inline float Clamp(float fValue, float fMinValue, float fMaxValue)
-{
-	if(fValue < fMinValue)
-		return fMinValue;
-
-	if(fValue > fMaxValue)
-		return fMaxValue;
-
-	return fValue;
-}
-
-glm::mat3 RotateX(float fAngDeg)
-{
-	float fAngRad = DegToRad(fAngDeg);
-	float fCos = cosf(fAngRad);
-	float fSin = sinf(fAngRad);
-
-	glm::mat3 theMat(1.0f);
-	theMat[1].y = fCos; theMat[2].y = -fSin;
-	theMat[1].z = fSin; theMat[2].z = fCos;
-	return theMat;
-}
-
-glm::mat3 RotateY(float fAngDeg)
-{
-	float fAngRad = DegToRad(fAngDeg);
-	float fCos = cosf(fAngRad);
-	float fSin = sinf(fAngRad);
-
-	glm::mat3 theMat(1.0f);
-	theMat[0].x = fCos; theMat[2].x = fSin;
-	theMat[0].z = -fSin; theMat[2].z = fCos;
-	return theMat;
-}
-
-glm::mat3 RotateZ(float fAngDeg)
-{
-	float fAngRad = DegToRad(fAngDeg);
-	float fCos = cosf(fAngRad);
-	float fSin = sinf(fAngRad);
-
-	glm::mat3 theMat(1.0f);
-	theMat[0].x = fCos; theMat[1].x = -fSin;
-	theMat[0].y = fSin; theMat[1].y = fCos;
-	return theMat;
-}
-
-class MatrixStack
-{
-public:
-	MatrixStack()
-		: m_currMat(1.0f)
-	{
-	}
-
-	const glm::mat4 &Top()
-	{
-		return m_currMat;
-	}
-
-	void RotateX(float fAngDeg)
-	{
-		m_currMat = m_currMat * glm::mat4(::RotateX(fAngDeg));
-	}
-
-	void RotateY(float fAngDeg)
-	{
-		m_currMat = m_currMat * glm::mat4(::RotateY(fAngDeg));
-	}
-
-	void RotateZ(float fAngDeg)
-	{
-		m_currMat = m_currMat * glm::mat4(::RotateZ(fAngDeg));
-	}
-
-	void Scale(const glm::vec3 &scaleVec)
-	{
-		glm::mat4 scaleMat(1.0f);
-		scaleMat[0].x = scaleVec.x;
-		scaleMat[1].y = scaleVec.y;
-		scaleMat[2].z = scaleVec.z;
-
-		m_currMat = m_currMat * scaleMat;
-	}
-
-	void Translate(const glm::vec3 &offsetVec)
-	{
-		glm::mat4 translateMat(1.0f);
-		translateMat[3] = glm::vec4(offsetVec, 1.0f);
-
-		m_currMat = m_currMat * translateMat;
-	}
-
-	void Push()
-	{
-		m_matrices.push(m_currMat);
-	}
-
-	void Pop()
-	{
-		m_currMat = m_matrices.top();
-		m_matrices.pop();
-	}
-
-private:
-	glm::mat4 m_currMat;
-	std::stack<glm::mat4> m_matrices;
-};
-
-void DrawGimbalSides(MatrixStack &currMatrix, float fGimbalSidesOffset, float fGimbalSidesScale)
-{
-	//Draw the top
-	{
-		currMatrix.Push();
-		currMatrix.Translate(glm::vec3(0.0f, fGimbalSidesOffset, 0.0f));
-		currMatrix.Scale(glm::vec3(fGimbalSidesScale, 1.0f, 1.0f));
-		glUniformMatrix4fv(modelToCameraMatrixUnif, 1, GL_FALSE, glm::value_ptr(currMatrix.Top()));
-		glDrawElements(GL_TRIANGLES, ARRAY_COUNT(indexData), GL_UNSIGNED_SHORT, 0);
-		currMatrix.Pop();
-	}
-
-	//Draw the bottom
-	{
-		currMatrix.Push();
-		currMatrix.Translate(glm::vec3(0.0f, -fGimbalSidesOffset, 0.0f));
-		currMatrix.Scale(glm::vec3(fGimbalSidesScale, 1.0f, 1.0f));
-		glUniformMatrix4fv(modelToCameraMatrixUnif, 1, GL_FALSE, glm::value_ptr(currMatrix.Top()));
-		glDrawElements(GL_TRIANGLES, ARRAY_COUNT(indexData), GL_UNSIGNED_SHORT, 0);
-		currMatrix.Pop();
-	}
-
-	//Draw the right
-	{
-		currMatrix.Push();
-		currMatrix.Translate(glm::vec3(fGimbalSidesOffset, 0.0f, 0.0f));
-		currMatrix.Scale(glm::vec3(1.0f, fGimbalSidesScale, 1.0f));
-		glUniformMatrix4fv(modelToCameraMatrixUnif, 1, GL_FALSE, glm::value_ptr(currMatrix.Top()));
-		glDrawElements(GL_TRIANGLES, ARRAY_COUNT(indexData), GL_UNSIGNED_SHORT, 0);
-		currMatrix.Pop();
-	}
-
-	//Draw the left
-	{
-		currMatrix.Push();
-		currMatrix.Translate(glm::vec3(-fGimbalSidesOffset, 0.0f, 0.0f));
-		currMatrix.Scale(glm::vec3(1.0f, fGimbalSidesScale, 1.0f));
-		glUniformMatrix4fv(modelToCameraMatrixUnif, 1, GL_FALSE, glm::value_ptr(currMatrix.Top()));
-		glDrawElements(GL_TRIANGLES, ARRAY_COUNT(indexData), GL_UNSIGNED_SHORT, 0);
-		currMatrix.Pop();
-	}
-}
-
-void DrawGimbalAttachments(MatrixStack &currMatrix, float fGimbalAttachOffset)
-{
-	//Draw the right attachment.
-	{
-		currMatrix.Push();
-		currMatrix.Translate(glm::vec3(fGimbalAttachOffset, 0.0f, 0.0f));
-		currMatrix.Scale(glm::vec3(1.0f, 0.5f, 0.5f));
-		glUniformMatrix4fv(modelToCameraMatrixUnif, 1, GL_FALSE, glm::value_ptr(currMatrix.Top()));
-		glDrawElements(GL_TRIANGLES, ARRAY_COUNT(indexData), GL_UNSIGNED_SHORT, 0);
-		currMatrix.Pop();
-	}
-
-	//Draw the left attachment.
-	{
-		currMatrix.Push();
-		currMatrix.Translate(glm::vec3(-fGimbalAttachOffset, 0.0f, 0.0f));
-		currMatrix.Scale(glm::vec3(1.0f, 0.5f, 0.5f));
-		glUniformMatrix4fv(modelToCameraMatrixUnif, 1, GL_FALSE, glm::value_ptr(currMatrix.Top()));
-		glDrawElements(GL_TRIANGLES, ARRAY_COUNT(indexData), GL_UNSIGNED_SHORT, 0);
-		currMatrix.Pop();
-	}
-}
-
-
-void DrawBaseGimbal(MatrixStack &currMatrix, float fSize, glm::vec4 baseColor)
-{
-	//A Gimbal can only be 4 units in size or more.
-	assert(fSize > 4.0f);
-
-	glUseProgram(theProgram);
-	//Set the base color for this object.
-	glUniform4fv(baseColorUnif, 1, glm::value_ptr(baseColor));
-	glBindVertexArray(vao);
-
-	float fGimbalSidesOffset = (fSize / 2.0f) - 1.5f;
-	float fGimbalSidesScale = fSize - 2.0f;
-
-	DrawGimbalSides(currMatrix, fGimbalSidesOffset, fGimbalSidesScale);
-	
-	float fGimbalAttachOffset = (fSize / 2.0f) - 0.5f;
-
-	DrawGimbalAttachments(currMatrix, fGimbalAttachOffset);
-
-	glBindVertexArray(0);
-	glUseProgram(0);
-}
-
-enum GimbalAxis
-{
-	GIMBAL_X_AXIS,
-	GIMBAL_Y_AXIS,
-	GIMBAL_Z_AXIS,
-};
-
-void DrawGimbal(MatrixStack &currMatrix, GimbalAxis eAxis, float fSize, glm::vec4 baseColor)
-{
-	currMatrix.Push();
-
-	switch(eAxis)
-	{
-	case GIMBAL_X_AXIS:
-		break;
-	case GIMBAL_Y_AXIS:
-		currMatrix.RotateZ(90.0f);
-		currMatrix.RotateX(90.0f);
-		break;
-	case GIMBAL_Z_AXIS:
-		currMatrix.RotateY(90.0f);
-		currMatrix.RotateX(90.0f);
-		break;
-	}
-
-	DrawBaseGimbal(currMatrix, fSize, baseColor);
-	currMatrix.Pop();
-}
-
-//Called after the window and OpenGL are initialized. Called exactly once, before the main loop.
-void init()
-{
-	InitializeProgram();
-	InitializeVAO();
-
-
-	glEnable(GL_CULL_FACE);
-	glCullFace(GL_BACK);
-	glFrontFace(GL_CW);
-
-	glEnable(GL_DEPTH_TEST);
-	glDepthMask(GL_TRUE);
-	glDepthFunc(GL_LEQUAL);
-	glDepthRange(0.0f, 1.0f);
-}
-
-struct GimbalAngles
-{
-	GimbalAngles()
-		: fAngleX(0.0f)
-		, fAngleY(0.0f)
-		, fAngleZ(0.0f)
-	{}
-
-	float fAngleX;
-	float fAngleY;
-	float fAngleZ;
-};
-
-GimbalAngles g_angles;
-
-//Called to update the display.
-//You should call glutSwapBuffers after all of your rendering to display what you rendered.
-//If you need continuous updates of the screen, call glutPostRedisplay() at the end of the function.
-void display()
-{
-	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
-	glClearDepth(1.0f);
-	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-
-	MatrixStack currMatrix;
-	currMatrix.Translate(glm::vec3(0.0f, 0.0f, -60.0f));
-	currMatrix.RotateX(g_angles.fAngleX);
-	DrawGimbal(currMatrix, GIMBAL_X_AXIS, 30.0f, glm::vec4(0.4f, 0.4f, 1.0f, 1.0f));
-	currMatrix.RotateY(g_angles.fAngleY);
-	DrawGimbal(currMatrix, GIMBAL_Y_AXIS, 26.0f, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f));
-	currMatrix.RotateZ(g_angles.fAngleZ);
-	DrawGimbal(currMatrix, GIMBAL_Z_AXIS, 22.0f, glm::vec4(1.0f, 0.3f, 0.3f, 1.0f));
-
-	glutSwapBuffers();
-	glutPostRedisplay();
-}
-
-//Called whenever the window is resized. The new window size is given, in pixels.
-//This is an opportunity to call glViewport or glScissor to keep up with the change in size.
-void reshape (int w, int h)
-{
-	cameraToClipMatrix[0].x = fFrustumScale * (h / (float)w);
-	cameraToClipMatrix[1].y = fFrustumScale;
-
-	glUseProgram(theProgram);
-	glUniformMatrix4fv(cameraToClipMatrixUnif, 1, GL_FALSE, glm::value_ptr(cameraToClipMatrix));
-	glUseProgram(0);
-
-	glViewport(0, 0, (GLsizei) w, (GLsizei) h);
-}
-
-#define STANDARD_ANGLE_INCREMENT 11.25f
-#define SMALL_ANGLE_INCREMENT 9.0f
-
-//Called whenever a key on the keyboard was pressed.
-//The key is given by the ''key'' parameter, which is in ASCII.
-//It's often a good idea to have the escape key (ASCII value 27) call glutLeaveMainLoop() to 
-//exit the program.
-void keyboard(unsigned char key, int x, int y)
-{
-	switch (key)
-	{
-	case 27:
-		glutLeaveMainLoop();
-		break;
-	case 'w': g_angles.fAngleX += SMALL_ANGLE_INCREMENT; break;
-	case 's': g_angles.fAngleX -= SMALL_ANGLE_INCREMENT; break;
-
-	case 'a': g_angles.fAngleY += SMALL_ANGLE_INCREMENT; break;
-	case 'd': g_angles.fAngleY -= SMALL_ANGLE_INCREMENT; break;
-
-	case 'q': g_angles.fAngleZ += SMALL_ANGLE_INCREMENT; break;
-	case 'e': g_angles.fAngleZ -= SMALL_ANGLE_INCREMENT; break;
-	}
-}
-
-

Tut 06 Objects in Motion/tutorials.lua

 	"data/ColorPassthrough.frag", "data/PosColorLocalTransform.vert")
 SetupProject("Tut 06 Hierarchy", "Hierarchy.cpp",
 	"data/ColorPassthrough.frag", "data/PosColorLocalTransform.vert")
-SetupProject("Tut 06 Gimbal Lock", "GimbalLock.cpp",
-	"data/ColorMultUniform.frag", "data/PosColorLocalTransform.vert")

Tut 08 Getting Oriented/GimbalLock.cpp

+#include <string>
+#include <vector>
+#include <stack>
+#include <math.h>
+#include <glloader/gl_3_2_comp.h>
+#include <GL/freeglut.h>
+#include "../framework/framework.h"
+#include <glm/glm.hpp>
+#include <glm/gtc/type_ptr.hpp>
+
+#define ARRAY_COUNT( array ) (sizeof( array ) / (sizeof( array[0] ) * (sizeof( array ) != sizeof(void*) || sizeof( array[0] ) <= sizeof(void*))))
+
+GLuint theProgram;
+GLuint positionAttrib;
+GLuint colorAttrib;
+
+GLuint modelToCameraMatrixUnif;
+GLuint cameraToClipMatrixUnif;
+GLuint baseColorUnif;
+
+glm::mat4 cameraToClipMatrix(0.0f);
+
+float CalcFrustumScale(float fFovDeg)
+{
+	const float degToRad = 3.14159f * 2.0f / 360.0f;
+	float fFovRad = fFovDeg * degToRad;
+	return 1.0f / tan(fFovRad / 2.0f);
+}
+
+const float fFrustumScale = CalcFrustumScale(45.0f);
+
+void InitializeProgram()
+{
+	std::vector<GLuint> shaderList;
+
+	shaderList.push_back(Framework::LoadShader(GL_VERTEX_SHADER, "PosColorLocalTransform.vert"));
+	shaderList.push_back(Framework::LoadShader(GL_FRAGMENT_SHADER, "ColorMultUniform.frag"));
+
+	theProgram = Framework::CreateProgram(shaderList);
+
+	positionAttrib = glGetAttribLocation(theProgram, "position");
+	colorAttrib = glGetAttribLocation(theProgram, "color");
+
+	modelToCameraMatrixUnif = glGetUniformLocation(theProgram, "modelToCameraMatrix");
+	cameraToClipMatrixUnif = glGetUniformLocation(theProgram, "cameraToClipMatrix");
+	baseColorUnif = glGetUniformLocation(theProgram, "baseColor");
+
+	float fzNear = 1.0f; float fzFar = 100.0f;
+
+	cameraToClipMatrix[0].x = fFrustumScale;
+	cameraToClipMatrix[1].y = fFrustumScale;
+	cameraToClipMatrix[2].z = (fzFar + fzNear) / (fzNear - fzFar);
+	cameraToClipMatrix[2].w = -1.0f;
+	cameraToClipMatrix[3].z = (2 * fzFar * fzNear) / (fzNear - fzFar);
+
+	glUseProgram(theProgram);
+	glUniformMatrix4fv(cameraToClipMatrixUnif, 1, GL_FALSE, glm::value_ptr(cameraToClipMatrix));
+	glUseProgram(0);
+}
+
+const int numberOfVertices = 24;
+
+#define FULL_COLOR 1.0f, 1.0f, 1.0f, 1.0f
+#define LIGHT_COLOR 0.75f, 0.75f, 0.75f, 1.0f
+#define MID_COLOR 0.5f, 0.5f, 0.5f, 1.0f
+#define DARK_COLOR 	0.3f, 0.3f, 0.3f, 1.0f
+
+
+const float vertexData[] =
+{
+	//Front
+	+0.5f, +0.5f, +0.5f,
+	+0.5f, -0.5f, +0.5f,
+	-0.5f, -0.5f, +0.5f,
+	-0.5f, +0.5f, +0.5f,
+
+	//Top
+	+0.5f, +0.5f, +0.5f,
+	-0.5f, +0.5f, +0.5f,
+	-0.5f, +0.5f, -0.5f,
+	+0.5f, +0.5f, -0.5f,
+
+	//Left
+	+0.5f, +0.5f, +0.5f,
+	+0.5f, +0.5f, -0.5f,
+	+0.5f, -0.5f, -0.5f,
+	+0.5f, -0.5f, +0.5f,
+
+	//Back
+	+0.5f, +0.5f, -0.5f,
+	-0.5f, +0.5f, -0.5f,
+	-0.5f, -0.5f, -0.5f,
+	+0.5f, -0.5f, -0.5f,
+
+	//Bottom
+	+0.5f, -0.5f, +0.5f,
+	+0.5f, -0.5f, -0.5f,
+	-0.5f, -0.5f, -0.5f,
+	-0.5f, -0.5f, +0.5f,
+
+	//Right
+	-0.5f, +0.5f, +0.5f,
+	-0.5f, -0.5f, +0.5f,
+	-0.5f, -0.5f, -0.5f,
+	-0.5f, +0.5f, -0.5f,
+
+
+	FULL_COLOR,
+	FULL_COLOR,
+	FULL_COLOR,
+	FULL_COLOR,
+
+	LIGHT_COLOR,
+	LIGHT_COLOR,
+	LIGHT_COLOR,
+	LIGHT_COLOR,
+
+	MID_COLOR,
+	MID_COLOR,
+	MID_COLOR,
+	MID_COLOR,
+
+	FULL_COLOR,
+	FULL_COLOR,
+	FULL_COLOR,
+	FULL_COLOR,
+
+	LIGHT_COLOR,
+	LIGHT_COLOR,
+	LIGHT_COLOR,
+	LIGHT_COLOR,
+
+	MID_COLOR,
+	MID_COLOR,
+	MID_COLOR,
+	MID_COLOR,
+};
+
+const GLshort indexData[] =
+{
+	0, 1, 2,
+	2, 3, 0,
+
+	4, 5, 6,
+	6, 7, 4,
+
+	8, 9, 10,
+	10, 11, 8,
+
+	12, 13, 14,
+	14, 15, 12,
+
+	16, 17, 18,
+	18, 19, 16,
+
+	20, 21, 22,
+	22, 23, 20,
+};
+
+GLuint vertexBufferObject;
+GLuint indexBufferObject;
+GLuint vao;
+
+void InitializeVAO()
+{
+	glGenBuffers(1, &vertexBufferObject);
+
+	glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObject);
+	glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
+	glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+	glGenBuffers(1, &indexBufferObject);
+
+	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferObject);
+	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexData), indexData, GL_STATIC_DRAW);
+	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+	glGenVertexArrays(1, &vao);
+	glBindVertexArray(vao);
+
+	size_t colorDataOffset = sizeof(float) * 3 * numberOfVertices;
+	glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObject);
+	glEnableVertexAttribArray(positionAttrib);
+	glEnableVertexAttribArray(colorAttrib);
+	glVertexAttribPointer(positionAttrib, 3, GL_FLOAT, GL_FALSE, 0, 0);
+	glVertexAttribPointer(colorAttrib, 4, GL_FLOAT, GL_FALSE, 0, (void*)colorDataOffset);
+	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferObject);
+
+	glBindVertexArray(0);
+}
+
+inline float DegToRad(float fAngDeg)
+{
+	const float fDegToRad = 3.14159f * 2.0f / 360.0f;
+	return fAngDeg * fDegToRad;
+}
+
+inline float Clamp(float fValue, float fMinValue, float fMaxValue)
+{
+	if(fValue < fMinValue)
+		return fMinValue;
+
+	if(fValue > fMaxValue)
+		return fMaxValue;
+
+	return fValue;
+}
+
+glm::mat3 RotateX(float fAngDeg)
+{
+	float fAngRad = DegToRad(fAngDeg);
+	float fCos = cosf(fAngRad);
+	float fSin = sinf(fAngRad);
+
+	glm::mat3 theMat(1.0f);
+	theMat[1].y = fCos; theMat[2].y = -fSin;
+	theMat[1].z = fSin; theMat[2].z = fCos;
+	return theMat;
+}
+
+glm::mat3 RotateY(float fAngDeg)
+{
+	float fAngRad = DegToRad(fAngDeg);
+	float fCos = cosf(fAngRad);
+	float fSin = sinf(fAngRad);
+
+	glm::mat3 theMat(1.0f);
+	theMat[0].x = fCos; theMat[2].x = fSin;
+	theMat[0].z = -fSin; theMat[2].z = fCos;
+	return theMat;
+}
+
+glm::mat3 RotateZ(float fAngDeg)
+{
+	float fAngRad = DegToRad(fAngDeg);
+	float fCos = cosf(fAngRad);
+	float fSin = sinf(fAngRad);
+
+	glm::mat3 theMat(1.0f);
+	theMat[0].x = fCos; theMat[1].x = -fSin;
+	theMat[0].y = fSin; theMat[1].y = fCos;
+	return theMat;
+}
+
+class MatrixStack
+{
+public:
+	MatrixStack()
+		: m_currMat(1.0f)
+	{
+	}
+
+	const glm::mat4 &Top()
+	{
+		return m_currMat;
+	}
+
+	void RotateX(float fAngDeg)
+	{
+		m_currMat = m_currMat * glm::mat4(::RotateX(fAngDeg));
+	}
+
+	void RotateY(float fAngDeg)
+	{
+		m_currMat = m_currMat * glm::mat4(::RotateY(fAngDeg));
+	}
+
+	void RotateZ(float fAngDeg)
+	{
+		m_currMat = m_currMat * glm::mat4(::RotateZ(fAngDeg));
+	}
+
+	void Scale(const glm::vec3 &scaleVec)
+	{
+		glm::mat4 scaleMat(1.0f);
+		scaleMat[0].x = scaleVec.x;
+		scaleMat[1].y = scaleVec.y;
+		scaleMat[2].z = scaleVec.z;
+
+		m_currMat = m_currMat * scaleMat;
+	}
+
+	void Translate(const glm::vec3 &offsetVec)
+	{
+		glm::mat4 translateMat(1.0f);
+		translateMat[3] = glm::vec4(offsetVec, 1.0f);
+
+		m_currMat = m_currMat * translateMat;
+	}
+
+	void Push()
+	{
+		m_matrices.push(m_currMat);
+	}
+
+	void Pop()
+	{
+		m_currMat = m_matrices.top();
+		m_matrices.pop();
+	}
+
+private:
+	glm::mat4 m_currMat;
+	std::stack<glm::mat4> m_matrices;
+};
+
+void DrawGimbalSides(MatrixStack &currMatrix, float fGimbalSidesOffset, float fGimbalSidesScale)
+{
+	//Draw the top
+	{
+		currMatrix.Push();
+		currMatrix.Translate(glm::vec3(0.0f, fGimbalSidesOffset, 0.0f));
+		currMatrix.Scale(glm::vec3(fGimbalSidesScale, 1.0f, 1.0f));
+		glUniformMatrix4fv(modelToCameraMatrixUnif, 1, GL_FALSE, glm::value_ptr(currMatrix.Top()));
+		glDrawElements(GL_TRIANGLES, ARRAY_COUNT(indexData), GL_UNSIGNED_SHORT, 0);
+		currMatrix.Pop();
+	}
+
+	//Draw the bottom
+	{
+		currMatrix.Push();
+		currMatrix.Translate(glm::vec3(0.0f, -fGimbalSidesOffset, 0.0f));
+		currMatrix.Scale(glm::vec3(fGimbalSidesScale, 1.0f, 1.0f));
+		glUniformMatrix4fv(modelToCameraMatrixUnif, 1, GL_FALSE, glm::value_ptr(currMatrix.Top()));
+		glDrawElements(GL_TRIANGLES, ARRAY_COUNT(indexData), GL_UNSIGNED_SHORT, 0);
+		currMatrix.Pop();
+	}
+
+	//Draw the right
+	{
+		currMatrix.Push();
+		currMatrix.Translate(glm::vec3(fGimbalSidesOffset, 0.0f, 0.0f));
+		currMatrix.Scale(glm::vec3(1.0f, fGimbalSidesScale, 1.0f));
+		glUniformMatrix4fv(modelToCameraMatrixUnif, 1, GL_FALSE, glm::value_ptr(currMatrix.Top()));
+		glDrawElements(GL_TRIANGLES, ARRAY_COUNT(indexData), GL_UNSIGNED_SHORT, 0);
+		currMatrix.Pop();
+	}
+
+	//Draw the left
+	{
+		currMatrix.Push();
+		currMatrix.Translate(glm::vec3(-fGimbalSidesOffset, 0.0f, 0.0f));
+		currMatrix.Scale(glm::vec3(1.0f, fGimbalSidesScale, 1.0f));
+		glUniformMatrix4fv(modelToCameraMatrixUnif, 1, GL_FALSE, glm::value_ptr(currMatrix.Top()));
+		glDrawElements(GL_TRIANGLES, ARRAY_COUNT(indexData), GL_UNSIGNED_SHORT, 0);
+		currMatrix.Pop();
+	}
+}
+
+void DrawGimbalAttachments(MatrixStack &currMatrix, float fGimbalAttachOffset)
+{
+	//Draw the right attachment.
+	{
+		currMatrix.Push();
+		currMatrix.Translate(glm::vec3(fGimbalAttachOffset, 0.0f, 0.0f));
+		currMatrix.Scale(glm::vec3(1.0f, 0.5f, 0.5f));
+		glUniformMatrix4fv(modelToCameraMatrixUnif, 1, GL_FALSE, glm::value_ptr(currMatrix.Top()));
+		glDrawElements(GL_TRIANGLES, ARRAY_COUNT(indexData), GL_UNSIGNED_SHORT, 0);
+		currMatrix.Pop();
+	}
+
+	//Draw the left attachment.
+	{
+		currMatrix.Push();
+		currMatrix.Translate(glm::vec3(-fGimbalAttachOffset, 0.0f, 0.0f));
+		currMatrix.Scale(glm::vec3(1.0f, 0.5f, 0.5f));
+		glUniformMatrix4fv(modelToCameraMatrixUnif, 1, GL_FALSE, glm::value_ptr(currMatrix.Top()));
+		glDrawElements(GL_TRIANGLES, ARRAY_COUNT(indexData), GL_UNSIGNED_SHORT, 0);
+		currMatrix.Pop();
+	}
+}
+
+
+void DrawBaseGimbal(MatrixStack &currMatrix, float fSize, glm::vec4 baseColor)
+{
+	//A Gimbal can only be 4 units in size or more.
+	assert(fSize > 4.0f);
+
+	glUseProgram(theProgram);
+	//Set the base color for this object.
+	glUniform4fv(baseColorUnif, 1, glm::value_ptr(baseColor));
+	glBindVertexArray(vao);
+
+	float fGimbalSidesOffset = (fSize / 2.0f) - 1.5f;
+	float fGimbalSidesScale = fSize - 2.0f;
+
+	DrawGimbalSides(currMatrix, fGimbalSidesOffset, fGimbalSidesScale);
+	
+	float fGimbalAttachOffset = (fSize / 2.0f) - 0.5f;
+
+	DrawGimbalAttachments(currMatrix, fGimbalAttachOffset);
+
+	glBindVertexArray(0);
+	glUseProgram(0);
+}
+
+enum GimbalAxis
+{
+	GIMBAL_X_AXIS,
+	GIMBAL_Y_AXIS,
+	GIMBAL_Z_AXIS,
+};
+
+void DrawGimbal(MatrixStack &currMatrix, GimbalAxis eAxis, float fSize, glm::vec4 baseColor)
+{
+	currMatrix.Push();
+
+	switch(eAxis)
+	{
+	case GIMBAL_X_AXIS:
+		break;
+	case GIMBAL_Y_AXIS:
+		currMatrix.RotateZ(90.0f);
+		currMatrix.RotateX(90.0f);
+		break;
+	case GIMBAL_Z_AXIS:
+		currMatrix.RotateY(90.0f);
+		currMatrix.RotateX(90.0f);
+		break;
+	}
+
+	DrawBaseGimbal(currMatrix, fSize, baseColor);
+	currMatrix.Pop();
+}
+
+//Called after the window and OpenGL are initialized. Called exactly once, before the main loop.
+void init()
+{
+	InitializeProgram();
+	InitializeVAO();
+
+
+	glEnable(GL_CULL_FACE);
+	glCullFace(GL_BACK);
+	glFrontFace(GL_CW);
+
+	glEnable(GL_DEPTH_TEST);
+	glDepthMask(GL_TRUE);
+	glDepthFunc(GL_LEQUAL);
+	glDepthRange(0.0f, 1.0f);
+}
+
+struct GimbalAngles
+{
+	GimbalAngles()
+		: fAngleX(0.0f)
+		, fAngleY(0.0f)
+		, fAngleZ(0.0f)
+	{}
+
+	float fAngleX;
+	float fAngleY;
+	float fAngleZ;
+};
+
+GimbalAngles g_angles;
+
+//Called to update the display.
+//You should call glutSwapBuffers after all of your rendering to display what you rendered.
+//If you need continuous updates of the screen, call glutPostRedisplay() at the end of the function.
+void display()
+{
+	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+	glClearDepth(1.0f);
+	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+	MatrixStack currMatrix;
+	currMatrix.Translate(glm::vec3(0.0f, 0.0f, -60.0f));
+	currMatrix.RotateX(g_angles.fAngleX);
+	DrawGimbal(currMatrix, GIMBAL_X_AXIS, 30.0f, glm::vec4(0.4f, 0.4f, 1.0f, 1.0f));
+	currMatrix.RotateY(g_angles.fAngleY);
+	DrawGimbal(currMatrix, GIMBAL_Y_AXIS, 26.0f, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f));
+	currMatrix.RotateZ(g_angles.fAngleZ);
+	DrawGimbal(currMatrix, GIMBAL_Z_AXIS, 22.0f, glm::vec4(1.0f, 0.3f, 0.3f, 1.0f));
+
+	glutSwapBuffers();
+	glutPostRedisplay();
+}
+
+//Called whenever the window is resized. The new window size is given, in pixels.
+//This is an opportunity to call glViewport or glScissor to keep up with the change in size.
+void reshape (int w, int h)
+{
+	cameraToClipMatrix[0].x = fFrustumScale * (h / (float)w);
+	cameraToClipMatrix[1].y = fFrustumScale;
+
+	glUseProgram(theProgram);
+	glUniformMatrix4fv(cameraToClipMatrixUnif, 1, GL_FALSE, glm::value_ptr(cameraToClipMatrix));
+	glUseProgram(0);
+
+	glViewport(0, 0, (GLsizei) w, (GLsizei) h);
+}
+
+#define STANDARD_ANGLE_INCREMENT 11.25f
+#define SMALL_ANGLE_INCREMENT 9.0f
+
+//Called whenever a key on the keyboard was pressed.
+//The key is given by the ''key'' parameter, which is in ASCII.
+//It's often a good idea to have the escape key (ASCII value 27) call glutLeaveMainLoop() to 
+//exit the program.
+void keyboard(unsigned char key, int x, int y)
+{
+	switch (key)
+	{
+	case 27:
+		glutLeaveMainLoop();
+		break;
+	case 'w': g_angles.fAngleX += SMALL_ANGLE_INCREMENT; break;
+	case 's': g_angles.fAngleX -= SMALL_ANGLE_INCREMENT; break;
+
+	case 'a': g_angles.fAngleY += SMALL_ANGLE_INCREMENT; break;
+	case 'd': g_angles.fAngleY -= SMALL_ANGLE_INCREMENT; break;
+
+	case 'q': g_angles.fAngleZ += SMALL_ANGLE_INCREMENT; break;
+	case 'e': g_angles.fAngleZ -= SMALL_ANGLE_INCREMENT; break;
+	}
+}
+
+

Tut 08 Getting Oriented/data/ColorMultUniform.frag

+#version 330
+
+smooth in vec4 theColor;
+uniform vec4 baseColor;
+
+out vec4 outputColor;
+
+void main()
+{
+	outputColor = theColor * baseColor;
+}

Tut 08 Getting Oriented/data/PosColorLocalTransform.vert

+#version 330
+
+layout(location = 0) in vec4 position;
+layout(location = 1) in vec4 color;
+
+smooth out vec4 theColor;
+
+uniform mat4 cameraToClipMatrix;
+uniform mat4 modelToCameraMatrix;
+
+void main()
+{
+	vec4 cameraPos = modelToCameraMatrix * position;
+	gl_Position = cameraToClipMatrix * cameraPos;
+	theColor = color;
+}

Tut 08 Getting Oriented/premake4.lua

+
+dofile("../framework/framework.lua")
+
+SetupSolution("Tutorial6")
+
+dofile("tutorials.lua")

Tut 08 Getting Oriented/tutorials.lua

+
+SetupProject("Tut 08 Gimbal Lock", "GimbalLock.cpp",
+	"data/ColorMultUniform.frag", "data/PosColorLocalTransform.vert")
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.