From 1cc4d4b83c94b4e54645a80bafeefce8f6c63b25 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Sat, 11 Feb 2023 15:56:06 +0200 Subject: [PATCH] Add components and systems for creating terrains --- source/game/heightmapterrain.cpp | 53 ++++++ source/game/heightmapterrain.h | 42 +++++ source/game/landscape.cpp | 60 +++++++ source/game/landscape.h | 52 ++++++ source/game/resources.cpp | 1 + source/game/setups.mgs | 7 + source/gameview/presenter.cpp | 4 + source/gameview/terrainmeshcreator.cpp | 224 +++++++++++++++++++++++++ source/gameview/terrainmeshcreator.h | 36 ++++ 9 files changed, 479 insertions(+) create mode 100644 source/game/heightmapterrain.cpp create mode 100644 source/game/heightmapterrain.h create mode 100644 source/game/landscape.cpp create mode 100644 source/game/landscape.h create mode 100644 source/gameview/terrainmeshcreator.cpp create mode 100644 source/gameview/terrainmeshcreator.h diff --git a/source/game/heightmapterrain.cpp b/source/game/heightmapterrain.cpp new file mode 100644 index 0000000..8e4d1a2 --- /dev/null +++ b/source/game/heightmapterrain.cpp @@ -0,0 +1,53 @@ +#include +#include "heightmapterrain.h" + +using namespace std; + +namespace Msp::Game { + +HeightmapTerrain::HeightmapTerrain(Handle e, const Setup &s, const LinAl::Vector &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(data); + materials = reinterpret_cast(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 &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 &i, unsigned mat) +{ + materials[i.x+i.y*grid_size.x] = mat; + ++generation; +} + +float HeightmapTerrain::get_elevation(const LinAl::Vector &p) const +{ + if(p.x<0 || p.y<0 || p.x>=grid_size.x || p.y>=grid_size.y) + return -numeric_limits::infinity(); + + LinAl::Vector i = p; + LinAl::Vector 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 index 0000000..3a9b8a6 --- /dev/null +++ b/source/game/heightmapterrain.h @@ -0,0 +1,42 @@ +#ifndef MSP_GAME_HEIGHTMAPTERRAIN_H_ +#define MSP_GAME_HEIGHTMAPTERRAIN_H_ + +#include +#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 grid_size; + char *data = nullptr; + std::int16_t *elevations = nullptr; + std::uint8_t *materials = nullptr; + unsigned generation = 0; + +public: + HeightmapTerrain(Handle, const Setup &, const LinAl::Vector &); + ~HeightmapTerrain(); + + float get_grid_spacing() const { return setup.grid_spacing; } + const LinAl::Vector &get_grid_size() const { return grid_size; } + LinAl::Vector get_size() const { return { (grid_size.x-1)*setup.grid_spacing, (grid_size.y-1)*setup.grid_spacing }; } + void set_elevation(const LinAl::Vector &, float); + void set_material_index(const LinAl::Vector &, unsigned); + float get_elevation(const LinAl::Vector &i) const { return elevations[i.x+i.y*grid_size.x]*setup.elevation_scale; } + float get_elevation(const LinAl::Vector &) const; + unsigned get_material_index(const LinAl::Vector &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 index 0000000..1612d44 --- /dev/null +++ b/source/game/landscape.cpp @@ -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 &bounds) +{ + const LinAl::Vector &min_pt = bounds.get_minimum_point(); + const LinAl::Vector &max_pt = bounds.get_maximum_point(); + LinAl::Vector min_index(round(min_pt.x/block_size), round(min_pt.y/block_size)); + LinAl::Vector max_index(round(max_pt.x/block_size), round(max_pt.y/block_size)); + LinAl::Vector 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(min_index.x, y), [](const Terrain &t, const LinAl::Vector &n){ + return t.index.yindex.x>x) + { + LinAl::Vector index(x ,y); + LinAl::Vector center = LinAl::Vector(index)*block_size; + Owned entity(subroot, compose(center, 0.0f)); + Owned terrain(entity, terrain_setup, block_size_vec); + if(generator) + { + LinAl::Vector 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 index 0000000..b31402b --- /dev/null +++ b/source/game/landscape.h @@ -0,0 +1,52 @@ +#ifndef MSP_GAME_LANDSCAPE_H_ +#define MSP_GAME_LANDSCAPE_H_ + +#include +#include +#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 &, Handle) = 0; +}; + +class MSPGAME_API Landscape: public System +{ +private: + struct Terrain + { + LinAl::Vector index; + Owned entity; + Owned terrain; + }; + + Owned subroot; + const HeightmapTerrainSetup &terrain_setup; + float block_size = 100.0f; + LandscapeGenerator *generator = nullptr; + std::vector terrains; + +public: + Landscape(Stage &, const HeightmapTerrainSetup &, float); + + void set_generator(LandscapeGenerator *); + void generate(const Geometry::BoundingBox &); + LinAl::Vector get_ground_position(const LinAl::Vector &) const; + + void tick(Time::TimeDelta) override { } +}; + +} // namespace Msp::Game + +#endif diff --git a/source/game/resources.cpp b/source/game/resources.cpp index fb8f197..9ab482f 100644 --- a/source/game/resources.cpp +++ b/source/game/resources.cpp @@ -11,6 +11,7 @@ namespace Msp::Game { Resources::Resources() { add_type().suffix(".camera.setup"); + add_type().suffix(".hmap.setup"); add_type().suffix(".light.setup"); } diff --git a/source/game/setups.mgs b/source/game/setups.mgs index 3392df1..6cad214 100644 --- a/source/game/setups.mgs +++ b/source/game/setups.mgs @@ -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; +}; diff --git a/source/gameview/presenter.cpp b/source/gameview/presenter.cpp index 194ac80..06a644d 100644 --- a/source/gameview/presenter.cpp +++ b/source/gameview/presenter.cpp @@ -1,8 +1,10 @@ #include "presenter.h" #include #include +#include #include #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()) systems.push_back(&event.stage.add_system(ref(gl_view))); + if(event.stage.get_system() && !event.stage.get_system()) + systems.push_back(&event.stage.add_system()); } } // namespace Msp::GameView diff --git a/source/gameview/terrainmeshcreator.cpp b/source/gameview/terrainmeshcreator.cpp new file mode 100644 index 0000000..b70cded --- /dev/null +++ b/source/gameview/terrainmeshcreator.cpp @@ -0,0 +1,224 @@ +#include "terrainmeshcreator.h" +#include +#include +#include +#include "dynamicmeshsource.h" + +using namespace std; + +namespace Msp::GameView { + +struct TerrainMeshCreator::TerrainMesh +{ + Game::Handle terrain; + Game::Owned mesh; + unsigned generation = 0; +}; + +struct Quad +{ + array materials; + array local_mats; + uint16_t batch; + array vertices; +}; + +struct Vertex +{ + LinAl::Vector position; + LinAl::Vector normal; + uint16_t material; +}; + + +TerrainMeshCreator::TerrainMeshCreator(Game::Stage &s): + System(s), + observer(stage.get_event_bus()) +{ + observer.observe([this](auto &e){ component_created(e); }); + + stage.synthesize_initial_events(observer); +} + +// Hide ~vector from the header +TerrainMeshCreator::~TerrainMeshCreator() +{ } + +void TerrainMeshCreator::component_created(const Game::Events::ComponentCreated &event) +{ + if(Game::Handle terrain = dynamic_handle_cast(event.component)) + { + const GL::Technique &tech = stage.get_resources().get(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(terrain->get_entity(), fmt, tech)); + } +} + +void TerrainMeshCreator::build_mesh(TerrainMesh &mesh) const +{ + Game::Handle terrain = mesh.terrain; + + const LinAl::Vector right(1, 0); + const LinAl::Vector up(0, 1); + const LinAl::Vector half(0.5f, 0.5f); + + const LinAl::Vector &grid_size = terrain->get_grid_size(); + LinAl::Vector origin = terrain->get_size()*-0.5f; + float grid_spacing = terrain->get_grid_spacing(); + const float dz_scale = -0.5f/grid_spacing; + + vector 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 c(x, y); + float z = terrain->get_elevation(c); + float z_l = (x>0 ? terrain->get_elevation(c-right) : z); + float z_r = (x+1get_elevation(c+right) : z); + float z_d = (y>0 ? terrain->get_elevation(c-up) : z); + float z_u = (y+1get_elevation(c+up) : z); + LinAl::Vector dz(z_r-z_l, z_u-z_d); + + Vertex &v = vertices.emplace_back(); + v.position = compose(origin+LinAl::Vector(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 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 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(c)+half, z); + v.material = 0xFFFF; + } + } + + vector quads; + vector> batches; + quads.reserve((grid_size.x-1)*(grid_size.y-1)); + for(unsigned y=0; y+1 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+10) + { + 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 index 0000000..108f777 --- /dev/null +++ b/source/gameview/terrainmeshcreator.h @@ -0,0 +1,36 @@ +#ifndef MSP_GAMEVIEW_TERRAINMESHCREATOR_H_ +#define MSP_GAMEVIEW_TERRAINMESHCREATOR_H_ + +#include +#include +#include + +namespace Msp::GameView { + +class DynamicMeshSource; + +class TerrainMeshCreator: public Game::System +{ +private: + struct TerrainMesh; + + Game::EventObserver observer; + unsigned tessellation = 1; + std::vector 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 -- 2.45.2