mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 05:09:20 +02: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:
@@ -10,446 +10,33 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "stdafx.h"
|
||||
#include "editor/editorTerrain.hpp"
|
||||
|
||||
#include "scene/scene.h"
|
||||
#include "scene/scenenode.h"
|
||||
#include "simulation/simulation.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "model/vertex.h"
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace
|
||||
heightmap::chunk_data &editor_terrain::edit(bool const Shape, bool const Paint)
|
||||
{
|
||||
constexpr double kPi = 3.14159265358979323846;
|
||||
}
|
||||
|
||||
bool editor_terrain::create(glm::dvec3 const &Center, int Cells, float CellSize, std::string const &TextureName,
|
||||
height_sampler const &Sampler)
|
||||
{
|
||||
if (Cells < 1 || CellSize <= 0.0f || simulation::Region == nullptr)
|
||||
return false;
|
||||
|
||||
m_cells = Cells;
|
||||
m_cellsize = CellSize;
|
||||
double const half = 0.5 * static_cast<double>(Cells) * CellSize;
|
||||
m_x0 = Center.x - half;
|
||||
m_z0 = Center.z - half;
|
||||
m_heights.assign(static_cast<std::size_t>(Cells + 1) * (Cells + 1), static_cast<float>(Center.y));
|
||||
|
||||
// optionally seed the grid by sampling whatever geometry is already there (terrain capture)
|
||||
if (Sampler)
|
||||
{
|
||||
for (int iz = 0; iz <= Cells; ++iz)
|
||||
for (int ix = 0; ix <= Cells; ++ix)
|
||||
{
|
||||
double const vx = m_x0 + static_cast<double>(ix) * CellSize;
|
||||
double const vz = m_z0 + static_cast<double>(iz) * CellSize;
|
||||
double y;
|
||||
if (Sampler(vx, vz, y))
|
||||
m_heights[index(ix, iz)] = static_cast<float>(y);
|
||||
}
|
||||
}
|
||||
|
||||
m_material = TextureName.empty() ? null_handle : GfxRenderer->Fetch_Material(TextureName);
|
||||
|
||||
// section-level shapes are rendered relative to the section centre, so that is our geometry origin
|
||||
scene::basic_section &sec = simulation::Region->section(Center);
|
||||
sec.create_geometry(); // ensure existing section geometry is already built (idempotent)
|
||||
m_origin = sec.m_area.center;
|
||||
m_section = &sec;
|
||||
|
||||
std::vector<world_vertex> verts;
|
||||
build_vertices(verts, false);
|
||||
m_vertexcount = verts.size();
|
||||
|
||||
scene::shape_node shape;
|
||||
shape.make_terrain(m_material, std::move(verts), m_origin);
|
||||
|
||||
// upload to a dedicated bank; the renderer resolves draw calls by handle regardless of bank
|
||||
m_bank = GfxRenderer->Create_Bank();
|
||||
shape.create_geometry(m_bank); // sets the shape's geometry handle, clears its CPU vertices
|
||||
m_geometry = shape.data().geometry;
|
||||
|
||||
glm::dvec3 const shapecenter = shape.data().area.center;
|
||||
float const shaperadius = shape.radius(); // cached inside make_terrain, vertices already gone
|
||||
|
||||
sec.m_shapes.emplace_back(std::move(shape));
|
||||
// extend the section bounds so the new terrain isn't frustum-culled at its edges
|
||||
sec.m_area.radius = std::max(
|
||||
sec.m_area.radius,
|
||||
static_cast<float>(glm::length(sec.m_area.center - shapecenter) + shaperadius));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
glm::dvec3 editor_terrain::vertex_position(int Ix, int Iz) const
|
||||
{
|
||||
return glm::dvec3(
|
||||
m_x0 + static_cast<double>(Ix) * m_cellsize,
|
||||
static_cast<double>(m_heights[index(Ix, Iz)]),
|
||||
m_z0 + static_cast<double>(Iz) * m_cellsize);
|
||||
}
|
||||
|
||||
glm::vec3 editor_terrain::vertex_normal(int Ix, int Iz) const
|
||||
{
|
||||
// central differences on the heightfield; clamp to edges
|
||||
int const xl = std::max(0, Ix - 1), xr = std::min(m_cells, Ix + 1);
|
||||
int const zl = std::max(0, Iz - 1), zr = std::min(m_cells, Iz + 1);
|
||||
float const hl = m_heights[index(xl, Iz)], hr = m_heights[index(xr, Iz)];
|
||||
float const hd = m_heights[index(Ix, zl)], hu = m_heights[index(Ix, zr)];
|
||||
float const dx = static_cast<float>((xr - xl)) * m_cellsize;
|
||||
float const dz = static_cast<float>((zr - zl)) * m_cellsize;
|
||||
glm::vec3 n(-(hr - hl) / (dx > 0.f ? dx : 1.f), 1.0f, -(hu - hd) / (dz > 0.f ? dz : 1.f));
|
||||
return glm::normalize(n);
|
||||
}
|
||||
|
||||
world_vertex editor_terrain::make_vertex(int Ix, int Iz) const
|
||||
{
|
||||
world_vertex v;
|
||||
v.position = vertex_position(Ix, Iz);
|
||||
v.normal = vertex_normal(Ix, Iz);
|
||||
v.texture = glm::vec2(static_cast<float>(Ix), static_cast<float>(Iz));
|
||||
return v;
|
||||
}
|
||||
|
||||
// emits one quad (two upward-facing triangles) spanning grid corners (X0,Z0)..(X1,Z1)
|
||||
void editor_terrain::emit_quad(int X0, int Z0, int X1, int Z1, std::vector<world_vertex> &Out) const
|
||||
{
|
||||
world_vertex const v00 = make_vertex(X0, Z0);
|
||||
world_vertex const v10 = make_vertex(X1, Z0);
|
||||
world_vertex const v01 = make_vertex(X0, Z1);
|
||||
world_vertex const v11 = make_vertex(X1, Z1);
|
||||
|
||||
Out.push_back(v00);
|
||||
Out.push_back(v01);
|
||||
Out.push_back(v10);
|
||||
|
||||
Out.push_back(v11);
|
||||
Out.push_back(v10);
|
||||
Out.push_back(v01);
|
||||
}
|
||||
|
||||
// true if every grid vertex inside the block stays within Error of the bilinear plane of its corners
|
||||
bool editor_terrain::block_flat(int X0, int Z0, int X1, int Z1, float Error) const
|
||||
{
|
||||
float const h00 = m_heights[index(X0, Z0)];
|
||||
float const h10 = m_heights[index(X1, Z0)];
|
||||
float const h01 = m_heights[index(X0, Z1)];
|
||||
float const h11 = m_heights[index(X1, Z1)];
|
||||
double const wx = X1 - X0, wz = Z1 - Z0;
|
||||
|
||||
for (int iz = Z0; iz <= Z1; ++iz)
|
||||
for (int ix = X0; ix <= X1; ++ix)
|
||||
{
|
||||
double const tx = (wx > 0.0) ? (ix - X0) / wx : 0.0;
|
||||
double const tz = (wz > 0.0) ? (iz - Z0) / wz : 0.0;
|
||||
double const top = h00 + tx * (h10 - h00);
|
||||
double const bot = h01 + tx * (h11 - h01);
|
||||
double const interp = top + tz * (bot - top);
|
||||
if (std::abs(static_cast<double>(m_heights[index(ix, iz)]) - interp) > Error)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// adaptive quadtree: flat blocks stay whole, the others are split into four
|
||||
void editor_terrain::collect_blocks(int X0, int Z0, int X1, int Z1, float Error, std::vector<std::array<int, 4>> &Out) const
|
||||
{
|
||||
bool const splitx = (X1 - X0) > 1;
|
||||
bool const splitz = (Z1 - Z0) > 1;
|
||||
|
||||
if ((!splitx && !splitz) || block_flat(X0, Z0, X1, Z1, Error))
|
||||
{
|
||||
Out.push_back({X0, Z0, X1, Z1});
|
||||
return;
|
||||
}
|
||||
|
||||
int const xm = splitx ? (X0 + X1) / 2 : X1;
|
||||
int const zm = splitz ? (Z0 + Z1) / 2 : Z1;
|
||||
|
||||
collect_blocks(X0, Z0, xm, zm, Error, Out);
|
||||
if (splitx)
|
||||
collect_blocks(xm, Z0, X1, zm, Error, Out);
|
||||
if (splitz)
|
||||
collect_blocks(X0, zm, xm, Z1, Error, Out);
|
||||
if (splitx && splitz)
|
||||
collect_blocks(xm, zm, X1, Z1, Error, Out);
|
||||
}
|
||||
|
||||
void editor_terrain::emit_block(std::array<int, 4> const &Block, std::vector<char> const &Marked, std::vector<world_vertex> &Out) const
|
||||
{
|
||||
int const x0 = Block[0], z0 = Block[1], x1 = Block[2], z1 = Block[3];
|
||||
// the edges walked the way the quads wind
|
||||
std::vector<std::pair<int, int>> ring;
|
||||
for (int z = z0; z < z1; ++z)
|
||||
if (z == z0 || Marked[index(x0, z)] != 0)
|
||||
ring.push_back({x0, z});
|
||||
for (int x = x0; x < x1; ++x)
|
||||
if (x == x0 || Marked[index(x, z1)] != 0)
|
||||
ring.push_back({x, z1});
|
||||
for (int z = z1; z > z0; --z)
|
||||
if (z == z1 || Marked[index(x1, z)] != 0)
|
||||
ring.push_back({x1, z});
|
||||
for (int x = x1; x > x0; --x)
|
||||
if (x == x1 || Marked[index(x, z0)] != 0)
|
||||
ring.push_back({x, z0});
|
||||
if (ring.size() <= 4)
|
||||
{
|
||||
emit_quad(x0, z0, x1, z1, Out);
|
||||
return;
|
||||
}
|
||||
|
||||
world_vertex centre;
|
||||
if ((x1 - x0) % 2 == 0 && (z1 - z0) % 2 == 0)
|
||||
centre = make_vertex((x0 + x1) / 2, (z0 + z1) / 2);
|
||||
else
|
||||
{
|
||||
auto const a = make_vertex(x0, z0), b = make_vertex(x1, z0), c = make_vertex(x0, z1), d = make_vertex(x1, z1);
|
||||
centre = a;
|
||||
centre.position = (a.position + b.position + c.position + d.position) * 0.25;
|
||||
centre.normal = glm::normalize(a.normal + b.normal + c.normal + d.normal);
|
||||
centre.texture = (a.texture + b.texture + c.texture + d.texture) * 0.25f;
|
||||
}
|
||||
for (std::size_t i = 0; i < ring.size(); ++i)
|
||||
{
|
||||
auto const &from = ring[i];
|
||||
auto const &to = ring[(i + 1) % ring.size()];
|
||||
Out.push_back(centre);
|
||||
Out.push_back(make_vertex(from.first, from.second));
|
||||
Out.push_back(make_vertex(to.first, to.second));
|
||||
}
|
||||
}
|
||||
|
||||
void editor_terrain::build_vertices(std::vector<world_vertex> &Out, bool Simplify) const
|
||||
{
|
||||
Out.clear();
|
||||
Out.reserve(static_cast<std::size_t>(m_cells) * m_cells * 6);
|
||||
|
||||
if (Simplify)
|
||||
{
|
||||
std::vector<std::array<int, 4>> blocks;
|
||||
collect_blocks(0, 0, m_cells, m_cells, m_simplify_error, blocks);
|
||||
// the corners of the blocks, and the whole edge of the patch which the neighbouring patch meets in its own way
|
||||
std::vector<char> marked(static_cast<std::size_t>(m_cells + 1) * (m_cells + 1), 0);
|
||||
for (int i = 0; i <= m_cells; ++i)
|
||||
marked[index(i, 0)] = marked[index(i, m_cells)] = marked[index(0, i)] = marked[index(m_cells, i)] = 1;
|
||||
for (auto const &block : blocks)
|
||||
marked[index(block[0], block[1])] = marked[index(block[2], block[1])] = marked[index(block[0], block[3])] = marked[index(block[2], block[3])] = 1;
|
||||
for (auto const &block : blocks)
|
||||
emit_block(block, marked, Out);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int iz = 0; iz < m_cells; ++iz)
|
||||
for (int ix = 0; ix < m_cells; ++ix)
|
||||
emit_quad(ix, iz, ix + 1, iz + 1, Out);
|
||||
}
|
||||
|
||||
void editor_terrain::regenerate(bool Simplify)
|
||||
{
|
||||
if (!valid())
|
||||
return;
|
||||
|
||||
std::vector<world_vertex> verts;
|
||||
build_vertices(verts, Simplify);
|
||||
|
||||
gfx::vertex_array gpuverts;
|
||||
gpuverts.reserve(verts.size());
|
||||
for (auto const &v : verts)
|
||||
gpuverts.emplace_back(gfx::basic_vertex::convert(v, m_origin));
|
||||
gfx::userdata_array nouserdata;
|
||||
|
||||
// fast path: same vertex count -> in-place swap into the existing chunk
|
||||
if (gpuverts.size() == m_vertexcount && (m_geometry.bank != 0 || m_geometry.chunk != 0))
|
||||
{
|
||||
GfxRenderer->Replace(gpuverts, nouserdata, m_geometry, GL_TRIANGLES);
|
||||
return;
|
||||
}
|
||||
|
||||
// count changed (optimize / un-optimize): upload a fresh chunk and point the shape at it
|
||||
gfx::geometry_handle const newhandle = GfxRenderer->Insert(gpuverts, nouserdata, m_bank, GL_TRIANGLES);
|
||||
if (m_section != nullptr)
|
||||
{
|
||||
for (auto &shape : m_section->m_shapes)
|
||||
{
|
||||
auto const h = shape.data().geometry;
|
||||
if (h.bank == m_geometry.bank && h.chunk == m_geometry.chunk)
|
||||
{
|
||||
shape.geometry(newhandle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_geometry = newhandle;
|
||||
m_vertexcount = gpuverts.size();
|
||||
}
|
||||
|
||||
void editor_terrain::optimize(float ErrorMetres)
|
||||
{
|
||||
m_simplify = true;
|
||||
m_simplify_error = (ErrorMetres > 0.0f ? ErrorMetres : 0.01f);
|
||||
m_dirty = false;
|
||||
regenerate(true);
|
||||
}
|
||||
|
||||
void editor_terrain::unoptimize()
|
||||
{
|
||||
m_simplify = false;
|
||||
regenerate(false);
|
||||
}
|
||||
|
||||
void editor_terrain::destroy()
|
||||
{
|
||||
if (m_section != nullptr)
|
||||
{
|
||||
// erase the shape whose geometry handle is ours; other shapes keep their handles (so they
|
||||
// keep rendering), and the geometry GC reclaims our now-undrawn chunk's GPU memory
|
||||
for (auto it = m_section->m_shapes.begin(); it != m_section->m_shapes.end(); ++it)
|
||||
{
|
||||
auto const h = it->data().geometry;
|
||||
if (h.bank == m_geometry.bank && h.chunk == m_geometry.chunk)
|
||||
{
|
||||
m_section->m_shapes.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_section = nullptr;
|
||||
m_geometry = gfx::geometry_handle{0, 0};
|
||||
m_cells = 0; // mark invalid
|
||||
m_heights.clear();
|
||||
}
|
||||
|
||||
bool editor_terrain::contains(double X, double Z) const
|
||||
{
|
||||
double const x1 = m_x0 + static_cast<double>(m_cells) * m_cellsize;
|
||||
double const z1 = m_z0 + static_cast<double>(m_cells) * m_cellsize;
|
||||
return (X >= m_x0 && X <= x1 && Z >= m_z0 && Z <= z1);
|
||||
}
|
||||
|
||||
double editor_terrain::height_at(double X, double Z) const
|
||||
{
|
||||
return height_in(m_heights, X, Z);
|
||||
}
|
||||
|
||||
double editor_terrain::height_in(std::vector<float> const &Heights, double X, double Z) const
|
||||
{
|
||||
auto const &m_heights = Heights;
|
||||
double const fx = (X - m_x0) / m_cellsize;
|
||||
double const fz = (Z - m_z0) / m_cellsize;
|
||||
int ix = static_cast<int>(std::floor(fx));
|
||||
int iz = static_cast<int>(std::floor(fz));
|
||||
ix = std::clamp(ix, 0, m_cells - 1);
|
||||
iz = std::clamp(iz, 0, m_cells - 1);
|
||||
double const tx = std::clamp(fx - ix, 0.0, 1.0);
|
||||
double const tz = std::clamp(fz - iz, 0.0, 1.0);
|
||||
|
||||
double const h00 = m_heights[index(ix, iz)];
|
||||
double const h10 = m_heights[index(ix + 1, iz)];
|
||||
double const h01 = m_heights[index(ix, iz + 1)];
|
||||
double const h11 = m_heights[index(ix + 1, iz + 1)];
|
||||
|
||||
// matches the triangulation in build_vertices
|
||||
if (tx + tz <= 1.0)
|
||||
return h00 + tx * (h10 - h00) + tz * (h01 - h00);
|
||||
return h11 + (1.0 - tx) * (h01 - h11) + (1.0 - tz) * (h10 - h11);
|
||||
}
|
||||
|
||||
bool editor_terrain::sculpt(double X, double Z, double Radius, double Strength)
|
||||
{
|
||||
if (!valid() || Radius <= 0.0)
|
||||
return false;
|
||||
|
||||
bool changed = false;
|
||||
for (int iz = 0; iz <= m_cells; ++iz)
|
||||
for (int ix = 0; ix <= m_cells; ++ix)
|
||||
{
|
||||
double const vx = m_x0 + static_cast<double>(ix) * m_cellsize;
|
||||
double const vz = m_z0 + static_cast<double>(iz) * m_cellsize;
|
||||
double const d = std::sqrt((vx - X) * (vx - X) + (vz - Z) * (vz - Z));
|
||||
if (d > Radius)
|
||||
continue;
|
||||
// smooth cosine falloff: full strength at the centre, zero at the rim
|
||||
double const falloff = 0.5 * (std::cos(kPi * d / Radius) + 1.0);
|
||||
m_heights[index(ix, iz)] += static_cast<float>(Strength * falloff);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
// sculpting edits the full-resolution mesh (fixed vertex count => fast in-place update);
|
||||
// mark dirty so it can be auto-simplified once the stroke finishes, and modified for saving
|
||||
m_simplify = false;
|
||||
m_dirty = true;
|
||||
m_modified = true;
|
||||
regenerate(false);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool editor_terrain::smooth(double X, double Z, double Radius, double Amount, std::function<double(double X, double Z, double Fallback)> const &Ground)
|
||||
{
|
||||
if (!valid() || Radius <= 0.0 || Amount <= 0.0)
|
||||
return false;
|
||||
double const reach = Radius + m_cellsize;
|
||||
if (X + reach < m_x0 || X - reach > m_x0 + m_cells * m_cellsize || Z + reach < m_z0 || Z - reach > m_z0 + m_cells * m_cellsize)
|
||||
return false;
|
||||
|
||||
double const step = m_cellsize;
|
||||
return reshape([&](double const Vx, double const Vz, float &Height) {
|
||||
double const d = std::sqrt((Vx - X) * (Vx - X) + (Vz - Z) * (Vz - Z));
|
||||
if (d > Radius)
|
||||
return false;
|
||||
double const own = Height;
|
||||
// the neighbours a cell away, read from the ground as it was before the stroke, whichever patch holds them
|
||||
double const average = (Ground(Vx - step, Vz, own) + Ground(Vx + step, Vz, own) + Ground(Vx, Vz - step, own) + Ground(Vx, Vz + step, own) + own * 4.0) / 8.0;
|
||||
double const falloff = 0.5 * (std::cos(kPi * d / Radius) + 1.0);
|
||||
double const smoothed = own + (average - own) * std::min(1.0, Amount * falloff);
|
||||
if (std::abs(smoothed - own) < 1e-5)
|
||||
return false;
|
||||
Height = static_cast<float>(smoothed);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
bool editor_terrain::reshape(std::function<bool(double X, double Z, float &Height)> const &Shaper)
|
||||
{
|
||||
if (!valid())
|
||||
return false;
|
||||
|
||||
bool changed = false;
|
||||
for (int iz = 0; iz <= m_cells; ++iz)
|
||||
for (int ix = 0; ix <= m_cells; ++ix)
|
||||
changed |= Shaper(m_x0 + static_cast<double>(ix) * m_cellsize, m_z0 + static_cast<double>(iz) * m_cellsize, m_heights[index(ix, iz)]);
|
||||
|
||||
if (changed)
|
||||
{
|
||||
m_simplify = false;
|
||||
m_dirty = true;
|
||||
m_modified = true;
|
||||
regenerate(false);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
void editor_terrain::restore(std::vector<float> const &Heights)
|
||||
{
|
||||
if (!valid() || Heights.size() != m_heights.size())
|
||||
return;
|
||||
m_heights = Heights;
|
||||
m_simplify = false;
|
||||
m_dirty = true;
|
||||
// a worker may still be making a mesh of the current data, so a copy is changed instead
|
||||
if (m_data.use_count() > 1)
|
||||
m_data = std::make_shared<heightmap::chunk_data>(*m_data);
|
||||
m_modified = true;
|
||||
regenerate(false);
|
||||
if (Shape)
|
||||
++m_shapeversion;
|
||||
if (Paint)
|
||||
++m_paintversion;
|
||||
// the pointer is to const for everyone else; this chunk is its only holder now
|
||||
return const_cast<heightmap::chunk_data &>(*m_data);
|
||||
}
|
||||
|
||||
void editor_terrain::replace(heightmap::chunk_ptr Data, bool const Shape, bool const Paint)
|
||||
{
|
||||
m_data = std::move(Data);
|
||||
m_modified = true;
|
||||
if (Shape)
|
||||
++m_shapeversion;
|
||||
if (Paint)
|
||||
++m_paintversion;
|
||||
}
|
||||
|
||||
glm::dvec3 editor_terrain::centre() const
|
||||
{
|
||||
double const c = 0.5 * static_cast<double>(m_cells) * m_cellsize;
|
||||
double y = 0.0;
|
||||
if (!m_heights.empty())
|
||||
y = m_heights[index(m_cells / 2, m_cells / 2)];
|
||||
return glm::dvec3(m_x0 + c, y, m_z0 + c);
|
||||
auto const low{m_data->corner()};
|
||||
auto const half{heightmap::chunk_size * 0.5};
|
||||
return {low.x + half, m_data->height_at(low.x + half, low.y + half), low.y + half};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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