view glxdragon.cpp @ 14:62be2036f604

Read mesh vertices/faces information from "<modelfilename>.info" instead of having the values hardcoded.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 29 Oct 2019 12:48:36 +0200
parents c1e8057cc4d0
children 2d2aadfa3df3
line wrap: on
line source

//
// Copyright (c) 2009, Thomas Trummer
// All rights reserved.
//
// Port to libSDL2 and cleanups by Matti Hämäläinen <ccr@tnsp.org>
// (C) Copyright 2019 Tecnic Software productions (TNSP)
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of the <organization> nor the
//       names of its contributors may be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY Thomas Trummer ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//

#include <SDL.h>
#include <SDL_opengl.h>
#include <GL/glu.h>
#include <GL/glext.h>

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <cstdio>

/* Default settings etc. constants
 */
#define SET_DEF_WIDTH   1280
#define SET_DEF_HEIGHT  960
#define SET_FRAMES      (180)


/* Structures
 */
struct Mesh
{
    int nvertices, nfaces;
    std::vector<float>    vertices;
    std::vector<unsigned> faces;

    GLuint id_prog, id_fs, id_vs;
};


/* Options
 */
bool   optUseShaders = false;
int    optWidth = SET_DEF_WIDTH,
       optHeight = SET_DEF_HEIGHT,
       optVSyncMode = 1;

std::string optModelPrefix = "dragon";


/* Globals
 */
SDL_Window *dmWindow = NULL;
SDL_GLContext dmGLContext = NULL;


bool dmInitSDLGL(const int width, const int height, const char *title)
{
    int ret;
    std::string msg;

    // Set GL attributes
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

    // Attempt to initialize libSDL
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_EVENTS) != 0)
    {
        printf("ERROR: Unable to initialize SDL: %s\n",
            SDL_GetError());
        return false;
    }

    // Attempt to create a window
    if ((dmWindow = SDL_CreateWindow(title,
            SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
            width, height,
            SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE)) == NULL)
    {
        printf("ERROR: Could not create SDL window: %s",
            SDL_GetError());
        return false;
    }

    if ((dmGLContext = SDL_GL_CreateContext(dmWindow)) == NULL)
    {
        printf("ERROR: Unable to create SDL OpenGL context: %s\n",
            SDL_GetError());
        return false;
    }

    // Check if we want to attempt to use vsync
    switch (optVSyncMode)
    {
        case 3:
            ret = SDL_GL_SetSwapInterval(-1);
            msg = "adaptive vsync";
            break;

        case 2:
            ret = SDL_GL_SetSwapInterval(1);
            msg = "synchronized (vsync)";
            break;

        case 1:
            ret = SDL_GL_SetSwapInterval(0);
            msg = "immediate (no vsync)";
            break;

        default:
            ret = -1;
            msg = "INVALID VSYNC MODE";
            break;
    }

    if (ret != 0)
    {
        printf("ERROR: Could not set vsync mode to %s.\n",
            msg.c_str());
        return false;
    }

    // Dump some information
    printf(
        "GL_VENDOR   : %s\n"
        "GL_RENDERER : %s\n"
        "GL_VERSION  : %s\n"
        "VSync mode  : %s\n",
        glGetString(GL_VENDOR),
        glGetString(GL_RENDERER),
        glGetString(GL_VERSION),
        msg.c_str());

    // Setup the window and view port
    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective(45.0f, GLfloat(width) / GLfloat(height), 0.1f, 1000.0f);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();


    // Enable back face culling
    glEnable(GL_CULL_FACE);

    // Enable smooth shading
    glShadeModel(GL_SMOOTH);

    // Enable the depth buffer
    glEnable(GL_DEPTH_TEST);

    // Setup depth buffer
    glClearDepth(1.0f);

    // Set the depth buffer function
    glDepthFunc(GL_LEQUAL);

    // Enable vertex and and normal arrays
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);

    // Set correct perspective correction
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

    return true;
}


void dmDrawModelVA(const Mesh &mesh)
{
    int maxIndices;

    glGetIntegerv(GL_MAX_ELEMENTS_INDICES, &maxIndices);

    glVertexPointer(3, GL_FLOAT, 24, &mesh.vertices[0]);
    glNormalPointer(GL_FLOAT, 24, &mesh.vertices[3]);

    for (int n = 0; n < mesh.nfaces; n += maxIndices)
    {
        const int count = std::min(maxIndices, int(mesh.nfaces - n));
        glDrawElements(GL_TRIANGLES, count * 3, GL_UNSIGNED_INT, &mesh.faces[n * 3]);
    }
}


void dmPaintGL(Mesh &mesh)
{
    glClear(GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();

    glOrtho(0.0, 1.0, 0.0, 1.0, -1, 1);

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();


    glDisable(GL_DEPTH_TEST);
    glDisable(GL_LIGHTING);

    // Draw the background gradient
    glBegin(GL_QUADS);
    {
        glColor3ub(0x3B, 0x3B, 0x75);
        glVertex2f(0.0f, 0.0f);
        glVertex2f(1.0f, 0.0f);

        glColor3ub(0x00, 0x00, 0x00);
        glVertex2f(1.0f, 1.0f);
        glVertex2f(0.0f, 1.0f);
    }
    glEnd();


    // Restore the 3D projection
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();

    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();

    glEnable(GL_DEPTH_TEST);

    // Render the model
    if (optUseShaders)
    {
        // Enable shader program
        glUseProgram(mesh.id_prog);
        dmDrawModelVA(mesh);
        glUseProgram(0);
    }
    else
    {
        // Set the color of the model
        glEnable(GL_LIGHTING);
        glColor3ub(0x90, 0x80, 0x90);
        dmDrawModelVA(mesh);
    }
}


bool dmReadText(const std::string &filename, std::string &tstr)
{
    std::ifstream in(filename.c_str());

    if (!in.is_open())
    {
        printf("ERROR: Unable to open file '%s'.\n",
            filename.c_str());
        return false;
    }

    in.seekg(0, std::ios::end);
    tstr.reserve(in.tellg());
    in.seekg(0, std::ios::beg);

    tstr.assign((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());

    return true;
}


bool dmLoadMesh(const std::string &filename, const std::string &infofilename, Mesh &mesh)
{
    std::ifstream info(infofilename.c_str());
    int required = 0, nline = 0;

    printf("INFO: Trying to read mesh meta from '%s'.\n",
        infofilename.c_str());

    if (!info.is_open())
    {
        printf("ERROR: Unable to open file '%s'.\n",
            infofilename.c_str());
        return false;
    }

    while (required != 0x03)
    {
        std::string tmp;
        int value;
        char key;

        // Read one line
        if (!std::getline(info, tmp))
        {
            printf("ERROR: Could not read from file '%s'.\n",
                infofilename.c_str());
            return false;
        }

        nline++;

        // Skip empty lines and comments
        if (tmp.empty() || tmp.substr(0, 1) == "#")
            continue;

        if (sscanf(tmp.c_str(), "%c:%d", &key, &value) != 2)
        {
            printf("ERROR: Syntax error in '%s' line #%d\n'%s'\n",
                infofilename.c_str(),
                nline,
                tmp.c_str());
            return false;
        }

        switch (key)
        {
            case 'v':
                mesh.nvertices = value;
                required |= 0x01;
                break;

            case 'f':
                mesh.nfaces = value;
                required |= 0x02;
                break;

            default:
                printf("ERROR: Syntax error in '%s' line #%d\nUnknown key value '%c'.\n",
                    infofilename.c_str(),
                    nline,
                    key);
                break;
        }
    }

    if (mesh.nvertices < 3 || mesh.nfaces < 1)
    {
        printf("ERROR: Invalid nvertices (%d) and/or nfaces (%d) in '%s'.\n",
            mesh.nvertices, mesh.nfaces,
            infofilename.c_str());
        return false;
    }

    printf("INFO: %d vertices, %d faces\n", mesh.nvertices, mesh.nfaces);
    printf("INFO: Trying to read mesh data from '%s'.\n",
        filename.c_str());

    std::ifstream in(filename.c_str(), std::ios::binary);

    if (!in.is_open())
    {
        printf("ERROR: Unable to open file '%s'.\n",
            filename.c_str());
        return false;
    }

    mesh.vertices.resize(mesh.nvertices * 6);
    in.read(reinterpret_cast<char*>(&mesh.vertices[0]), mesh.nvertices * 6 * 4);

    mesh.faces.resize(mesh.nfaces * 3);

    for (int i = 0; i < mesh.nfaces; i++)
    {
        in.seekg(1, std::ios::cur);
        in.read(reinterpret_cast<char*>(&mesh.faces[i * 3]), 3 * 4);
    }

    return true;
}


GLuint dmCompileShader(const GLenum stype, const std::string &src)
{
    GLuint shader = glCreateShader(stype);
    const char *tmp = src.c_str();

    glShaderSource(shader, 1, &tmp, 0);
    glCompileShader(shader);

    return shader;
}


void dmLinkMeshShaders(Mesh &mesh)
{
    mesh.id_prog = glCreateProgram();
    glAttachShader(mesh.id_prog, mesh.id_fs);
    glAttachShader(mesh.id_prog, mesh.id_vs);
    glLinkProgram(mesh.id_prog);
}


int main(int argc, char *argv[])
{
    std::string modelVertStr, modelFragStr;
    struct Mesh modelMesh;
    bool exitFlag = false, optShowHelp = false;
    int startTime, cycleStart, cycleFrames = 0, totalFrames = 0;
    double totalTime;

    // Check commandline argument for enabling shaders
    for (int narg = 1; narg < argc; narg++)
    {
        char *arg = argv[narg];
        if (arg[0] == '-')
        {
            char *opt = arg + 1;

            if ((opt[0] == '-' && opt[1] == 'h' && opt[2] == 'e') ||
                opt[0] == '?' || (opt[0] == '-' && opt[1] == '?'))
            {
                optShowHelp = true;
                break;
            }
            else
            if (opt[0] == '-')
                opt++;

            if (opt[0] == 'g')
                optUseShaders = true;
            else
            switch (opt[0])
            {
                case 'w':
                case 'h':
                case 'm':
                case 'v':
                    if (opt[1] == 0)
                    {
                        printf("Option '%s' requires an argument.\n", opt);
                        goto exit;
                    }

                    switch (opt[0])
                    {
                        case 'w': optWidth = atoi(opt + 1); break;
                        case 'h': optHeight = atoi(opt + 1); break;
                        case 'm': optModelPrefix = std::string(opt + 1); break;
                        case 'v': optVSyncMode = atoi(opt + 1); break;
                    }
                    break;

                default:
                    printf("Unknown option '%s'.\n", opt);
                    goto exit;
            }
        }
    }

    if (optShowHelp)
    {
        printf(
            "Usage: %s [options]\n"
            "-?            Show this help\n"
            "-g            Use GLSL shader instead of basic OpenGL lighting\n"
            "-w<width>     Window width (default %d)\n"
            "-h<height>    Window height (default %d)\n"
            "-m<modelfile> Set model filenames prefix. Using \"-mfoo\" will\n"
            "              specify \"foo.mesh\", \"foo.frag\", \"foo.vert\".\n"
            "-v<1-3>       Set vsync mode: 1 = no vsync, 2 = vsync, 3 = adaptive\n"
            "              Default is no vsync.\n"
            "\n",
            argv[0],
            SET_DEF_WIDTH, SET_DEF_HEIGHT
            );

        goto exit;
    }

    if (optWidth < 100 || optWidth > 8192 || optHeight < 100 || optHeight > 8192)
    {
        printf("ERROR: Invalid window width or height (%d x %d).\n",
            optWidth, optHeight);
        goto exit;
    }

    if (optModelPrefix.empty())
    {
        printf("Model file prefix empty.\n");
        goto exit;
    }

    if (!dmLoadMesh(optModelPrefix + ".mesh", optModelPrefix + ".info", modelMesh))
        goto exit;

    if (optUseShaders)
    {
        // Read shader files
        if (!dmReadText(optModelPrefix + ".frag", modelFragStr) ||
            !dmReadText(optModelPrefix + ".vert", modelVertStr))
            goto exit;
    }

    // Initialize SDL + OpenGL
    if (!dmInitSDLGL(optWidth, optHeight, "GLXDragon2"))
        goto exit;

    // According to our mode ..
    if (optUseShaders)
    {
        modelMesh.id_fs = dmCompileShader(GL_FRAGMENT_SHADER, modelFragStr);
        modelMesh.id_vs = dmCompileShader(GL_VERTEX_SHADER, modelVertStr);
        dmLinkMeshShaders(modelMesh);
    }
    else
    {
        float specReflection[] = { 0.8f, 0.8f, 0.8f, 1.0f };

        glEnable(GL_COLOR_MATERIAL);

        glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
        glMateriali(GL_FRONT, GL_SHININESS, 96);

        glMaterialfv(GL_FRONT, GL_SPECULAR, specReflection);

        glEnable(GL_LIGHT0);

        // Define the light components and position
        GLfloat ambient[] = {    0.2f, 0.2f, 0.2f, 1.0f };
        GLfloat diffuse[] = {    0.8f, 0.8f, 0.8f, 1.0f };
        GLfloat specular[] = {    0.5f, 0.5f, 0.5f, 1.0f };
        GLfloat position[] = { 10.0f, 10.0f, 0.0f, 0.0f };

        // Define the light components and position
        glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
        glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
        glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
        glLightfv(GL_LIGHT0, GL_POSITION, position);
    }

    // Define the camera
    gluLookAt(0, 0.12, 0.24, 0, 0.12, 0, 0, 1, 0);


    // Main loop starts
    startTime = cycleStart = SDL_GetTicks();

    while (!exitFlag)
    {
        SDL_Event event;

        // Check for quit events
        while (SDL_PollEvent(&event))
        switch (event.type)
        {
            case SDL_QUIT:
                exitFlag = true;
                break;

            case SDL_KEYDOWN:
                switch (event.key.keysym.sym)
                {
                    case SDLK_ESCAPE:
                    case SDLK_q:
                        exitFlag = true;
                        break;
                }
        }

        // Render the next frame
        dmPaintGL(modelMesh);

        // Draw the current frame
        SDL_GL_SwapWindow(dmWindow);

        // Rotate for 2 degrees
        glRotatef(2.0f, 0, 1, 0);

        // Return true if a full rotation was done
        if (cycleFrames++ == SET_FRAMES)
        {
            // Reset cycleFrames
            totalFrames += cycleFrames;
            cycleFrames = 0;

            // Get the time it took to render a full turn
            int cycleEnd = SDL_GetTicks();
            double cycleTime = cycleEnd - cycleStart;

            // Restart the timer
            cycleStart = SDL_GetTicks();

            // Print the current frames per second
            printf("%.1lf ms for %d frames = %.1lf FPS\n",
                cycleTime, SET_FRAMES, (SET_FRAMES * 1000.0f) / cycleTime);
        }
    }

    // Show totals
    totalTime = SDL_GetTicks() - startTime;
    printf("%.1lf ms total for %d total frames = %.2lf FPS average\n",
        totalTime, totalFrames, (totalFrames * 1000.0f) / totalTime);

exit:
    if (dmGLContext != NULL)
        SDL_GL_DeleteContext(dmGLContext);

    if (dmWindow != NULL)
        SDL_DestroyWindow(dmWindow);

    SDL_Quit();

    return 0;
}