diff --git a/demo/main.cpp b/demo/main.cpp index db2e3c73c20fb23518b2639941b051d50d096395..e14e2d5175f4901e4fcc9e895ee9d2f1a8575ff0 100644 --- a/demo/main.cpp +++ b/demo/main.cpp @@ -133,6 +133,20 @@ public: cameraKeys->rotate_ccw = glfwGetKeyScancode(GLFW_KEY_E); } + { + fggl::ecs3::entity_t terrain = m_world.create(false); + m_world.add(terrain, types.find(fggl::math::Transform::name)); + + auto camTf = m_world.get<fggl::math::Transform>(terrain); + camTf->origin( glm::vec3(0.0f, 0.0f, 3.0f) ); + + //auto terrainData = m_world.get<fggl::data::HeightMap>(terrain); + fggl::data::HeightMap terrainData{}; + terrainData.clear(); + terrainData.heightValues[50 * 255 + 50] = 50.0f; + m_world.set<fggl::data::HeightMap>(terrain, &terrainData); + } + // create building prototype fggl::ecs3::entity_t bunker; { @@ -146,7 +160,7 @@ public: fggl::data::Mesh mesh; for (int j=-(nSections/2); j<=nSections/2; j++) { - const auto shapeOffset = glm::vec3( 0.0f, 0.0f, (float)j * 1.0f ); + const auto shapeOffset = glm::vec3( 0.0f, 0.5f, (float)j * 1.0f ); const auto cubeMat = glm::translate( fggl::math::mat4( 1.0f ) , shapeOffset ); const auto leftSlope = fggl::math::modelMatrix( @@ -170,7 +184,7 @@ public: for ( int i=0; i<nCubes; i++ ) { auto bunkerClone = m_world.copy(bunker); auto result = m_world.get<fggl::math::Transform>(bunkerClone); - result->origin( glm::vec3( (float)i * 5.0f, 0.0f, 0.0f) ); + result->origin( glm::vec3( (float)i * 5.0f + 1.0f, 0.0f, 0.0f) ); } } diff --git a/fggl/CMakeLists.txt b/fggl/CMakeLists.txt index 19de7ba3c24a5bd9d5b4ea8fff119608278d96b0..00b4c99b34b109ac41f89dbf51caf3a22624ef95 100644 --- a/fggl/CMakeLists.txt +++ b/fggl/CMakeLists.txt @@ -4,12 +4,13 @@ add_library(fggl fggl.cpp ecs/ecs.cpp data/model.cpp data/procedural.cpp + data/heightmap.cpp ecs3/fast/Container.cpp ecs3/prototype/world.cpp scenes/Scene.cpp ecs3/module/module.cpp - input/camera_input.cpp -) + input/camera_input.cpp data/heightmap.cpp) + target_include_directories(fggl PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../) # Graphics backend diff --git a/fggl/data/heightmap.cpp b/fggl/data/heightmap.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1009cb677539ef392bcaaf9b3a8b80bfa2233826 --- /dev/null +++ b/fggl/data/heightmap.cpp @@ -0,0 +1,107 @@ +// +// Created by webpigeon on 20/11/2021. +// + +#include <fggl/data/model.hpp> +#include <fggl/data/heightmap.h> + +// adapted from https://www.mbsoftworks.sk/tutorials/opengl4/016-heightmap-pt1-random-terrain/ + +namespace fggl::data { + + void gridVertexNormals(data::Vertex *locations) { + int sizeX = data::heightMaxX; + int sizeY = data::heightMaxZ; + const int gridOffset = sizeX * sizeY; + + // calculate normals for each triangle + math::vec3 triNormals[sizeX * sizeY * 2]; + for (int i = 0; i < sizeX - 1; i++) { + for (int j = 0; j < sizeY - 1; j++) { + // calculate vertex + const auto &a = locations[i * sizeY + j].posititon; + const auto &b = locations[(i + 1) * sizeY + j].posititon; + const auto &c = locations[i * sizeY + (j + 1)].posititon; + const auto &d = locations[(i + 1) * sizeY + (j + 1)].posititon; + + const auto normalA = glm::cross(b - a, a - d); + const auto normalB = glm::cross(d - c, c - b); + + // store the normals + int idx1 = idx(i, j, sizeY); + int idx2 = idx1 + gridOffset; + triNormals[idx1] = glm::normalize(normalA); + triNormals[idx2] = glm::normalize(normalB); + } + } + + // calculate normals for each vertex + for (int i = 0; i < sizeX; i++) { + for (int j = 0; j < sizeY; j++) { + const auto firstRow = (i == 0); + const auto firstCol = (j == 0); + const auto lastRow = (i == sizeX - 1); + const auto lastCol = (i == sizeY - 1); + + auto finalNormal = glm::vec3(0.0f, 0.0f, 0.0f); + + if (!firstRow && !firstCol) { + finalNormal += triNormals[idx(i - 1, j - 1, sizeY)]; + } + + if (!lastRow && !lastCol) { + finalNormal += triNormals[idx(i, j, sizeY)]; + } + + if (!firstRow && lastCol) { + finalNormal += triNormals[idx(i - 1, j, sizeY)]; + finalNormal += triNormals[idx(i - 1, j, sizeY) + gridOffset]; + } + + if (!lastRow && !firstCol) { + finalNormal += triNormals[idx(i, j - 1, sizeY)]; + finalNormal += triNormals[idx(i, j - 1, sizeY) + gridOffset]; + } + + locations[idx(i, j, sizeY)].normal = glm::normalize(finalNormal) * -1.0f; //FIXME the normals seem wrong. + } + } + } + + void generateHeightMesh(const data::HeightMap *heights, data::Mesh &mesh) { + + // step 1: convert height data into vertex locations + data::Vertex locations[data::heightMaxZ * data::heightMaxZ]; + for (std::size_t x = 0; x < data::heightMaxX; x++) { + for (std::size_t z = 0; z < data::heightMaxZ; z++) { + float level = heights->getValue(x, z); + auto xPos = float(x); + auto zPos = float(z); + + std::size_t idx1 = idx(x, z, data::heightMaxZ); + locations[idx1].colour = fggl::math::vec3(1.0f, 1.0f, 1.0f); + locations[idx1].posititon = math::vec3(-0.5f + xPos, level, -0.5f - zPos); + } + } + gridVertexNormals(locations); + + mesh.restartVertex = data::heightMaxZ * data::heightMaxX; + + // populate mesh + for (auto & location : locations) { + mesh.pushVertex(location); + } + + for (std::size_t x = 0; x < data::heightMaxX - 1; x++) { + for (std::size_t z = 0; z < data::heightMaxZ; z++) { + for (int k=0; k < 2; k++) { + int idx = (x+k) * data::heightMaxZ + z; + mesh.pushIndex(idx); + } + } + mesh.pushIndex(mesh.restartVertex); + } + + } + +} diff --git a/fggl/data/heightmap.h b/fggl/data/heightmap.h new file mode 100644 index 0000000000000000000000000000000000000000..48908bde83b4c8876acf134313ab45f2157f4798 --- /dev/null +++ b/fggl/data/heightmap.h @@ -0,0 +1,44 @@ +// +// Created by webpigeon on 20/11/2021. +// + +#ifndef FGGL_HEIGHTMAP_H +#define FGGL_HEIGHTMAP_H + +#include <cstdint> + +namespace fggl::data { + + constexpr std::size_t heightMaxX = 255; + constexpr std::size_t heightMaxZ = 255; + constexpr float heightSeaLevel = 0.0f; + + struct HeightMap { + constexpr static const char name[] = "Heightmap"; + float heightValues[heightMaxX * heightMaxZ]; + + void clear() { + for (float & heightValue : heightValues){ + heightValue = heightSeaLevel; + } + } + + [[nodiscard]] + inline float getValue(std::size_t x, std::size_t z) const { + return heightValues[x * heightMaxZ + z]; + } + + inline void setValue(std::size_t x, std::size_t z, float value) { + heightValues[x * heightMaxZ + z] = value; + } + }; + + + inline int idx(int x, int z, int zMax) { + return x * zMax + z; + } + + void generateHeightMesh(const data::HeightMap* heights, data::Mesh &mesh); +} + +#endif //FGGL_HEIGHTMAP_H diff --git a/fggl/data/model.cpp b/fggl/data/model.cpp index 0cded14df73ec6693b4ae7664089e48934c20ada..6c0a08abc069e3b2a92525af32ed4274b41692a0 100644 --- a/fggl/data/model.cpp +++ b/fggl/data/model.cpp @@ -9,7 +9,7 @@ Mesh::Mesh() : m_verts(0), m_index(0) { } void Mesh::pushIndex(unsigned int idx) { - assert( idx < m_verts.size() ); + assert( idx < m_verts.size() || idx == this->restartVertex ); m_index.push_back(idx); } diff --git a/fggl/data/model.hpp b/fggl/data/model.hpp index 2f57d00b9def77166f3a3cd60ad8ca71770d6a18..bc90ed10d0c934fcba8c89bc3a45c96876e807cd 100644 --- a/fggl/data/model.hpp +++ b/fggl/data/model.hpp @@ -43,10 +43,12 @@ namespace fggl::data { */ inline void push(Vertex vert) { auto idx = indexOf(vert); - if ( idx == -1 ) - pushVertex(vert); - else - pushIndex(idx); + if ( idx == -1 ) { + idx = pushVertex(vert); + pushIndex(idx); + } else { + pushIndex(idx); + } } /** @@ -97,6 +99,7 @@ namespace fggl::data { inline Vertex& vertex(int idx) { return m_verts[idx]; } + unsigned int restartVertex; private: std::vector<Vertex> m_verts; diff --git a/fggl/gfx/ogl/compat.hpp b/fggl/gfx/ogl/compat.hpp index 9c792ebac623b81355ff164e1bb9f9f85a58373e..6108dadf6a4e102e08f17567a920ccf14e3ae587 100644 --- a/fggl/gfx/ogl/compat.hpp +++ b/fggl/gfx/ogl/compat.hpp @@ -20,9 +20,12 @@ #include <fggl/ecs/ecs.hpp> #include <utility> #include <fggl/input/camera_input.h> +#include <fggl/data/heightmap.h> namespace fggl::gfx { + void generateHeightMesh(data::HeightMap* heightMap, data::Mesh); + // // fake module support - allows us to still RAII // @@ -48,9 +51,23 @@ namespace fggl::gfx { world->set<fggl::gfx::GlRenderToken>(entity, &glMesh); } + void uploadHeightmap(ecs3::World *world, ecs::entity_t entity) { + const auto heightmap = world->get<data::HeightMap>(entity); + + data::Mesh tmpMesh{}; + data::generateHeightMesh(heightmap, tmpMesh); + auto glMesh = renderer.upload( tmpMesh ); + + auto pipeline = cache.get("phong"); + glMesh.pipeline = pipeline; + glMesh.renderType = GlRenderType::trinagle_strip; + world->set<fggl::gfx::GlRenderToken>(entity, &glMesh); + } + void onLoad(ecs3::ModuleManager& manager, ecs3::TypeRegistry& types) override { // TODO implement dependencies types.make<fggl::gfx::StaticMesh>(); + types.make<fggl::data::HeightMap>(); types.make<fggl::gfx::Camera>(); // FIXME probably shouldn't be doing this... @@ -62,6 +79,7 @@ namespace fggl::gfx { // callbacks auto upload_cb = [this](auto a, auto b) { this->uploadMesh(a, b); }; manager.onAdd<fggl::gfx::StaticMesh>( upload_cb ); + manager.onAdd<fggl::data::HeightMap>( [this](auto a, auto b) { this->uploadHeightmap(a, b); }); } }; @@ -110,11 +128,7 @@ namespace fggl::gfx { auto camera = cameras[0]; // get the models - auto renderables = ecs.findMatching<fggl::gfx::GlRenderToken>(); - - for ( auto renderable : renderables ) { - mod->renderer.render(ecs, camera, dt); - } + mod->renderer.render(ecs, camera, dt); } } diff --git a/fggl/gfx/ogl/renderer.cpp b/fggl/gfx/ogl/renderer.cpp index eff7e8e9cdf06b7a1712c54a0d9d6f2dd2175003..1132df950a168e7bdfc684afd629a062587dc5fd 100644 --- a/fggl/gfx/ogl/renderer.cpp +++ b/fggl/gfx/ogl/renderer.cpp @@ -47,6 +47,7 @@ static GLuint createIndexBuffer(std::vector<uint32_t>& indexData) { return buffId; } + GlRenderToken MeshRenderer::upload(fggl::data::Mesh& mesh) { GlRenderToken token{}; glGenVertexArrays(1, &token.vao); @@ -61,6 +62,7 @@ GlRenderToken MeshRenderer::upload(fggl::data::Mesh& mesh) { token.buffs[1] = createIndexBuffer( mesh.indexList() ); token.idxOffset = 0; token.idxSize = mesh.indexCount(); + token.restartVertex = mesh.restartVertex; glBindVertexArray(0); return token; @@ -116,10 +118,18 @@ void MeshRenderer::render(fggl::ecs3::World& ecs, ecs3::entity_t camera, float d glUniform3fv( lightID, 1, glm::value_ptr( lightPos ) ); glBindVertexArray( mesh->vao ); - glDrawElements( GL_TRIANGLES, mesh->idxSize, GL_UNSIGNED_INT, reinterpret_cast<void*>(mesh->idxOffset) ); + + if ( mesh->renderType == GlRenderType::trinagle_strip) { + glEnable(GL_PRIMITIVE_RESTART); + glPrimitiveRestartIndex(mesh->restartVertex); + } + + glDrawElements( mesh->renderType, mesh->idxSize, GL_UNSIGNED_INT, reinterpret_cast<void*>(mesh->idxOffset) ); + if ( mesh->renderType == GlRenderType::trinagle_strip) { + glDisable(GL_PRIMITIVE_RESTART); + } } glBindVertexArray(0); - } diff --git a/fggl/gfx/ogl/renderer.hpp b/fggl/gfx/ogl/renderer.hpp index b13647f27a844bc007f27776d809f322843b137f..2e900cb4df7297d2f1444ed20c5b986d7d942164 100644 --- a/fggl/gfx/ogl/renderer.hpp +++ b/fggl/gfx/ogl/renderer.hpp @@ -9,6 +9,10 @@ namespace fggl::gfx { + enum GlRenderType { + triangles = GL_TRIANGLES, trinagle_strip = GL_TRIANGLE_STRIP + }; + struct GlRenderToken { constexpr static const char name[] = "RenderToken"; GLuint vao; @@ -16,6 +20,8 @@ namespace fggl::gfx { GLuint idxOffset; GLuint idxSize; GLuint pipeline; + GLuint restartVertex; + GlRenderType renderType = triangles; }; class GlMeshRenderer {