17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 13:19:20 +02:00
Files
maszyna/editor/editorTerrainStreamer.hpp

307 lines
14 KiB
C++

/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <condition_variable>
#include <deque>
#include <map>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include <vector>
#include <glm/glm.hpp>
#include "editor/editorTerrain.hpp"
struct world_vertex;
namespace scene { class basic_section; class basic_cell; }
// the heightmap terrain of the scenery (scene/heightmapterrain.h), loaded around the camera.
//
// the pack files are read and the meshes made by worker threads; the main thread only puts finished meshes on the gpu,
// within a few milliseconds of each frame. chunks are loaded around the camera and around the place the camera is heading
// to, so a moving train finds its terrain loaded. each chunk is drawn with a grid as coarse as its distance allows.
//
// the editor changes the chunks in memory; they're written to the pack files when the scenery is saved, until then the
// edited chunks stay in memory, also when they're far from the camera
class terrain_streamer
{
public:
using chunk_key = heightmap::chunk_key;
terrain_streamer() = default;
~terrain_streamer();
// opens the terrain kept in terrain/<Name>/ (the folder may not exist yet). returns: true if there's terrain to show
bool open(std::string const &Name);
// stops showing the terrain
void close();
bool active() const { return m_active; }
std::string const &name() const { return m_name; }
// the folder of the terrain, with a trailing slash
std::string const &directory() const { return m_directory; }
// loads and unloads the chunks around the camera, and puts finished meshes on the gpu
void update(glm::dvec3 const &Camera);
// hard reset used when a new scenery loads: drops everything WITHOUT touching scene sections
// (the old region is being torn down, so its section pointers are already dangling)
void reset();
// drawn detail: metres of distance per metre of grid spacing (larger: finer grid further away)
void detail(float const Detail) { m_detail = std::max(10.f, Detail); }
float detail() const { return m_detail; }
// smallest distance (chunks) the terrain is loaded to, whatever the draw range
void radius(int const Radius) { m_radius = std::max(0, Radius); }
int radius() const { return m_radius; }
// chunks held in memory
void collect(std::vector<editor_terrain *> &Out) const;
// chunk covering (X,Z), or nullptr
editor_terrain *terrain_at(double X, double Z) const;
// height of the terrain at (X,Z); false where there's no chunk in memory
bool height_at(double X, double Z, double &Height) const;
// triangles of the full grid of the chunks in memory overlapping the rectangle (x, z)
void gather_triangles(glm::dvec2 const &Min, glm::dvec2 const &Max, std::vector<std::array<glm::dvec3, 3>> &Out) const;
// true if the geometry belongs to the terrain (or its water)
bool owns(gfx::geometry_handle const &Geometry) const;
std::size_t resident() const { return m_chunks.size(); }
// chunks in the pack files and in memory, less the removed ones; the files known so far
bool exists(chunk_key const &Key) const;
// editing; the chunks changed are written to the files by save()
heightmap::manifest const &manifest() const { return m_manifest; }
// a flat chunk at (Cx, Cz) of specified spacing and height, covered with the material of palette entry Layer;
// returns false if there's a chunk already
bool add_chunk(int Cx, int Cz, float Spacing, float Height, std::uint16_t Layer = 0);
// a chunk at (Cx, Cz), heights given for each grid point by Height (from its world position)
bool add_chunk(int Cx, int Cz, float Spacing, std::function<float(double X, double Z)> const &Height, std::uint16_t Layer = 0);
// a chunk made elsewhere, at the place its key says; its layers are indices of the palette
bool add_chunk(std::shared_ptr<heightmap::chunk_data> Data);
void remove_chunk(int Cx, int Cz);
// changes the point spacing of the chunk at (Cx, Cz), heights resampled; Paint: paint samples along a side less one
// the chunk gets, 0 keeps the ones it has. returns: true if the chunk changed
bool spacing(int Cx, int Cz, float Spacing, int Paint = 0);
// raises (Strength > 0) or lowers the ground within Radius of (X,Z), with a smooth falloff
bool sculpt(double X, double Z, double Radius, double Strength);
// evens out the ground within Radius of (X,Z), Amount (0..1) of the way at the centre
bool smooth(double X, double Z, double Radius, double Amount);
// leads the ground within Radius of (X,Z) towards the height Target, Amount (0..1) of the way at the centre;
// Mode: 0 up and down, 1 only raises it, 2 only lowers it
bool level(double X, double Z, double Radius, double Target, double Amount, int Mode = 0);
// gives every grid point within the rectangle to Shaper, with its world position and height; Shaper returns true if it
// changed the height. where the modifiers shape the ground the change is a touch-up kept over them (see touch_up()),
// elsewhere it changes the ground under them
bool reshape(glm::dvec2 const &Min, glm::dvec2 const &Max, std::function<bool(double X, double Z, float &Height)> const &Shaper);
// true: the brushes shape the terrain as it's seen, also where the modifiers (tracks) shape it, the changes kept there
// as touch-ups over the modifiers; false: the brushes shape only the ground under the modifiers, which keep their shape
void touch_up(bool const State) { m_touchup = State; }
bool touch_up() const { return m_touchup; }
// takes the touch-ups within Radius of (X,Z) back, Amount (0..1) of the way at the centre: the terrain goes back to the
// shape the modifiers give it
bool restore_stitching(double X, double Z, double Radius, double Amount);
// grid points with touch-ups over the modifiers, in the chunks in memory
std::size_t touched_up() const;
// paints the material of the palette entry Layer within Radius of (X,Z), Strength (0..1) of the way at the centre
bool paint(double X, double Z, double Radius, double Strength, std::uint16_t Layer);
// fills the area within Outline (x, z) with the material of the palette entry Layer, Strength (0..1) of the way.
// Feather: metres across the outline the material blends in over, half of them inside; 0: a sharp edge, as sharp as
// the paint samples of the chunks go. the chunks are loaded where needed
bool fill(std::vector<glm::dvec2> const &Outline, double Feather, std::uint16_t Layer, double Strength = 1.0);
// palette entry of the material (repeated at Size, 0: any, turned by Rotation degrees), added to the palette if needed
std::uint16_t layer(std::string const &Material, float Size = 0.f, float Rotation = 0.f);
void layer_size(std::uint16_t Layer, float Size);
// turns the textures of the palette entry Layer by Rotation degrees, on every chunk painted with it
void layer_rotation(std::uint16_t Layer, float Rotation);
// gives the palette entry Layer another material; the chunks painted with it change with it
void layer_material(std::uint16_t Layer, std::string const &Material);
void compress(bool State);
void default_spacing(float Spacing);
// bodies of water
std::vector<heightmap::water_body> const &water() const { return m_manifest.water; }
void water(std::vector<heightmap::water_body> Water);
// material of the bodies of water which don't name their own
void water_material(std::string const &Material);
// modifiers: added or replaced by name
void modifier(heightmap::modifier Modifier);
bool remove_modifier(std::string const &Name);
// the ground takes the shape the modifier gives it for good, and the modifier is removed: from then on the
// terrain there is shaped by hand like anywhere else
bool bake_modifier(std::string const &Name);
std::vector<heightmap::modifier> const &modifiers() const { return m_manifest.modifiers; }
// changed since the last save
bool modified() const;
// undo: the state of what a change touched, from before it
struct change
{
std::map<chunk_key, heightmap::chunk_ptr> chunks; // the data the chunks had; null: there was no chunk
bool manifest{false}; // the palette, the water (and its material) and the modifiers below were changed
std::vector<heightmap::layer_def> layers;
std::vector<heightmap::water_body> water;
std::string water_material;
std::vector<heightmap::modifier> modifiers;
bool empty() const { return chunks.empty() && false == manifest; }
};
// from now on the state of what's changed is kept, until end_change(); nested calls join the change in progress
void begin_change();
// returns: the state from before the changes made since begin_change() (empty if nothing changed)
change end_change();
bool recording() const { return m_recording > 0; }
// puts the terrain back in the state recorded. returns: the state it replaced, to go forward again
change restore(change const &Change);
// writes the description and the changed pack files. returns: true on success, Error describing the failure
bool save(std::string *Error = nullptr);
private:
struct pack_state
{
enum class state
{
unknown,
loading,
ready,
missing
} status{state::unknown};
std::shared_ptr<heightmap::pack_file const> file;
std::uint32_t generation{0};
};
struct job
{
enum class kind
{
pack,
chunk,
mesh
} type{kind::pack};
heightmap::pack_key pack{0, 0};
chunk_key chunk{0, 0};
std::shared_ptr<heightmap::pack_file const> file;
std::uint32_t generation{0};
heightmap::chunk_ptr data;
std::array<heightmap::chunk_ptr, 4> neighbours; // -x, +x, -z, +z
int stride{1};
glm::dvec3 origin{0.0};
std::uint64_t version{0};
double priority{0.0};
};
struct result
{
job::kind type{job::kind::pack};
heightmap::pack_key pack{0, 0};
chunk_key chunk{0, 0};
std::uint32_t generation{0};
std::shared_ptr<heightmap::pack_file const> file;
std::shared_ptr<heightmap::chunk_data> data;
heightmap::mesh mesh;
int stride{1};
std::uint64_t version{0};
std::string error;
};
struct geometry_slot
{
gfx::geometrybank_handle bank{0, 0};
gfx::geometry_handle geometry{0, 0};
int cells{0};
int stride{0};
bool used{false};
};
struct water_shape
{
gfx::geometry_handle geometry{0, 0};
scene::basic_section *section{nullptr};
scene::basic_cell *cell{nullptr};
};
void start_workers();
void stop_workers();
void worker();
void submit(job Job);
void process(result &Result);
// the chunk is drawn: its shape put in the scene, or put back with a new mesh
void upload(editor_terrain &Chunk, heightmap::mesh &Mesh, int const Stride);
void update_material(editor_terrain &Chunk);
// puts the current material of the chunk in its shape
void upload_shape_material(editor_terrain &Chunk);
// takes the chunk out of the scene, its geometry slot and material given back
void undraw(editor_terrain &Chunk);
void request_mesh(editor_terrain &Chunk, int const Stride, double const Priority);
int wanted_stride(editor_terrain const &Chunk, glm::dvec3 const &Camera) const;
heightmap::chunk_ptr neighbour(chunk_key const &Key) const;
void touch_neighbours(chunk_key const &Key);
material_handle palette_material(std::uint16_t const Layer);
// paints the material of the palette entry Layer over the paint samples within the rectangle, Amount (0..1) of the way
// at each, as Amount gives it for the place of the sample. Load: chunks not in memory are loaded
bool paint_area(glm::dvec2 const &Min, glm::dvec2 const &Max, std::uint16_t Layer, bool Load, std::function<double(double X, double Z)> const &Amount);
// chunk loaded right away from its pack file, for editing far from the camera
editor_terrain *load_now(chunk_key const &Key);
// chunks in memory or in the files overlapping the rectangle, loaded if needed
std::vector<editor_terrain *> chunks_in(glm::dvec2 const &Min, glm::dvec2 const &Max, bool const Load);
// leads the heights of the chunk from its base heights through the modifiers, within the rectangle
void apply_modifiers(editor_terrain &Chunk, glm::dvec2 const &Min, glm::dvec2 const &Max);
void apply_modifiers(glm::dvec2 const &Min, glm::dvec2 const &Max);
void build_water();
void clear_water(bool const Touch);
std::string pack_path(heightmap::pack_key const &Key) const;
// the state of a chunk (null: no chunk) or of the description is kept for the change in progress, if it's the first time
void remember(chunk_key const &Key, heightmap::chunk_ptr const &Data);
void remember(editor_terrain const &Chunk) { remember(Chunk.key(), Chunk.shared()); }
void remember_manifest();
bool m_active{false};
std::string m_name;
std::string m_directory;
heightmap::manifest m_manifest;
bool m_manifestchanged{false};
float m_detail{120.f};
bool m_touchup{true};
int m_radius{2};
std::map<heightmap::pack_key, pack_state> m_packs;
std::map<chunk_key, std::unique_ptr<editor_terrain>> m_chunks;
std::set<chunk_key> m_loading; // asked from a worker
std::set<chunk_key> m_removed; // removed in the editor, still in the files until saved
std::vector<geometry_slot> m_slots;
std::map<std::uint64_t, int> m_slotofgeometry; // geometry key -> slot
std::map<std::uint16_t, material_handle> m_palette;
std::vector<water_shape> m_watershapes;
std::vector<gfx::geometry_handle> m_waterfree; // geometry of the water shown before, to be filled anew
gfx::geometrybank_handle m_waterbank{0, 0};
bool m_waterchanged{false};
int m_recording{0};
change m_change;
glm::dvec3 m_camera{0.0};
glm::dvec3 m_velocity{0.0};
double m_cameratime{0.0};
bool m_hascamera{false};
std::vector<chunk_key> m_ring; // offsets of the chunks around a point, the nearest first
int m_ringradius{-1};
// shared with the workers, guarded by m_mutex
std::mutex m_mutex;
std::condition_variable m_wakeup;
std::vector<job> m_jobs;
std::deque<result> m_results;
std::size_t m_busy{0}; // jobs taken by the workers and not finished yet
bool m_stop{false};
std::vector<std::thread> m_workers;
// the pack files are read by the workers and written by save()
std::mutex m_filemutex;
};
// single, simulation-level instance shared by the editor (authoring) and the scenery loader, so the terrain
// renders in every mode. opened by the `heightmap_terrain` scenery directive and updated each frame with the active camera.
extern terrain_streamer EditorTerrain;