17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-20 22:14:07 +02:00
Files
maszyna/editor/editorTerrainStreamer.hpp
maj00r d1dbd40566 feat(editor): the top view of the tracks draws the terrain to the corners of the screen, the ballast optional
- In the top view of the tracks work area, the renderer's reach is the half diagonal of the screen. It used to be three extents, which cut wide windows short.
- Cells far from the camera no longer draw only their tracks: the terrain shapes are drawn too. Models keep their own ranges.
- In that view, the heightmap streamer loads chunks as far as the corners, up to 50 km. The detail comes from the size of a pixel, not from the distance.
- The walk over the chunks around the camera is skipped while it would ask for nothing, so a wide reach costs little.
- A setting, kept in the editor settings, leaves out the ballast of the railway in the top view, so the ground under the tracks shows. It is in the View menu and next to the extent slider.
- Self-test: ortho <extent> [noballast|ballast], orthophoto off.
2026-10-19 17:10:27 +02:00

321 lines
15 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);
// many at once, as the formation of all the tracks: each chunk they reach is led anew once
void modifiers(std::vector<heightmap::modifier> Modifiers);
// the modifiers whose names start so are forgotten, the chunks keep their heights till they're made anew; returns how many
std::size_t drop_modifiers(std::string const &Prefix);
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);
// the same with the rectangles the modifiers reach, as modifier_bounds() gives them, worked out once for many chunks
void apply_modifiers(editor_terrain &Chunk, glm::dvec2 const &Min, glm::dvec2 const &Max, std::vector<std::pair<glm::dvec2, glm::dvec2>> const &Bounds);
void apply_modifiers(glm::dvec2 const &Min, glm::dvec2 const &Max);
std::vector<std::pair<glm::dvec2, glm::dvec2>> modifier_bounds() const;
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};
double m_pixel{0.0}; // metres a pixel of the top view spans; none in the 3D view
// the last walk over the chunks around the camera, and whether it found nothing more to ask for
bool m_settled{false};
chunk_key m_walkhere{0, 0};
chunk_key m_walkthere{0, 0};
int m_walkradius{0};
double m_walktime{0.0};
// 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;