diff options
author | Clyne Sullivan <clyne@bitgloo.com> | 2019-09-17 13:55:22 -0400 |
---|---|---|
committer | Clyne Sullivan <clyne@bitgloo.com> | 2019-09-17 13:55:22 -0400 |
commit | ceda39e4bd2e3a7794f0cb4f96df1de6ebee47d2 (patch) | |
tree | ba5451b6dcade324114d145d526e7c5b5465689a | |
parent | dbb26902ed54ce308fdcec4697616e152f2894fd (diff) | |
parent | 0236eb7f6391c9d925dcaaddb8cb01ecfb7d5e55 (diff) |
update with master; using VBOs for fonts
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Assets/world.png | bin | 0 -> 236 bytes | |||
-rw-r--r-- | Assets/world_normal.png | bin | 0 -> 1204 bytes | |||
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | Scripts/init.lua | 5 | ||||
-rw-r--r-- | Scripts/world.lua | 101 | ||||
-rw-r--r-- | src/engine.cpp | 19 | ||||
-rw-r--r-- | src/events/world.hpp | 43 | ||||
-rw-r--r-- | src/main.cpp | 1 | ||||
-rw-r--r-- | src/physics.hpp | 2 | ||||
-rw-r--r-- | src/render.cpp | 39 | ||||
-rw-r--r-- | src/render.hpp | 23 | ||||
-rw-r--r-- | src/script.cpp | 18 | ||||
-rw-r--r-- | src/script.hpp | 10 | ||||
-rw-r--r-- | src/shader.hpp | 1 | ||||
-rw-r--r-- | src/text.cpp | 114 | ||||
-rw-r--r-- | src/text.hpp | 44 | ||||
-rw-r--r-- | src/texture.cpp | 128 | ||||
-rw-r--r-- | src/texture.hpp | 15 | ||||
-rw-r--r-- | src/world.cpp | 220 | ||||
-rw-r--r-- | src/world.hpp | 156 | ||||
-rwxr-xr-x | todo.sh | 19 |
22 files changed, 863 insertions, 100 deletions
@@ -4,4 +4,4 @@ out .*.swo *.o *.json - +TODOS diff --git a/Assets/world.png b/Assets/world.png Binary files differnew file mode 100644 index 0000000..c972c87 --- /dev/null +++ b/Assets/world.png diff --git a/Assets/world_normal.png b/Assets/world_normal.png Binary files differnew file mode 100644 index 0000000..e5f013c --- /dev/null +++ b/Assets/world_normal.png @@ -33,7 +33,8 @@ The libraries used to develop gamedev2 are as follows: * [sol2](https://github.com/ThePhD/sol2) * [EntityX](https://github.com/alecthomas/entityx) * [FreeType 2](https://www.freetype.org/) - +* [SOIL](https://lonesock.net/soil.html) +* [cereal](https://uscilab.github.io/cereal/) ## Building gamedev2 ### Build Requirements diff --git a/Scripts/init.lua b/Scripts/init.lua index 2eeee18..65513ec 100644 --- a/Scripts/init.lua +++ b/Scripts/init.lua @@ -131,6 +131,9 @@ wall = { } } +-- Create the world +dofile("Scripts/world.lua") + birdSpawn = game.spawn(bird); dogSpawn = game.spawn(cat); @@ -158,8 +161,6 @@ game.spawn({ end }); -dofile("Scripts/world.lua") - ------------------- -- SERIALIZING -- ------------------- diff --git a/Scripts/world.lua b/Scripts/world.lua index db0dc70..8fb3136 100644 --- a/Scripts/world.lua +++ b/Scripts/world.lua @@ -1,35 +1,92 @@ world = { - Registry = { - dirt = { - id = "world0:dirt", - texture = "Assets/dirt.png", - normal = "Assets/dirt_normal.png" - }, - stone = { - id = "world0:stone", - texture = "Assets/stone.png", - normal = "Assets/dirt_normal.png" - } - }, Seed = 5345345, - Layers = 3, + Layers = 2, + + -- This is run when the world is registered and not after, + -- although it is possible to register materials later + Register = function(self) + + -- TODO make world have global textures to speed up rendering + + self:registerMaterial("grass", { + -- TODO combine both of these into 1 + texture = { + file = "Assets/world.png", + offset = { x = 0, y = 0 }, + size = { x = 8, y = 8 } + }, + normal = { + file = "Assets/world_normal.png", + offset = { x = 0, y = 0 }, + size = { x = 8, y = 8 } + } + }); + self:registerMaterial("dirt", { + texture = { + file = "Assets/world.png", + offset = { x = 8, y = 0 }, + size = { x = 8, y = 8 } + }, + normal = { + file = "Assets/world_normal.png", + offset = { x = 8, y = 0 }, + size = { x = 8, y = 8 } + } + }); + self:registerMaterial("stone", { + texture = { + file = "Assets/world.png", + offset = { x = 16, y = 0 }, + size = { x = 8, y = 8 } + }, + normal = { + file = "Assets/world_normal.png", + offset = { x = 16, y = 0 }, + size = { x = 8, y = 8 } + } + }); + self:registerMaterial("flower", { + texture = { + file = "Assets/world.png", + offset = { x = 24, y = 0 }, + size = { x = 8, y = 8 } + }, + normal = { + file = "Assets/world_normal.png", + offset = { x = 24, y = 0 }, + size = { x = 8, y = 8 } + }, + passable = true + }); + end, + Generate = function(self) - self.data = {} - for Z = 0,2 do - self.data[Z] = {} - for X = 0,250 do - self.data[Z][X] = {} + math.randomseed(self.Seed) + xsize, ysize, zsize = self:setSize(250, 128, 3) + --self.data = {} + for Z = 0,zsize-1 do + --self.data[Z] = {} + for X = 0,xsize-1 do + --self.data[Z][X] = {} YGen = math.floor(6*math.sin(X/20) + Z) + 64 - for Y = 0,128 do + YDepth = math.random(2,5) + for Y = 0,ysize-1 do if Y == YGen then - self.data[Z][X][Y] = 1 + self:setData(X, Y, Z, "grass"); + elseif Y < YGen and Y > (YGen - YDepth) then + self:setData(X, Y, Z, "dirt"); elseif Y < YGen then - self.data[Z][X][Y] = 2 + --self:setData(X, Y, Z, "stone"); + self:setData(X, Y, Z, "grass"); end + --print(X..","..Y..","..Z); end end end + self:setData(1000, 1345, 5, "grass"); -- Test error checking + print("Done with world gen"); end } -world:Generate() +--world:Generate() +game.worldRegister(world) diff --git a/src/engine.cpp b/src/engine.cpp index 37f6e68..70b0b45 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -45,8 +45,9 @@ int Engine::init(void) systems.add<GameRunSystem>(); systems.add<InputSystem>(); systems.add<PlayerSystem>(entities); + systems.add<WorldSystem>(); systems.add<RenderSystem>(); - systems.add<ScriptSystem>(entities); + systems.add<ScriptSystem>(entities, *(systems.system<WorldSystem>().get())); systems.add<PhysicsSystem>(); systems.add<TextSystem>(); systems.configure(); @@ -65,13 +66,19 @@ int Engine::init(void) "it." << std::endl; } + // Initially update the world to send all systems world data + systems.update<WorldSystem>(0); return 0; } void Engine::logicLoop(void) { entityx::TimeDelta dt = 0; /**< Elapsed milliseconds since each loop */ - double elapsed = 0; + double elapsed = 1000; /**< Time elapsed since last logic loop. This + should be initialized to something larger + than our logic loop period (50ms), so + the logic loop is run during our first + loop. */ while (shouldRun()) { auto start = mc::now(); @@ -86,6 +93,8 @@ void Engine::logicLoop(void) // Update 20 times a second if (elapsed > 50) { elapsed = 0; + + systems.update<WorldSystem>(dt); // All entities with an idle function should be run here entities.each<Scripted>([](entityx::Entity, Scripted &f){ @@ -155,9 +164,11 @@ void Engine::run(void) GameState::save("save.json", entities); // Remove all Lua references from entities - entities.each<Scripted>([](entityx::Entity, Scripted &f){ f.cleanup(); }); - entities.each<EventListener>([](entityx::Entity, EventListener &f){ + entities.each<Scripted>([](entityx::Entity, Scripted &f) { + f.cleanup(); }); + entities.each<EventListener>([](entityx::Entity, EventListener &f) { f.cleanup(); }); + systems.system<WorldSystem>()->cleanup(); } bool Engine::shouldRun(void) diff --git a/src/events/world.hpp b/src/events/world.hpp new file mode 100644 index 0000000..e5969c0 --- /dev/null +++ b/src/events/world.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2019 Belle-Isle, Andrew <drumsetmonkey@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef EVENTS_WORLD_HPP_ +#define EVENTS_WORLD_HPP_ + +#include "world.hpp" + +struct WorldChangeEvent +{ + World* newWorld; + + WorldChangeEvent(World* w) : + newWorld(w) {} +}; + +struct WorldMeshUpdateEvent +{ + GLuint worldVBO; + unsigned int numVertex; + GLuint worldTexture; + GLuint worldNormal; + + WorldMeshUpdateEvent(GLuint v, unsigned int p, + GLuint t, GLuint n) : + worldVBO(v), numVertex(p), worldTexture(t), worldNormal(n) {} +}; + +#endif//EVENTS_WORLD_HPP diff --git a/src/main.cpp b/src/main.cpp index a0632fd..5f39d95 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,6 +18,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +#define GLEW_STATIC #define SOL_ALL_SAFETIES_ON = 1 #include "engine.hpp" diff --git a/src/physics.hpp b/src/physics.hpp index 63443c4..8231d6d 100644 --- a/src/physics.hpp +++ b/src/physics.hpp @@ -1,6 +1,6 @@ /** * @file position.hpp - * Manages all Lua scripts. + * Manages all entity movements * * Copyright (C) 2019 Belle-Isle, Andrew <drumsetmonkey@gmail.com> * diff --git a/src/render.cpp b/src/render.cpp index ceef025..b991b2f 100644 --- a/src/render.cpp +++ b/src/render.cpp @@ -27,6 +27,7 @@ void RenderSystem::configure([[maybe_unused]] entityx::EntityManager& entities, [[maybe_unused]] entityx::EventManager& events) { + events.subscribe<WorldMeshUpdateEvent>(*this); init(); } @@ -69,7 +70,7 @@ void RenderSystem::update([[maybe_unused]] entityx::EntityManager& entities, ); glm::mat4 model = glm::mat4(1.0f); - model = glm::scale(model, glm::vec3(2.0f)); + model = glm::scale(model, glm::vec3(20.0f, 20.0f, 1.0f)); glUseProgram(s); @@ -178,7 +179,29 @@ void RenderSystem::update([[maybe_unused]] entityx::EntityManager& entities, 5*sizeof(float), (void*)(3*sizeof(float))); glDrawArrays(GL_TRIANGLES, 0, 6); }); - + + // If we were given a world VBO render it + if (worldVBO) { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, worldTexture); + glUniform1i(q, 0); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, worldNormal); + glUniform1i(n, 1); + + glBindBuffer(GL_ARRAY_BUFFER, worldVBO); + //glBufferData(GL_ARRAY_BUFFER, + // wm.size() * sizeof(WorldMeshData), + // &wm.front(), + // GL_STREAM_DRAW); + + glVertexAttribPointer(a, 3, GL_FLOAT, GL_FALSE, + 6*sizeof(float), 0); + glVertexAttribPointer(t, 2, GL_FLOAT, GL_FALSE, + 6*sizeof(float), (void*)(3*sizeof(float))); + glDrawArrays(GL_TRIANGLES, 0, worldVertex); + } /************* * CLEANUP * @@ -261,8 +284,18 @@ int RenderSystem::init(void) //glClearColor(0.6, 0.8, 1.0, 0.0); - camPos = glm::vec3(0.0f, 0.0f, 0.5f); + camPos = glm::vec3(0.0f, 0.0f, 5.0f); return 0; } +/************ +* EVENTS * +************/ +void RenderSystem::receive(const WorldMeshUpdateEvent &wmu) +{ + worldVBO = wmu.worldVBO; + worldVertex = wmu.numVertex; + worldTexture = wmu.worldTexture; + worldNormal = wmu.worldNormal; +} diff --git a/src/render.hpp b/src/render.hpp index 0e4275e..26e525b 100644 --- a/src/render.hpp +++ b/src/render.hpp @@ -22,11 +22,11 @@ #ifndef SYSTEM_RENDER_HPP_ #define SYSTEM_RENDER_HPP_ -#include "shader.hpp" - #include <entityx/entityx.h> +#include <GL/glew.h> #include <SDL2/SDL.h> +#include <SDL2/SDL_opengl.h> #define GLM_FORCE_RADIANS #include <glm/glm.hpp> @@ -34,7 +34,12 @@ #include <glm/gtc/type_ptr.hpp> #include <glm/gtc/noise.hpp> -class RenderSystem : public entityx::System<RenderSystem> +#include "shader.hpp" +#include "world.hpp" +#include "events/world.hpp" + +class RenderSystem : public entityx::System<RenderSystem>, + public entityx::Receiver<RenderSystem> { private: constexpr static const char *title = "gamedev2"; @@ -47,8 +52,12 @@ private: Shader worldShader; glm::vec3 camPos; + GLuint worldVBO = 0; + unsigned int worldVertex = 0; + GLuint worldTexture = 0; + GLuint worldNormal = 0; public: - RenderSystem(void) : + RenderSystem() : window(nullptr, SDL_DestroyWindow) {} ~RenderSystem(void) @@ -75,6 +84,12 @@ public: * @return Zero on success, non-zero on error */ int init(void); + + /************ + * EVENTS * + ************/ + void receive(const WorldMeshUpdateEvent &wmu); + }; #endif // SYSTEM_RENDER_HPP_ diff --git a/src/script.cpp b/src/script.cpp index 61aa136..ec8f5c7 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -87,6 +87,12 @@ void ScriptSystem::doFile(void) void ScriptSystem::scriptExport(void) { + std::function<sol::table(sol::table)> entitySpawn = + [this](sol::table t){ return spawn(t);}; + + std::function<World* (sol::object)> worldRegister = + [this](sol::object t){ return worldSystem.addWorld(t); }; + lua.new_usertype<Position>("Position", sol::constructors<Position(double x, double y), Position()>(), "x", &Position::x, @@ -121,8 +127,18 @@ void ScriptSystem::scriptExport(void) sol::constructors<Physics(void), Physics()>(), "standing", &Physics::standing); + lua.new_usertype<World>("World", + sol::constructors<World(sol::object), World(void)>(), + "Generate", &World::generate, + "Seed", sol::property(&World::setSeed, &World::getSeed), + "setData", &World::setData, + "registerMaterial", &World::registerMaterial, + "setSize", &World::setSize, + "getSize", &World::getSize); + game = lua["game"].get_or_create<sol::table>(); - game.set_function("spawn", [this](sol::table t) { return spawn(t); }); + game.set_function("spawn", entitySpawn); + game.set_function("worldRegister", worldRegister); } sol::table ScriptSystem::spawn(sol::object param) diff --git a/src/script.hpp b/src/script.hpp index f86c8cd..0ac9e63 100644 --- a/src/script.hpp +++ b/src/script.hpp @@ -24,6 +24,8 @@ #include <entityx/entityx.h> #include <sol/sol.hpp> +#include "world.hpp" + struct EntitySpawnEvent { sol::object ref; @@ -48,10 +50,14 @@ private: sol::table game; entityx::EntityManager& manager; + + // TODO possibly emit events to spawn worlds instead of passing the + // world system around like a toy + WorldSystem &worldSystem; public: - ScriptSystem(entityx::EntityManager& _manager): - manager(_manager) {} + ScriptSystem(entityx::EntityManager& _mg, WorldSystem& _ws): + manager(_mg), worldSystem(_ws) {} ~ScriptSystem(void) {} diff --git a/src/shader.hpp b/src/shader.hpp index 8691ab0..2a93f9c 100644 --- a/src/shader.hpp +++ b/src/shader.hpp @@ -24,6 +24,7 @@ #include <unordered_map> #include <GL/glew.h> +#include <SDL2/SDL_opengl.h> class Shader { diff --git a/src/text.cpp b/src/text.cpp index 85c0ceb..8726b88 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -2,10 +2,6 @@ #include <iostream> -//FT_Library freetype; -//std::map<std::string, FT_Face> fonts; -//std::map<std::string, std::vector<FT_Info>> fontData; - void TextSystem::configure([[maybe_unused]] entityx::EntityManager& entities, [[maybe_unused]] entityx::EventManager& events) { @@ -28,6 +24,8 @@ void TextSystem::loadFont(const std::string& name, const std::string& file, int size) { + // Find or load font at given size + // if (fonts.find(file) == fonts.end()) { FT_Face face; @@ -39,34 +37,88 @@ void TextSystem::loadFont(const std::string& name, auto& face = fonts[file]; FT_Set_Pixel_Sizes(face, 0, size); - fontData.try_emplace(name, 95); - - char c = 32; - for (auto& d : fontData[name]) { - FT_Load_Char(face, c++, FT_LOAD_RENDER); - - glGenTextures(1, &d.tex); - glBindTexture(GL_TEXTURE_2D, d.tex); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER , GL_LINEAR); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER , GL_LINEAR); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - // convert red-on-black to RGBA - auto& g = face->glyph; - std::vector<uint32_t> buf (g->bitmap.width * g->bitmap.rows, 0xFFFFFF); - for (auto j = buf.size(); j--;) - buf[j] |= g->bitmap.buffer[j] << 24; - - d.wh = { g->bitmap.width, g->bitmap.rows }; - d.bl = { g->bitmap_left, g->bitmap_top }; - d.ad = { g->advance.x >> 6, g->advance.y >> 6 }; - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, g->bitmap.width, g->bitmap.rows, - 0, GL_RGBA, GL_UNSIGNED_BYTE, buf.data()); + fontData.try_emplace(name); + + // Calculate dimensions of final texture + // + + float width = 0, height = 0; + for (int c = 32; c < 128; c++) { + FT_Load_Char(face, c, FT_LOAD_RENDER); + width += face->glyph->bitmap.width + 1; + height = std::max(height, static_cast<float>(face->glyph->bitmap.rows)); } - std::cout << "Loaded font: " << file << " (size: " << size << ')' - << std::endl; + // Generate texture to hold entire font + // + + auto& font = fontData[name]; + glGenTextures(1, &font.tex); + glGenBuffers(1, &font.vbo); + glBindTexture(GL_TEXTURE_2D, font.tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width, height, + 0, GL_RED, GL_UNSIGNED_BYTE, 0); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + // // convert red-on-black to RGBA + // auto& g = face->glyph; + // std::vector<uint32_t> buf (g->bitmap.width * g->bitmap.rows, 0xFFFFFF); + // for (auto j = buf.size(); j--;) + // buf[j] |= g->bitmap.buffer[j] << 24; + + // Load each character and add it to the texture + // + + float offsetX = 0, offsetY = 0; + for (int c = 32; c < 128; c++) { + FT_Load_Char(face, c, FT_LOAD_RENDER); + + auto* g = face->glyph; + glTexSubImage2D(GL_TEXTURE_2D, 0, offsetX, offsetY, + g->bitmap.width, g->bitmap.rows, + GL_RED, GL_UNSIGNED_BYTE, + g->bitmap.buffer); + + auto& d = font.data[c - 32]; + d.dim = { g->bitmap.width, g->bitmap.rows }; + d.bitmap = { g->bitmap_left, g->bitmap_top }; + d.advance = { g->advance.x >> 6, g->advance.y >> 6 }; + + d.offset = { offsetX / width, offsetY / height }; + offsetX += g->bitmap.width; + // Keep offsetY at zero? + } + + std::cout << "Loaded font: " << file << " (size: " << size << ", tex: " + << font.tex << ")" << std::endl; +} + +void TextSystem::updateVBOs(void) +{ + for (auto& data : fontData) { + auto& d = data.second; + d.buffer.clear(); + for (auto& text : d.text) { + for (char c : text.text) { + if (c < 32) + continue; + + d.buffer += { + text.x, text.y, text.z, + d.data[c].offset.first, d.data[c].offset.second, + 1.0f + }; + } + } + + glBindBuffer(GL_ARRAY_BUFFER, d.vbo); + glBufferData(GL_ARRAY_BUFFER, + d.text.size() * sizeof(TextMeshData), d.text.data(), + GL_STREAM_DRAW); + } } diff --git a/src/text.hpp b/src/text.hpp index d7fb790..f456500 100644 --- a/src/text.hpp +++ b/src/text.hpp @@ -24,28 +24,46 @@ #include <entityx/entityx.h> #include <ft2build.h> #include <freetype/freetype.h> +#include <GL/glew.h> #include <SDL2/SDL_opengl.h> - #include <map> #include <string> #include <tuple> #include <vector> +struct TextMeshData +{ + float posX, posY, posZ; + float texX, texY; + float transparency; +} __attribute__ ((packed)); + struct FT_Info { - std::pair<float, float> wh; - std::pair<float, float> bl; - std::pair<float, float> ad; - GLuint tex; + std::pair<float, float> offset; + std::pair<float, float> dim; + std::pair<float, float> bitmap; + std::pair<float, float> advance; +}; - FT_Info(void) - : tex(0) {} +struct Text { + std::string text; + float x; + float y; + float z; +}; + +// Stores texture and placement data for a font at a size. +struct Font { + GLuint tex; + GLuint vbo; + + std::array<FT_Info, 96> data; + // Stores currently shown text at given index into VBO? + std::vector<Text> text; + std::basic_string<TextMeshData> buffer; }; -/** - * @class PhysicsSystem - * Handles the position and velocity updating of all entities - */ class TextSystem : public entityx::System<TextSystem> { public: @@ -67,7 +85,9 @@ public: private: FT_Library freetype; std::map<std::string, FT_Face> fonts; - std::map<std::string, std::vector<FT_Info>> fontData; + std::map<std::string, Font> fontData; + + void updateVBOs(void); }; #endif // SYSTEM_TEXT_HPP_ diff --git a/src/texture.cpp b/src/texture.cpp index 5604812..2870b9f 100644 --- a/src/texture.cpp +++ b/src/texture.cpp @@ -1,20 +1,118 @@ +/** + * @file texture.cpp + * Handles all texture loading + * + * Copyright (C) 2019 Belle-Isle, Andrew <drumsetmonkey@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + #include "texture.hpp" +#include <unordered_map> +#include <iostream> + +struct TextureData +{ + GLuint tex = 0; + int width = 0; + int height= 0; +}; + +// Stores a list of all textures we've already loaded. This makes sure we don't +// waste our precious CPU cycles reloading a texture. +std::unordered_map<std::string, TextureData> textureCache; + +TextureData loadTexture(std::string filename) +{ + TextureData tex; + + // Search to see if this texture has already been loading + auto cacheSearch = textureCache.find(filename); + + if (cacheSearch == textureCache.end()) { + // If this texture hasn't been loading + + unsigned char* image = SOIL_load_image(filename.c_str(), + &(tex.width), &(tex.height), 0, + SOIL_LOAD_RGBA); + + glGenTextures(1, &(tex.tex)); + glBindTexture(GL_TEXTURE_2D, tex.tex); + glPixelStoref(GL_UNPACK_ALIGNMENT, 1); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.width, tex.height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, image); + + SOIL_free_image_data(image); + + // Add new texture to the texture cache + textureCache.emplace(filename, tex); + + } else { + // If this texture has been loaded, just return the loaded texture + tex = cacheSearch->second; + } + + return tex; +} + +void Texture::loadFromString(std::string filename) +{ + TextureData d = loadTexture(filename); + + tex = d.tex; + width = d.width; + height = d.height; +} + Texture::Texture(std::string filename) { - unsigned char* image = SOIL_load_image(filename.c_str(), - &width, &height, 0, - SOIL_LOAD_RGBA); - - glGenTextures(1, &tex); // Turns "object" into a texture - glBindTexture(GL_TEXTURE_2D, tex); // Binds "object" to the top of the stack - glPixelStoref(GL_UNPACK_ALIGNMENT, 1); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Sets the "min" filter - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // The the "max" filter of the stack - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // Wrap the texture to the matrix - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, - GL_RGBA, GL_UNSIGNED_BYTE, image); - - SOIL_free_image_data(image); + loadFromString(filename); +} + +Texture::Texture(sol::object param) +{ + if (param.get_type() == sol::type::string) { + loadFromString(param.as<std::string>()); + } else if (param.get_type() == sol::type::table) { + sol::table tab = param; + + // If there is a filename given, load that file to get image data + if (tab["file"] == sol::type::string) + loadFromString(tab.get<std::string>("file")); + else + return; // If we don't have image data just return a null image + + if (tab["offset"] == sol::type::table) { + sol::table off = tab["offset"]; + if (off["x"] == sol::type::number) + offset.x = off.get<float>("x")/width; + if (off["y"] == sol::type::number) + offset.y = off.get<float>("y")/height; + } + + if (tab["size"] == sol::type::table) { + sol::table siz = tab["size"]; + if (siz["x"] == sol::type::number) + size.x = siz.get<float>("x")/width; + if (siz["y"] == sol::type::number) + size.y = siz.get<float>("y")/height; + } + } } diff --git a/src/texture.hpp b/src/texture.hpp index 16987f8..3daebbd 100644 --- a/src/texture.hpp +++ b/src/texture.hpp @@ -21,18 +21,31 @@ #define TEXTURE_HPP_ #include <soil/SOIL.h> + +#include <sol/sol.hpp> + +#include <GL/glew.h> #include <SDL2/SDL_opengl.h> +#include <glm/glm.hpp> + #include <string> class Texture { private: + void loadFromString(std::string); public: - GLuint tex; + GLuint tex = 0; + + glm::vec2 offset = glm::vec2(0); + glm::vec2 size = glm::vec2(1); + int width; int height; + Texture() {}; Texture(std::string); + Texture(sol::object); }; #endif//TEXTURE_HPP_ diff --git a/src/world.cpp b/src/world.cpp new file mode 100644 index 0000000..7710ecc --- /dev/null +++ b/src/world.cpp @@ -0,0 +1,220 @@ +/** + * @file world.cpp + * + * Copyright (C) 2019 Belle-Isle, Andrew <drumsetmonkey@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "world.hpp" +#include "events/world.hpp" + +/***************** +* WORLD CLASS * +*****************/ +/* CONSTRUCTORS */ +World::World(sol::object param) +{ + if (param.get_type() == sol::type::table) { + sol::table tab = param; + + if (tab["Seed"] == sol::type::number) { + seed = tab["Seed"]; + } + if (tab["Layers"] == sol::type::number) { + layers = tab["Layers"]; + } + if (tab["Register"] == sol::type::function) { + registerMat = tab["Register"]; + } + if (tab["Generate"] == sol::type::function) { + generate = tab["Generate"]; + } + + } else { + // TODO better logging + std::cerr << "World paramaters must be stored in a table" << std::endl; + } + + // If there is a register function, we should call it here + if (registerMat != sol::nil) + registerMat(this); + + // If a generate function is defined, call it + if (generate != sol::nil) + generate(this); + + // Create our world VBO + glGenBuffers(1, &worldVBO); + // Generate our mesh + generateMesh(); +} + +/* REGISTRY */ +void World::registerMaterial(std::string name, sol::object data) +{ + if (data.get_type() == sol::type::table) { + sol::table tab = data; + + // Make sure this material has not been registered before + auto it = string_registry.find(name); + if (it == string_registry.end()) { + string_registry.emplace(name, registry.size()); + registry.push_back(WorldMaterial(tab)); + } else { + std::cerr << "Material: " << name + << " was already registered" << std::endl; + } + } else { + // TODO better logging + std::cerr << "Material registration must have a table" << std::endl; + } +} + +/* DATA */ +void World::setData(unsigned int x, + unsigned int y, + unsigned int z, + std::string d) +{ + unsigned int discovered = -1; + + auto found = string_registry.find(d); + if (found != string_registry.end()) + discovered = found->second; + + try { + data.at(z).at(x).at(y) = discovered; + } catch (std::out_of_range &oor) { + // Make sure any assignments that are outsize specified world size are + // caught to avoid any seg faults + std::cerr << "Unable to set data at: " + << x << "," << y << "," << z + << " Exception: " << oor.what() << std::endl; + } +} + +/* SIZE */ +std::tuple<unsigned int, unsigned int, unsigned int> +World::setSize(unsigned int x, unsigned int y, unsigned int z) +{ + width = x; + height = y; + layers = z; + + data = std::vector<std::vector<std::vector<int>>> + (z, std::vector<std::vector<int>> + (x,std::vector<int> + (y, -1) + ) + ); + + return {width, height, layers}; +} + +std::tuple<unsigned int, unsigned int, unsigned int> +World::getSize() +{ + return {width, height, layers}; +} + +/* RENDERING */ +void World::generateMesh() +{ + //const unsigned int voxelLength = 6; // 2 triangles @ 3 points each + + // Preallocate size of vertexes + mesh = std::basic_string<WorldMeshData>(); + for (float Z = 0; Z < data.size(); Z++) { + for (float X = 0; X < data.at(Z).size(); X++) { + for (float Y = 0; Y < data.at(Z).at(X).size(); Y++) { + int d = data.at(Z).at(X).at(Y); + + if (d == -1) // Don't make a mesh for air of course + continue; + + Texture &t = registry.at(d).texture; + glm::vec2& to = t.offset; + glm::vec2& ts = t.size; + + mesh += {X , Y , Z, to.x , to.y+ts.y, 1.0}; + mesh += {X+1, Y , Z, to.x+ts.x, to.y+ts.y, 1.0}; + mesh += {X , Y+1, Z, to.x , to.y , 1.0}; + + mesh += {X+1, Y , Z, to.x+ts.x, to.y+ts.y, 1.0}; + mesh += {X+1, Y+1, Z, to.x+ts.x, to.y , 1.0}; + mesh += {X , Y+1, Z, to.x , to.y , 1.0}; + } + } + } + + glBindBuffer(GL_ARRAY_BUFFER, worldVBO); + glBufferData(GL_ARRAY_BUFFER, + mesh.size() * sizeof(mesh), + &mesh.front(), + GL_STREAM_DRAW); + + meshUpdated = true; +} + +/* SEED */ +unsigned int World::getSeed() +{ + return seed; +} + +unsigned int World::setSeed(unsigned int s) +{ + seed = s; + return seed; +} + + +/****************** +* WORLD SYSTEM * +******************/ + +World* WorldSystem::addWorld(sol::object t) +{ + worlds.push_back(World(t)); + if (currentWorld == nullptr) + currentWorld = &(worlds.back()); + return &(worlds.back()); +} + +void WorldSystem::configure([[maybe_unused]]entityx::EntityManager& entities, + [[maybe_unused]]entityx::EventManager& events) +{ + +} + +void WorldSystem::update([[maybe_unused]]entityx::EntityManager& entities, + [[maybe_unused]]entityx::EventManager& events, + [[maybe_unused]]entityx::TimeDelta dt) +{ + if (currentWorld == nullptr) { + currentWorld = &(worlds.front()); + events.emit<WorldChangeEvent>(currentWorld); + std::cout << "Emitted" << std::endl; + } + + if (currentWorld->meshUpdated) { + events.emit<WorldMeshUpdateEvent>( + currentWorld->worldVBO, + currentWorld->mesh.size(), + currentWorld->getTexture(), + currentWorld->getNormal() + ); + } +} diff --git a/src/world.hpp b/src/world.hpp new file mode 100644 index 0000000..696f0aa --- /dev/null +++ b/src/world.hpp @@ -0,0 +1,156 @@ +/** + * @file world.hpp + * Manages the world systems + * + * Copyright (C) 2019 Belle-Isle, Andrew <drumsetmonkey@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SYSTEM_WORLD_HPP_ +#define SYSTEM_WORLD_HPP_ + +#include <vector> + +#include <entityx/entityx.h> +#include <sol/sol.hpp> + +#include "texture.hpp" + +struct WorldMeshData +{ + float posX, posY, posZ; + float texX, texY; + float transparency; +}__attribute__((packed)); + +struct WorldMaterial +{ + bool passable = false; + + Texture texture; + Texture normal; + + WorldMaterial(sol::table tab) { + if (tab["texture"] != nullptr) { + sol::object t = tab["texture"]; + texture = Texture(t); + } + if (tab["normal"] != nullptr) { + sol::object n = tab["normal"]; + normal = Texture(n); + } + if (tab["passable"] == sol::type::boolean) { + passable = tab["passable"]; + } + } +}; + +class World +{ + friend class WorldSystem; +private: + unsigned int seed; + unsigned int layers; + + unsigned int height; + unsigned int width; + + std::vector<std::vector<std::vector<int>>> data; + + std::unordered_map<std::string, int> string_registry; + std::vector<WorldMaterial> registry; + +protected: + // RENDER + std::basic_string<WorldMeshData> mesh; + GLuint worldVBO; + bool meshUpdated = false; +public: + /* VARS */ + sol::function generate; + sol::function registerMat; + + World() {} + World(sol::object ref); + ~World() { + registerMat = sol::nil; + generate = sol::nil; + registry.clear(); + data.clear(); + } + + /* REGISTRY */ + void registerMaterial(std::string, sol::object); + + /* DATA */ + void setData(unsigned int, unsigned int, unsigned int, std::string); + + /* SIZE */ + std::tuple<unsigned int, unsigned int, unsigned int> setSize(unsigned int, + unsigned int, + unsigned int); + std::tuple<unsigned int, unsigned int, unsigned int> getSize(); + + /* RENDERING */ + void generateMesh(); + std::basic_string<WorldMeshData>& getMesh() {return mesh;} + GLuint getTexture() {return registry.at(0).texture.tex;} + GLuint getNormal() {return registry.at(0).normal.tex;}; + + /* SEED */ + unsigned int getSeed(); + unsigned int setSeed(unsigned int); +}; + +/** + * @class WorldSystem + * Handles the game's world system + */ +class WorldSystem : public entityx::System<WorldSystem> +{ +private: + std::vector<World> worlds; + World* currentWorld; +public: + WorldSystem(void): + currentWorld(nullptr) {} + + ~WorldSystem(void) { + currentWorld = nullptr; + worlds.clear(); + } + + World* addWorld(sol::object); + World* current() {return currentWorld;}; + void cleanup() + { + worlds.clear(); + } + + /** + * Prepares the system for running. + */ + void configure(entityx::EntityManager& entities, + entityx::EventManager& events) final; + + /** + * Updates the world ticks (entity spawns and world events) + */ + void update(entityx::EntityManager& entites, + entityx::EventManager& events, + entityx::TimeDelta dt) final; +}; + +#endif // SYSTEM_WORLD_HPP_ @@ -0,0 +1,19 @@ +#!/bin/bash + +# +# Searches for all TODOs and tosses them in a file. +# +TODO_COUNT=0 +rm -f TODOS +touch TODOS + +for file in $(find {src,Shaders,Scripts} -type f -name '*' -print) +do + echo "########################################" >> TODOS + echo $file >> TODOS + echo "========================================" >> TODOS + grep -n -B 5 -A 5 "TODO" $file | sed s/--/========================================/g >> TODOS + TODO_COUNT=$((TODO_COUNT+$(grep -c "TODO" $file))) +done + +echo "Found" $TODO_COUNT "TODOs." |