Skip to content

Instantly share code, notes, and snippets.

@ConnorRigby
Last active July 8, 2023 22:41
Show Gist options
  • Save ConnorRigby/40eea1bb5b14305548e8b021ab3e373e to your computer and use it in GitHub Desktop.
Save ConnorRigby/40eea1bb5b14305548e8b021ab3e373e to your computer and use it in GitHub Desktop.
Example TMX TileMap renderer with C++, OpenGL, GLM, GLFW, Glad and stb
all: test
test: test.cpp
$(CXX) test.cpp -o test -lglfw -lGL -lX11 -lpthread -lXrandr -lXi -ldl -Ilib/tmx/src/ -I glad/include -I lib/stb/ -Llib/tmx/build -ltmx -lxml2 -lz glad/src/glad.c
run: test
./test untitled.tmx
#include <stdio.h>
#include <tmx.h>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#include <vector>
#define DISPLAY_W 1920
#define DISPLAY_H 1080
unsigned int image_data_width_in_pixels = 0;
unsigned int image_data_height_in_pixels = 0;
const char* vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
uniform mat4 projection;
void main()
{
gl_Position = projection * vec4(aPos, 0.0, 1.0);
TexCoord = aTexCoord;
}
)";
const char* fragmentShaderSource = R"(
#version 330 core
in vec2 TexCoord;
out vec4 FragColor;
uniform sampler2D texture1;
void main()
{
FragColor = texture(texture1, TexCoord);
}
)";
static GLuint vertexArray, vertexBuffer, indexBuffer;
static GLuint shaderProgram;
static GLint projectionLocation;
struct Vertex {
glm::vec2 position;
glm::vec2 texCoord;
};
std::vector<Vertex> vertices;
std::vector<unsigned int> indices;
static GLFWwindow* window;
void errorCallback(int error, const char* description)
{
fputs(description, stderr);
}
void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
glfwSetWindowShouldClose(window, GLFW_TRUE);
}
}
GLuint createShader(GLenum type, const char* source)
{
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, NULL);
glCompileShader(shader);
GLint success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
char infoLog[512];
glGetShaderInfoLog(shader, 512, NULL, infoLog);
printf("Shader compilation error:\n%s\n", infoLog);
}
return shader;
}
GLuint createShaderProgram(const char* vertexShaderSource, const char* fragmentShaderSource)
{
GLuint vertexShader = createShader(GL_VERTEX_SHADER, vertexShaderSource);
GLuint fragmentShader = createShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
GLint success;
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
char infoLog[512];
glGetProgramInfoLog(program, 512, NULL, infoLog);
printf("Shader program linking error:\n%s\n", infoLog);
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return program;
}
void initializeOpenGL()
{
if (!glfwInit()) {
fputs("GLFW initialization failed\n", stderr);
return;
}
glfwSetErrorCallback(errorCallback);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
window = glfwCreateWindow(DISPLAY_W, DISPLAY_H, "OpenGL Example", NULL, NULL);
if (!window) {
fputs("GLFW window creation failed\n", stderr);
glfwTerminate();
return;
}
glfwSetKeyCallback(window, keyCallback);
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
fputs("GLAD initialization failed\n", stderr);
glfwDestroyWindow(window);
glfwTerminate();
return;
}
// glViewport(0, 0, DISPLAY_W, DISPLAY_H);
// Create vertex array object (VAO)
glGenVertexArrays(1, &vertexArray);
glBindVertexArray(vertexArray);
// Create vertex buffer object (VBO)
glGenBuffers(1, &vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glGenBuffers(1, &indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
// Specify the vertex attributes
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, position));
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, texCoord));
glEnableVertexAttribArray(1);
// Compile and link the shader program
shaderProgram = createShaderProgram(vertexShaderSource, fragmentShaderSource);
// Get the location of the projection matrix uniform
projectionLocation = glGetUniformLocation(shaderProgram, "projection");
glClear(GL_COLOR_BUFFER_BIT);
}
void render_map(tmx_map* map)
{
// Bind the vertex buffer
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
unsigned int mapWidthTiles = map->width;
unsigned int mapHeightTiles = map->height;
unsigned int tileWidth = map->tile_width;
unsigned int tileHeight = map->tile_height;
unsigned int mapWidth = mapWidthTiles * tileWidth;
unsigned int mapHeight = mapHeightTiles * tileHeight;
unsigned int index = 0;
for (unsigned int i = 0; i < map->height; i++) {
for (unsigned int j = 0; j < map->width; j++) {
unsigned int gid = (map->ly_head->content.gids[(i * map->width) + j]) & TMX_FLIP_BITS_REMOVAL;
if (map->tiles[gid] != nullptr) {
unsigned int x = map->tiles[gid]->ul_x;
unsigned int y = map->tiles[gid]->ul_y;
float texCoordLeft = static_cast<float>(x) / static_cast<float>(image_data_width_in_pixels);
float texCoordBottom = static_cast<float>(y + tileHeight) / static_cast<float>(image_data_height_in_pixels);
float texCoordRight = static_cast<float>(x + tileWidth) / static_cast<float>(image_data_width_in_pixels);
float texCoordTop = static_cast<float>(y) / static_cast<float>(image_data_height_in_pixels);
Vertex v1 = { { j * tileWidth, i * tileHeight },{ texCoordLeft, texCoordTop} };
Vertex v2 = { { j * tileWidth, (i + 1) * tileHeight },{ texCoordLeft, texCoordBottom } };
Vertex v3 = { { (j + 1) * tileWidth, (i + 1) * tileHeight },{ texCoordRight, texCoordBottom } };
Vertex v4 = { { (j + 1) * tileWidth, i * tileHeight },{ texCoordRight, texCoordTop } };
printf("x/y=(%d, %d)\n", x, y);
printf("l=%0.2f t=%0.2f r=%0.2f b=%0.2f\n", texCoordLeft, texCoordTop, texCoordRight, texCoordBottom);
printf("ul=(%0.2f, %0.2f)\nbl=(%0.2f, %0.2f)\nbr=(%0.2f,%0.2f)\ntr=(%0.2f, %0.2f) \n\n",
texCoordLeft, texCoordTop,
texCoordLeft, texCoordBottom,
texCoordRight, texCoordBottom,
texCoordRight, texCoordTop);
vertices.push_back(v1); // top left
vertices.push_back(v2); // bottom left
vertices.push_back(v3); // bottom right
vertices.push_back(v1); // top left
vertices.push_back(v3); // botom right
vertices.push_back(v4); // top right
// first triangle
indices.push_back(index + 0);
indices.push_back(index + 1);
indices.push_back(index + 2);
// second triangle
indices.push_back(index + 3);
indices.push_back(index + 4);
indices.push_back(index + 5);
index += 6;
}
}
}
// Upload the vertex data to the GPU
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), vertices.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW);
}
int main(int argc, char** argv)
{
initializeOpenGL();
// Set the callback globs in the main function
tmx_img_load_func = [](const char* path) {
int width, height, numChannels;
stbi_set_flip_vertically_on_load(0);
unsigned char* data = stbi_load(path, &width, &height, &numChannels, 4);
if (data) {
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
stbi_image_free(data);
image_data_width_in_pixels = width;
image_data_height_in_pixels = height;
return reinterpret_cast<void*>(textureID);
}
return static_cast<void*>(nullptr);
};
tmx_img_free_func = [](void* image) {
if (image) {
GLuint textureID = static_cast<GLuint>(reinterpret_cast<uintptr_t>(image));
glDeleteTextures(1, &textureID);
}
};
tmx_map* map = tmx_load(argv[1]);
if (!map) {
tmx_perror("Cannot load map");
glfwDestroyWindow(window);
glfwTerminate();
return 1;
}
// Set the orthographic projection matrix
glm::mat4 projection = glm::ortho(0.0f, static_cast<float>(DISPLAY_W), static_cast<float>(DISPLAY_H), 0.0f, -1.0f, 1.0f);
glUseProgram(shaderProgram);
glUniformMatrix4fv(projectionLocation, 1, GL_FALSE, &projection[0][0]);
render_map(map);
while (!glfwWindowShouldClose(window)) {
glEnable(GL_DEPTH_TEST);
glClearColor(0.1, 0.1, 0.1, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Draw the tilemap
// glDrawArrays(GL_TRIANGLES, 0, vertices.size());
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
glfwSwapBuffers(window);
glfwPollEvents();
}
tmx_map_free(map);
glDeleteBuffers(1, &vertexBuffer);
glDeleteVertexArrays(1, &vertexArray);
glDeleteProgram(shaderProgram);
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.8" tiledversion="1.8.0" name="tilemap" tilewidth="8" tileheight="8" tilecount="8" columns="4">
<image source="tilemap.png" width="32" height="16"/>
</tileset>
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.8" tiledversion="1.8.0" orientation="orthogonal" renderorder="right-down" width="4" height="4" tilewidth="8" tileheight="8" infinite="0" nextlayerid="2" nextobjectid="1">
<tileset firstgid="1" source="tilemap.tsx"/>
<layer id="1" name="Tile Layer 1" width="4" height="4">
<data encoding="csv">
1,1,2,2,
1,1,2,2,
3,3,1,4,
3,3,5,5
</data>
</layer>
</map>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment