3D C/C++ tutorials - OpenGL 3.3 - Flying camera, collision detection
3D C/C++ tutorials -> OpenGL 3.3 -> Flying camera, collision detection
Use for personal or educational purposes only. Commercial and other profit uses strictly prohibited. Exploitation of content on a website or in a publication prohibited.
To compile and run these tutorials some or all of these libraries are required: FreeImage 3.16.0, GLEW 1.11.0, GLUT 3.7.6 / GLUT for Dev-C++, GLM 0.9.5.4
object.h
#include "shaderprogram.h"

// ----------------------------------------------------------------------------------------------------------------------------

#define RADIUS 0.15625f

// ----------------------------------------------------------------------------------------------------------------------------

class CTriangle
{
public:
    vec3 Vertex[3], Edge[3], Normal[4];
    float EdgeLength[3], D[4];
    bool Close;
};

// ----------------------------------------------------------------------------------------------------------------------------

class CObject
{
public:
    mat3x3 NormalMatrix;
    mat4x4 ModelMatrix;
    CTexture Texture;
    CBuffer Buffer;
    GLuint VBO;
    int VertexOffset, Stride, VerticesCount, TrianglesCount;
    CTriangle *Triangles;
    vec3 Min, Max;
    bool Close;

public:
    CObject();
    ~CObject();

    void AddData(void *Data, int DataSize);
    void Destroy();
    void InitVBO(int VertexOffset, int Stride);
    void PrepareTriangles();
    void SetModelMatrix(const mat4x4 &ModelMatrix);

private:
    void SetDefaults();
};
object.cpp
#include "object.h"

// ----------------------------------------------------------------------------------------------------------------------------

CObject::CObject()
{
    SetDefaults();
}

CObject::~CObject()
{
}

void CObject::AddData(void *Data, int DataSize)
{
    Buffer.AddData(Data, DataSize);
}

void CObject::Destroy()
{
    Texture.Destroy();

    Buffer.Empty();

    glDeleteBuffers(1, &VBO);

    delete [] Triangles;

    SetDefaults();
}

void CObject::InitVBO(int VertexOffset, int Stride)
{
    this->VertexOffset = VertexOffset;
    this->Stride = Stride;

    Buffer.FreeUnusedMemory();

    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, Buffer.GetDataSize(), Buffer.GetData(), GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    VerticesCount = Buffer.GetDataSize() / Stride;
    TrianglesCount = VerticesCount / 3;

    Triangles = new CTriangle[TrianglesCount];

    PrepareTriangles();
}

void CObject::PrepareTriangles()
{
    BYTE *Vertices = Buffer.GetData() + VertexOffset;

    CTriangle *Triangle = Triangles;

    for(int t = 0; t < TrianglesCount; t++)
    {
        for(int v = 0; v < 3; v++)
        {
            vec3 *Vertex = (vec3*)(Vertices + Stride * v);

            Triangle->Vertex[v].x = ModelMatrix.M[0] * Vertex->x + ModelMatrix.M[4] * Vertex->y + ModelMatrix.M[8] * Vertex->z + ModelMatrix.M[12];
            Triangle->Vertex[v].y = ModelMatrix.M[1] * Vertex->x + ModelMatrix.M[5] * Vertex->y + ModelMatrix.M[9] * Vertex->z + ModelMatrix.M[13];
            Triangle->Vertex[v].z = ModelMatrix.M[2] * Vertex->x + ModelMatrix.M[6] * Vertex->y + ModelMatrix.M[10] * Vertex->z + ModelMatrix.M[14];
        }

        Triangle->Normal[0] = normalize(cross(Triangle->Vertex[1] - Triangle->Vertex[0], Triangle->Vertex[2] - Triangle->Vertex[0]));
        Triangle->D[0] = -dot(Triangle->Normal[0], Triangle->Vertex[0]);

        for(int e = 0; e < 3; e++)
        {
            Triangle->Edge[e] = Triangle->Vertex[(e + 1) % 3] - Triangle->Vertex[e];
            Triangle->EdgeLength[e] = length(Triangle->Edge[e]);
            Triangle->Edge[e] /= Triangle->EdgeLength[e];
            Triangle->Normal[1 + e] = cross(Triangle->Edge[e], Triangle->Normal[0]);
            Triangle->D[1 + e] = -dot(Triangle->Normal[1 + e], Triangle->Vertex[e]);
        }

        Vertices += Stride * 3;

        Triangle++;

    }

    Min = Max = Triangles[0].Vertex[0];

    Triangle = Triangles;

    for(int t = 0; t < TrianglesCount; t++)
    {
        for(int v = 0; v < 3; v++)
        {
            if(Triangle->Vertex[v].x < Min.x) Min.x = Triangle->Vertex[v].x;
            if(Triangle->Vertex[v].x > Max.x) Max.x = Triangle->Vertex[v].x;
            if(Triangle->Vertex[v].y < Min.y) Min.y = Triangle->Vertex[v].y;
            if(Triangle->Vertex[v].y > Max.y) Max.y = Triangle->Vertex[v].y;
            if(Triangle->Vertex[v].z < Min.z) Min.z = Triangle->Vertex[v].z;
            if(Triangle->Vertex[v].z > Max.z) Max.z = Triangle->Vertex[v].z;
        }

        Triangle++;
    }

    Min -= RADIUS;
    Max += RADIUS;
}

void CObject::SetModelMatrix(const mat4x4 &ModelMatrix)
{
    this->ModelMatrix = ModelMatrix;

    NormalMatrix = transpose(inverse(mat3x3(ModelMatrix)));

    PrepareTriangles();
}

void CObject::SetDefaults()
{
    NormalMatrix = mat3x3();
    ModelMatrix = mat4x4();

    VBO = 0;

    VertexOffset = 0;
    Stride = 0;
    VerticesCount = 0;
    TrianglesCount = 0;

    Triangles = NULL;
}
camera.h
...

class CCamera
{
    ...

public:
    ...
    virtual bool CheckCollisions(CObject *Objects, int ObjectsCount, vec3 &Movement, int Depth = 0);
    ...
};

...

class CUniverseCamera : public CCamera
{
public:
    ...
    bool CheckCollisions(CObject *Objects, int ObjectsCount, vec3 &Movement, int Depth = 0);
    ...
};

...

class CFlyingCamera : public CUniverseCamera
{
    ...
};

...
camera.cpp
...

bool CCamera::CheckCollisions(CObject *Objects, int ObjectsCount, vec3 &Movement, int Depth)
{
    return false;
}

...

bool CUniverseCamera::CheckCollisions(CObject *Objects, int ObjectsCount, vec3 &Movement, int Depth)
{
    if(Depth < 4)
    {
        vec3 Position = this->Position + Movement;

        // check the distance of the camera position from each triangle plane

        for(int o = 0; o < ObjectsCount; o++)
        {
            Objects[o].Close = false;

            if(Position.x < Objects[o].Min.x) continue;
            if(Position.x > Objects[o].Max.x) continue;
            if(Position.y < Objects[o].Min.y) continue;
            if(Position.y > Objects[o].Max.y) continue;
            if(Position.z < Objects[o].Min.z) continue;
            if(Position.z > Objects[o].Max.z) continue;

            Objects[o].Close = true;

            CTriangle *Triangle = Objects[o].Triangles;

            for(int t = 0; t < Objects[o].TrianglesCount; t++)
            {
                float Distance = dot(Triangle->Normal[0], Position) + Triangle->D[0];

                Triangle->Close = Distance > 0.0f && Distance < RADIUS;

                if(Triangle->Close)
                {
                    if(dot(Triangle->Normal[1], Position) + Triangle->D[1] < 0.0f)
                    {
                        if(dot(Triangle->Normal[2], Position) + Triangle->D[2] < 0.0f)
                        {
                            if(dot(Triangle->Normal[3], Position) + Triangle->D[3] < 0.0f)
                            {
                                Movement += Triangle->Normal[0] * (RADIUS - Distance);

                                CheckCollisions(Objects, ObjectsCount, Movement, Depth + 1);

                                return true;
                            }
                        }
                    }
                }

                Triangle++;
            }
        }

        // check the distance of the camera position from each edge

        for(int o = 0; o < ObjectsCount; o++)
        {
            if(!Objects[o].Close) continue;

            CTriangle *Triangle = Objects[o].Triangles;

            for(int t = 0; t < Objects[o].TrianglesCount; t++)
            {
                if(Triangle->Close)
                {
                    for(int e = 0; e < 3; e++)
                    {
                        vec3 VCP = Position - Triangle->Vertex[e];

                        float EdotVCP = dot(Triangle->Edge[e], VCP);

                        if(EdotVCP > 0.0f && EdotVCP < Triangle->EdgeLength[e])
                        {
                            vec3 Normal = VCP - Triangle->Edge[e] * EdotVCP;

                            float Distance = length(Normal);

                            if(Distance > 0.0f && Distance < RADIUS)
                            {
                                Movement += Normal * (RADIUS / Distance - 1.0f);

                                CheckCollisions(Objects, ObjectsCount, Movement, Depth + 1);

                                return true;
                            }
                        }
                    }
                }

                Triangle++;
            }
        }

        // check the distance of the camera position from each vertex

        for(int o = 0; o < ObjectsCount; o++)
        {
            if(!Objects[o].Close) continue;

            CTriangle *Triangle = Objects[o].Triangles;

            for(int t = 0; t < Objects[o].TrianglesCount; t++)
            {
                if(Triangle->Close)
                {
                    for(int v = 0; v < 3; v++)
                    {
                        vec3 Normal = Position - Triangle->Vertex[v];

                        float Distance = length(Normal);

                        if(Distance > 0.0f && Distance < RADIUS)
                        {
                            Movement += Normal * (RADIUS / Distance - 1.0f);

                            CheckCollisions(Objects, ObjectsCount, Movement, Depth + 1);

                            return true;
                        }
                    }
                }

                Triangle++;
            }
        }
    }

    return false;
}

...
opengl33renderer.h
...

class COpenGL33Renderer
{
    ...

protected:
    CObject *Objects;
    int ObjectsCount;

    ...

public:
    ...
    virtual bool CheckCollisions(vec3 &Movement);
    ...
};

...
opengl33renderer.cpp
...

COpenGL33Renderer::COpenGL33Renderer()
{
    Objects = NULL;
    ObjectsCount = 0;

    ...
}

...

bool COpenGL33Renderer::CheckCollisions(vec3 &Movement)
{
    if(Camera != NULL)
    {
        return Camera->CheckCollisions(Objects, ObjectsCount, Movement);
    }

    return false;
}

...
opengl33view.cpp
...

void COpenGL33View::OnPaint()
{
    ...

    if(OpenGL33Renderer != NULL)
    {
        bool Animated = OpenGL33Renderer->Animate(FrameTime);

        SHORT Keys = 0x0000;

        ...

        vec3 Movement;

        bool MoveCamera = OpenGL33Renderer->OnCameraKeys(Keys, FrameTime, Movement);

        if(MoveCamera || Animated)
        {
            bool CollisionsDetected = OpenGL33Renderer->CheckCollisions(Movement);

            if(MoveCamera || CollisionsDetected)
            {
                OpenGL33Renderer->MoveCamera(Movement);
            }
        }

        OpenGL33Renderer->Render();
    }

    ...
}

...
myopengl33renderer.h
#include "opengl33view.h"

...

class CMyOpenGL33Renderer : public COpenGL33Renderer
{
private:
    mat4x4 ViewProjectionMatrix;
    CTexture SkyBoxTexture;
    CShaderProgram SkyBox, Lighting;
    GLuint SkyBoxVBO, VAO;

private:
    bool Pause;

public:
    CMyOpenGL33Renderer();
    ~CMyOpenGL33Renderer();

    bool Init();
    void Resize(int Width, int Height);
    void Render();
    bool Animate(float FrameTime);
    void Destroy();

    void OnKeyDown(UINT Key);
};

...
myopengl33renderer.cpp
...

CMyOpenGL33Renderer::CMyOpenGL33Renderer()
{
    Camera = new CFlyingCamera();

    Camera->SetViewMatrixPointer(&ViewMatrix);

    Pause = false;
}

CMyOpenGL33Renderer::~CMyOpenGL33Renderer()
{
    delete Camera;
}

bool CMyOpenGL33Renderer::Init()
{
    bool Error = false;

    // ------------------------------------------------------------------------------------------------------------------------

    ObjectsCount = 3;
    Objects = new CObject[ObjectsCount];

    // load textures ----------------------------------------------------------------------------------------------------------

    char *CubeMapTextureFileNames[] = {"jajlands1_ft.jpg", "jajlands1_bk.jpg", "jajlands1_dn.jpg", "jajlands1_up.jpg", "jajlands1_rt.jpg", "jajlands1_lf.jpg"};

    Error |= !SkyBoxTexture.LoadTextureCubeMap(CubeMapTextureFileNames);

    Error |= !Objects[0].Texture.LoadTexture2D("grass.jpg");
    Error |= !Objects[1].Texture.LoadTexture2D("crate.jpg");
    Error |= !Objects[2].Texture.LoadTexture2D("metalplate.jpg");

    // load shaders -----------------------------------------------------------------------------------------------------------
    
    Error |= !SkyBox.Load("skybox.vert", "skybox.frag");
    Error |= !Lighting.Load("lighting.vert", "lighting.frag");

    // if an error occured, return false --------------------------------------------------------------------------------------
    
    if(Error)
    {
        return false;
    }

    // get uniform and attribute locations ------------------------------------------------------------------------------------
    
    SkyBox.UniformLocations = new GLuint[2];
    SkyBox.UniformLocations[0] = glGetUniformLocation(SkyBox, "CameraPosition");
    SkyBox.UniformLocations[1] = glGetUniformLocation(SkyBox, "ViewProjectionMatrix");

    SkyBox.AttribLocations = new GLuint[1];
    SkyBox.AttribLocations[0] = glGetAttribLocation(SkyBox, "vert_Position");

    Lighting.UniformLocations = new GLuint[2];
    Lighting.UniformLocations[0] = glGetUniformLocation(Lighting, "NormalMatrix");
    Lighting.UniformLocations[1] = glGetUniformLocation(Lighting, "ModelViewProjectionMatrix");

    Lighting.AttribLocations = new GLuint[3];
    Lighting.AttribLocations[0] = glGetAttribLocation(Lighting, "vert_TexCoord");
    Lighting.AttribLocations[1] = glGetAttribLocation(Lighting, "vert_Normal");
    Lighting.AttribLocations[2] = glGetAttribLocation(Lighting, "vert_Position");

    // set constant uniforms --------------------------------------------------------------------------------------------------

    glUseProgram(Lighting);
    glUniform1f(glGetUniformLocation(Lighting, "Light.Ambient"), 0.333333f);
    glUniform1f(glGetUniformLocation(Lighting, "Light.Diffuse"), 0.666666f);
    vec3 LightDirection = vec3(0.467757f, 0.424200f, -0.775409f);
    glUniform3fv(glGetUniformLocation(Lighting, "Light.Direction"), 1, &LightDirection);
    glUseProgram(0);

    // init skybox ------------------------------------------------------------------------------------------------------------

    glGenBuffers(1, &SkyBoxVBO);
    glBindBuffer(GL_ARRAY_BUFFER, SkyBoxVBO);
    glBufferData(GL_ARRAY_BUFFER, 432, SkyBoxVertices, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    // init terrain -----------------------------------------------------------------------------------------------------------

    vec3 GrassNormal = vec3(0.0f, 1.0f, 0.0f);

    for(int i = 0; i < 6; i++)
    {
        Objects[0].Buffer.AddData(&GrassTexCoords[i], 8);
        Objects[0].Buffer.AddData(&GrassNormal, 12);
        Objects[0].Buffer.AddData(&GrassVertices[i], 12);
    }

    Objects[0].InitVBO(20, 32);

    // init cubes -------------------------------------------------------------------------------------------------------------

    for(int y = 0; y < 8; y++)
    {
        for(int x = 0; x < 8 - y; x++)
        {
            vec3 offset = vec3(-5.5f + x * 1.5f + 0.75f * y, 0.5f + y, -5.0f);

            for(int i = 0; i < 36; i++)
            {
                Objects[1].Buffer.AddData(&CubeTexCoords[i % 6], 8);
                Objects[1].Buffer.AddData(&CubeNormals[i / 6], 12);
                vec3 Vertex = CubeVertices[i] + offset;
                Objects[1].Buffer.AddData(&Vertex, 12);
            }
        }
    }

    Objects[1].InitVBO(20, 32);

    // init tori --------------------------------------------------------------------------------------------------------------
    
    GenerateTorus(Objects[2].Buffer, 1.0f, 0.25f, 32, 16, mat4x4());
    GenerateTorus(Objects[2].Buffer, 1.0f, 0.25f, 32, 16, rotate(90.0f, vec3(0.0f, 1.0f, 0.0f)));
    GenerateTorus(Objects[2].Buffer, 1.0f, 0.25f, 32, 16, rotate(90.0f, vec3(1.0f, 0.0f, 0.0f)));

    Objects[2].InitVBO(20, 32);

    // generate VAO -----------------------------------------------------------------------------------------------------------

    glGenVertexArrays(1, &VAO);

    // set camera--------------------------------------------------------------------------------------------------------------

    Camera->Look(vec3(0.0f, 1.75f, 0.0f), vec3(0.0f, 1.75f, -1.0f));

    // ------------------------------------------------------------------------------------------------------------------------

    return true;
}

void CMyOpenGL33Renderer::Resize(int Width, int Height)
{
    this->Width = Width;
    this->Height = Height;

    glViewport(0, 0, Width, Height);

    ProjectionMatrix = perspective(45.0f, (float)Width / (float)Height, 0.125f, 512.0f);
}

void CMyOpenGL33Renderer::Render()
{
    // clear frame buffer -----------------------------------------------------------------------------------------------------

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // set up matrices --------------------------------------------------------------------------------------------------------

    ViewProjectionMatrix = ProjectionMatrix * ViewMatrix;

    // render skybox - depth test must be disabled ----------------------------------------------------------------------------

    glUseProgram(SkyBox);
    glUniform3fv(SkyBox.UniformLocations[0], 1, &Camera->Position);
    glUniformMatrix4fv(SkyBox.UniformLocations[1], 1, GL_FALSE, &ViewProjectionMatrix);
    glBindTexture(GL_TEXTURE_CUBE_MAP, SkyBoxTexture);
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, SkyBoxVBO);
    glEnableVertexAttribArray(SkyBox.AttribLocations[0]);
    glVertexAttribPointer(SkyBox.AttribLocations[0], 3, GL_FLOAT, GL_FALSE, 12, (void*)0);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    glDisableVertexAttribArray(SkyBox.AttribLocations[0]);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
    glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
    glUseProgram(0);

    // render scene -----------------------------------------------------------------------------------------------------------
    
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

    glUseProgram(Lighting);

    glBindVertexArray(VAO);

    glEnableVertexAttribArray(Lighting.AttribLocations[0]);
    glEnableVertexAttribArray(Lighting.AttribLocations[1]);
    glEnableVertexAttribArray(Lighting.AttribLocations[2]);

    for(int i = 0; i < ObjectsCount; i++)
    {
        ModelViewProjectionMatrix = ViewProjectionMatrix * Objects[i].ModelMatrix;

        glUniformMatrix3fv(Lighting.UniformLocations[0], 1, GL_FALSE, &Objects[i].NormalMatrix);
        glUniformMatrix4fv(Lighting.UniformLocations[1], 1, GL_FALSE, &ModelViewProjectionMatrix);

        glBindTexture(GL_TEXTURE_2D, Objects[i].Texture);

        glBindBuffer(GL_ARRAY_BUFFER, Objects[i].VBO);
        
        glVertexAttribPointer(Lighting.AttribLocations[0], 2, GL_FLOAT, GL_FALSE, 32, (void*)0);
        glVertexAttribPointer(Lighting.AttribLocations[1], 3, GL_FLOAT, GL_FALSE, 32, (void*)8);
        glVertexAttribPointer(Lighting.AttribLocations[2], 3, GL_FLOAT, GL_FALSE, 32, (void*)20);

        glDrawArrays(GL_TRIANGLES, 0, Objects[i].VerticesCount);
    }

    // unbind and disable what was bound and enabled

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    glBindTexture(GL_TEXTURE_2D, 0);

    glDisableVertexAttribArray(Lighting.AttribLocations[2]);
    glDisableVertexAttribArray(Lighting.AttribLocations[1]);
    glDisableVertexAttribArray(Lighting.AttribLocations[0]);

    glBindVertexArray(0);
    
    glUseProgram(0);

    glDisable(GL_CULL_FACE);
    glDisable(GL_DEPTH_TEST);
}

bool CMyOpenGL33Renderer::Animate(float FrameTime)
{
    static float Angle = 0.0f;

    // increase angle - rotating object ---------------------------------------------------------------------------------------

    if(!Pause)
    {
        Angle += 22.5f * FrameTime;

        Objects[2].SetModelMatrix(translate(0.0f, 1.75f, 5.0f) * rotate(Angle, vec3(0.0f, 1.0f, 0.0f)));
    }

    return !Pause;
}

void CMyOpenGL33Renderer::Destroy()
{
    SkyBoxTexture.Destroy();
    
    for(int i = 0; i < ObjectsCount; i++)
    {
        Objects[i].Destroy();
    }

    delete [] Objects;

    SkyBox.Destroy();
    Lighting.Destroy();

    glDeleteBuffers(1, &SkyBoxVBO);

    glDeleteVertexArrays(1, &VAO);
}

void CMyOpenGL33Renderer::OnKeyDown(UINT Key)
{
    switch(Key)
    {
        case 'P':
            Pause = !Pause;
            break;
    }

    COpenGL33Renderer::OnKeyDown(Key);
}

...
Download
flying_camera_collision_detection.zip (Visual Studio 2005 Professional)

© 2010 - 2016 Bc. Michal Belanec, michalbelanec (at) centrum (dot) sk
Last update June 25, 2016
OpenGL® is a registered trademark of Silicon Graphics Inc.