--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
Resources::Resources()
{
add_type<CameraSetup>().suffix(".camera.setup");
+ add_type<HeightmapTerrainSetup>().suffix(".hmap.setup");
add_type<LightSetup>().suffix(".light.setup");
}
{
field object_name string;
};
+
+component HeightmapTerrain
+{
+ field grid_spacing float { default "1.0f"; };
+ field elevation_scale float { default "0.01f"; };
+ field technique_name string;
+};
#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;
{
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
--- /dev/null
+#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
--- /dev/null
+#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