17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-20 17:24:53 +02:00
Files
maszyna/scene/heightmapterrain.h
maj00r ff1710d9ea feat(editor): labels for a layman, light at the start; whole sceneries fitted to the terrain model
Labels:
- every button, field, tooltip and message of the editor reread for a beginner: short nouns for the fields with the
  sentence in the tooltip, the less used options under More settings, one glossary (field of work, template, track,
  grade), sides told from directions, degrees and per mille as signs, messages built from one format string
- plural forms through STR_P; the last entry of a po file is read too, and the Polish plural rule is in the header
- lang/editor_pl.po: the code pass entries merged, 391 entries no code uses and 3 duplicates dropped
- roads: the notes and errors naming a road translated; the build options and the layout fold the texture length,
  kerb sizes, traffic share, roundabout part, height over the ground and junction speed limit under More settings, the
  lanes start folded

Grade lines fitted to the terrain model (NMT), a line or the whole scenery at once:
- fit_ground: a robust fit on nodes 5 m apart; the ground parting from the line (a bridge, a viaduct, a wall) is gone
  past rather than followed, the bends keep off the switches as far as their curves reach, a run of bends the same way
  is one point of intersection, a curve with no room is as sharp as the room lets it be
- the ends of the line and the other paths of its switches leading into lines fitted already bound the line by what
  the tracks between can climb; ends meeting in the plan, laid apart in height by hand, are joined before the run
- apply_profile: the paths are split where split_path takes the parameter (a straight was cut in the wrong place, into
  pieces of a few centimetres the game takes for zero length), no piece shorter than the margin; the paths off the
  ends of the route are brought to its height
- Bytom: 1600 lines, grade breaks at the joints 89 -> 20; Tarnowskie Gory: 848 lines, 223 -> 3; no step over 2 cm
2026-10-19 15:27:50 +02:00

260 lines
12 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 <array>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <glm/glm.hpp>
#include "rendering/geometrybank.h"
// heightmap terrain: the ground of a scenery as a grid of square chunks of heights, kept in the folder
// terrain/<name>/ of the simulator: a description of the terrain (terrain.yaml) and pack files (.tch, terrain chunks)
// holding 16 x 16 chunks each. the chunks are loaded around the camera as they're needed.
//
// a chunk has its own point spacing (0.5, 1, 2 or 5 m), heights of its grid points including the points of its edges
// (shared with the neighbouring chunk), and weights of the materials painted over it, up to 8 of them per chunk.
namespace heightmap
{
double constexpr chunk_size{250.0}; // metres, side of a chunk; whole cells of every allowed spacing, and of the scene
int constexpr pack_side{16}; // chunks along the side of a pack file
double constexpr height_step{0.005}; // metres, quantum of the stored heights
std::size_t constexpr max_chunk_layers{8}; // materials painted over a single chunk
std::uint16_t constexpr no_layer{0xffff};
int constexpr default_paint_samples{250}; // paint samples along the side of a chunk, less one: a sample per metre
// point spacings a chunk can have, finest first
std::array<float, 4> constexpr spacings{0.5f, 1.f, 2.f, 5.f};
float constexpr default_spacing{2.f};
// cells along the side of a chunk with specified point spacing
int cells_for(float const Spacing);
// the allowed spacing closest to specified one
float valid_spacing(float const Spacing);
using chunk_key = std::pair<int, int>;
using pack_key = std::pair<int, int>;
// chunk covering specified point
chunk_key chunk_at(double const X, double const Z);
// corner of the chunk with the lowest coordinates
glm::dvec2 chunk_corner(chunk_key const &Key);
// pack holding specified chunk
pack_key pack_of(chunk_key const &Key);
// corner of the pack with the lowest coordinates; texture coordinates of the terrain start over at it
glm::dvec2 pack_corner(pack_key const &Key);
// name of the file of specified pack
std::string pack_file_name(pack_key const &Key);
struct chunk_data
{
chunk_key key{0, 0};
int cells{0}; // quads along a side; (cells + 1)^2 grid points
std::vector<float> heights; // world y of the grid points, rows of growing z, from the lowest x
std::vector<float> base; // the heights before the modifiers were applied; empty where no modifier reaches the chunk
// touch-ups made by hand over what the modifiers give, added to it: heights = modifiers(base) + adjust.
// empty when there are none, and always when base is empty
std::vector<float> adjust;
int paint{0}; // paint samples along a side, less one. 0: the chunk is covered with its first layer alone
std::array<std::uint16_t, max_chunk_layers> layers; // indices of the materials in the terrain palette, no_layer for unused
std::vector<std::uint8_t> weights; // planar, (paint + 1)^2 per used layer, rows like the heights; weights of a sample add up to 255
chunk_data() { layers.fill(no_layer); }
int side() const { return cells + 1; }
double spacing() const { return chunk_size / cells; }
glm::dvec2 corner() const { return chunk_corner(key); }
bool valid() const { return cells > 0 && heights.size() == static_cast<std::size_t>(side()) * side(); }
// number of layers in use, the used ones come first
std::size_t layer_count() const;
float height(int const Ix, int const Iz) const { return heights[static_cast<std::size_t>(Iz) * side() + Ix]; }
// height of the surface at specified world point, the way the full grid is triangulated; points outside are clamped to the chunk
double height_at(double const X, double const Z) const;
// as above, from provided heights of the grid points of the chunk
double height_in(std::vector<float> const &Heights, double const X, double const Z) const;
bool contains(double const X, double const Z) const;
// a flat chunk of specified spacing at specified height, covered with the first material of the palette
static std::shared_ptr<chunk_data> make_flat(chunk_key const &Key, float const Spacing, float const Height);
// the same chunk with the grid of another spacing, heights and paint taken over. a coarser grid takes the average
// of the ground around each of its points (the finer one only its own heights). Paint: paint samples along a side
// less one the chunk gets, 0 keeps the ones it has
std::shared_ptr<chunk_data> resampled(float const Spacing, int const Paint = 0) const;
// weight of specified layer slot at paint sample (Ix, Iz)
std::uint8_t weight(std::size_t const Slot, int const Ix, int const Iz) const;
// layer slot of specified palette index, or -1
int slot_of(std::uint16_t const Layer) const;
// layer slot of the material covering the most of the chunk
std::size_t dominant_slot() const;
// gives the chunk paint samples (with its first layer as the only one painted) if it has none yet
void ensure_paint(int const Samples = default_paint_samples);
// drops the layers left without weight anywhere, and the paint samples altogether when a single layer is left
void compact_layers();
};
using chunk_ptr = std::shared_ptr<chunk_data const>;
// body of water: a level surface within an outline
struct water_body
{
std::string name;
double level{0.0};
std::string material; // empty: the default water material
float size{0.f}; // metres the texture repeats at; 0: the size given by the material, or 8 m
std::vector<glm::dvec2> outline; // x, z
};
// modifier of the terrain: a corridor along a line (axis of a track or a road) with a formation of specified
// height and width, and slopes from its edges to the ground
struct corridor_point
{
glm::dvec2 position{0.0}; // x, z
double formation{0.0}; // height of the formation
double half_width{3.5}; // from the axis to the edge of the formation
};
struct modifier
{
std::string name;
double slope{1.5}; // horizontal run of the slopes per metre of height
double reach{60.0}; // from the edge of the formation, where the slopes end even if they don't get to the ground
double rounding{1.5}; // over which the slopes bend into the formation and into the ground
std::vector<corridor_point> points;
// rectangle (x, z) the modifier can change the ground in
std::pair<glm::dvec2, glm::dvec2> bounds() const;
};
// modifier prepared for many questions
class modifier_shape
{
public:
explicit modifier_shape(modifier const &Modifier);
// leads Height at specified point the way the modifier does; returns: true if the height changed
bool apply(double const X, double const Z, float &Height) const;
// what the modifier makes of Ground at the point, and how far the point lies off the formation: 0 or less on it; none out of
// its reach
struct effect
{
double height{0.0};
double outside{0.0};
double distance{0.0}; // from the axis
};
std::optional<effect> evaluate(double const X, double const Z, double const Ground) const;
bool reaches(glm::dvec2 const &Min, glm::dvec2 const &Max) const;
private:
modifier const &m_modifier;
double m_widest{0.0};
glm::dvec2 m_min{0.0}, m_max{0.0};
double m_cell{16.0};
std::vector<std::pair<std::uint64_t, std::uint32_t>> m_grid; // (cell key, segment), sorted
};
// material of the palette, and the size (metres) its textures repeat at; 0: the size given by the material, or 8 m.
// the same material can be in the palette more than once, e.g. turned by another angle
struct layer_def
{
std::string material;
float size{0.f};
float rotation{0.f}; // degrees the textures are turned by, counterclockwise seen from above
};
// description of the terrain, terrain.yaml
struct manifest
{
int version{1};
std::vector<layer_def> layers{{"grass", 0.f}}; // the palette of materials, the first covers new chunks
std::string water_material{"water"}; // of the bodies of water which don't name their own
bool compress{true}; // pack files store their data compressed with zstd
float spacing{default_spacing}; // point spacing of new chunks
std::vector<water_body> water;
std::vector<modifier> modifiers;
};
bool load_manifest(std::string const &Path, manifest &Manifest, std::string *Error = nullptr);
bool save_manifest(std::string const &Path, manifest const &Manifest, std::string *Error = nullptr);
// pack file (.tch), little endian:
//
// header, 64 bytes: 'TCH1', uint32 version, int32 pack x, int32 pack z, uint32 chunks along a side, float chunk size,
// uint64 offset of the chunk index, reserved
// chunk index: per chunk (rows of growing z): uint64 offset of its slice table (0: no chunk), uint32 slice count, uint32 reserved
// slice tables: per slice: uint32 tag, uint32 codec (0: stored, 1: zstd), uint64 offset, uint32 stored size, uint32 size,
// uint32 crc-32 of the data, uint32 reserved
// slice data
//
// slices of a chunk:
// 'CHNK' uint16 cells, uint16 paint samples less one, uint16 layers[8]
// 'HGHT' heights: uint8 encoding (0: int32 base and uint16 steps of height_step above it, delta coded in row order;
// 1: float32), then the values
// 'HBAS' heights before the modifiers, as 'HGHT'
// 'HADJ' touch-ups over the modifiers, as 'HGHT' (only with 'HBAS'; older versions skip it and keep the heights)
// 'SPLT' paint weights: uint8 per sample per used layer, planar
class pack_file
{
public:
// reads the header and the index of specified file. returns: true on success
bool open(std::string const &Path, std::string *Error = nullptr);
bool has(chunk_key const &Key) const;
// chunks the file holds
std::vector<chunk_key> chunks() const;
// reads specified chunk. returns: the chunk, or null on failure
std::shared_ptr<chunk_data> read(chunk_key const &Key, std::string *Error = nullptr) const;
pack_key key() const { return m_key; }
private:
struct slice
{
std::uint32_t tag{0};
std::uint32_t codec{0};
std::uint64_t offset{0};
std::uint32_t stored{0};
std::uint32_t size{0};
std::uint32_t crc{0};
};
std::string m_path;
pack_key m_key{0, 0};
std::array<std::vector<slice>, pack_side * pack_side> m_slices;
};
// writes specified chunks (all of the same pack) to the pack file. returns: true on success
bool write_pack(std::string const &Path, pack_key const &Key, std::vector<chunk_ptr> const &Chunks, bool const Compress, std::string *Error = nullptr);
// triangles (indices of the points, three to a triangle) covering a simple polygon in the XZ plane
std::vector<std::uint32_t> triangulate(std::vector<glm::dvec2> const &Outline);
// samples the height of the ground outside of a chunk; returns false where it doesn't know
using height_source = std::function<bool(double const X, double const Z, double &Height)>;
// geometry of a chunk at specified level of detail: grid points taken every Stride points of the grid, with a skirt
// along the edges hiding the gaps to the neighbours drawn with another grid. positions relative to Origin
struct mesh
{
gfx::vertex_array vertices;
gfx::index_array indices;
float radius{0.f}; // from the centre of the vertices
glm::dvec3 centre{0.0};
glm::dvec3 low{0.0}, high{0.0}; // world bounds of the grid, the skirts included
};
// strides the grid of a chunk with specified cells can be drawn with
int max_stride(int const Cells);
void build_mesh(chunk_data const &Chunk, int const Stride, glm::dvec3 const &Origin, height_source const &Outside, mesh &Out);
// indices of the mesh of a chunk with specified cells and stride; the same for every chunk of these
void build_indices(int const Cells, int const Stride, gfx::index_array &Out);
// vertices the mesh of a chunk with specified cells and stride has
std::size_t mesh_vertex_count(int const Cells, int const Stride);
} // namespace heightmap