]> git.tdb.fi Git - libs/game.git/commitdiff
Add components and systems for creating terrains
authorMikko Rasa <tdb@tdb.fi>
Sat, 11 Feb 2023 13:56:06 +0000 (15:56 +0200)
committerMikko Rasa <tdb@tdb.fi>
Sat, 11 Feb 2023 13:56:06 +0000 (15:56 +0200)
source/game/heightmapterrain.cpp [new file with mode: 0644]
source/game/heightmapterrain.h [new file with mode: 0644]
source/game/landscape.cpp [new file with mode: 0644]
source/game/landscape.h [new file with mode: 0644]
source/game/resources.cpp
source/game/setups.mgs
source/gameview/presenter.cpp
source/gameview/terrainmeshcreator.cpp [new file with mode: 0644]
source/gameview/terrainmeshcreator.h [new file with mode: 0644]

diff --git a/source/game/heightmapterrain.cpp b/source/game/heightmapterrain.cpp
new file mode 100644 (file)
index 0000000..8e4d1a2
--- /dev/null
@@ -0,0 +1,53 @@
+#include <limits>
+#include "heightmapterrain.h"
+
+using namespace std;
+
+namespace Msp::Game {
+
+HeightmapTerrain::HeightmapTerrain(Handle<Entity> e, const Setup &s, const LinAl::Vector<float, 2> &size):
+       Component(e),
+       setup(s),
+       grid_size(round(size.x/setup.grid_spacing)+1, round(size.y/setup.grid_spacing)+1)
+{
+       size_t total_count = grid_size.x*grid_size.y;
+       data = new char[total_count*(sizeof(int16_t)+sizeof(uint8_t))];
+       elevations = reinterpret_cast<int16_t *>(data);
+       materials = reinterpret_cast<uint8_t *>(data+total_count*sizeof(int16_t));
+       fill(elevations, elevations+total_count, 0);
+       fill(materials, materials+total_count, 0);
+}
+
+HeightmapTerrain::~HeightmapTerrain()
+{
+       delete[] data;
+}
+
+void HeightmapTerrain::set_elevation(const LinAl::Vector<unsigned, 2> &i, float elev)
+{
+       int16_t q_elev = round(elev/setup.elevation_scale);
+       elevations[i.x+i.y*grid_size.x] = q_elev;
+       ++generation;
+}
+
+void HeightmapTerrain::set_material_index(const LinAl::Vector<unsigned, 2> &i, unsigned mat)
+{
+       materials[i.x+i.y*grid_size.x] = mat;
+       ++generation;
+}
+
+float HeightmapTerrain::get_elevation(const LinAl::Vector<float, 2> &p) const
+{
+       if(p.x<0 || p.y<0 || p.x>=grid_size.x || p.y>=grid_size.y)
+               return -numeric_limits<float>::infinity();
+
+       LinAl::Vector<unsigned, 2> i = p;
+       LinAl::Vector<float, 2> f(p.x-i.x, p.y-i.y);
+
+       unsigned base = i.x+i.y*grid_size.x;
+       float weighted_elev = (elevations[base]*(1-f.x)+elevations[base+1]*f.x)*(1-f.y)+
+               (elevations[base+grid_size.x]*(1-f.x)+elevations[base+grid_size.x+1]*f.x)*f.y;
+       return weighted_elev*setup.elevation_scale;
+}
+
+} // namespace Msp::Game
diff --git a/source/game/heightmapterrain.h b/source/game/heightmapterrain.h
new file mode 100644 (file)
index 0000000..3a9b8a6
--- /dev/null
@@ -0,0 +1,42 @@
+#ifndef MSP_GAME_HEIGHTMAPTERRAIN_H_
+#define MSP_GAME_HEIGHTMAPTERRAIN_H_
+
+#include <msp/core/noncopyable.h>
+#include "component.h"
+#include "mspgame_api.h"
+#include "setups.h"
+
+namespace Msp::Game {
+
+class MSPGAME_API HeightmapTerrain: public Component, public NonCopyable
+{
+public:
+       using Setup = HeightmapTerrainSetup;
+
+private:
+       const Setup &setup;
+       LinAl::Vector<unsigned, 2> grid_size;
+       char *data = nullptr;
+       std::int16_t *elevations = nullptr;
+       std::uint8_t *materials = nullptr;
+       unsigned generation = 0;
+
+public:
+       HeightmapTerrain(Handle<Entity>, const Setup &, const LinAl::Vector<float, 2> &);
+       ~HeightmapTerrain();
+
+       float get_grid_spacing() const { return setup.grid_spacing; }
+       const LinAl::Vector<unsigned, 2> &get_grid_size() const { return grid_size; }
+       LinAl::Vector<float, 2> get_size() const { return { (grid_size.x-1)*setup.grid_spacing, (grid_size.y-1)*setup.grid_spacing }; }
+       void set_elevation(const LinAl::Vector<unsigned, 2> &, float);
+       void set_material_index(const LinAl::Vector<unsigned, 2> &, unsigned);
+       float get_elevation(const LinAl::Vector<unsigned, 2> &i) const { return elevations[i.x+i.y*grid_size.x]*setup.elevation_scale; }
+       float get_elevation(const LinAl::Vector<float, 2> &) const;
+       unsigned get_material_index(const LinAl::Vector<unsigned, 2> &i) const { return materials[i.x+i.y*grid_size.x]; }
+       const std::string &get_technique_name() const { return setup.technique_name; }
+       unsigned get_generation() const { return generation; }
+};
+
+} // namespace Msp::Game
+
+#endif
diff --git a/source/game/landscape.cpp b/source/game/landscape.cpp
new file mode 100644 (file)
index 0000000..1612d44
--- /dev/null
@@ -0,0 +1,60 @@
+#include "landscape.h"
+#include "entity.h"
+#include "root.h"
+#include "transform.h"
+
+using namespace std;
+
+namespace Msp::Game {
+
+Landscape::Landscape(Stage &s, const HeightmapTerrainSetup &t, float b):
+       System(s),
+       subroot(stage.get_root(), Entity::NO_TRANSFORM),
+       terrain_setup(t),
+       block_size(b)
+{
+       float grid_size = block_size/terrain_setup.grid_spacing;
+       grid_size = max(round(grid_size), 1.0f);
+       block_size = grid_size*terrain_setup.grid_spacing;
+}
+
+void Landscape::set_generator(LandscapeGenerator *g)
+{
+       generator = g;
+}
+
+void Landscape::generate(const Geometry::BoundingBox<float, 2> &bounds)
+{
+       const LinAl::Vector<float, 2> &min_pt = bounds.get_minimum_point();
+       const LinAl::Vector<float, 2> &max_pt = bounds.get_maximum_point();
+       LinAl::Vector<int, 2> min_index(round(min_pt.x/block_size), round(min_pt.y/block_size));
+       LinAl::Vector<int, 2> max_index(round(max_pt.x/block_size), round(max_pt.y/block_size));
+       LinAl::Vector<float, 2> block_size_vec(block_size, block_size);
+
+       for(int y=min_index.y; y<=max_index.y; ++y)
+       {
+               auto i = lower_bound(terrains, LinAl::Vector<int, 2>(min_index.x, y), [](const Terrain &t, const LinAl::Vector<int, 2> &n){
+                       return t.index.y<n.y || (t.index.y==n.y && t.index.x<n.x);
+               });
+
+               for(int x=min_index.x; x<=max_index.x; ++x)
+               {
+                       if(i==terrains.end() || i->index.x>x)
+                       {
+                               LinAl::Vector<int, 2> index(x ,y);
+                               LinAl::Vector<float, 2> center = LinAl::Vector<float, 2>(index)*block_size;
+                               Owned<Entity> entity(subroot, compose(center, 0.0f));
+                               Owned<HeightmapTerrain> terrain(entity, terrain_setup, block_size_vec);
+                               if(generator)
+                               {
+                                       LinAl::Vector<float, 2> base = center-block_size_vec/2.0f;
+                                       generator->generate_block({ base, base+block_size_vec }, terrain);
+                               }
+                               i = terrains.emplace(i, index, move(entity), move(terrain));
+                       }
+                       ++i;
+               }
+       }
+}
+
+} // namespace Msp::Game
diff --git a/source/game/landscape.h b/source/game/landscape.h
new file mode 100644 (file)
index 0000000..b31402b
--- /dev/null
@@ -0,0 +1,52 @@
+#ifndef MSP_GAME_LANDSCAPE_H_
+#define MSP_GAME_LANDSCAPE_H_
+
+#include <msp/geometry/boundingbox.h>
+#include <msp/linal/vector.h>
+#include "heightmapterrain.h"
+#include "mspgame_api.h"
+#include "owned.h"
+#include "system.h"
+
+namespace Msp::Game {
+
+class Entity;
+
+class MSPGAME_API LandscapeGenerator
+{
+protected:
+       LandscapeGenerator() = default;
+
+public:
+       virtual void generate_block(const Geometry::BoundingBox<float, 2> &, Handle<HeightmapTerrain>) = 0;
+};
+
+class MSPGAME_API Landscape: public System
+{
+private:
+       struct Terrain
+       {
+               LinAl::Vector<int, 2> index;
+               Owned<Entity> entity;
+               Owned<HeightmapTerrain> terrain;
+       };
+
+       Owned<Entity> subroot;
+       const HeightmapTerrainSetup &terrain_setup;
+       float block_size = 100.0f;
+       LandscapeGenerator *generator = nullptr;
+       std::vector<Terrain> terrains;
+
+public:
+       Landscape(Stage &, const HeightmapTerrainSetup &, float);
+
+       void set_generator(LandscapeGenerator *);
+       void generate(const Geometry::BoundingBox<float, 2> &);
+       LinAl::Vector<float, 3> get_ground_position(const LinAl::Vector<float, 3> &) const;
+
+       void tick(Time::TimeDelta) override { }
+};
+
+} // namespace Msp::Game
+
+#endif
index fb8f1970443e714a00592f45dc523ad71bc01034..9ab482fd703db3ab17af73c3dce10e5fe0c58e36 100644 (file)
@@ -11,6 +11,7 @@ namespace Msp::Game {
 Resources::Resources()
 {
        add_type<CameraSetup>().suffix(".camera.setup");
+       add_type<HeightmapTerrainSetup>().suffix(".hmap.setup");
        add_type<LightSetup>().suffix(".light.setup");
 }
 
index 3392df1ce07a2875bafe38b23ea9c53e5cc663e1..6cad21475d860230277e62353b2a44806fcc671f 100644 (file)
@@ -34,3 +34,10 @@ component MeshSource
 {
        field object_name string;
 };
+
+component HeightmapTerrain
+{
+       field grid_spacing float { default "1.0f"; };
+       field elevation_scale float { default "0.01f"; };
+       field technique_name string;
+};
index 194ac80638f17c4698aef68ecf1488d33b2428b0..06a644dd6bfc9fd159edac7c0caa08998f4f4b9f 100644 (file)
@@ -1,8 +1,10 @@
 #include "presenter.h"
 #include <functional>
 #include <msp/game/events.h>
+#include <msp/game/landscape.h>
 #include <msp/game/stage.h>
 #include "renderer.h"
+#include "terrainmeshcreator.h"
 
 using namespace std;
 
@@ -30,6 +32,8 @@ void Presenter::stage_activated(const Game::Events::StageActivated &event)
 {
        if(!event.stage.get_system<Renderer>())
                systems.push_back(&event.stage.add_system<Renderer>(ref(gl_view)));
+       if(event.stage.get_system<Game::Landscape>() && !event.stage.get_system<TerrainMeshCreator>())
+               systems.push_back(&event.stage.add_system<TerrainMeshCreator>());
 }
 
 } // namespace Msp::GameView
diff --git a/source/gameview/terrainmeshcreator.cpp b/source/gameview/terrainmeshcreator.cpp
new file mode 100644 (file)
index 0000000..b70cded
--- /dev/null
@@ -0,0 +1,224 @@
+#include "terrainmeshcreator.h"
+#include <msp/game/entity.h>
+#include <msp/gl/meshbuilder.h>
+#include <msp/gl/technique.h>
+#include "dynamicmeshsource.h"
+
+using namespace std;
+
+namespace Msp::GameView {
+
+struct TerrainMeshCreator::TerrainMesh
+{
+       Game::Handle<Game::HeightmapTerrain> terrain;
+       Game::Owned<DynamicMeshSource> mesh;
+       unsigned generation = 0;
+};
+
+struct Quad
+{
+       array<uint16_t, 4> materials;
+       array<uint8_t, 4> local_mats;
+       uint16_t batch;
+       array<uint16_t, 5> vertices;
+};
+
+struct Vertex
+{
+       LinAl::Vector<float, 3> position;
+       LinAl::Vector<float, 3> normal;
+       uint16_t material;
+};
+
+
+TerrainMeshCreator::TerrainMeshCreator(Game::Stage &s):
+       System(s),
+       observer(stage.get_event_bus())
+{
+       observer.observe<Game::Events::ComponentCreated>([this](auto &e){ component_created(e); });
+
+       stage.synthesize_initial_events(observer);
+}
+
+// Hide ~vector<TerrainMesh> from the header
+TerrainMeshCreator::~TerrainMeshCreator()
+{ }
+
+void TerrainMeshCreator::component_created(const Game::Events::ComponentCreated &event)
+{
+       if(Game::Handle<Game::HeightmapTerrain> terrain = dynamic_handle_cast<Game::HeightmapTerrain>(event.component))
+       {
+               const GL::Technique &tech = stage.get_resources().get<GL::Technique>(terrain->get_technique_name());
+               GL::VertexFormat fmt = (GL::VERTEX3, GL::TEXCOORD2, GL::NORMAL3,GL::UNSIGNED_BYTE,
+                       GL::GROUP4,GL::UNSIGNED_BYTE, GL::WEIGHT4,GL::UNSIGNED_BYTE, GL::PADDING1,GL::UNSIGNED_BYTE);
+               meshes.emplace_back(terrain, Game::Owned<DynamicMeshSource>(terrain->get_entity(), fmt, tech));
+       }
+}
+
+void TerrainMeshCreator::build_mesh(TerrainMesh &mesh) const
+{
+       Game::Handle<Game::HeightmapTerrain> terrain = mesh.terrain;
+
+       const LinAl::Vector<unsigned, 2> right(1, 0);
+       const LinAl::Vector<unsigned, 2> up(0, 1);
+       const LinAl::Vector<float, 2> half(0.5f, 0.5f);
+
+       const LinAl::Vector<unsigned, 2> &grid_size = terrain->get_grid_size();
+       LinAl::Vector<float, 2> origin = terrain->get_size()*-0.5f;
+       float grid_spacing = terrain->get_grid_spacing();
+       const float dz_scale = -0.5f/grid_spacing;
+
+       vector<Vertex> vertices;
+       unsigned grid_stride = grid_size.x*2-1;
+       vertices.reserve(grid_stride*(grid_size.y-1)+grid_size.x);
+       for(unsigned y=0; y<grid_size.y; ++y)
+       {
+               for(unsigned x=0; x<grid_size.x; ++x)
+               {
+                       LinAl::Vector<unsigned, 2> c(x, y);
+                       float z = terrain->get_elevation(c);
+                       float z_l = (x>0 ? terrain->get_elevation(c-right) : z);
+                       float z_r = (x+1<grid_size.x ? terrain->get_elevation(c+right) : z);
+                       float z_d = (y>0 ? terrain->get_elevation(c-up) : z);
+                       float z_u = (y+1<grid_size.y ? terrain->get_elevation(c+up) : z);
+                       LinAl::Vector<float, 2> dz(z_r-z_l, z_u-z_d);
+
+                       Vertex &v = vertices.emplace_back();
+                       v.position = compose(origin+LinAl::Vector<float, 2>(c), z);
+                       v.normal = normalize(compose(dz*dz_scale, 1.0f));
+                       v.material = terrain->get_material_index(c);
+               }
+
+               if(y+1>=grid_size.y)
+                       break;
+
+               for(unsigned x=0; x+1<grid_size.x; ++x)
+               {
+                       LinAl::Vector<unsigned, 2> c(x, y);
+                       float z_dl = terrain->get_elevation(c);
+                       float z_dr = terrain->get_elevation(c+right);
+                       float z_ul = terrain->get_elevation(c+up);
+                       float z_ur = terrain->get_elevation(c+up+right);
+                       float z = (z_dl+z_dr+z_ul+z_ur)/4;
+                       LinAl::Vector<float, 2> dz(z_dr+z_ur-z_dl-z_ul, z_ul+z_ur-z_dl-z_dr);
+
+                       Vertex &v = vertices.emplace_back();
+                       v.normal = normalize(compose(dz*dz_scale, 1.0f));
+                       v.position = compose(origin+LinAl::Vector<float, 2>(c)+half, z);
+                       v.material = 0xFFFF;
+               }
+       }
+
+       vector<Quad> quads;
+       vector<array<uint16_t, 4>> batches;
+       quads.reserve((grid_size.x-1)*(grid_size.y-1));
+       for(unsigned y=0; y+1<grid_size.y; ++y)
+               for(unsigned x=0; x+1<grid_size.x; ++x)
+               {
+                       unsigned vi = x+y*grid_stride;
+
+                       array<uint16_t, 4> materials;
+                       materials[0] = vertices[vi].material;
+                       materials[1] = vertices[vi+1].material;
+                       materials[2] = vertices[vi+grid_stride].material;
+                       materials[3] = vertices[vi+grid_stride+1].material;
+
+                       Quad &q = quads.emplace_back();
+                       q.materials = materials;
+                       ranges::sort(q.materials);
+                       auto end = ranges::unique(q.materials).end();
+                       fill(end, std::end(q.materials), 0xFFFF);
+
+                       for(unsigned i=0; i<4; ++i)
+                               q.local_mats[i] = ranges::find(q.materials, materials[i])-begin(q.materials);
+
+                       auto i = ranges::find(batches, q.materials);
+                       if(i!=batches.end())
+                               q.batch = i-batches.begin();
+                       else
+                       {
+                               q.batch = batches.size();
+                               batches.push_back(q.materials);
+                       }
+               }
+
+       GL::MeshBuilder bld(mesh.mesh->get_mesh());
+       unsigned vertex_count = 0;
+       static constexpr unsigned next[4] = { 1, 3, 0, 2 };
+       for(unsigned y=0; y+1<grid_size.y; ++y)
+               for(unsigned x=0; x+1<grid_size.x; ++x)
+               {
+                       unsigned qi = x+y*(grid_size.x-1);
+                       Quad &q = quads[qi];
+
+                       unsigned shared_vertices = 0;
+                       if(y>0)
+                       {
+                               const Quad &q_down = quads[qi-(grid_size.x-1)];
+                               if(q.batch==q_down.batch)
+                               {
+                                       shared_vertices |= 3;
+                                       q.vertices[0] = q_down.vertices[2];
+                                       q.vertices[1] = q_down.vertices[3];
+                               }
+                       }
+
+                       if(x>0)
+                       {
+                               const Quad &q_left = quads[qi-1];
+                               if(q.batch==q_left.batch)
+                               {
+                                       shared_vertices |= 5;
+                                       q.vertices[0] = q_left.vertices[1];
+                                       q.vertices[2] = q_left.vertices[3];
+                               }
+                       }
+
+                       unsigned base_vi = x+y*grid_stride;
+                       for(unsigned i=0; i<5; ++i)
+                               if(!((shared_vertices>>i)&1))
+                               {
+                                       const Vertex &v = vertices[base_vi+(i&1)+((i>>1)&1)*grid_stride+((i>>2)&1)*grid_size.x];
+                                       bld.normal(v.normal);
+                                       bld.group(q.materials[0], q.materials[1], q.materials[2], q.materials[3]);
+
+                                       float weights[4] = { };
+                                       if(i<4)
+                                               weights[q.local_mats[i]] = 1.0f;
+                                       else
+                                       {
+                                               for(unsigned j=0; j<4; ++j)
+                                                       weights[q.local_mats[j]] += 0.25f;
+                                       }
+                                       bld.weight(weights[0], weights[1], weights[2], weights[3]);
+
+                                       bld.vertex(v.position);
+
+                                       q.vertices[i] = vertex_count++;
+                               }
+
+                       bld.begin(GL::TRIANGLES);
+                       for(unsigned i=0; i<4; ++i)
+                       {
+                               bld.element(q.vertices[i]);
+                               bld.element(q.vertices[next[i]]);
+                               bld.element(q.vertices[4]);
+                       }
+                       bld.end();
+               }
+}
+
+void TerrainMeshCreator::tick(Time::TimeDelta)
+{
+       for(TerrainMesh &m: meshes)
+       {
+               unsigned gen = m.terrain->get_generation();
+               if(gen!=m.generation)
+               {
+                       build_mesh(m);
+                       m.generation = gen;
+               }
+       }
+}
+
+} // namespace Msp::GameView
diff --git a/source/gameview/terrainmeshcreator.h b/source/gameview/terrainmeshcreator.h
new file mode 100644 (file)
index 0000000..108f777
--- /dev/null
@@ -0,0 +1,36 @@
+#ifndef MSP_GAMEVIEW_TERRAINMESHCREATOR_H_
+#define MSP_GAMEVIEW_TERRAINMESHCREATOR_H_
+
+#include <msp/game/owned.h>
+#include <msp/game/system.h>
+#include <msp/game/heightmapterrain.h>
+
+namespace Msp::GameView {
+
+class DynamicMeshSource;
+
+class TerrainMeshCreator: public Game::System
+{
+private:
+       struct TerrainMesh;
+
+       Game::EventObserver observer;
+       unsigned tessellation = 1;
+       std::vector<TerrainMesh> meshes;
+
+public:
+       TerrainMeshCreator(Game::Stage &);
+       ~TerrainMeshCreator();
+
+private:
+       void component_created(const Game::Events::ComponentCreated &);
+
+       void build_mesh(TerrainMesh &) const;
+
+public:
+       void tick(Time::TimeDelta) override;
+};
+
+} // namespace Msp::GameView
+
+#endif