mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-30 23:40:29 +01:00
Heightmap terrain: .tch chunk packs, streaming with LOD, terrain painting, water and modifiers - terrain/<name>/terrain.yaml + <px>_<pz>.tch packs of 16x16 chunks (250 m, 0.5/1/2/5 m spacing), slices with crc-32 and optional zstd (vendored zstd 1.5.6 in ref/zstd) - heightmap_terrain [name] endheightmap_terrain directive replaces editorterrain - streamer: worker threads, camera + look-ahead loading, geomipmap LOD with skirts, pooled geometry - mat_terrain shader: up to 8 painted layer materials per chunk via texture arrays (opengl33); other renderers fall back to the dominant material - water bodies (outline + level), terrain modifiers (vertical profile shaping kept non-destructive) - F11 Terrain work mode rebuilt: chunks, sculpt, smooth, paint, point spacing, water tools
This commit is contained in:
@@ -9,112 +9,237 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "editor/editorTerrain.hpp"
|
||||
|
||||
// Streams editable terrain chunks around the camera for an effectively unbounded world.
|
||||
struct world_vertex;
|
||||
namespace scene { class basic_section; class basic_cell; }
|
||||
|
||||
// the heightmap terrain of the scenery (scene/heightmapterrain.h), loaded around the camera.
|
||||
//
|
||||
// Phase 1: keeps a ring of resident chunks (each an editor_terrain mesh) within a radius of the
|
||||
// camera; chunks entering the radius are built, chunks leaving it are destroyed (the renderer's
|
||||
// geometry GC reclaims their GPU memory). Heights are flat + a gentle procedural roll so streaming
|
||||
// is observable. Phase 2 will add 16-bit on-disk chunk paging, per-chunk flag bits and persistence.
|
||||
// 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 = std::pair<int, int>; // (cx, cz) chunk coordinate
|
||||
using chunk_key = heightmap::chunk_key;
|
||||
|
||||
terrain_streamer() = default;
|
||||
~terrain_streamer();
|
||||
|
||||
void active(bool State) { m_active = State; }
|
||||
// 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; }
|
||||
|
||||
// per-chunk configuration (applied to chunks built from now on)
|
||||
void configure(int Cells, float CellSize, int Radius, float BaseHeight, std::string const &Texture);
|
||||
void simplify(bool Auto, float Error) { m_auto_optimize = Auto; m_simplify_error = Error; }
|
||||
// safe to change while resident (only affects how far chunks load/unload)
|
||||
void radius(int Radius) { m_radius = Radius < 0 ? 0 : Radius; }
|
||||
// persist edited chunks to disk on unload, and load them back instead of regenerating
|
||||
void persist(bool Enable) { m_persist = Enable; }
|
||||
void directory(std::string const &Dir)
|
||||
{
|
||||
if (!Dir.empty() && Dir != m_dir)
|
||||
{
|
||||
m_dir = Dir;
|
||||
m_known.clear(); // existence cache is per-folder; drop it when the folder changes
|
||||
}
|
||||
}
|
||||
std::string const &directory() const { return m_dir; }
|
||||
|
||||
// loads/unloads chunks so the resident set matches the radius around CameraPos
|
||||
void update(glm::dvec3 const &CameraPos);
|
||||
// drops all resident chunks (e.g. when streaming is switched off), saving edits first
|
||||
void clear();
|
||||
// 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();
|
||||
|
||||
// appends raw pointers to every resident chunk (for sculpt / snap / optimize routing)
|
||||
void collect(std::vector<editor_terrain *> &Out) const;
|
||||
// resident chunk whose footprint covers (X,Z), or nullptr
|
||||
editor_terrain *terrain_at(double X, double Z) const;
|
||||
|
||||
// authoring: create a flat chunk at (Cx,Cz), make it resident and persist it to disk
|
||||
void add_chunk(int Cx, int Cz);
|
||||
// authoring: delete the chunk at (Cx,Cz) from memory and disk
|
||||
void remove_chunk(int Cx, int Cz);
|
||||
// persist an externally-authored chunk (e.g. a manual grid chunk being handed over to streaming)
|
||||
void save_chunk(int Cx, int Cz, editor_terrain const &Terrain);
|
||||
// chunk coordinate covering (X,Z)
|
||||
chunk_key key_for(double X, double Z) const { return key_at(X, Z); }
|
||||
|
||||
std::size_t resident() const { return m_chunks.size(); }
|
||||
float chunk_world_size() const { return m_cells * m_cellsize; }
|
||||
int cells() const { return m_cells; }
|
||||
float cellsize() const { return m_cellsize; }
|
||||
// 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; }
|
||||
std::string const &texture() const { return m_texture; }
|
||||
|
||||
// writes every resident, edited chunk to disk (without unloading) - used on save
|
||||
void flush();
|
||||
// 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);
|
||||
void remove_chunk(int Cx, int Cz);
|
||||
// changes the point spacing of the chunk at (Cx, Cz), heights resampled
|
||||
void spacing(int Cx, int Cz, float Spacing);
|
||||
// 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);
|
||||
// gives every grid point within the rectangle to Shaper, with its world position and height (before the modifiers);
|
||||
// Shaper returns true if it changed the height
|
||||
bool reshape(glm::dvec2 const &Min, glm::dvec2 const &Max, std::function<bool(double X, double Z, float &Height)> const &Shaper);
|
||||
// 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);
|
||||
// palette entry of the material, added to the palette if needed
|
||||
std::uint16_t layer(std::string const &Material, float Size = 0.f);
|
||||
void layer_size(std::uint16_t Layer, float Size);
|
||||
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);
|
||||
// modifiers: added or replaced by name
|
||||
void modifier(heightmap::modifier Modifier);
|
||||
bool remove_modifier(std::string const &Name);
|
||||
std::vector<heightmap::modifier> const &modifiers() const { return m_manifest.modifiers; }
|
||||
// changed since the last save
|
||||
bool modified() const;
|
||||
// writes the description and the changed pack files. returns: true on success, Error describing the failure
|
||||
bool save(std::string *Error = nullptr);
|
||||
|
||||
private:
|
||||
// per-chunk state bits cached so we don't stat the filesystem repeatedly
|
||||
enum chunk_flag : uint8_t
|
||||
struct pack_state
|
||||
{
|
||||
cf_exists_on_disk = 1u << 0, // a saved 16-bit chunk file exists
|
||||
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};
|
||||
};
|
||||
|
||||
chunk_key key_at(double X, double Z) const;
|
||||
glm::dvec3 chunk_centre(int Cx, int Cz) const;
|
||||
std::string chunk_path(int Cx, int Cz) const;
|
||||
bool chunk_on_disk(chunk_key const &Key); // cached existence test
|
||||
bool load_heights(int Cx, int Cz, std::vector<float> &Out) const; // reads a 16-bit chunk file
|
||||
void save_heights(int Cx, int Cz, editor_terrain const &Terrain); // writes a 16-bit chunk file
|
||||
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);
|
||||
// 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;
|
||||
|
||||
bool m_active{false};
|
||||
int m_cells{32};
|
||||
float m_cellsize{2.0f};
|
||||
int m_radius{2}; // chunks loaded around the camera (Chebyshev radius)
|
||||
float m_baseheight{0.0f};
|
||||
std::string m_texture;
|
||||
bool m_auto_optimize{true};
|
||||
float m_simplify_error{0.5f};
|
||||
bool m_persist{true};
|
||||
std::string m_dir{"editor_terrain"}; // folder for 16-bit chunk files
|
||||
std::string m_name;
|
||||
std::string m_directory;
|
||||
heightmap::manifest m_manifest;
|
||||
bool m_manifestchanged{false};
|
||||
float m_detail{120.f};
|
||||
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::map<chunk_key, std::uint8_t> m_known; // cached per-chunk flag bits
|
||||
// offsets of the chunks around the camera, the nearest first, for the radius in effect
|
||||
std::vector<chunk_key> m_ring;
|
||||
int m_ring_radius{-1};
|
||||
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};
|
||||
|
||||
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 streamer instance shared by the editor (authoring) and the scenery
|
||||
// loader (so streamed terrain renders in every mode, including the driver). loaded from the
|
||||
// `editorterrain` scenery directive and updated each frame with the active camera.
|
||||
// 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;
|
||||
|
||||
Reference in New Issue
Block a user