From 7fa0d777af0285886571b35ac0e632be450fe49e Mon Sep 17 00:00:00 2001 From: Joseph Walton-Rivers <joseph@walton-rivers.uk> Date: Sat, 20 Nov 2021 19:10:42 +0000 Subject: [PATCH] add heightmap code --- demo/main.cpp | 18 ++++++- fggl/CMakeLists.txt | 5 +- fggl/data/heightmap.cpp | 107 ++++++++++++++++++++++++++++++++++++++ fggl/data/heightmap.h | 44 ++++++++++++++++ fggl/data/model.cpp | 2 +- fggl/data/model.hpp | 11 ++-- fggl/gfx/ogl/compat.hpp | 24 +++++++-- fggl/gfx/ogl/renderer.cpp | 14 ++++- fggl/gfx/ogl/renderer.hpp | 6 +++ 9 files changed, 215 insertions(+), 16 deletions(-) create mode 100644 fggl/data/heightmap.cpp create mode 100644 fggl/data/heightmap.h diff --git a/demo/main.cpp b/demo/main.cpp index db2e3c7..e14e2d5 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 19de7ba..00b4c99 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 0000000..1009cb6 --- /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 0000000..48908bd --- /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 0cded14..6c0a08a 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 2f57d00..bc90ed1 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 9c792eb..6108dad 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 eff7e8e..1132df9 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 b13647f..2e900cb 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 { -- GitLab