Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia / OpenGL

Geometric Primitives for OpenGL

5.00/5 (11 votes)
9 Mar 2021CPOL2 min read 9K  
Box, cylinder, cone and sphere construction for OpenGL with texture and light effects
Ready-to-use algorithms for the construction of box, cylinder, cone and sphere for OpenGL applications with preparation for texture and light effects.

Introduction

I want to write a simple X3DOM viewer, based on OpenGL, and searched the web for the construction of box, cylinder, cone and sphere. What I found were a lot of good forum posts, but nothing "ready-to-use". In this tip, I would like to share my findings with all those who are also looking for an introduction to exactly this topic. So please don't expect "rocket science" in the following, but just "ready-to-use" code.

There is another tip about "light source features and related material properties" that I also wrote in connection with the simple X3DOM viewer.

Background

The X3DOM specification supports the objects Box, Cylinder, Cone and Sphere, so I need an algorithm to construct these graphical primitives.

I am not yet very confident with OpenGL, so it is important to me that the algorithms are easy to understand and to follow. (One of my sources was the OpenGL Programming Guide.)

The results are geometrical primitives that support all types of OpenGL lighting (ambient, diffuse and specular) as well as texture. Unfortunately, the simultaneous use of all types of lighting and texture in OpenGL requires some tricks and extensions (EXT_secondary_color), but that would be material for the next tip. What is already possible with on-board means is shown in the following pictures:

Geometric Promitives With Full Lighting

Geometric Promitives With Simple Texture

The triangle with the spectral colours marks the position of the light source in each case.

For cone and sphere, you can see well the approximation of the curved surface by plain surface sections. The quality of the approximation can be adjusted via the step size of the angle that is used for the plain surface sections - for the pictures, I use 60 steps per full circle/360°.

Using the Code

Box

Let's start with the box, straightforward code - no fancy tricks:

C++
/// <summary>
/// Draws a box into the scene.
/// The default orientation (on identity GL_MODELVIEW matrix) is upright.
/// The default center position (on identity GL_MODELVIEW matrix) is [0.0, 0.0, 0.0].
/// </summary>
/// <param name="fWidthHalf">Specifies the half edge length in x direction.</param>
/// <param name="fWidthHalf">Specifies the half edge length in y direction.</param>
/// <param name="fDepthHalf">Specifies the half edge length in z direction.</param>
/// <param name="bTexture">Determine whether texture coordinates shall be calculated.</param>
/// <remarks>
///     +-----------+                y (height)
///    /   top     /|               /|\
///   /           / |                |
///  +-----------+  |                |      \
///  |           | right             +-------> x (width)
///  |   front   |  +               /       /
///  |           | /               /
///  |           |/              |/_
///  +-----------+               z (depth)
/// </remarks>
void OpenGL::DrawBox(float fWidthHalf, float fHeightHalf, float fDepthHalf, bool bTexture)
{
    // -- FRONT
    ::glBegin(GL_TRIANGLES);
    ::glNormal3f(0.0f, 0.0f, +1.0f);
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(0.0f, 0.0f);
    ::glVertex3f(-fWidthHalf, -fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, +fDepthHalf);
 
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 1.0f);
    ::glVertex3f(+fWidthHalf, +fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, +fDepthHalf);
    ::glEnd();

    // -- BACK
    ::glBegin(GL_TRIANGLES);
    ::glNormal3f(0.0f, 0.0f, -1.0f);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(0.0f, 0.0f);
    ::glVertex3f(-fWidthHalf, -fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, -fDepthHalf);
 
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 1.0f);
    ::glVertex3f(+fWidthHalf, +fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, -fDepthHalf);
    ::glEnd();

    // -- LEFT
    ::glBegin(GL_TRIANGLES);
    ::glNormal3f(-1.0f, 0.0f, 0.0f);
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, -fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(0.0f, 0.0f);
    ::glVertex3f(-fWidthHalf, -fHeightHalf, -fDepthHalf);
 
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, -fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, -fDepthHalf);
    ::glEnd();

    // -- RIGHT
    ::glBegin(GL_TRIANGLES);
    ::glNormal3f(+1.0f, 0.0f, 0.0f);
    if (bTexture) ::glTexCoord2f(0.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, +fHeightHalf, -fDepthHalf);
 
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, +fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 1.0f);
    ::glVertex3f(+fWidthHalf, +fHeightHalf, +fDepthHalf);
    ::glEnd();

    // -- TOP
    ::glBegin(GL_TRIANGLES);
    ::glNormal3f(0.0f, +1.0f, 0.0f);
    if (bTexture) ::glTexCoord2f(0.0f, 0.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, +fHeightHalf, -fDepthHalf);
 
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, +fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 1.0f);
    ::glVertex3f(+fWidthHalf, +fHeightHalf, +fDepthHalf);
    ::glEnd();

    // BOTTOM
    ::glBegin(GL_TRIANGLES);
    ::glNormal3f(0.0f, -1.0f, 0.0f);
    if (bTexture) ::glTexCoord2f(0.0f, 0.0f);
    ::glVertex3f(-fWidthHalf, -fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, -fHeightHalf, +fDepthHalf);
 
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, -fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 1.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, +fDepthHalf);
    ::glEnd();
}

Cylinder

/// <summary>
/// Draws a cylinder into the scene.
/// The default orientation (on identity GL_MODELVIEW matrix) is upright.
/// The default center position (on identity GL_MODELVIEW matrix) is [0.0, 0.0, 0.0].
/// </summary>
/// <param name="fWidthHalf">Specifies the radius in x direction.</param>
/// <param name="fWidthHalf">Specifies the half height in y direction.</param>
/// <param name="fDepthHalf">Specifies the radius in z direction.</param>
/// <param name="dAngleStep">Specifies the angle in radiants, plain surface sections
///                          approximate a curved surface.</param>
/// <param name="pColors">Exactly three colors for top, cover and bottom or <c>NULL</c>
///                       if no individual colors are to apply.</param>
/// <param name="bTexture">Determine  whether texture coordinates shall be calculated.</param>
/// <remarks>
///    _------_                 y (height)
///   /        \   top         /|\
///  |\_      _/|               |
///  |  ------  |               |      \
///  |          |  cover        +-------> x (width)
///  |          |              /       /
///  |          |             /
///   \_      _/   bottom   |/_
///     ------              z (depth)
/// </remarks>
void OpenGL::DrawCylinder(float fWidthHalf, float fHeightHalf, float fDepthHalf,
                          double dAngleStep, const COLORREF* pColors = NULL, bool bTexture)
{
    float fReciprocalPrecisition = (float)(dAngleStep / M_2PI);
 
    // Cylinder top
    if (pColors != NULL)
        ::glColor3f(MIN(1.0F, GetRValue(pColors[0]) / 255.0F),
                    MIN(1.0F, GetGValue(pColors[0]) / 255.0F),
                    MIN(1.0F, GetBValue(pColors[0]) / 255.0F));
    int iSectorCount = 0;
    ::glBegin(GL_POLYGON); // ? TRIANGLE_FAN ?
    for (double dTopAngle = M_2PI; dTopAngle > 0.0; dTopAngle -= dAngleStep)
    {
        // face normal
        ::glNormal3f(0.0F, +1.0F0.0F);
 
        double c = cos(dTopAngle);
        double s = sin(dTopAngle);
 
        // relative texture coordinates
        float fTextureOffsetS = 0.5F + (float)(0.5F * c);
        float fTextureOffsetT = 0.5F + (float)(0.5F * s);
 
        if (bTexture) ::glTexCoord2f(fTextureOffsetS, fTextureOffsetT);
        ::glVertex3f((float)(fWidthHalf * cos(dTopAngle)),
                     fHeightHalf,
                     (float)(fDepthHalf * sin(dTopAngle)));
    }
    ::glEnd();
 
    // Cylinder cover
    if (pColors != NULL)
        ::glColor3f(MIN(1.0F, GetRValue(pColors[1]) / 255.0F),
                    MIN(1.0F, GetGValue(pColors[1]) / 255.0F),
                    MIN(1.0F, GetBValue(pColors[1]) / 255.0F));
    iSectorCount = 0;
    ::glBegin(GL_TRIANGLES);
    for (double dCoverAngle1 = 0; dCoverAngle1 < M_2PI; dCoverAngle1 += dAngleStep)
    {
        double dCoverAngle2 = dCoverAngle1 + dAngleStep;
        float  fWidthPart1 = (float)(fWidthHalf * cos(dCoverAngle1));
        float  fWidthPart2 = (float)(fWidthHalf * cos(dCoverAngle2));
        float  fDepthPart1 = (float)(fDepthHalf * sin(dCoverAngle1));
        float  fDepthPart2 = (float)(fDepthHalf * sin(dCoverAngle2));
 
        // face normal
        ::glNormal3f((float)(1.0F * cos(dCoverAngle1 + dAngleStep / 2)),
                     0.0F,
                     (float)(1.0F * sin(dCoverAngle1 + dAngleStep / 2)));
 
        // relative texture coordinates
        float fTextureOffsetS1 = (float)(iSectorCount    ) * fReciprocalPrecisition;
        float fTextureOffsetT1 = 0.0F;
        float fTextureOffsetS2 = (float)(iSectorCount + 1) * fReciprocalPrecisition;
        float fTextureOffsetT2 = 1.0F;
 
        if (bTexture) ::glTexCoord2f(fTextureOffsetS1, fTextureOffsetT1);
        ::glVertex3f(fWidthPart1, -fHeightHalf, fDepthPart1);
        if (bTexture) ::glTexCoord2f(fTextureOffsetS1, fTextureOffsetT2);
        ::glVertex3f(fWidthPart1, fHeightHalf, fDepthPart1);
        if (bTexture) ::glTexCoord2f(fTextureOffsetS2, fTextureOffsetT2);
        ::glVertex3f(fWidthPart2, fHeightHalf, fDepthPart2);
 
        if (bTexture) ::glTexCoord2f(fTextureOffsetS2, fTextureOffsetT2);
        ::glVertex3f(fWidthPart2, fHeightHalf, fDepthPart2);
        if (bTexture) ::glTexCoord2f(fTextureOffsetS2, fTextureOffsetT1);
        ::glVertex3f(fWidthPart2, -fHeightHalf, fDepthPart2);
        if (bTexture) ::glTexCoord2f(fTextureOffsetS1, fTextureOffsetT1);
        ::glVertex3f(fWidthPart1, -fHeightHalf, fDepthPart1);
        iSectorCount++;
    }
    ::glEnd();
 
    // Cylinder bottom
    if (pColors != NULL)
        ::glColor3f(MIN(1.0F, GetRValue(pColors[2]) / 255.0F),
                    MIN(1.0F, GetGValue(pColors[2]) / 255.0F),
                    MIN(1.0F, GetBValue(pColors[2]) / 255.0F));
    iSectorCount = 0;
    ::glBegin(GL_POLYGON); // ? TRIANGLE_FAN ?
    for (double dBottotmAngle = 0; dBottotmAngle < M_2PI; dBottotmAngle += dAngleStep)
    {
        // face normal
        ::glNormal3f(0.0F, -1.0F0.0F);
 
        double c = cos(dBottotmAngle);
        double s = sin(dBottotmAngle);
 
        // relative texture coordinates
        float fTextureOffsetS = 0.5F + (float)(0.5F * c);
        float fTextureOffsetT = 0.5F + (float)(0.5F * s);
 
        if (bTexture) ::glTexCoord2f(fTextureOffsetS, fTextureOffsetT);
        ::glVertex3f((float)(fWidthHalf * cos(dBottotmAngle)),
                      -fHeightHalf,
                      (float)(fDepthHalf * sin(dBottotmAngle)));
    }
    ::glEnd();
}

Cone

C++
/// <summary>
/// Draws a cone into the scene.
/// The default orientation (on identity GL_MODELVIEW matrix) is upright.
/// The default center position (on identity GL_MODELVIEW matrix) is [0.0, 0.0, 0.0].
/// </summary>
/// <param name="fWidthHalf">Specifies the radius in x direction.</param>
/// <param name="fWidthHalf">Specifies the half height in y direction.</param>
/// <param name="fDepthHalf">Specifies the radius in z direction.</param>
/// <param name="dAngleStep">Specifies the angle in radiants, plain surface sections
                             approximate a curved surface.</param>
/// <param name="pColors">Exactly two colors for cover and bottom or <c>NULL</c>
///                       if no individual colors are to apply.</param>
/// <param name="bTexture">Determine  whether texture coordinates shall be calculated.
/// </param>
/// <remarks>
///       /\                    y (height)
///      |  |      top         /|\
///     /    \                  |
///    |      |                 |      \
///   /_------_\   cover        +-------> x (width)
///  |/        \|              /       /
///  |          |             /
///   \_      _/   bottom   |/_
///     ------              z (depth)
/// </remarks>
void DrawCone(float fWidthHalf, float fHeightHalf, float fDepthHalf,
              double dAngleStep, const COLORREF* pColors = NULL, bool bTexture = false);
{
    float fReciprocalPrecisition = (float)(dAngleStep / M_2PI);
 
    // Cone "Cover"
    if (pColors != NULL)
        ::glColor3f(MIN(1.0F, GetRValue(pColors[0]) / 255.0F),
                    MIN(1.0F, GetGValue(pColors[0]) / 255.0F),
                    MIN(1.0F, GetBValue(pColors[0]) / 255.0F));
    int iSectorCount = 0;
    ::glBegin(GL_TRIANGLES);
    for (double dCoverAngle1 = 0.0F; dCoverAngle1 < M_2PI; dCoverAngle1 += dAngleStep)
    {
        double dCoverAngle2 = dCoverAngle1 + dAngleStep;
        float  fWidthPart1 = (float)(fWidthHalf * cos(dCoverAngle1));
        float  fWidthPart2 = (float)(fWidthHalf * cos(dCoverAngle2));
        float  fDepthPart1 = (float)(fDepthHalf * sin(dCoverAngle1));
        float  fDepthPart2 = (float)(fDepthHalf * sin(dCoverAngle2));
 
        // face normal
        ::glNormal3f((fWidthPart1 + fWidthPart2) / 20.0F, (fDepthPart1 + fDepthPart2) / 2);
 
        // relative texture coordinates
        float fTextureOffsetS1 = iSectorCount       * fReciprocalPrecisition;
        float fTextureOffsetS2 = (iSectorCount + 1) * fReciprocalPrecisition;
 
        if (bTexture) ::glTexCoord2f(fTextureOffsetS1, 1.0);
        ::glVertex3f(0.0F, fHeightHalf, 0.0F);
        if (bTexture) ::glTexCoord2f(fTextureOffsetS2, 0.0);
        ::glVertex3f(fWidthPart2, -fHeightHalf, fDepthPart2);
        if (bTexture) ::glTexCoord2f(fTextureOffsetS2, 0.0);
        ::glVertex3f(fWidthPart1, -fHeightHalf, fDepthPart1);
 
        iSectorCount++;
    }
    ::glEnd();
 
    // Cone Bottom
    if (pColors != NULL)
        ::glColor3f(MIN(1.0F, GetRValue(pColors[1]) / 255.0F),
                    MIN(1.0F, GetGValue(pColors[1]) / 255.0F),
                    MIN(1.0F, GetBValue(pColors[1]) / 255.0F));
    iSectorCount = 0;
    ::glBegin(GL_POLYGON); // ? TRIANGLE_FAN ?
    for (double dBottomAngle = 0; dBottomAngle < M_2PI; dBottomAngle += dAngleStep)
    {
        // face normal
        ::glNormal3f(0.0F, -1.0F0.0F);
 
        double c = cos(dBottomAngle);
        double s = sin(dBottomAngle);
 
        // relative texture coordinates
        float fTextureOffsetS = 0.5F + (float)(0.5F * c);
        float fTextureOffsetT = 0.5F + (float)(0.5F * s);
 
        if (bTexture) ::glTexCoord2f(fTextureOffsetS, fTextureOffsetT);
       ::glVertex3f((float)(fWidthHalf * c), -fHeightHalf, (float)(fDepthHalf * s));
    }
    ::glEnd();
}

Sphere

C++
/// <summary>
/// Draws a sphere into the scene.
/// The default orientation (on identity GL_MODELVIEW matrix) is upright.
/// The default center position (on identity GL_MODELVIEW matrix) is [0.0, 0.0, 0.0].
/// </summary>
/// <param name="fWidthHalf">Specifies the radius in x direction.</param>
/// <param name="fWidthHalf">Specifies the radius in y direction.</param>
/// <param name="fDepthHalf">Specifies the radius in z direction.</param>
/// <param name="dAngleStep">Specifies the angle in radiants, plain surface sections
                             approximate a curved surface.</param>
/// <param name="bTexture">Determine  whether texture coordinates shall be calculated.</param>
/// <remarks>
///                stack  -----sector----->    y (height)
///     _-------_    |        _-------_       /|\
///    /         \   |       /  / | \  \       |
///   /___     ___\  |      /  /  |  \  \      |      \
///  |    -----    | |     |  |   |   |  |     +-------> x (width)
///  |___       ___| |     |  |   |   |  |    /       /
///   \  -------  /  |      \  \  |  /  /    /
///    \_       _/   |       \_ \ | / _/   |/_
///      -------    \|/        -------     z (depth)
/// </remarks>
void DrawSphere(float fWidthHalf, float fHeightHalf, float fDepthHalf,
                double dAngleStep, bool bTexture = false)
{
    float f1_10thPi = (float)(M_PI * 0.1f);
    float f2_10thPi = (float)(M_PI * 0.2f);
    float f3_10thPi = (float)(M_PI * 0.3f);
    float f7_10thPi = (float)(M_PI * 0.7f);
    float f8_10thPi = (float)(M_PI * 0.8f);
    float f9_10thPi = (float)(M_PI * 0.9f);
 
    float fPiQuarter = (float)(M_PI * 0.25f);
    float fPiHalf = (float)(M_PI * 0.50f);
 
    float fReciprocalPrecisition = dAngleStep / M_2PI;
    int   iStackCount = 0;
    ::glBegin(GL_TRIANGLES);
    // Plane: X/Y; Rotation axis: Z; 0° in positive X direction
    for (double dStackAngle1 = 0.0f; dStackAngle1 < M_PI; dStackAngle1 += dAngleStep)
    {
        double dStackAngle2 = dStackAngle1 + dAngleStep;
        double s1 = sin(dStackAngle1);
        double s2 = sin(dStackAngle2);
        float fStackHeightHalf1 = (float)(-fHeightHalf * cos(dStackAngle1));
        float fStackHeightHalf2 = (float)(-fHeightHalf * cos(dStackAngle2));
        float fStackWidthHalfTop = (float)(fWidthHalf  * s1);
        float fStackWidthHalfBtm = (float)(fWidthHalf  * s2);
        float fStackDepthHalfTop = (float)(fDepthHalf  * s1);
        float fStackDepthHalfBtm = (float)(fDepthHalf  * s2);
        int   iSectorCount = 0;
 
        // Plane: X/Z; Rotation axis: Y; 0° in positive X direction
        for (double dSectorAngle1 = 0.0f; dSectorAngle1 < M_2PI; dSectorAngle1 += dAngleStep)
        {
            double dSectorAngle2 = dSectorAngle1 + dAngleStep;
 
            // face corners
            float fLeftTop[3] = { (float)(fStackWidthHalfTop * cos(dSectorAngle1)),
                                  fStackHeightHalf1,
                                  (float)(fStackDepthHalfTop * sin(dSectorAngle1)) };
            float fRghtTop[3] = { (float)(fStackWidthHalfTop * cos(dSectorAngle2)),
                                  fStackHeightHalf1,
                                  (float)(fStackDepthHalfTop * sin(dSectorAngle2)) };
            float fLeftBtm[3] = { (float)(fStackWidthHalfBtm * cos(dSectorAngle1)),
                                  fStackHeightHalf2,
                                  (float)(fStackDepthHalfBtm * sin(dSectorAngle1)) };
            float fRghtBtm[3] = { (float)(fStackWidthHalfBtm * cos(dSectorAngle2)),
                                  fStackHeightHalf2,
                                  (float)(fStackDepthHalfBtm * sin(dSectorAngle2)) };
 
            // face normal
            float n[3] = { (fLeftTop[0] + fRghtBtm[0]) / 2,
                           (fStackHeightHalf1 + fStackHeightHalf2) / 2,
                           (fLeftTop[2] + fRghtBtm[2]) / 2 };
 
            // relative texture coordinates
            float fTextureOffsetS1 = iSectorCount           * fReciprocalPrecisition;
            float fTextureOffsetT1 = 2 * iStackCount        * fReciprocalPrecisition;
            float fTextureOffsetS2 = (iSectorCount + 1)     * fReciprocalPrecisition;
            float fTextureOffsetT2 = 2 * (iStackCount  + 1) * fReciprocalPrecisition;
 
 
            if (fLeftBtm[0] != fRghtBtm[0] || fLeftBtm[2] != fRghtBtm[2])
            {
                // face normal
                ::glNormal3f(n[0], n[1], n[2]);
 
                if (bTexture) ::glTexCoord2f(fTextureOffsetS1, fTextureOffsetT1);
                ::glVertex3fv(fLeftTop);
                if (bTexture) ::glTexCoord2f(fTextureOffsetS1, fTextureOffsetT2);
                ::glVertex3fv(fLeftBtm);
                if (bTexture) ::glTexCoord2f(fTextureOffsetS2, fTextureOffsetT2);
                ::glVertex3fv(fRghtBtm);
            }
            if (fLeftTop[0] != fRghtTop[0] || fLeftTop[2] != fRghtTop[2])
            {
                // face normal
                ::glNormal3f(n[0], n[1], n[2]);
 
                if (bTexture) ::glTexCoord2f(fTextureOffsetS1, fTextureOffsetT1);
                ::glVertex3fv(fLeftTop);
                if (bTexture) ::glTexCoord2f(fTextureOffsetS2, fTextureOffsetT2);
                ::glVertex3fv(fRghtBtm);
                if (bTexture) ::glTexCoord2f(fTextureOffsetS2, fTextureOffsetT1);
                ::glVertex3fv(fRghtTop);
            }
 
            iSectorCount++;
        }
        iStackCount++;
    }
    ::glEnd();
}

Environment

The calling environment for Box, Cylinder, Cone and Sphere are like two peas in a pod - that's why I only present the calling environment of Box here:

C++
void Box::RenderToOpenGL(X3DNode* pParentNode, HDC dcOpenGlWindow)
{
	Vector3f size = GetSize();
	GLuint   uiTextureID = 0;
 
	X3DShapeNode* pParentShape = dynamic_cast<X3DShapeNode*>(pParentNode);
	if (pParentShape != NULL)
	{
		X3DAppearanceNode* pAppearence = pParentShape->GetAppearance();
		if ((Appearance*)pAppearence != NULL)
		{
			((Appearance*)pAppearence)->ApplyMaterial(dcOpenGlWindow);
			uiTextureID = ((Appearance*)pAppearence)->ApplyTexture(dcOpenGlWindow);
		}
	} 
 
	if (uiTextureID != 0)
	{
		::glBindTexture(GL_TEXTURE_2D, uiTextureID);
		::glEnable(GL_TEXTURE_2D);
	}
 
	OpenGL::DrawBox(size.x * 0.5f, size.y * 0.5f, size.z * 0.5f, (uiTextureID != 0));
	
	if (uiTextureID != 0)
	{
		::glDisable(GL_TEXTURE_2D);
		::glBindTexture(GL_TEXTURE_2D, 0);
	}
}

Since my application is to be an X3DOM viewer, I also use the class hierarchy of X3DOM. According to this, Material and Texture (grouped by Appearance) together with the Box/Cylinder/Cone/Sphere are children of a Shape.

Material and Texture

The following code is behind ApplyMaterial(...) and ApplyTexture(...) (currently only a very small part of the X3DOM specification is implemented):

C++
/// <summary>Applies the <c>X3DMaterialNode</c> to the subsequent scene objects.</summary>
/// <param name="dcOpenGlWindow">The device context of the OpenGL window.</param>
void Appearance::ApplyMaterial(HDC dcOpenGlWindow)
{
	X3DMaterialNode* pMaterial = GetMaterial();
	if (pMaterial != NULL)
	{
		SFColor oColor = pMaterial->GetDiffuseColor();
		::glColor3f(oColor.r, oColor.g, oColor.b);
	}
	else
	{
		SFColor oColor = CSSColors::Colors[CSSColors::IndexOf(L"Gray")].Value;
		::glColor3f(oColor.r, oColor.g, oColor.b);
	}
}

/// <summary>Applies the <c>X3DTextureNode</c> to the subsequent scene objects.</summary>
/// <param name="dcOpenGlWindow">The device context of the OpenGL window.</param>
/// <returns>The applied texture (name/id) on success, or <c>0</c> otherwise.</returns>
GLuint Appearance::ApplyTexture(HDC dcOpenGlWindow)
{
	GLuint           uiTextureID = 0;
	X3DTextureNode*  pTexture    = GetTexture();
 
	if (pTexture != NULL)
	{
		String strUri = pTexture->GetUri();
		if (!String::IsNullOrEmpty(strUri))
		{
			uiTextureID = OpenGL::GetTexture(strUri.Value());
			if (uiTextureID == 0)
			{
				uiTextureID = OpenGL::AddTexture(strUri.Value(), dcOpenGlWindow);
			}
		}
	}
 
	return uiTextureID;
}

Just for the sake of completeness, here is the code of AddTexture(...) (currently only 24bpp bitmaps are supported, as they can be created with MS Paint, for example):

C++
/// <summary>
/// Register a new texture (name/id) based on the indicated image URI.
/// </summary>
/// <param name="wszFilename">The file name of the bitmap file to load.</param>
/// <param name="dcOpenGlWindow">The device context of the OpenGL window.</param>
/// <returns>The texture (name/id) on success, or <c>0</c> otherwise.</returns>
GLuint OpenGL::AddTexture(const WCHAR* wszFilename, HDC dcOpenGlWindow)
{
    GLuint  uiTextureID = 0;
 
    ::glGenTextures(1, &uiTextureID);
 
    if (uiTextureID == 0)
    {
        Console::WriteError(
            L"OpenGL::AddTexture() Failed to acquire a new texture (name/id)!\n");
        return uiTextureID;
    }
 
    int   iWidth = 0;
    int   iHeight = 0;
    BYTE* pbyBitmapPixel = NULL;
 
    if (OpenGL::LoadTextureImageFile(wszFilename, dcOpenGlWindow, iWidth, iHeight,
                                     &pbyBitmapPixel))
    {
        ::glEnable(GL_TEXTURE_2D);
        ::glBindTexture(GL_TEXTURE_2D, uiTextureID);
 
        // set the texture wrapping
        ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
 
        // scale linearly when image bigger/smaller than texture
        ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 
        ::glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
        ::glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
        ::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
 
        ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, iWidth, iHeight, 0,
                       GL_BGR_EXT, GL_UNSIGNED_BYTE, pbyBitmapPixel);
        __GLEXTFP_GLGENERATEMIPMAPS __glextGenerateMipmaps =
            (__GLEXTFP_GLGENERATEMIPMAPS)__GLEXT_GetProcAddress("glGenerateMipmaps");
        if (__glextGenerateMipmaps != NULL)
            __glextGenerateMipmaps(GL_TEXTURE_2D);
        else
            Console::WriteWarning(
                L" OpenGL::AddTexture() Extension 'glGenerateMipmaps' not available!\n");
 
        //::gluBuild2DMipmaps(GL_TEXTURE_2D, 3, iWidth, iHeight, GL_BGR_EXT,
        //                    GL_UNSIGNED_BYTE, pbyBitmapPixel);
 
        free(pbyBitmapPixel);
        pbyBitmapPixel = NULL;
 
        ::glBindTexture(GL_TEXTURE_2D, 0);
        ::glDisable(GL_TEXTURE_2D);
 
        _textures[wszFilename] = uiTextureID;
    }
    else
    {
        Console::WriteError(
            L"OpenGL::AddTexture() Failed to assign bitmap to new texture (name/id)!\n");
        ::glDeleteTextures(1, &uiTextureID);
        uiTextureID = 0;
    }
 
    return uiTextureID;
}

That's it. Have fun with OpenGL!

Points of Interest

Even though the mathematics behind the ::glBegin(GL_TRIANGLES) and ::glEnd() is clear - setting all coordinates correctly took a bit of trial & error. The same applies to ::glNormal3f(...) and ::glTexCoord2f(...).

History

  • 10th March, 2021: Initial tip
  • 17th April, 2021: Added the pColors argument to cylinder and cone

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)