Window background visible through textures

Hello,

What can I try to solve this problem? In this example I am using OpenGL 1.1 with deprecated functions like: glEnableClientState, glMatrixMode, glTexCoordPointer, and so on. Yes, I know that using deprecated functions is bad. I also use shaders. But this is a task that I do for a student. His teacher demands to use deprecated functions. Thanks in advance.

image

main.cpp

#include <GLFW/glfw3.h>
#include <iostream>
#include <map>
#include <box2d/box2d.h>
#include <fmod.h>

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

using namespace std;

#define W 516
#define H 432

// Font: https://www.fontspace.com/category/mario

FMOD_SYSTEM *soundSystem;
FMOD_SOUND *mainTheme;
FMOD_SOUND *jumpSound;

map<int, bool> keys;

b2Vec2 gravity(0.0f, 9.8f);
b2World world(gravity);
const float WORLD_SCALE = 30.f;
b2Body *playerBody;

GLFWwindow *window;

GLuint spriteTexture;
char spritePath[] = "assets/sprites/small-mario.png";
GLuint backgroundTexture;
char backgroundPath[] = "assets/sprites/background.png";

const float imageWidth = 288.f;

float vertPositions[] = {-0.5f, -0.5f, 0.f, // Idle
                         -0.5f, 0.5f, 0.f,
                         0.5f, -0.5f, 0.f,
                         0.5f, 0.5f, 0.f,
                         -0.5f, -0.5f, 0.f, // Run 0
                         -0.5f, 0.5f, 0.f,
                         0.5f, -0.5f, 0.f,
                         0.5f, 0.5f, 0.f,
                         -0.5f, -0.5f, 0.f, // Run 1
                         -0.5f, 0.5f, 0.f,
                         0.5f, -0.5f, 0.f,
                         0.5f, 0.5f, 0.f,
                         -0.5f, -0.5f, 0.f, // Run 2
                         -0.5f, 0.5f, 0.f,
                         0.5f, -0.5f, 0.f,
                         0.5f, 0.5f, 0.f,
                         0.f, 0.f, 0.f, // Background
                         0.f, 1.f, 0.f,
                         1.f, 0.f, 0.f,
                         1.f, 1.f, 0.f,
                         -0.5f, -0.5f, 0.f, // Enemy 0
                         -0.5f, 0.5f, 0.f,
                         0.5f, -0.5f, 0.f,
                         0.5f, 0.5f, 0.f,
                         -0.5f, -0.5f, 0.f, // Coin
                         -0.5f, 0.5f, 0.f,
                         0.5f, -0.5f, 0.f,
                         0.5f, 0.5f, 0.f};
float texCoords[] = {0.f, 0.f, // Idle
                     0.f, 1.f,
                     16.f / imageWidth, 0.f,
                     16.f / imageWidth, 1.f,
                     32.f / imageWidth, 0.f, // Run 0
                     32.f / imageWidth, 1.f,
                     48.f / imageWidth, 0.f,
                     48.f / imageWidth, 1.f,
                     48.f / imageWidth, 0.f, // Run 1
                     48.f / imageWidth, 1.f,
                     64.f / imageWidth, 0.f,
                     64.f / imageWidth, 1.f,
                     64.f / imageWidth, 0.f, // Run 2
                     64.f / imageWidth, 1.f,
                     80.f / imageWidth, 0.f,
                     80.f / imageWidth, 1.f,
                     0.f, 0.f, // Background
                     0.f, 1.f,
                     1.f, 0.f,
                     1.f, 1.f,
                     224.f / imageWidth, 0.f, // Enemy 0
                     224.f / imageWidth, 1.f,
                     240.f / imageWidth, 0.f,
                     240.f / imageWidth, 1.f,
                     272.f / imageWidth, 0.f, // Coin
                     272.f / imageWidth, 1.f,
                     288.f / imageWidth, 0.f,
                     288.f / imageWidth, 1.f};

GLuint createTexture(char *path)
{
    int h_image, w_image, cnt;

    unsigned char *data = stbi_load(path, &w_image, &h_image, &cnt, 0);

    if (data == NULL)
    {
        cout << "Failed to load an image" << endl;
        glfwTerminate();
        exit(-1);
    }

    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w_image, h_image, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    }
    glBindTexture(GL_TEXTURE_2D, 0);

    stbi_image_free(data);

    return texture;
}

void keyboard(GLFWwindow *window, int key, int scode, int action, int smode)
{
    // Jump Press
    if ((key == GLFW_KEY_W || key == GLFW_KEY_UP || key == GLFW_KEY_SPACE) && (action == GLFW_REPEAT || action == GLFW_PRESS))
    {
        // Check if onGround
        bool onGround = false;
        b2Vec2 pos = playerBody->GetPosition();
        pos.y += 9 / WORLD_SCALE;

        for (b2Body *it = world.GetBodyList(); it != 0; it = it->GetNext())
        {
            for (b2Fixture *f = it->GetFixtureList(); f != 0; f = f->GetNext())
            {
                if (f->TestPoint(pos))
                {
                    onGround = true;
                }
            }
        }

        if (onGround)
        {
            float impulse = playerBody->GetMass() * -6.7f;
            playerBody->ApplyLinearImpulse(b2Vec2(0, impulse), playerBody->GetWorldCenter(), true);
            // FMOD_System_PlaySound(soundSystem, jumpSound, 0, false, 0);
        }
    }

    // Press a left key
    if ((key == GLFW_KEY_A || key == GLFW_KEY_LEFT) && (action == GLFW_REPEAT || action == GLFW_PRESS))
    {
        keys[key] = true;
    }
    // Press a right key
    if ((key == GLFW_KEY_D || key == GLFW_KEY_RIGHT) && (action == GLFW_REPEAT || action == GLFW_PRESS))
    {
        keys[key] = true;
    }

    // Release a left key
    if ((key == GLFW_KEY_A || key == GLFW_KEY_LEFT) && action == GLFW_RELEASE) // Left
    {
        keys[key] = false;
    }
    // Release a right key
    if ((key == GLFW_KEY_D || key == GLFW_KEY_RIGHT) && action == GLFW_RELEASE) // Right
    {
        keys[key] = false;
    }
}

int main()
{
    if (!glfwInit())
    {
        cout << "Failed to init GLFW" << endl;
        return -1;
    }

    glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

    window = glfwCreateWindow(W, H, "GLFW, OpenGL 1.1", nullptr, nullptr);
    if (!window)
    {
        cout << "Failed to create the GLFW window" << endl;
        glfwTerminate();
        return -1;
    }

    glfwMakeContextCurrent(window);

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_TEXTURE_2D);

    glClearColor(0.2f, 0.5f, 0.3f, 1.f);

    glViewport(0, 0, W, H);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, 256, 216, 0, -100.f, 100.f);

    spriteTexture = createTexture(spritePath);
    backgroundTexture = createTexture(backgroundPath);

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, vertPositions);
    glTexCoordPointer(2, GL_FLOAT, 0, texCoords);

    glfwSetKeyCallback(window, keyboard);

    FMOD_System_Create(&soundSystem, FMOD_VERSION);
    FMOD_System_Init(soundSystem, 32, FMOD_INIT_NORMAL, 0);
    // FMOD_System_CreateSound(soundSystem, "assets/sounds/01-main-theme-overworld.mp3", FMOD_LOOP_NORMAL | FMOD_2D, 0, &mainTheme);
    // FMOD_System_PlaySound(soundSystem, mainTheme, 0, false, 0);
    FMOD_System_CreateSound(soundSystem, "assets/sounds/smb_jump-small.wav", FMOD_LOOP_OFF | FMOD_2D, 0, &jumpSound);

    // Player
    b2CircleShape playerShape;
    playerShape.m_radius = 8.f / WORLD_SCALE;
    b2BodyDef playerBodyDef;
    playerBodyDef.type = b2_dynamicBody;
    playerBody = world.CreateBody(&playerBodyDef);
    playerBody->CreateFixture(&playerShape, 5.f);
    playerBody->SetTransform(b2Vec2(100.f / WORLD_SCALE, 100.f / WORLD_SCALE), 0.f);
    playerBody->SetFixedRotation(true);
    const float playerSpeed = 3.f;

    // Ground
    b2PolygonShape groundShape;
    groundShape.SetAsBox(500.f / WORLD_SCALE, 16.f / WORLD_SCALE);
    b2BodyDef groundBodyDef;
    groundBodyDef.type = b2_staticBody;
    b2Body *groundBody = world.CreateBody(&groundBodyDef);
    groundBody->CreateFixture(&groundShape, 0.f);
    groundBody->SetTransform(b2Vec2(100.f / WORLD_SCALE, 208.f / WORLD_SCALE), 0.f);

    b2BodyDef platformBodyDef;
    platformBodyDef.type = b2_staticBody;

    // Platform 1
    b2PolygonShape platform1Shape;
    platform1Shape.SetAsBox(8.f / WORLD_SCALE, 8.f / WORLD_SCALE);
    b2Body *platform1Body = world.CreateBody(&platformBodyDef);
    platform1Body->CreateFixture(&platform1Shape, 0.f);
    platform1Body->SetTransform(b2Vec2(98.f / WORLD_SCALE, 136.f / WORLD_SCALE), 0.f);

    // Platform 2
    b2PolygonShape platform2Shape;
    platform2Shape.SetAsBox(40.f / WORLD_SCALE, 8.f / WORLD_SCALE);
    b2Body *platform2Body = world.CreateBody(&platformBodyDef);
    platform2Body->CreateFixture(&platform2Shape, 0.f);
    platform2Body->SetTransform(b2Vec2(194.f / WORLD_SCALE, 136.f / WORLD_SCALE), 0.f);

    // Platform 3
    b2PolygonShape platform3Shape;
    platform3Shape.SetAsBox(8.f / WORLD_SCALE, 8.f / WORLD_SCALE);
    b2Body *platform3Body = world.CreateBody(&platformBodyDef);
    platform3Body->CreateFixture(&platform3Shape, 0.f);
    platform3Body->SetTransform(b2Vec2(194.f / WORLD_SCALE, 72.f / WORLD_SCALE), 0.f);

    float currentTime, deltaTime;
    float lastTime = glfwGetTime();

    float animationTime = 0.f;
    const float timePeriod = 0.1f;
    unsigned int drawingIndex = 0;

    while (!glfwWindowShouldClose(window))
    {
        glfwPollEvents();

        currentTime = glfwGetTime();
        deltaTime = currentTime - lastTime;
        lastTime = currentTime;

        world.Step(deltaTime, 6, 2);

        if (keys[GLFW_KEY_A] || keys[GLFW_KEY_LEFT])
        {
            b2Vec2 vel = playerBody->GetLinearVelocity();
            vel.x = -playerSpeed;
            playerBody->SetLinearVelocity(vel);
        }
        if (keys[GLFW_KEY_D] || keys[GLFW_KEY_RIGHT])
        {
            b2Vec2 vel = playerBody->GetLinearVelocity();
            vel.x = playerSpeed;
            playerBody->SetLinearVelocity(vel);
        }
        if (!keys[GLFW_KEY_A] && !keys[GLFW_KEY_LEFT] && !keys[GLFW_KEY_D] && !keys[GLFW_KEY_RIGHT])
        {
            b2Vec2 vel = playerBody->GetLinearVelocity();
            vel.x = 0.f;
            playerBody->SetLinearVelocity(vel);
        }

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Player
        glBindTexture(GL_TEXTURE_2D, spriteTexture);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTranslatef(playerBody->GetPosition().x * WORLD_SCALE,
                     playerBody->GetPosition().y * WORLD_SCALE, 10.f);
        glScalef(16.f, 16.f, 1.f);
        glDrawArrays(GL_TRIANGLE_STRIP, drawingIndex, 4);

        // Coin
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTranslatef(178.f, 120.f, 10.f);
        glScalef(16.f, 16.f, 1.f);
        glDrawArrays(GL_TRIANGLE_STRIP, 24, 4);

        // Enemy
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTranslatef(194.f, 184.f, 10.f);
        glScalef(16.f, 16.f, 1.f);
        glDrawArrays(GL_TRIANGLE_STRIP, 20, 4);

        // Background
        glBindTexture(GL_TEXTURE_2D, backgroundTexture);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTranslatef(0.f, 0.f, 0.f);
        glScalef(256.f, 216.f, 1.f);
        glDrawArrays(GL_TRIANGLE_STRIP, 16, 4);

        glfwSwapBuffers(window);

        // animationTime += deltaTime;
        // if (animationTime >= timePeriod)
        // {
        //     animationTime = 0.f;

        //     glMatrixMode(GL_MODELVIEW);
        //     glLoadIdentity();
        //     glTranslatef(playerBody->GetPosition().x * WORLD_SCALE,
        //                  playerBody->GetPosition().y * WORLD_SCALE, 0.f);
        //     glScalef(50.f, 50.f, 1.f);
        //     glDrawArrays(GL_TRIANGLE_STRIP, drawingIndex, 4);
        //     glfwSwapBuffers(window);
        //     // cout << drawingIndex << endl;

        //     drawingIndex += 4;
        //     if (drawingIndex >= 12)
        //     {
        //         drawingIndex = 0;
        //     }
        // }

        // glPushMatrix();
        // {
        // }
        // glPopMatrix();
    }

    glfwTerminate();
    return 0;
}

makefile

# command to build: mingw32-make

INC = -I"E:\Libs\glfw-3.3.8-mingw-64bit\include" \
	  -I"E:\Libs\stb_image-2.27\include" \
	  -I"E:\Libs\box2d-2.4.1-mingw-64-bit\include" \
	  -I"C:\Program Files\FMOD SoundSystem\FMOD Studio API Windows\api\core\inc"
LIB = -L"E:\Libs\glfw-3.3.8-mingw-64bit\lib" \
	  -L"E:\Libs\box2d-2.4.1-mingw-64-bit\lib"

all: main.o
	g++ main.o $(LIB) -lglfw3 -lgdi32 -lopengl32 -lbox2d -o app.exe \
	"C:\Program Files\FMOD SoundSystem\FMOD Studio API Windows\api\core\lib\x64\fmod.dll"

main.o: main.cpp
	g++ -c $(INC) main.cpp

This problem was solved on StackOverflow: c++ - Window background visible through textures - Stack Overflow

Solution:

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// Background
glBindTexture(GL_TEXTURE_2D, backgroundTexture);
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.f, 0.f, 0.f);
glScalef(256.f, 216.f, 1.f);
glDrawArrays(GL_TRIANGLE_STRIP, 16, 4);

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_DEPTH_TEST);
glBindTexture(GL_TEXTURE_2D, spriteTexture);

// Coin
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(178.f, 120.f, 5.f);
glScalef(16.f, 16.f, 1.f);
glDrawArrays(GL_TRIANGLE_STRIP, 24, 4);

// Enemy
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(194.f, 184.f, 10.f);
glScalef(16.f, 16.f, 1.f);
glDrawArrays(GL_TRIANGLE_STRIP, 20, 4);

// Player
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(playerBody->GetPosition().x * WORLD_SCALE,
                playerBody->GetPosition().y * WORLD_SCALE, 15.f);
glScalef(16.f, 16.f, 1.f);
glDrawArrays(GL_TRIANGLE_STRIP, drawingIndex, 4);

glfwSwapBuffers(window);

This works for coin:

image

And for enemy:

image

This is additional information to Rabbid76’s solution above (from StackOverflow). Not only the drawing order plays a role, but also the location along the z-axis. For example, this is the correct location on the z-axis: z = 0 for background, z = 5 for coin, z = 10 for enemy and z = 15 for player:

// Background
glBindTexture(GL_TEXTURE_2D, backgroundTexture);
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.f, 0.f, 0.f);
glScalef(256.f, 216.f, 1.f);
glDrawArrays(GL_TRIANGLE_STRIP, 16, 4);

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_DEPTH_TEST);
glBindTexture(GL_TEXTURE_2D, spriteTexture);

// Coin
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(178.f, 120.f, 5.f);
glScalef(16.f, 16.f, 1.f);
glDrawArrays(GL_TRIANGLE_STRIP, 24, 4);

// Enemy
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(194.f, 184.f, 10.f);
glScalef(16.f, 16.f, 1.f);
glDrawArrays(GL_TRIANGLE_STRIP, 20, 4);

// Player
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(playerBody->GetPosition().x * WORLD_SCALE,
                playerBody->GetPosition().y * WORLD_SCALE, 15.f);
glScalef(16.f, 16.f, 1.f);
glDrawArrays(GL_TRIANGLE_STRIP, drawingIndex, 4);

glfwSwapBuffers(window);

image

image

Wrong order on z-axis, for example: z = 15 for coin, z = 10 for enemy and z = 5 for player:

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// Background
glBindTexture(GL_TEXTURE_2D, backgroundTexture);
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.f, 0.f, 0.f);
glScalef(256.f, 216.f, 1.f);
glDrawArrays(GL_TRIANGLE_STRIP, 16, 4);

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_DEPTH_TEST);
glBindTexture(GL_TEXTURE_2D, spriteTexture);

// Coin
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(178.f, 120.f, 15.f);
glScalef(16.f, 16.f, 1.f);
glDrawArrays(GL_TRIANGLE_STRIP, 24, 4);

// Enemy
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(194.f, 184.f, 10.f);
glScalef(16.f, 16.f, 1.f);
glDrawArrays(GL_TRIANGLE_STRIP, 20, 4);

// Player
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(playerBody->GetPosition().x * WORLD_SCALE,
                playerBody->GetPosition().y * WORLD_SCALE, 5.f);
glScalef(16.f, 16.f, 1.f);
glDrawArrays(GL_TRIANGLE_STRIP, drawingIndex, 4);

glfwSwapBuffers(window);

image

image

Commands with depth test are not needed. Without them, it works even easier without them - no need to think about the position along the Z axis. Without the depth test, you can set the position along the Z equal to zero for all sprites:

        glClear(GL_COLOR_BUFFER_BIT /*| GL_DEPTH_BUFFER_BIT*/);
 
        // Background
        glBindTexture(GL_TEXTURE_2D, backgroundTexture);
        glDisable(GL_BLEND);
        // glDisable(GL_DEPTH_TEST);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTranslatef(0.f, 0.f, 0.f);
        glScalef(256.f, 216.f, 1.f);
        glDrawArrays(GL_TRIANGLE_STRIP, 16, 4);
 
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        // glEnable(GL_DEPTH_TEST);
        glBindTexture(GL_TEXTURE_2D, spriteTexture);
 
        // Coin
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTranslatef(178.f, 120.f, 0.f);
        glScalef(16.f, 16.f, 1.f);
        glDrawArrays(GL_TRIANGLE_STRIP, 24, 4);
 
        // Enemy
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTranslatef(194.f, 184.f, 0.f);
        glScalef(16.f, 16.f, 1.f);
        glDrawArrays(GL_TRIANGLE_STRIP, 20, 4);
 
        // Player
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTranslatef(playerBody->GetPosition().x * WORLD_SCALE,
                     playerBody->GetPosition().y * WORLD_SCALE, 0.f);
        glScalef(16.f, 16.f, 1.f);
        glDrawArrays(GL_TRIANGLE_STRIP, drawingIndex, 4);
 
        glfwSwapBuffers(window);

Just an example:

mario-switch-sprite-animations

small-mario

makefile

# command to build: mingw32-make

INC = -I"E:\Libs\glfw-3.3.8-mingw-64bit\include" \
	  -I"E:\Libs\stb_image-2.27\include" \
	  -I"E:\Libs\box2d-2.4.1-mingw-64-bit\include" \
	  -I"C:\Program Files\FMOD SoundSystem\FMOD Studio API Windows\api\core\inc"
LIB = -L"E:\Libs\glfw-3.3.8-mingw-64bit\lib" \
	  -L"E:\Libs\box2d-2.4.1-mingw-64-bit\lib"

all: main.o
	g++ main.o $(LIB) -mwindows -lglfw3 -lgdi32 -lopengl32 -lbox2d -o app.exe \
	"C:\Program Files\FMOD SoundSystem\FMOD Studio API Windows\api\core\lib\x64\fmod.dll"

main.o: main.cpp
	g++ -c $(INC) main.cpp

main.cpp

#include <GLFW/glfw3.h>
#include <iostream>
#include <map>
#include <box2d/box2d.h>
#include <fmod.h>

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

using namespace std;

#define W 516
#define H 432

// Font: https://www.fontspace.com/category/mario

FMOD_SYSTEM *soundSystem;
FMOD_SOUND *mainTheme;
FMOD_SOUND *jumpSound;

map<int, bool> keys;

b2Vec2 gravity(0.0f, 9.8f);
b2World world(gravity);
const float WORLD_SCALE = 30.f;
b2Body *playerBody;

GLFWwindow *window;

GLuint spriteTexture;
char spritePath[] = "assets/sprites/small-mario.png";
GLuint backgroundTexture;
char backgroundPath[] = "assets/sprites/background.png";

enum PlayerAnimation
{
    Idle,
    Run,
    Jump,
    Dead
};
PlayerAnimation currentPlayerAnimation = PlayerAnimation::Idle;
int playerDirection = 1; // 1 - right, -1 - left

const float imageWidth = 288.f;

float vertPositions[] = {-0.5f, -0.5f, 0.f, // Idle
                         -0.5f, 0.5f, 0.f,
                         0.5f, -0.5f, 0.f,
                         0.5f, 0.5f, 0.f,
                         -0.5f, -0.5f, 0.f, // Run 0
                         -0.5f, 0.5f, 0.f,
                         0.5f, -0.5f, 0.f,
                         0.5f, 0.5f, 0.f,
                         -0.5f, -0.5f, 0.f, // Run 1
                         -0.5f, 0.5f, 0.f,
                         0.5f, -0.5f, 0.f,
                         0.5f, 0.5f, 0.f,
                         -0.5f, -0.5f, 0.f, // Run 2
                         -0.5f, 0.5f, 0.f,
                         0.5f, -0.5f, 0.f,
                         0.5f, 0.5f, 0.f,
                         -0.5f, -0.5f, 0.f, // Jump
                         -0.5f, 0.5f, 0.f,
                         0.5f, -0.5f, 0.f,
                         0.5f, 0.5f, 0.f,
                         0.f, 0.f, 0.f, // Background
                         0.f, 1.f, 0.f,
                         1.f, 0.f, 0.f,
                         1.f, 1.f, 0.f,
                         -0.5f, -0.5f, 0.f, // Enemy 0
                         -0.5f, 0.5f, 0.f,
                         0.5f, -0.5f, 0.f,
                         0.5f, 0.5f, 0.f,
                         -0.5f, -0.5f, 0.f, // Coin
                         -0.5f, 0.5f, 0.f,
                         0.5f, -0.5f, 0.f,
                         0.5f, 0.5f, 0.f};
float texCoords[] = {0.f, 0.f, // Idle
                     0.f, 1.f,
                     16.f / imageWidth, 0.f,
                     16.f / imageWidth, 1.f,
                     16.f / imageWidth, 0.f, // Run 0
                     16.f / imageWidth, 1.f,
                     32.f / imageWidth, 0.f,
                     32.f / imageWidth, 1.f,
                     32.f / imageWidth, 0.f, // Run 1
                     32.f / imageWidth, 1.f,
                     48.f / imageWidth, 0.f,
                     48.f / imageWidth, 1.f,
                     48.f / imageWidth, 0.f, // Run 2
                     48.f / imageWidth, 1.f,
                     64.f / imageWidth, 0.f,
                     64.f / imageWidth, 1.f,
                     80.f / imageWidth, 0.f, // Jump
                     80.f / imageWidth, 1.f,
                     96.f / imageWidth, 0.f,
                     96.f / imageWidth, 1.f,
                     0.f, 0.f, // Background
                     0.f, 1.f,
                     1.f, 0.f,
                     1.f, 1.f,
                     224.f / imageWidth, 0.f, // Enemy 0
                     224.f / imageWidth, 1.f,
                     240.f / imageWidth, 0.f,
                     240.f / imageWidth, 1.f,
                     272.f / imageWidth, 0.f, // Coin
                     272.f / imageWidth, 1.f,
                     288.f / imageWidth, 0.f,
                     288.f / imageWidth, 1.f};

GLuint createTexture(char *path)
{
    int h_image, w_image, cnt;

    unsigned char *data = stbi_load(path, &w_image, &h_image, &cnt, 0);

    if (data == NULL)
    {
        cout << "Failed to load an image" << endl;
        glfwTerminate();
        exit(-1);
    }

    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w_image, h_image, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    }
    glBindTexture(GL_TEXTURE_2D, 0);

    stbi_image_free(data);

    return texture;
}

bool onGround()
{
    // Check if onGround
    bool onGround = false;
    b2Vec2 pos = playerBody->GetPosition();
    pos.y += 9 / WORLD_SCALE;

    for (b2Body *it = world.GetBodyList(); it != 0; it = it->GetNext())
    {
        for (b2Fixture *f = it->GetFixtureList(); f != 0; f = f->GetNext())
        {
            if (f->TestPoint(pos))
            {
                onGround = true;
            }
        }
    }

    return onGround;
}

void keyboard(GLFWwindow *window, int key, int scode, int action, int smode)
{
    // Jump Press
    if ((key == GLFW_KEY_W || key == GLFW_KEY_UP || key == GLFW_KEY_SPACE) && (action == GLFW_REPEAT || action == GLFW_PRESS))
    {
        keys[key] = true;

        if (onGround())
        {
            float impulse = playerBody->GetMass() * -6.7f;
            playerBody->ApplyLinearImpulse(b2Vec2(0, impulse), playerBody->GetWorldCenter(), true);
            FMOD_System_PlaySound(soundSystem, jumpSound, 0, false, 0);
            // Jump state
            currentPlayerAnimation = PlayerAnimation::Jump;
        }
    }
    // Jump release
    if ((key == GLFW_KEY_W || key == GLFW_KEY_UP || key == GLFW_KEY_SPACE) && action == GLFW_RELEASE)
    {
        keys[key] = false;
    }

    // Press a left key
    if ((key == GLFW_KEY_A || key == GLFW_KEY_LEFT) && (action == GLFW_REPEAT || action == GLFW_PRESS))
    {
        keys[key] = true;
    }
    // Press a right key
    if ((key == GLFW_KEY_D || key == GLFW_KEY_RIGHT) && (action == GLFW_REPEAT || action == GLFW_PRESS))
    {
        keys[key] = true;
    }

    // Release a left key
    if ((key == GLFW_KEY_A || key == GLFW_KEY_LEFT) && action == GLFW_RELEASE) // Left
    {
        keys[key] = false;
    }
    // Release a right key
    if ((key == GLFW_KEY_D || key == GLFW_KEY_RIGHT) && action == GLFW_RELEASE) // Right
    {
        keys[key] = false;
    }
}

int main()
{
    if (!glfwInit())
    {
        cout << "Failed to init GLFW" << endl;
        return -1;
    }

    glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

    window = glfwCreateWindow(W, H, "GLFW, OpenGL 1.1", nullptr, nullptr);
    if (!window)
    {
        cout << "Failed to create the GLFW window" << endl;
        glfwTerminate();
        return -1;
    }

    glfwMakeContextCurrent(window);

    glEnable(GL_TEXTURE_2D);

    glClearColor(1.f, 0.f, 0.f, 1.f);

    glViewport(0, 0, W, H);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, 256, 216, 0, -100.f, 100.f);

    spriteTexture = createTexture(spritePath);
    backgroundTexture = createTexture(backgroundPath);

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, vertPositions);
    glTexCoordPointer(2, GL_FLOAT, 0, texCoords);

    glfwSetKeyCallback(window, keyboard);

    FMOD_System_Create(&soundSystem, FMOD_VERSION);
    FMOD_System_Init(soundSystem, 32, FMOD_INIT_NORMAL, 0);
    FMOD_System_CreateSound(soundSystem, "assets/sounds/01-main-theme-overworld.mp3", FMOD_LOOP_NORMAL | FMOD_2D, 0, &mainTheme);
    FMOD_System_PlaySound(soundSystem, mainTheme, 0, false, 0);
    FMOD_System_CreateSound(soundSystem, "assets/sounds/smb_jump-small.wav", FMOD_LOOP_OFF | FMOD_2D, 0, &jumpSound);

    // Player
    b2CircleShape playerShape;
    playerShape.m_radius = 8.f / WORLD_SCALE;
    b2BodyDef playerBodyDef;
    playerBodyDef.type = b2_dynamicBody;
    playerBody = world.CreateBody(&playerBodyDef);
    playerBody->CreateFixture(&playerShape, 5.f);
    playerBody->SetTransform(b2Vec2(100.f / WORLD_SCALE, 100.f / WORLD_SCALE), 0.f);
    playerBody->SetFixedRotation(true);
    const float playerSpeed = 3.f;

    // Ground
    b2PolygonShape groundShape;
    groundShape.SetAsBox(500.f / WORLD_SCALE, 16.f / WORLD_SCALE);
    b2BodyDef groundBodyDef;
    groundBodyDef.type = b2_staticBody;
    b2Body *groundBody = world.CreateBody(&groundBodyDef);
    groundBody->CreateFixture(&groundShape, 0.f);
    groundBody->SetTransform(b2Vec2(100.f / WORLD_SCALE, 208.f / WORLD_SCALE), 0.f);

    b2BodyDef platformBodyDef;
    platformBodyDef.type = b2_staticBody;

    // Platform 1
    b2PolygonShape platform1Shape;
    platform1Shape.SetAsBox(8.f / WORLD_SCALE, 8.f / WORLD_SCALE);
    b2Body *platform1Body = world.CreateBody(&platformBodyDef);
    platform1Body->CreateFixture(&platform1Shape, 0.f);
    platform1Body->SetTransform(b2Vec2(98.f / WORLD_SCALE, 136.f / WORLD_SCALE), 0.f);

    // Platform 2
    b2PolygonShape platform2Shape;
    platform2Shape.SetAsBox(40.f / WORLD_SCALE, 8.f / WORLD_SCALE);
    b2Body *platform2Body = world.CreateBody(&platformBodyDef);
    platform2Body->CreateFixture(&platform2Shape, 0.f);
    platform2Body->SetTransform(b2Vec2(194.f / WORLD_SCALE, 136.f / WORLD_SCALE), 0.f);

    // Platform 3
    b2PolygonShape platform3Shape;
    platform3Shape.SetAsBox(8.f / WORLD_SCALE, 8.f / WORLD_SCALE);
    b2Body *platform3Body = world.CreateBody(&platformBodyDef);
    platform3Body->CreateFixture(&platform3Shape, 0.f);
    platform3Body->SetTransform(b2Vec2(194.f / WORLD_SCALE, 72.f / WORLD_SCALE), 0.f);

    float currentTime, deltaTime;
    float lastTime = glfwGetTime();

    float animationTime = 0.f;
    const float timePeriod = 0.1f;
    unsigned int drawingIndex = 4;

    while (!glfwWindowShouldClose(window))
    {
        glfwPollEvents();

        currentTime = glfwGetTime();
        deltaTime = currentTime - lastTime;
        lastTime = currentTime;

        world.Step(deltaTime, 6, 2);

        if (keys[GLFW_KEY_A] || keys[GLFW_KEY_LEFT])
        {
            // Movement
            b2Vec2 vel = playerBody->GetLinearVelocity();
            vel.x = -playerSpeed;
            playerBody->SetLinearVelocity(vel);
            // Run animation
            playerDirection = -1;
            if (onGround())
            {
                currentPlayerAnimation = PlayerAnimation::Run;
            }
        }
        if (keys[GLFW_KEY_D] || keys[GLFW_KEY_RIGHT])
        {
            // Movement
            b2Vec2 vel = playerBody->GetLinearVelocity();
            vel.x = playerSpeed;
            playerBody->SetLinearVelocity(vel);
            // Run animation
            playerDirection = 1;
            if (onGround())
            {
                currentPlayerAnimation = PlayerAnimation::Run;
            }
        }
        if (!keys[GLFW_KEY_A] && !keys[GLFW_KEY_LEFT] && !keys[GLFW_KEY_D] && !keys[GLFW_KEY_RIGHT])
        {
            // Stop player
            b2Vec2 vel = playerBody->GetLinearVelocity();
            vel.x = 0.f;
            playerBody->SetLinearVelocity(vel);
            if (onGround())
            {
                currentPlayerAnimation = PlayerAnimation::Idle;
            }
            else
            {
                currentPlayerAnimation = PlayerAnimation::Jump;
            }
        }

        glClear(GL_COLOR_BUFFER_BIT);

        // Background
        glBindTexture(GL_TEXTURE_2D, backgroundTexture);
        glDisable(GL_BLEND);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTranslatef(0.f, 0.f, 0.f);
        glScalef(256.f, 216.f, 1.f);
        glDrawArrays(GL_TRIANGLE_STRIP, 20, 4);

        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glBindTexture(GL_TEXTURE_2D, spriteTexture);

        // Coin
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTranslatef(178.f, 120.f, 0.f);
        glScalef(16.f, 16.f, 1.f);
        glDrawArrays(GL_TRIANGLE_STRIP, 28, 4);

        // Enemy
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTranslatef(194.f, 184.f, 0.f);
        glScalef(16.f, 16.f, 1.f);
        glDrawArrays(GL_TRIANGLE_STRIP, 24, 4);

        switch (currentPlayerAnimation)
        {
        case PlayerAnimation::Idle:
            drawingIndex = 0;
            break;
        case PlayerAnimation::Run:
            animationTime += deltaTime;
            if (animationTime >= timePeriod)
            {
                animationTime = 0.f;

                drawingIndex += 4;
                if (drawingIndex >= 16)
                {
                    drawingIndex = 4;
                }
            }
            break;
        case PlayerAnimation::Jump:
            drawingIndex = 16;
            break;
        case PlayerAnimation::Dead:
            break;
        default:
            cout << "Uknown player animation" << endl;
            break;
        }

        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTranslatef(playerBody->GetPosition().x * WORLD_SCALE,
                     playerBody->GetPosition().y * WORLD_SCALE, 0.f);
        glScalef(playerDirection * 16.f, 16.f, 1.f);
        glDrawArrays(GL_TRIANGLE_STRIP, drawingIndex, 4);

        glfwSwapBuffers(window);
    }

    glfwTerminate();
    return 0;
}