Search code examples
c++classglfw

(2 questions) 'class' type redefinition (even with #pragma once), and static member object initialization inside a static function?


I've been working on my first opengl, glfw, and glm project (in cmake) while following tutorials online, and I wanted to start organizing my code into specific classes for performing certain tasks (like a model class for organizing shaders and verticies); however, once I organized my code, I ran into two problems.

First, after I finally got rid of all syntax errors in my code, I came across the compilation error 'class' type redefinition twice for two separate classes (one being a static class). I have #pragma once at the very top of my only header file (zachos.h), so I have no idea what the issue is here. Why does my code (see below) throw this error? The exact error messages as listed are:

zachos.cpp(94): error C2011: 'zachos::Model': 'class' type redefinition
zachos.h(53): note: see declaration of 'zachos::Model'
zachos.cpp(173): error C2011: 'zachos::Mainframe': 'class' type redefinition
zachos.h(84): note: see declaration of 'zachos::Mainframe'

Second, I needed to initialize an static member object from inside a static function of that class. I defined the object within my header file (zachos.h) using static Model mModel;, then redefined it inside of my code file (zachos.cpp), and finally I created the class inside the static function using a copy operator Model model(gVertexBufferData, gColorBufferData); mModel = model;. I have serious doubts about this working (since the copy operator only does a shallow copy), and I don't know how to use pointers to define mModel within the function (I believe I have to use delete in some manner). Is there a problem with this form of object initialization, and is there any better way to do it?

Code (all files are in the same directory and referenced by CMake, I can provide the CMakeLists.txt file too if needed):

zachos.h:

#pragma once

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <stdio.h>
#include <string.h>

#include <vector>

#include <sstream>
#include <iostream>
#include <fstream>

#include <cstdlib>
#include <ctime>

/*
Namespace that contains all basic codestuff for zachos.
*/
namespace zachos {
    // Constant data

    static const std::string DATA_DEF("#version 330 core\nlayout(location = 0) in vec3 vertexPosition_modelspace;\nlayout(location = 1) in vec3 vertexColor;\nout vec3 fragmentColor;\n\nuniform mat4 MVP;\n\nvoid main()\n{\n\tgl_Position =  MVP * vec4(vertexPosition_modelspace, 1);\n\t\n\tfragmentColor = vertexColor;\n}\n\n#version 330 core\nin vec3 fragmentColor;\nout vec3 color;\n\nvoid main()\n{\n\tcolor = fragmentColor;\n}");
    std::vector<GLfloat> gVertexBufferData = {
                -1.0f, -1.0f, -1.0f, // triangle 1 : begin
                -1.0f, -1.0f, 1.0f,
                -1.0f, 1.0f, 1.0f, // triangle 1 : end
                1.0f, 1.0f, -1.0f, // triangle 2 : begin
                -1.0f, -1.0f, -1.0f,
                -1.0f, 1.0f, -1.0f, // triangle 2 : end
                1.0f, -1.0f, 1.0f,
                -1.0f, -1.0f, -1.0f,
                1.0f, -1.0f, -1.0f,
                1.0f, 1.0f, -1.0f,
                1.0f, -1.0f, -1.0f,
                -1.0f, -1.0f, -1.0f,
                -1.0f, -1.0f, -1.0f,
                -1.0f, 1.0f, 1.0f,
                -1.0f, 1.0f, -1.0f,
                1.0f, -1.0f, 1.0f,
                -1.0f, -1.0f, 1.0f,
                -1.0f, -1.0f, -1.0f,
                -1.0f, 1.0f, 1.0f,
                -1.0f, -1.0f, 1.0f,
                1.0f, -1.0f, 1.0f,
                1.0f, 1.0f, 1.0f,
                1.0f, -1.0f, -1.0f,
                1.0f, 1.0f, -1.0f,
                1.0f, -1.0f, -1.0f,
                1.0f, 1.0f, 1.0f,
                1.0f, -1.0f, 1.0f,
                1.0f, 1.0f, 1.0f,
                1.0f, 1.0f, -1.0f,
                -1.0f, 1.0f, -1.0f,
                1.0f, 1.0f, 1.0f,
                -1.0f, 1.0f, -1.0f,
                -1.0f, 1.0f, 1.0f,
                1.0f, 1.0f, 1.0f,
                -1.0f, 1.0f, 1.0f,
                1.0f, -1.0f, 1.0f
    };
    std::vector<GLfloat> gColorBufferData(12 * 3 * 3, 0);

    /*
    Enum of all possible error codes for the program to return on its own
    */
    enum ErrorCodes {
        SUCCESS,
        GLFW_INIT_FAIL,
        GLFW_WINDOW_CREATION_FAIL,
        GLAD_INIT_FAIL,
        WINDOW_FAIL
    };

    /*
    Error callback function for glfw. Nothing special.
    */
    void errorCallback(int error, const char* description);

    /*
    Load shaders function for convenience. Used especially by the Model class.
    */
    GLuint loadShaders(std::string shaders);

    /*
    Class for creating models; it holds a VBO, CBO, and programID.
    */
    class Model {
    public:
        std::vector<GLfloat> mVertexBufferData;
        std::vector<GLfloat> mColorBufferData;

        GLuint VBO;
        GLuint CBO;

        GLuint programID;

        glm::mat4 model;

        /*
        Default Constructor. Must contain all vertex and color data from the get go.
        */
        Model(std::vector<GLfloat> vertexBufferData, std::vector<GLfloat> colorBufferData);

        /*
        Model update() function, called every "tick"
        */
        virtual void update();

        /*
        Model draw() function, called to draw the model
        */
        virtual void draw();
    };

    /*
    Wrapper class of the main window for handling events and perspective.
    */
    class Mainframe {
    public:
        static GLFWwindow* window;

        static glm::mat4 projection;
        static glm::mat4 view;

        static Model mModel;

        static int i;

        // Event functions

        static void framebufferSizeCallback(GLFWwindow* window, int width, int height);
        static void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);

        /*
        Class initialization function.
        */
        static int init();

        /*
        Frame by frame drawing function.
        */
        static int draw();

        /*
        Get whether the window should close or not
        */
        static bool shouldClose();
    };
}

zachos.cpp:

#include "zachos.h"

namespace zachos {

    void errorCallback(int error, const char* description) {
        fprintf(stderr, "GLFW error %d: %s\n", error, description);
    }

    GLuint loadShaders(std::string shaders) {

        // Create the shaders
        GLuint vertexShaderID = glCreateShader(GL_VERTEX_SHADER);
        GLuint fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

        // Read the shader code
        std::string shaderCode;
        std::string vertexShaderCode;
        std::string fragmentShaderCode;

        //std::ifstream shaderStream(file_path, std::ios::in);
        if (/*shaderStream.is_open()*/ true) {
            //std::stringstream sstr;
            //sstr << shaderStream.rdbuf();
            //shaderCode = sstr.str();
            shaderCode = shaders;
            //shaderStream.close();

            size_t val = shaderCode.find("#version", 8);
            vertexShaderCode = shaderCode.substr(0, val);
            fragmentShaderCode = shaderCode.substr(val);
        }
        else {
            //printf("Impossible to open \"%s\". Are you in the right directory? Don't forget to read the FAQ!\n", file_path);
            getchar();
            return 0;
        }

        GLint result = GL_FALSE;
        int infoLogLength;

        // Compile Vertex Shader
        char const * vertexSourcePointer = vertexShaderCode.c_str();
        glShaderSource(vertexShaderID, 1, &vertexSourcePointer, nullptr);
        glCompileShader(vertexShaderID);

        // Check Vertex Shader
        glGetShaderiv(vertexShaderID, GL_COMPILE_STATUS, &result);
        glGetShaderiv(vertexShaderID, GL_INFO_LOG_LENGTH, &infoLogLength);
        if (infoLogLength > 0) {
            std::vector<char> vertexShaderErrorMessage(infoLogLength + 1);
            glGetShaderInfoLog(vertexShaderID, infoLogLength, nullptr, &vertexShaderErrorMessage[0]);
            printf("%s\n", &vertexShaderErrorMessage[0]);
        }

        // Compile Fragment Shader
        char const * fragmentSourcePointer = fragmentShaderCode.c_str();
        glShaderSource(fragmentShaderID, 1, &fragmentSourcePointer, nullptr);
        glCompileShader(fragmentShaderID);

        // Check Fragment Shader
        glGetShaderiv(fragmentShaderID, GL_COMPILE_STATUS, &result);
        glGetShaderiv(fragmentShaderID, GL_INFO_LOG_LENGTH, &infoLogLength);
        if (infoLogLength > 0) {
            std::vector<char> fragmentShaderErrorMessage(infoLogLength + 1);
            glGetShaderInfoLog(fragmentShaderID, infoLogLength, nullptr, &fragmentShaderErrorMessage[0]);
            printf("%s\n", &fragmentShaderErrorMessage[0]);
        }

        // Link the program
        printf("Linking program\n");
        GLuint programID = glCreateProgram();
        glAttachShader(programID, vertexShaderID);
        glAttachShader(programID, fragmentShaderID);
        glLinkProgram(programID);

        // Check the program
        glGetProgramiv(programID, GL_LINK_STATUS, &result);
        glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &infoLogLength);
        if (infoLogLength > 0) {
            std::vector<char> programErrorMessage(infoLogLength + 1);
            glGetProgramInfoLog(programID, infoLogLength, nullptr, &programErrorMessage[0]);
            printf("%s\n", &programErrorMessage[0]);
        }

        glDetachShader(programID, vertexShaderID);
        glDetachShader(programID, fragmentShaderID);

        glDeleteShader(vertexShaderID);
        glDeleteShader(fragmentShaderID);

        return programID;
    }

    class Model {
    public:

        std::vector<GLfloat> mVertexBufferData;
        std::vector<GLfloat> mColorBufferData;

        GLuint VBO;
        GLuint CBO;

        GLuint programID;

        glm::mat4 model = glm::mat4(1.0f);

        Model(std::vector<GLfloat> vertexBufferData, std::vector<GLfloat> colorBufferData) {

            mVertexBufferData = vertexBufferData;
            mColorBufferData = colorBufferData;

            // Generate 1 buffer, put the resulting identifier in vertexbuffer
            glGenBuffers(1, &VBO);
            // The following commands will talk about our 'vertexbuffer' buffer
            glBindBuffer(GL_ARRAY_BUFFER, VBO);
            // Give our vertices to OpenGL.
            glBufferData(GL_ARRAY_BUFFER, sizeof(mVertexBufferData), &mVertexBufferData.front(), GL_STATIC_DRAW);

            glGenBuffers(1, &CBO);
            glBindBuffer(GL_ARRAY_BUFFER, CBO);
            glBufferData(GL_ARRAY_BUFFER, sizeof(mColorBufferData), &mColorBufferData.front(), GL_STATIC_DRAW);

            // Create and compile our GLSL program from the shaders
            GLuint programID = loadShaders(zachos::DATA_DEF);
            glUseProgram(programID);
        }

        virtual void update() {
            for (int v = 0; v < 12 * 3; v++) {
                mColorBufferData[3 * v + 0] = (float)std::rand() / RAND_MAX;
                mColorBufferData[3 * v + 1] = (float)std::rand() / RAND_MAX;
                mColorBufferData[3 * v + 2] = (float)std::rand() / RAND_MAX;
            }
            glBufferData(GL_ARRAY_BUFFER, sizeof(mColorBufferData), &mColorBufferData.front(), GL_STATIC_DRAW);
        }

        virtual void draw() {
            glEnableVertexAttribArray(0);
            glBindBuffer(GL_ARRAY_BUFFER, VBO);
            glVertexAttribPointer(
                0,                  // attribute 0. No particular reason for 0, but must match the layout in the shader.
                3,                  // size
                GL_FLOAT,           // type
                GL_FALSE,           // normalized?
                0,                  // stride
                (void*)0            // array buffer offset
            );
            glEnableVertexAttribArray(1);
            glBindBuffer(GL_ARRAY_BUFFER, CBO);
            glVertexAttribPointer(
                1,                                // attribute. No particular reason for 1, but must match the layout in the shader.
                3,                                // size
                GL_FLOAT,                         // type
                GL_FALSE,                         // normalized?
                0,                                // stride
                (void*)0                          // array buffer offset
            );

            // Setup some 3D stuff
            glm::mat4 mvp = Mainframe::projection * Mainframe::view * model;

            GLuint MatrixID = glGetUniformLocation(programID, "MVP");
            glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &mvp[0][0]);

            // Draw the array
            glDrawArrays(GL_TRIANGLES, 0, mVertexBufferData.size() / 3);

            glDisableVertexAttribArray(0);
            glDisableVertexAttribArray(1);
        }
    };

    class Mainframe {
    public:
        static GLFWwindow* window;

        static glm::mat4 projection;
        static glm::mat4 view;

        static Model mModel;

        static int i;

        static void framebufferSizeCallback(GLFWwindow* window, int width, int height) {
            glViewport(0, 0, width, height);
            glm::perspective(glm::radians(45.0f), (float)width / (float)height, 0.1f, 100.0f);
        }

        static void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
            switch (key) {
            case GLFW_KEY_ESCAPE:

                break;
            }
        }

        static int init() {
            // Initialize GLFW
            int glfwInitRes = glfwInit();
            if (!glfwInitRes) {
                fprintf(stderr, "Unable to initialize GLFW\n");
                return GLFW_INIT_FAIL;
            }

            // Set GLFW flags. Not exactly sure if these are needed.
            glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
            glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
            glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
            glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

            // Attempt to create the main window.
            window = glfwCreateWindow(1280, 720, "InitGL", nullptr, nullptr);
            if (!window) {
                fprintf(stderr, "Unable to create GLFW window\n");
                glfwTerminate();
                return GLFW_WINDOW_CREATION_FAIL;
            }
            glfwMakeContextCurrent(window);

            // Initialize GLAD and OpenGL
            int gladInitRes = gladLoadGL();
            if (!gladInitRes) {
                fprintf(stderr, "Unable to initialize glad\n");
                glfwTerminate();
                return GLAD_INIT_FAIL;
            }

            glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

            glfwSetFramebufferSizeCallback(window, framebufferSizeCallback);
            glfwSetKeyCallback(window, keyCallback);

            // Create da CUBANGLÉ
            GLuint VertexArrayID;
            glGenVertexArrays(1, &VertexArrayID);
            glBindVertexArray(VertexArrayID);

            for (int v = 0; v < 12 * 3; v++) {
                gColorBufferData[3 * v + 0] = (float)std::rand() / RAND_MAX;
                gColorBufferData[3 * v + 1] = (float)std::rand() / RAND_MAX;
                gColorBufferData[3 * v + 2] = (float)std::rand() / RAND_MAX;
            }
            Model model(gVertexBufferData, gColorBufferData);
            mModel = model;

            // Set the clear color
            glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

            projection = glm::perspective(glm::radians(45.0f), (float)1280 / (float)720, 0.1f, 100.0f);

            view = glm::lookAt(
                glm::vec3(4, 3, 3), // Camera is at (4,3,3), in World Space
                glm::vec3(0, 0, 0), // and looks at the origin
                glm::vec3(0, 1, 0)  // Head is up (set to 0,-1,0 to look upside-down)
            );

            i = 60;

            return SUCCESS;
        }

        static int draw() {

            if (i <= 0) {
                view = glm::lookAt(
                    glm::vec3(3 + 2 * (float)std::rand() / RAND_MAX, 3 + 2 * (float)std::rand() / RAND_MAX, 3 + 2 * (float)std::rand() / RAND_MAX), // Camera is at (), in World Space
                    glm::vec3(0, 0, 0), // and looks at the origin
                    glm::vec3(0, 1, 0)  // Head is up (set to 0,-1,0 to look upside-down)
                );
                mModel.update();

                i = 60;
            }
            i--;

            mModel.draw();

            glfwSwapBuffers(window);
        }

        static bool shouldClose() {
            return glfwWindowShouldClose(window);
        }
    };

}

main.cpp:

#include "zachos.h"

/*
I'm pretty sure you know what this function is.
*/
int main(int argc, char* argv[]) {
    // Seed the random number generator
    std::srand(static_cast<unsigned int>(std::time(nullptr)));

    // Implement Mainframe for shorthand
    using zachos::Mainframe;
    // set GLFW error callback function
    glfwSetErrorCallback(zachos::errorCallback);

    // Run the window initiazation function
    int value = Mainframe::init();
    if (!value) {
        // Return if failed
        return value;
    }

    // Main loop
    while (!Mainframe::shouldClose()) {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_LESS);

        glfwPollEvents();

        // Actually does the hard work
        Mainframe::draw();
    }

    glfwTerminate();

    return zachos::SUCCESS;
}

I am also aware of other possible issues present in this code. You can mention them as well if you wish, but my main question revolves around these two main issues.


Solution

  • You appear to declare the classes 'Model' and 'Mainframe' twice, once in zachos.h and once in zachos.cpp. You should declare the class in the header with

    class Model {
    public:
       virtual void update();
       ...
    }
    

    And then define the class in the cpp with

    void Model::update() {
       ...
    }
    

    The header tells the compiler the structure and contents of the class, and the cpp provides what the functions actually do.

    For your second point, I'm not sure what you're trying the achieve, so don't know if there's a better way to do it.