17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-31 05:20:52 +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:
2026-10-09 21:02:25 +02:00
parent bbccce0e29
commit 35bda3778c
26 changed files with 52888 additions and 1624 deletions

View File

@@ -9,119 +9,72 @@ http://mozilla.org/MPL/2.0/.
#pragma once
#include <array>
#include <cstdint>
#include <memory>
#include <vector>
#include <string>
#include <functional>
#include <glm/glm.hpp>
#include "scene/heightmapterrain.h"
#include "utilities/Classes.h" // material_handle
#include "interfaces/ITexture.h" // null_handle
#include "rendering/geometrybank.h" // gfx::geometry_handle / geometrybank_handle
#include "rendering/geometrybank.h" // gfx::geometry_handle
namespace scene { class basic_section; }
// Editor-owned, editable terrain patch.
// a chunk of the heightmap terrain held in memory: its data, and what's drawn of it.
//
// The engine's scene::shape_node drops its CPU-side vertices the moment it uploads them to the GPU,
// so it can't be edited or raycast after load. This class keeps the authoritative, editable data
// (a regular grid heightmap) on the CPU, generates a shape_node purely for rendering, and answers
// height/raycast queries directly from the heightmap (fast and exact). Sculpting updates the
// heightmap and pushes the new vertex positions into the shape's existing geometry chunk.
// the data is shared with the worker threads making its meshes, so it's never changed in place: an edit gets a copy
// of its own when the current data is in use elsewhere. what's drawn is managed by the terrain streamer
class editor_terrain
{
public:
editor_terrain() = default;
explicit editor_terrain(heightmap::chunk_ptr Data) : m_data(std::move(Data)) {}
// builds an NxN-cell grid centred on Center, each quad CellSize metres across, using the
// material fetched from TextureName (empty => untextured). when Sampler is supplied it provides
// the starting height at each grid vertex (returns false to fall back to Center.y) - used to
// capture existing terrain. returns false on failure.
using height_sampler = std::function<bool(double X, double Z, double &OutY)>;
bool create(glm::dvec3 const &Center, int Cells, float CellSize, std::string const &TextureName,
height_sampler const &Sampler = {});
heightmap::chunk_data const &data() const { return *m_data; }
heightmap::chunk_ptr const &shared() const { return m_data; }
// the data, to be changed. marks the chunk as edited; Shape: the heights changed, Paint: the materials changed
heightmap::chunk_data &edit(bool const Shape, bool const Paint);
// puts provided data in place of the current one
void replace(heightmap::chunk_ptr Data, bool const Shape, bool const Paint);
// true if (X,Z) lies within the terrain's horizontal footprint
bool contains(double X, double Z) const;
// surface height at (X,Z) (bilinear over the covering quad); only valid when contains() is true
double height_at(double X, double Z) const;
heightmap::chunk_key key() const { return m_data->key; }
// true if (X,Z) lies within the chunk
bool contains(double X, double Z) const { return m_data->contains(X, Z); }
// surface height at (X,Z), the way the full grid is triangulated
double height_at(double X, double Z) const { return m_data->height_at(X, Z); }
// the same, from heights taken from heights() earlier
double height_in(std::vector<float> const &Heights, double X, double Z) const;
// raises/lowers vertices within Radius of (X,Z) by Strength (metres, signed), with a smooth
// falloff; regenerates the rendered geometry (full resolution). returns true if anything changed.
bool sculpt(double X, double Z, double Radius, double Strength);
// gives every grid vertex to Shaper, with its world position and height; Shaper returns true if it changed the height.
// regenerates the rendered geometry (full resolution). returns true if anything changed.
bool reshape(std::function<bool(double X, double Z, float &Height)> const &Shaper);
// pulls the vertices within Radius of (X,Z) towards the average of their neighbours, Amount (0..1) of the way at the centre,
// with a smooth falloff. Ground gives the height before the stroke at a world point, Fallback where there's no terrain
bool smooth(double X, double Z, double Radius, double Amount, std::function<double(double X, double Z, double Fallback)> const &Ground);
// puts back heights taken from heights() earlier
void restore(std::vector<float> const &Heights);
// rebuilds the rendered mesh, collapsing regions flatter than ErrorMetres into larger quads
// (adaptive quadtree). the editable heightmap is untouched, so sculpting/raycast stay exact.
void optimize(float ErrorMetres);
// rebuilds the rendered mesh at full resolution (undoes optimize)
void unoptimize();
// removes the rendered shape from its section (stops drawing it); the renderer's geometry
// garbage collector reclaims the GPU memory shortly after. used by terrain streaming to unload.
void destroy();
// horizontal centre and extent, handy for the UI / camera framing
double height_in(std::vector<float> const &Heights, double X, double Z) const { return m_data->height_in(Heights, X, Z); }
std::vector<float> const &heights() const { return m_data->heights; }
int cells() const { return m_data->cells; }
glm::dvec3 centre() const;
float extent() const { return m_cells * m_cellsize; }
bool valid() const { return m_cells > 0; }
float extent() const { return static_cast<float>(heightmap::chunk_size); }
bool valid() const { return m_data != nullptr && m_data->valid(); }
gfx::geometry_handle geometry() const { return m_geometry; }
bool optimized() const { return m_simplify; }
// set by sculpt() when the mesh changed and is currently full-resolution; cleared by optimize().
// used to auto-simplify only the chunks that were actually edited, once a stroke finishes.
bool dirty() const { return m_dirty; }
// set by sculpt() and never auto-cleared; used by streaming to know a chunk needs saving to disk
// rendered triangle count, and the count of the full grid
std::size_t triangles() const { return m_triangles; }
std::size_t full_triangles() const { return static_cast<std::size_t>(cells()) * cells() * 2; }
// changed since it was loaded or saved
bool modified() const { return m_modified; }
void clear_modified() { m_modified = false; }
// direct access to the editable heightmap (row-major world Y, (cells+1)^2), for save/load
std::vector<float> const &heights() const { return m_heights; }
int cells() const { return m_cells; }
// rendered triangle count (drops after optimize)
std::size_t triangles() const { return m_vertexcount / 3; }
// full-resolution triangle count, for reference
std::size_t full_triangles() const { return static_cast<std::size_t>(m_cells) * m_cells * 2; }
private:
int index(int Ix, int Iz) const { return Iz * (m_cells + 1) + Ix; }
glm::dvec3 vertex_position(int Ix, int Iz) const;
glm::vec3 vertex_normal(int Ix, int Iz) const;
world_vertex make_vertex(int Ix, int Iz) const;
// fills Out with the GL_TRIANGLES world-space vertex list; Simplify enables adaptive merging
void build_vertices(std::vector<world_vertex> &Out, bool Simplify) const;
// adaptive quadtree helpers (used when Simplify is on)
bool block_flat(int X0, int Z0, int X1, int Z1, float Error) const;
void collect_blocks(int X0, int Z0, int X1, int Z1, float Error, std::vector<std::array<int, 4>> &Out) const;
// a block as a fan through the marked grid vertices of its edges, so it meets its smaller neighbours without cracks
void emit_block(std::array<int, 4> const &Block, std::vector<char> const &Marked, std::vector<world_vertex> &Out) const;
void emit_quad(int X0, int Z0, int X1, int Z1, std::vector<world_vertex> &Out) const;
// rebuilds and re-uploads the rendered geometry (Replace when the count is unchanged, otherwise
// a fresh chunk whose handle is swapped into the shape)
void regenerate(bool Simplify);
friend class terrain_streamer;
int m_cells{0}; // quads per side; (m_cells+1)^2 grid vertices
float m_cellsize{1.0f}; // metres per quad
double m_x0{0.0}, m_z0{0.0}; // world position of grid corner (ix=0, iz=0)
std::vector<float> m_heights; // per-vertex world Y, row-major (m_cells+1)^2
heightmap::chunk_ptr m_data;
bool m_modified{false};
std::uint64_t m_shapeversion{1}; // grows with every change of the heights
std::uint64_t m_paintversion{1}; // grows with every change of the paint
// drawn state, managed by the streamer
int m_slot{-1}; // geometry slot holding the mesh
gfx::geometry_handle m_geometry{0, 0};
std::size_t m_triangles{0};
int m_stride{0}; // of the mesh in the slot, 0: none yet
std::uint64_t m_builtversion{0}; // shape version the mesh was made from
std::uint64_t m_paintedversion{0}; // paint version the material was made from
bool m_neighbours{false}; // a neighbour came or changed, the edges of the mesh are to be made again
bool m_building{false}; // a mesh is being made by a worker
material_handle m_material{null_handle};
gfx::geometrybank_handle m_bank{0, 0}; // geometry bank owning the rendered chunk
gfx::geometry_handle m_geometry{0, 0}; // rendered chunk
std::size_t m_vertexcount{0}; // current chunk's vertex count (for Replace vs recreate)
glm::dvec3 m_origin{0.0}; // origin the GPU vertices are stored relative to
scene::basic_section *m_section{nullptr}; // section holding the shape, for handle swaps
bool m_simplify{false}; // whether the rendered mesh is currently simplified
float m_simplify_error{0.5f}; // flatness tolerance used by optimize()
bool m_dirty{false}; // edited since the last optimize (full-res, awaiting simplification)
bool m_modified{false}; // edited since load/save (for streaming persistence)
scene::basic_section *m_section{nullptr}; // holding the shape, null: not drawn
glm::dvec3 m_low{0.0}, m_high{0.0}; // bounds of the drawn mesh
};