- Created a Texture Atlas class
- Added a secondary load method to texture that accepts a byte[] - Disable multisampling as it causes a problem with the atlas - Added TODO to fix the multisampling problem latermaster
parent
ee20219bf1
commit
0698bb3dd6
|
@ -7,6 +7,7 @@ include_directories(Libraries/glew/include/GL)
|
|||
include_directories(Libraries/glfw_linux/include)
|
||||
include_directories(Libraries/glm)
|
||||
include_directories(Libraries/stb_image)
|
||||
include_directories(Libraries/cute)
|
||||
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(glfw3 REQUIRED)
|
||||
|
@ -41,7 +42,7 @@ add_executable(GlProject
|
|||
GlProject/mesh/Vertex.cpp
|
||||
GlProject/mesh/Vertex.h
|
||||
GlProject/mesh/BlockModel.cpp
|
||||
GlProject/mesh/BlockModel.h)
|
||||
GlProject/mesh/BlockModel.h GlProject/engine/TextureAtlas.cpp GlProject/engine/TextureAtlas.h)
|
||||
|
||||
target_link_libraries(GlProject
|
||||
${OPENGL_gl_LIBRARY}
|
||||
|
|
|
@ -17,14 +17,14 @@
|
|||
#include "mesh/MeshGenerator.h"
|
||||
#include "engine/Entity.h"
|
||||
#include "engine/Timer.h"
|
||||
#include "engine/TextureAtlas.h"
|
||||
|
||||
Window* window;
|
||||
Shader* shader;
|
||||
std::vector<Entity*> entities;
|
||||
Camera* camera;
|
||||
|
||||
Texture* brickTexture;
|
||||
Texture* dirtTexture;
|
||||
TextureAtlas* atlas;
|
||||
|
||||
GLfloat deltaTime = 0.0f;
|
||||
GLfloat lastTime = 0.0f;
|
||||
|
@ -41,7 +41,7 @@ BlockModel* createBlockModel() {
|
|||
0, 1, 2, 2, 3, 0
|
||||
};
|
||||
|
||||
auto* topPart = new MeshPart(topVerts, 4, topInds, 6, "dirt");
|
||||
auto* topPart = new MeshPart(topVerts, 4, topInds, 6, "default_dirt", atlas);
|
||||
|
||||
return new BlockModel(nullptr, nullptr, topPart, nullptr, nullptr, nullptr, nullptr, true, true);
|
||||
}
|
||||
|
@ -82,14 +82,10 @@ int main() {
|
|||
window->initialize();
|
||||
|
||||
//Create camera
|
||||
camera = new Camera(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0, 1, 0), -90.0f, 0.0f, 10.0f, 0.1f);
|
||||
camera = new Camera(glm::vec3(0.0f, 16.0f, 0.0f), glm::vec3(0, 1, 0), -90.0f, -45.0f, 10.0f, 0.1f);
|
||||
|
||||
//Load Textures
|
||||
brickTexture = new Texture((char*)"../Textures/brick.png");
|
||||
brickTexture->load();
|
||||
|
||||
dirtTexture = new Texture((char*)"../Textures/default_dirt.png");
|
||||
dirtTexture->load();
|
||||
atlas = new TextureAtlas("../Textures");
|
||||
|
||||
//Create model
|
||||
BlockModel* model = createBlockModel();
|
||||
|
@ -101,9 +97,11 @@ int main() {
|
|||
shader = new Shader();
|
||||
shader->createFromFile("../GlProject/shader/world.vs", "../GlProject/shader/world.fs");
|
||||
|
||||
glm::mat4 projectionMatrix = glm::perspective(45.0f, window->getBufferWidth() / window->getBufferHeight(), 0.1f, 100.0f);
|
||||
glm::mat4 projectionMatrix = glm::perspective(45.0f, window->getBufferWidth() / window->getBufferHeight(), 0.1f, 1000.0f);
|
||||
|
||||
glEnable(GL_CULL_FACE);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
//Game Loop
|
||||
while (!window->getShouldClose()) {
|
||||
|
@ -129,7 +127,7 @@ int main() {
|
|||
glUniformMatrix4fv(shader->getProjectionLocation(), 1, GL_FALSE, glm::value_ptr(projectionMatrix));
|
||||
glUniformMatrix4fv(shader->getViewLocation(), 1, GL_FALSE, glm::value_ptr(camera->calculateViewMatrix()));
|
||||
|
||||
dirtTexture->use();
|
||||
atlas->getTexture()->use();
|
||||
|
||||
for (auto &entity : entities) {
|
||||
glUniformMatrix4fv(shader->getModelLocation(), 1, GL_FALSE, glm::value_ptr(entity->getModelMatrix()));
|
||||
|
@ -141,7 +139,7 @@ int main() {
|
|||
//Finish Drawing
|
||||
window->swapBuffers();
|
||||
|
||||
t.elapsedInMs();
|
||||
// t.elapsedInMs();
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
//
|
||||
// Created by aurailus on 06/12/18.
|
||||
//
|
||||
|
||||
#define CUTE_FILES_IMPLEMENTATION
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
|
||||
#include <cute_files.h>
|
||||
#include <stb_image.h>
|
||||
#include <stb_image_write.h>
|
||||
|
||||
#include "TextureAtlas.h"
|
||||
|
||||
struct TextureRef {
|
||||
TextureRef() = default;
|
||||
|
||||
char path[CUTE_FILES_MAX_PATH];
|
||||
char name[CUTE_FILES_MAX_FILENAME];
|
||||
int width;
|
||||
int height;
|
||||
int bitDepth;
|
||||
unsigned char* texData;
|
||||
};
|
||||
|
||||
TextureAtlas::TextureAtlas() {
|
||||
maxTexSize = 0;
|
||||
}
|
||||
|
||||
//The Texture Atlas finds all textures in the specified directory and creates texture pages out of them.
|
||||
TextureAtlas::TextureAtlas(const char* directory) {
|
||||
|
||||
//Get the max size of a texture on the system and store it in maxTexSize. This variable is the amount of *bytes*
|
||||
//wide and tall (to be verified) so it needs to be divided by the amount of bytes 1 pixel takes in the used format.
|
||||
//Since we're using RGBA it would be divided by 4.
|
||||
|
||||
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize);
|
||||
std::cout << "This GPU's max texture size is: " << maxTexSize / 4 << "px^2." << std::endl;
|
||||
|
||||
//Using cute_files to open the directory and recursively search for files.
|
||||
//Filter the files by extension and add all of them to a vector as TextureRefs.
|
||||
cf_dir_t dir;
|
||||
cf_dir_open(&dir, (std::string(directory) + std::string("/game")).c_str());
|
||||
|
||||
std::list<TextureRef> textureRefs;
|
||||
//
|
||||
// //Load Missing Texture
|
||||
// auto msg = TextureRef();
|
||||
// strcpy(msg.path, "../Textures/_missing.png");
|
||||
// strcpy(msg.name, "_missing.png");
|
||||
// msg.texData = stbi_load(msg.path, &msg.width, &msg.height, &msg.bitDepth, 4);
|
||||
// textureRefs.push_back(msg);
|
||||
|
||||
//Iterate though the files
|
||||
while (dir.has_next) {
|
||||
cf_file_t file;
|
||||
cf_read_file(&dir, &file);
|
||||
|
||||
if (!file.is_dir && strcmp(file.ext, ".png") == 0) {
|
||||
|
||||
// printf("Processing Texture: %s\n", file.name);
|
||||
|
||||
auto ref = TextureRef();
|
||||
strcpy(ref.path, file.path);
|
||||
strcpy(ref.name, file.name);
|
||||
|
||||
//Load the image and put it into the TextureRef
|
||||
ref.texData = stbi_load(file.path, &ref.width, &ref.height, &ref.bitDepth, 4);
|
||||
|
||||
textureRefs.push_back(ref);
|
||||
}
|
||||
|
||||
cf_dir_next(&dir);
|
||||
}
|
||||
|
||||
cf_dir_close(&dir);
|
||||
|
||||
//Sort by height descending, so that we get the largest images out of the way first. Also that means that images
|
||||
//organized by height, decreasing the inefficiency caused by having 1 or two tall images in rows of smaller ones.
|
||||
textureRefs.sort([](const TextureRef &a, const TextureRef &b) {
|
||||
return (a.height > b.height);
|
||||
});
|
||||
|
||||
//Define the texpage width and height
|
||||
int pageWidth = 512;
|
||||
int pageHeight = 512;
|
||||
|
||||
//Define that pagedata array and clean the memory it contains
|
||||
auto* pageData = new unsigned char[pageWidth*4 * pageHeight];
|
||||
|
||||
for (int i = 0; i < pageWidth * pageHeight; i++) {
|
||||
pageData[i * 4 + 0] = 0;
|
||||
pageData[i * 4 + 1] = 0;
|
||||
pageData[i * 4 + 2] = 0;
|
||||
pageData[i * 4 + 3] = 0;
|
||||
}
|
||||
|
||||
//Add all of the textures to the atlas and store the UVs in the associative array
|
||||
int widthOffset = 0;
|
||||
int heightOffset = 0;
|
||||
int tallestInRow = 0;
|
||||
|
||||
for (auto i : textureRefs) {
|
||||
if (widthOffset + i.width >= pageWidth) {
|
||||
widthOffset = 0;
|
||||
heightOffset += tallestInRow;
|
||||
tallestInRow = i.height;
|
||||
}
|
||||
else {
|
||||
tallestInRow = std::max(tallestInRow, i.height);
|
||||
}
|
||||
|
||||
std::string name = std::string(i.name);
|
||||
size_t extensionStart = name.find_last_of('.');
|
||||
name = name.substr(0, extensionStart);
|
||||
|
||||
glm::vec4 uv = glm::vec4(widthOffset / (double)pageWidth, heightOffset / (double)pageHeight,
|
||||
(widthOffset + i.width) / (double)pageWidth, (heightOffset + i.height) / (double)pageHeight);
|
||||
|
||||
textures.insert(std::pair<std::string, glm::vec4>(name, uv));
|
||||
|
||||
for (int y = 0; y < i.height; y++) {
|
||||
for (int x = 0; x < i.width; x++) {
|
||||
pageData[(heightOffset + y)*pageWidth*4 + (widthOffset + x)*4 + 0] = i.texData[(y * i.width * 4) + (x * 4) + 0];
|
||||
pageData[(heightOffset + y)*pageWidth*4 + (widthOffset + x)*4 + 1] = i.texData[(y * i.width * 4) + (x * 4) + 1];
|
||||
pageData[(heightOffset + y)*pageWidth*4 + (widthOffset + x)*4 + 2] = i.texData[(y * i.width * 4) + (x * 4) + 2];
|
||||
pageData[(heightOffset + y)*pageWidth*4 + (widthOffset + x)*4 + 3] = i.texData[(y * i.width * 4) + (x * 4) + 3];
|
||||
}
|
||||
}
|
||||
|
||||
widthOffset += i.width;
|
||||
}
|
||||
|
||||
stbi_write_png("../atlas.png", pageWidth, pageHeight, 4, pageData, pageWidth*4);
|
||||
texture = new Texture();
|
||||
texture->load(pageData, pageWidth, pageHeight);
|
||||
}
|
||||
|
||||
Texture* TextureAtlas::getTexture() {
|
||||
return texture;
|
||||
}
|
||||
glm::vec4* TextureAtlas::getUVs(std::string* texture) {
|
||||
if (textures.count(*texture) == 0) {
|
||||
std::cout << "Texture '" << *texture << "' Not found in atlas! Terminating." << std::endl;
|
||||
throw "Texture not found error";
|
||||
}
|
||||
return &textures.at(*texture);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// Created by aurailus on 06/12/18.
|
||||
//
|
||||
|
||||
#ifndef GLPROJECT_TEXTUREATLAS_H
|
||||
#define GLPROJECT_TEXTUREATLAS_H
|
||||
|
||||
#include <glew.h>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <vec4.hpp>
|
||||
#include <map>
|
||||
|
||||
#include "graphics/Texture.h"
|
||||
|
||||
class TextureAtlas {
|
||||
public:
|
||||
TextureAtlas();
|
||||
explicit TextureAtlas(const char* directory);
|
||||
|
||||
Texture* getTexture();
|
||||
glm::vec4* getUVs(std::string* texture);
|
||||
private:
|
||||
GLint maxTexSize;
|
||||
|
||||
Texture* texture;
|
||||
std::map<std::string, glm::vec4> textures;
|
||||
};
|
||||
|
||||
|
||||
#endif //GLPROJECT_TEXTUREATLAS_H
|
|
@ -35,9 +35,10 @@ int Window::initialize() {
|
|||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
||||
|
||||
//TODO: Fix msaa for texture altases.
|
||||
//MSAA
|
||||
glfwWindowHint(GLFW_SAMPLES, 16);
|
||||
glEnable(GL_MULTISAMPLE);
|
||||
// glfwWindowHint(GLFW_SAMPLES, 16);
|
||||
// glEnable(GL_MULTISAMPLE);
|
||||
|
||||
//Create the window
|
||||
mainWindow = glfwCreateWindow(width, height, "OpenGL Learning", nullptr, nullptr);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Created by aurailus on 29/11/18.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include "Texture.h"
|
||||
|
||||
Texture::Texture() = default;
|
||||
|
@ -32,13 +33,29 @@ void Texture::load() {
|
|||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData);
|
||||
|
||||
// glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
stbi_image_free(texData);
|
||||
}
|
||||
|
||||
void Texture::load(unsigned char* bytes, int width, int height) {
|
||||
glGenTextures(1, &textureID);
|
||||
glBindTexture(GL_TEXTURE_2D, textureID);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
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, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bytes);
|
||||
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
void Texture::use() {
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, textureID);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//
|
||||
|
||||
// Created by aurailus on 29/11/18.
|
||||
//
|
||||
|
||||
|
@ -16,6 +15,7 @@ public:
|
|||
explicit Texture(char* fileLocation);
|
||||
|
||||
void load();
|
||||
void load(unsigned char* bytes, int width, int height);
|
||||
void use();
|
||||
void clear();
|
||||
|
||||
|
|
|
@ -5,16 +5,16 @@
|
|||
#include <iostream>
|
||||
#include "MeshPart.h"
|
||||
|
||||
MeshPart::MeshPart(Vertex* vertices, int vSize, unsigned int* indices, int iSize, const char* texture, MeshMod meshMod, float modValue) {
|
||||
construct(vertices, vSize, indices, iSize, texture, meshMod, modValue);
|
||||
MeshPart::MeshPart(Vertex* vertices, int vSize, unsigned int* indices, int iSize, const char* texture, MeshMod meshMod, float modValue, TextureAtlas* atlas) {
|
||||
construct(vertices, vSize, indices, iSize, texture, meshMod, modValue, atlas);
|
||||
}
|
||||
|
||||
MeshPart::MeshPart(Vertex* vertices, int vSize, unsigned int* indices, int iSize, const char* texture) {
|
||||
construct(vertices, vSize, indices, iSize, texture, NONE, 0);
|
||||
MeshPart::MeshPart(Vertex* vertices, int vSize, unsigned int* indices, int iSize, const char* texture, TextureAtlas* atlas) {
|
||||
construct(vertices, vSize, indices, iSize, texture, NONE, 0, atlas);
|
||||
}
|
||||
|
||||
//Add normals and compute tex coordinates for the given Vertex array.
|
||||
void MeshPart::construct(Vertex* vertices, int vSize, unsigned int *indices, int iSize, const char* texture, MeshMod meshMod, float modValue) {
|
||||
void MeshPart::construct(Vertex* vertices, int vSize, unsigned int *indices, int iSize, const char* texture, MeshMod meshMod, float modValue, TextureAtlas* atlas) {
|
||||
|
||||
//Set the meshMod and modValue variables on the MeshPart.
|
||||
this->meshMod = meshMod;
|
||||
|
@ -45,12 +45,16 @@ void MeshPart::construct(Vertex* vertices, int vSize, unsigned int *indices, int
|
|||
p3->nml = nml;
|
||||
}
|
||||
|
||||
auto texString = std::string(texture);
|
||||
auto uv = atlas->getUVs(&texString);
|
||||
// std::cout << uv->x << ", " << uv->y << ", " << uv->z << ", " << uv->w << ", " << std::endl;
|
||||
|
||||
//Iterate through the vertices to adjust the texture coordinates to fit the atlas.
|
||||
for (int i = 0; i < vSize; i++) {
|
||||
Vertex* vertex = &vertices[i];
|
||||
|
||||
// vertex->tex->x /= 2;
|
||||
// vertex->tex->y /= 2;
|
||||
vertex->tex->x = uv->x + ((uv->z - uv->x) * vertex->tex->x);
|
||||
vertex->tex->y = uv->y + ((uv->w - uv->y) * vertex->tex->y);
|
||||
}
|
||||
|
||||
//Assign the inputted values to the struct
|
||||
|
|
|
@ -11,14 +11,15 @@
|
|||
#include <gtx/normal.hpp>
|
||||
#include "MeshMod.h"
|
||||
#include "Vertex.h"
|
||||
#include "../engine/TextureAtlas.h"
|
||||
|
||||
class MeshVertexIter;
|
||||
class MeshIndexIter;
|
||||
|
||||
struct MeshPart {
|
||||
public:
|
||||
MeshPart(Vertex* vertices, int vSize, unsigned int* indices, int iSize, const char* texture, MeshMod meshMod, float modValue);
|
||||
MeshPart(Vertex* vertices, int vSize, unsigned int* indices, int iSize, const char* texture);
|
||||
MeshPart(Vertex* vertices, int vSize, unsigned int* indices, int iSize, const char* texture, MeshMod meshMod, float modValue, TextureAtlas* atlas);
|
||||
MeshPart(Vertex* vertices, int vSize, unsigned int* indices, int iSize, const char* texture, TextureAtlas* atlas);
|
||||
|
||||
int getVertexCount();
|
||||
Vertex* getVertex(int index);
|
||||
|
@ -34,7 +35,7 @@ public:
|
|||
void cleanup();
|
||||
~MeshPart();
|
||||
private:
|
||||
void construct(Vertex* vertices, int vSize, unsigned int* indices, int iSize, const char* texture, MeshMod meshMod, float modValue);
|
||||
void construct(Vertex* vertices, int vSize, unsigned int* indices, int iSize, const char* texture, MeshMod meshMod, float modValue, TextureAtlas* atlas);
|
||||
|
||||
float modValue;
|
||||
|
||||
|
|
|
@ -8,5 +8,8 @@ out vec4 fragColor;
|
|||
uniform sampler2D tex;
|
||||
|
||||
void main() {
|
||||
fragColor = texture(tex, fragTex) * color;
|
||||
vec2 texCoord = fragTex;
|
||||
// texCoord.x *= 0.03125;
|
||||
// texCoord.y *= 0.03125;
|
||||
fragColor = texture(tex, texCoord) * color;
|
||||
}
|
|
@ -17,6 +17,7 @@ void main() {
|
|||
|
||||
vec4 myColor = vec4(1, 1, 1, 0) * (0.8 + abs(nor.x) * 0.15);
|
||||
myColor += nor.y * 0.15;
|
||||
myColor.a = 1;
|
||||
|
||||
color = myColor;
|
||||
fragTex = tex;
|
||||
|
|
|
@ -0,0 +1,515 @@
|
|||
/*
|
||||
------------------------------------------------------------------------------
|
||||
Licensing information can be found at the end of the file.
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
tinyfiles.h - v1.0
|
||||
|
||||
To create implementation (the function definitions)
|
||||
#define CUTE_FILES_IMPLEMENTATION
|
||||
in *one* C/CPP file (translation unit) that includes this file
|
||||
|
||||
Summary:
|
||||
Utility header for traversing directories to apply a function on each found file.
|
||||
Recursively finds sub-directories. Can also be used to iterate over files in a
|
||||
folder manually. All operations done in a cross-platform manner (thx posix!).
|
||||
|
||||
This header does no dynamic memory allocation, and performs internally safe string
|
||||
copies as necessary. Strings for paths, file names and file extensions are all
|
||||
capped, and intended to use primarily the C run-time stack memory. Feel free to
|
||||
modify the defines in this file to adjust string size limitations.
|
||||
|
||||
Read the header for specifics on each function.
|
||||
|
||||
Here's an example to print all files in a folder:
|
||||
cf_dir_t dir;
|
||||
cf_dir_open(&dir, "a");
|
||||
|
||||
while (dir.has_next)
|
||||
{
|
||||
cf_file_t file;
|
||||
cf_read_file(&dir, &file);
|
||||
printf("%s\n", file.name);
|
||||
cf_dir_next(&dir);
|
||||
}
|
||||
|
||||
cf_dir_close(&dir);
|
||||
*/
|
||||
|
||||
#if !defined(CUTE_FILES_H)
|
||||
|
||||
#define CUTE_FILES_WINDOWS 1
|
||||
#define CUTE_FILES_MAC 2
|
||||
#define CUTE_FILES_UNIX 3
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define CUTE_FILES_PLATFORM CUTE_FILES_WINDOWS
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
#elif defined(__APPLE__)
|
||||
#define CUTE_FILES_PLATFORM CUTE_FILES_MAC
|
||||
#else
|
||||
#define CUTE_FILES_PLATFORM CUTE_FILES_UNIX
|
||||
#endif
|
||||
|
||||
#include <string.h> // strerror, strncpy
|
||||
|
||||
// change to 0 to compile out any debug checks
|
||||
#define CUTE_FILES_DEBUG_CHECKS 1
|
||||
|
||||
#if CUTE_FILES_DEBUG_CHECKS
|
||||
|
||||
#include <stdio.h> // printf
|
||||
#include <assert.h> // assert
|
||||
#include <errno.h>
|
||||
#define CUTE_FILES_ASSERT assert
|
||||
|
||||
#else
|
||||
|
||||
#define CUTE_FILES_ASSERT(...)
|
||||
|
||||
#endif // CUTE_FILES_DEBUG_CHECKS
|
||||
|
||||
#define CUTE_FILES_MAX_PATH 1024
|
||||
#define CUTE_FILES_MAX_FILENAME 256
|
||||
#define CUTE_FILES_MAX_EXT 32
|
||||
|
||||
struct cf_file_t;
|
||||
struct cf_dir_t;
|
||||
struct cf_time_t;
|
||||
typedef struct cf_file_t cf_file_t;
|
||||
typedef struct cf_dir_t cf_dir_t;
|
||||
typedef struct cf_time_t cf_time_t;
|
||||
typedef void (cf_callback_t)(cf_file_t* file, void* udata);
|
||||
|
||||
// Stores the file extension in cf_file_t::ext, and returns a pointer to
|
||||
// cf_file_t::ext
|
||||
const char* cf_get_ext(cf_file_t* file);
|
||||
|
||||
// Applies a function (cb) to all files in a directory. Will recursively visit
|
||||
// all subdirectories. Useful for asset management, file searching, indexing, etc.
|
||||
void cf_traverse(const char* path, cf_callback_t* cb, void* udata);
|
||||
|
||||
// Fills out a cf_file_t struct with file information. Does not actually open the
|
||||
// file contents, and instead performs more lightweight OS-specific calls.
|
||||
int cf_read_file(cf_dir_t* dir, cf_file_t* file);
|
||||
|
||||
// Once a cf_dir_t is opened, this function can be used to grab another file
|
||||
// from the operating system.
|
||||
void cf_dir_next(cf_dir_t* dir);
|
||||
|
||||
// Performs lightweight OS-specific call to close internal handle.
|
||||
void cf_dir_close(cf_dir_t* dir);
|
||||
|
||||
// Performs lightweight OS-specific call to open a file handle on a directory.
|
||||
int cf_dir_open(cf_dir_t* dir, const char* path);
|
||||
|
||||
// Compares file last write times. -1 if file at path_a was modified earlier than path_b.
|
||||
// 0 if they are equal. 1 if file at path_b was modified earlier than path_a.
|
||||
int cf_compare_file_times_by_path(const char* path_a, const char* path_b);
|
||||
|
||||
// Retrieves time file was last modified, returns 0 upon failure
|
||||
int cf_get_file_time(const char* path, cf_time_t* time);
|
||||
|
||||
// Compares file last write times. -1 if time_a was modified earlier than path_b.
|
||||
// 0 if they are equal. 1 if time_b was modified earlier than path_a.
|
||||
int cf_compare_file_times(cf_time_t* time_a, cf_time_t* time_b);
|
||||
|
||||
// Returns 1 of file exists, otherwise returns 0.
|
||||
int cf_file_exists(const char* path);
|
||||
|
||||
// Returns 1 if the file's extension matches the string in ext
|
||||
// Returns 0 otherwise
|
||||
int cf_match_ext(cf_file_t* file, const char* ext);
|
||||
|
||||
// Prints detected errors to stdout
|
||||
void cf_do_unit_tests();
|
||||
|
||||
#if CUTE_FILES_PLATFORM == CUTE_FILES_WINDOWS
|
||||
|
||||
#if !defined _CRT_SECURE_NO_WARNINGS
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
#include <Windows.h>
|
||||
|
||||
struct cf_file_t
|
||||
{
|
||||
char path[CUTE_FILES_MAX_PATH];
|
||||
char name[CUTE_FILES_MAX_FILENAME];
|
||||
char ext[CUTE_FILES_MAX_EXT];
|
||||
int is_dir;
|
||||
int is_reg;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
struct cf_dir_t
|
||||
{
|
||||
char path[CUTE_FILES_MAX_PATH];
|
||||
int has_next;
|
||||
HANDLE handle;
|
||||
WIN32_FIND_DATAA fdata;
|
||||
};
|
||||
|
||||
struct cf_time_t
|
||||
{
|
||||
FILETIME time;
|
||||
};
|
||||
|
||||
#elif CUTE_FILES_PLATFORM == CUTE_FILES_MAC || CUTE_FILES_PLATFORM == CUTE_FILES_UNIX
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
struct cf_file_t
|
||||
{
|
||||
char path[CUTE_FILES_MAX_PATH];
|
||||
char name[CUTE_FILES_MAX_FILENAME];
|
||||
char ext[CUTE_FILES_MAX_EXT];
|
||||
int is_dir;
|
||||
int is_reg;
|
||||
int size;
|
||||
struct stat info;
|
||||
};
|
||||
|
||||
struct cf_dir_t
|
||||
{
|
||||
char path[CUTE_FILES_MAX_PATH];
|
||||
int has_next;
|
||||
DIR* dir;
|
||||
struct dirent* entry;
|
||||
};
|
||||
|
||||
struct cf_time_t
|
||||
{
|
||||
time_t time;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#define CUTE_FILES_H
|
||||
#endif
|
||||
|
||||
#ifdef CUTE_FILES_IMPLEMENTATION
|
||||
#ifndef CUTE_FILES_IMPLEMENTATION_ONCE
|
||||
#define CUTE_FILES_IMPLEMENTATION_ONCE
|
||||
|
||||
#define cf_safe_strcpy(dst, src, n, max) cf_safe_strcpy_internal(dst, src, n, max, __FILE__, __LINE__)
|
||||
static int cf_safe_strcpy_internal(char* dst, const char* src, int n, int max, const char* file, int line)
|
||||
{
|
||||
int c;
|
||||
const char* original = src;
|
||||
|
||||
do
|
||||
{
|
||||
if (n >= max)
|
||||
{
|
||||
if (!CUTE_FILES_DEBUG_CHECKS) break;
|
||||
printf("ERROR: String \"%s\" too long to copy on line %d in file %s (max length of %d).\n"
|
||||
, original
|
||||
, line
|
||||
, file
|
||||
, max);
|
||||
CUTE_FILES_ASSERT(0);
|
||||
}
|
||||
|
||||
c = *src++;
|
||||
dst[n] = c;
|
||||
++n;
|
||||
} while (c);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
const char* cf_get_ext(cf_file_t* file)
|
||||
{
|
||||
char* name = file->name;
|
||||
char* period = NULL;
|
||||
while (*name++) if (*name == '.') period = name;
|
||||
if (period) cf_safe_strcpy(file->ext, period, 0, CUTE_FILES_MAX_EXT);
|
||||
else file->ext[0] = 0;
|
||||
return file->ext;
|
||||
}
|
||||
|
||||
void cf_traverse(const char* path, cf_callback_t* cb, void* udata)
|
||||
{
|
||||
cf_dir_t dir;
|
||||
cf_dir_open(&dir, path);
|
||||
|
||||
while (dir.has_next)
|
||||
{
|
||||
cf_file_t file;
|
||||
cf_read_file(&dir, &file);
|
||||
|
||||
if (file.is_dir && file.name[0] != '.')
|
||||
{
|
||||
char path2[CUTE_FILES_MAX_PATH];
|
||||
int n = cf_safe_strcpy(path2, path, 0, CUTE_FILES_MAX_PATH);
|
||||
n = cf_safe_strcpy(path2, "/", n - 1, CUTE_FILES_MAX_PATH);
|
||||
cf_safe_strcpy(path2, file.name, n -1, CUTE_FILES_MAX_PATH);
|
||||
cf_traverse(path2, cb, udata);
|
||||
}
|
||||
|
||||
if (file.is_reg) cb(&file, udata);
|
||||
cf_dir_next(&dir);
|
||||
}
|
||||
|
||||
cf_dir_close(&dir);
|
||||
}
|
||||
|
||||
int cf_match_ext(cf_file_t* file, const char* ext)
|
||||
{
|
||||
return !strcmp(file->ext, ext);
|
||||
}
|
||||
|
||||
#if CUTE_FILES_PLATFORM == CUTE_FILES_WINDOWS
|
||||
|
||||
int cf_read_file(cf_dir_t* dir, cf_file_t* file)
|
||||
{
|
||||
CUTE_FILES_ASSERT(dir->handle != INVALID_HANDLE_VALUE);
|
||||
|
||||
int n = 0;
|
||||
char* fpath = file->path;
|
||||
char* dpath = dir->path;
|
||||
|
||||
n = cf_safe_strcpy(fpath, dpath, 0, CUTE_FILES_MAX_PATH);
|
||||
n = cf_safe_strcpy(fpath, "/", n - 1, CUTE_FILES_MAX_PATH);
|
||||
|
||||
char* dname = dir->fdata.cFileName;
|
||||
char* fname = file->name;
|
||||
|
||||
cf_safe_strcpy(fname, dname, 0, CUTE_FILES_MAX_FILENAME);
|
||||
cf_safe_strcpy(fpath, fname, n - 1, CUTE_FILES_MAX_PATH);
|
||||
|
||||
size_t max_dword = MAXDWORD;
|
||||
file->size = ((size_t)dir->fdata.nFileSizeHigh * (max_dword + 1)) + (size_t)dir->fdata.nFileSizeLow;
|
||||
cf_get_ext(file);
|
||||
|
||||
file->is_dir = !!(dir->fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
|
||||
file->is_reg = !!(dir->fdata.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) ||
|
||||
!(dir->fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void cf_dir_next(cf_dir_t* dir)
|
||||
{
|
||||
CUTE_FILES_ASSERT(dir->has_next);
|
||||
|
||||
if (!FindNextFileA(dir->handle, &dir->fdata))
|
||||
{
|
||||
dir->has_next = 0;
|
||||
DWORD err = GetLastError();
|
||||
CUTE_FILES_ASSERT(err == ERROR_SUCCESS || err == ERROR_NO_MORE_FILES);
|
||||
}
|
||||
}
|
||||
|
||||
void cf_dir_close(cf_dir_t* dir)
|
||||
{
|
||||
dir->path[0] = 0;
|
||||
dir->has_next = 0;
|
||||
if (dir->handle != INVALID_HANDLE_VALUE) FindClose(dir->handle);
|
||||
}
|
||||
|
||||
int cf_dir_open(cf_dir_t* dir, const char* path)
|
||||
{
|
||||
int n = cf_safe_strcpy(dir->path, path, 0, CUTE_FILES_MAX_PATH);
|
||||
n = cf_safe_strcpy(dir->path, "\\*", n - 1, CUTE_FILES_MAX_PATH);
|
||||
dir->handle = FindFirstFileA(dir->path, &dir->fdata);
|
||||
dir->path[n - 3] = 0;
|
||||
|
||||
if (dir->handle == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
printf("ERROR: Failed to open directory (%s): %s.\n", path, strerror(errno));
|
||||
cf_dir_close(dir);
|
||||
CUTE_FILES_ASSERT(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
dir->has_next = 1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int cf_compare_file_times_by_path(const char* path_a, const char* path_b)
|
||||
{
|
||||
FILETIME time_a = { 0 };
|
||||
FILETIME time_b = { 0 };
|
||||
WIN32_FILE_ATTRIBUTE_DATA data;
|
||||
|
||||
if (GetFileAttributesExA(path_a, GetFileExInfoStandard, &data)) time_a = data.ftLastWriteTime;
|
||||
if (GetFileAttributesExA(path_b, GetFileExInfoStandard, &data)) time_b = data.ftLastWriteTime;
|
||||
return CompareFileTime(&time_a, &time_b);
|
||||
}
|
||||
|
||||
int cf_get_file_time(const char* path, cf_time_t* time)
|
||||
{
|
||||
FILETIME initialized_to_zero = { 0 };
|
||||
time->time = initialized_to_zero;
|
||||
WIN32_FILE_ATTRIBUTE_DATA data;
|
||||
if (GetFileAttributesExA(path, GetFileExInfoStandard, &data))
|
||||
{
|
||||
time->time = data.ftLastWriteTime;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cf_compare_file_times(cf_time_t* time_a, cf_time_t* time_b)
|
||||
{
|
||||
return CompareFileTime(&time_a->time, &time_b->time);
|
||||
}
|
||||
|
||||
int cf_file_exists(const char* path)
|
||||
{
|
||||
WIN32_FILE_ATTRIBUTE_DATA unused;
|
||||
return GetFileAttributesExA(path, GetFileExInfoStandard, &unused);
|
||||
}
|
||||
|
||||
#elif CUTE_FILES_PLATFORM == CUTE_FILES_MAC || CUTE_FILES_PLATFORM == CUTE_FILES_UNIX
|
||||
|
||||
int cf_read_file(cf_dir_t* dir, cf_file_t* file)
|
||||
{
|
||||
CUTE_FILES_ASSERT(dir->entry);
|
||||
|
||||
int n = 0;
|
||||
char* fpath = file->path;
|
||||
char* dpath = dir->path;
|
||||
|
||||
n = cf_safe_strcpy(fpath, dpath, 0, CUTE_FILES_MAX_PATH);
|
||||
n = cf_safe_strcpy(fpath, "/", n - 1, CUTE_FILES_MAX_PATH);
|
||||
|
||||
char* dname = dir->entry->d_name;
|
||||
char* fname = file->name;
|
||||
|
||||
cf_safe_strcpy(fname, dname, 0, CUTE_FILES_MAX_FILENAME);
|
||||
cf_safe_strcpy(fpath, fname, n - 1, CUTE_FILES_MAX_PATH);
|
||||
|
||||
if (stat(file->path, &file->info))
|
||||
return 0;
|
||||
|
||||
file->size = file->info.st_size;
|
||||
cf_get_ext(file);
|
||||
|
||||
file->is_dir = S_ISDIR(file->info.st_mode);
|
||||
file->is_reg = S_ISREG(file->info.st_mode);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void cf_dir_next(cf_dir_t* dir)
|
||||
{
|
||||
CUTE_FILES_ASSERT(dir->has_next);
|
||||
dir->entry = readdir(dir->dir);
|
||||
dir->has_next = dir->entry ? 1 : 0;
|
||||
}
|
||||
|
||||
void cf_dir_close(cf_dir_t* dir)
|
||||
{
|
||||
dir->path[0] = 0;
|
||||
if (dir->dir) closedir(dir->dir);
|
||||
dir->dir = 0;
|
||||
dir->has_next = 0;
|
||||
dir->entry = 0;
|
||||
}
|
||||
|
||||
int cf_dir_open(cf_dir_t* dir, const char* path)
|
||||
{
|
||||
cf_safe_strcpy(dir->path, path, 0, CUTE_FILES_MAX_PATH);
|
||||
dir->dir = opendir(path);
|
||||
|
||||
if (!dir->dir)
|
||||
{
|
||||
printf("ERROR: Failed to open directory (%s): %s.\n", path, strerror(errno));
|
||||
cf_dir_close(dir);
|
||||
CUTE_FILES_ASSERT(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
dir->has_next = 1;
|
||||
dir->entry = readdir(dir->dir);
|
||||
if (!dir->dir) dir->has_next = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Warning : untested code! (let me know if it breaks)
|
||||
int cf_compare_file_times_by_path(const char* path_a, const char* path_b)
|
||||
{
|
||||
time_t time_a;
|
||||
time_t time_b;
|
||||
struct stat info;
|
||||
if (stat(path_a, &info)) return 0;
|
||||
time_a = info.st_mtime;
|
||||
if (stat(path_b, &info)) return 0;
|
||||
time_b = info.st_mtime;
|
||||
return (int)difftime(time_a, time_b);
|
||||
}
|
||||
|
||||
// Warning : untested code! (let me know if it breaks)
|
||||
int cf_get_file_time(const char* path, cf_time_t* time)
|
||||
{
|
||||
struct stat info;
|
||||
if (stat(path, &info)) return 0;
|
||||
time->time = info.st_mtime;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Warning : untested code! (let me know if it breaks)
|
||||
int cf_compare_file_times(cf_time_t* time_a, cf_time_t* time_b)
|
||||
{
|
||||
return (int)difftime(time_a->time, time_b->time);
|
||||
}
|
||||
|
||||
// Warning : untested code! (let me know if it breaks)
|
||||
int cf_file_exists(const char* path)
|
||||
{
|
||||
return access(path, F_OK) != -1;
|
||||
}
|
||||
|
||||
#endif // CUTE_FILES_PLATFORM
|
||||
|
||||
#endif // CUTE_FILES_IMPLEMENTATION_ONCE
|
||||
#endif // CUTE_FILES_IMPLEMENTATION
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------
|
||||
This software is available under 2 licenses - you may choose the one you like.
|
||||
------------------------------------------------------------------------------
|
||||
ALTERNATIVE A - zlib license
|
||||
Copyright (c) 2017 Randy Gaul http://www.randygaul.net
|
||||
This software is provided 'as-is', without any express or implied warranty.
|
||||
In no event will the authors be held liable for any damages arising from
|
||||
the use of this software.
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not
|
||||
be misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
------------------------------------------------------------------------------
|
||||
ALTERNATIVE B - Public Domain (www.unlicense.org)
|
||||
This is free and unencumbered software released into the public domain.
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
||||
software, either in source code form or as a compiled binary, for any purpose,
|
||||
commercial or non-commercial, and by any means.
|
||||
In jurisdictions that recognize copyright laws, the author or authors of this
|
||||
software dedicate any and all copyright interest in the software to the public
|
||||
domain. We make this dedication for the benefit of the public at large and to
|
||||
the detriment of our heirs and successors. We intend this dedication to be an
|
||||
overt act of relinquishment in perpetuity of all present and future rights to
|
||||
this software under copyright law.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
Binary file not shown.
Before Width: | Height: | Size: 1.9 MiB |
Binary file not shown.
Before Width: | Height: | Size: 813 B |
Binary file not shown.
Before Width: | Height: | Size: 766 KiB |
Loading…
Reference in New Issue