17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 00:29:19 +02:00
Files
maszyna/editor/editorGroundMesh.hpp
Hirek193 8cd0bab27b Editor terrain: texture browser, undo, conversion from terrain files
- Terrain work mode shows a browser of the textures folder in place of the
  node bank; textures/materials can be dragged onto the palette entries
  (replacing the material), the material field and the water material field
- Ctrl+Z/Ctrl+Y for heightmap terrain changes: a brush stroke or an
  operation is one step of the editor history
- Ground conversion reads only the files of the terrain directives
  (.txtf with origin/rotate, or .btf without text), paints the chunks with
  the materials of the triangles, drops only the shapes of those files
- Fix: ground grids converted out-of-range floats to int, so whole-scene
  queries returned the triangles of a single cell (shifted chunks)
- Fix: terrain layers copied from textures already uploaded without their
  data stayed grey
- Fix: ImGui ID conflict between terrain tool radios and section headers
- Fix: layers of terrain .txtf files named with the scenery/ prefix and
  could not be saved
- Editor selftest commands for the terrain
2026-10-09 23:39:26 +02:00

151 lines
5.9 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 <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <vector>
#include <glm/glm.hpp>
// triangles of a single piece of the scenery - a shape or a model - sorted on a grid seen from above, so the ones
// overlapping an area can be picked without going through all of them.
// the editor asks about the ground a tile at a time, frame after frame, and a terrain model can hold a million
// triangles: walking it whole for every question is what used to bring the simulator down to a frame a second
class ground_mesh
{
public:
using triangle = std::array<glm::vec3, 3>; // relative to the origin of the mesh
using world_triangle = std::array<glm::dvec3, 3>;
// takes over provided triangles. Origin: the point their vertices are relative to
void assign(std::vector<triangle> Triangles, glm::dvec3 const &Origin)
{
m_triangles = std::move(Triangles);
m_origin = Origin;
m_starts.clear();
m_items.clear();
m_seen.assign(m_triangles.size(), 0);
m_query = 0;
if (m_triangles.empty())
return;
glm::vec2 low{m_triangles.front()[0].x, m_triangles.front()[0].z};
glm::vec2 high{low};
for (auto const &entry : m_triangles)
{
for (auto const &vertex : entry)
{
low = glm::min(low, glm::vec2{vertex.x, vertex.z});
high = glm::max(high, glm::vec2{vertex.x, vertex.z});
}
}
m_low = low;
auto const extent{glm::max(high - low, glm::vec2{1.f})};
// cells of a few metres, larger for a large mesh so the grid stays small
m_cellsize = std::max(8.f, std::max(extent.x, extent.y) / 96.f);
m_columns = static_cast<int>(extent.x / m_cellsize) + 1;
m_rows = static_cast<int>(extent.y / m_cellsize) + 1;
// a triangle is listed in every cell its bounds reach. the lists are kept one after another, with the place each starts at
std::vector<std::uint32_t> counts(static_cast<std::size_t>(m_columns) * m_rows + 1, 0);
for (auto const &entry : m_triangles)
{
auto const span{cells(entry)};
for (int row = span[1]; row <= span[3]; ++row)
for (int column = span[0]; column <= span[2]; ++column)
++counts[static_cast<std::size_t>(row) * m_columns + column + 1];
}
for (std::size_t cell = 1; cell < counts.size(); ++cell)
counts[cell] += counts[cell - 1];
m_starts = counts;
m_items.resize(m_starts.back());
for (std::uint32_t index = 0; index < m_triangles.size(); ++index)
{
auto const span{cells(m_triangles[index])};
for (int row = span[1]; row <= span[3]; ++row)
for (int column = span[0]; column <= span[2]; ++column)
m_items[counts[static_cast<std::size_t>(row) * m_columns + column]++] = index;
}
}
std::size_t size() const
{
return m_triangles.size();
}
// appends the triangles whose bounds overlap specified rectangle of the world, seen from above (x, z)
void collect(glm::dvec2 const &Min, glm::dvec2 const &Max, std::vector<world_triangle> &Out) const
{
if (m_triangles.empty())
return;
// the rectangle as the mesh sees it, widened by what's lost turning the numbers into single precision
glm::vec2 const low{static_cast<float>(Min.x - m_origin.x) - 0.001f, static_cast<float>(Min.y - m_origin.z) - 0.001f};
glm::vec2 const high{static_cast<float>(Max.x - m_origin.x) + 0.001f, static_cast<float>(Max.y - m_origin.z) + 0.001f};
auto const firstcolumn{column(low.x)};
auto const lastcolumn{column(high.x)};
auto const firstrow{row(low.y)};
auto const lastrow{row(high.y)};
if (++m_query == 0)
{
// the counter went all the way round
std::fill(m_seen.begin(), m_seen.end(), 0);
m_query = 1;
}
for (int row = firstrow; row <= lastrow; ++row)
{
for (int column = firstcolumn; column <= lastcolumn; ++column)
{
auto const cell{static_cast<std::size_t>(row) * m_columns + column};
for (auto item = m_starts[cell]; item < m_starts[cell + 1]; ++item)
{
auto const index{m_items[item]};
if (m_seen[index] == m_query)
continue;
m_seen[index] = m_query;
auto const &entry{m_triangles[index]};
if (std::max({entry[0].x, entry[1].x, entry[2].x}) < low.x || std::min({entry[0].x, entry[1].x, entry[2].x}) > high.x || std::max({entry[0].z, entry[1].z, entry[2].z}) < low.y ||
std::min({entry[0].z, entry[1].z, entry[2].z}) > high.y)
continue;
Out.push_back({m_origin + glm::dvec3{entry[0]}, m_origin + glm::dvec3{entry[1]}, m_origin + glm::dvec3{entry[2]}});
}
}
}
}
private:
// NOTE: clamped before the conversion: a rectangle asked about can reach far beyond the mesh (to infinity even),
// and a float out of the range of int doesn't turn into anything sensible
int column(float const X) const
{
return static_cast<int>(std::clamp(std::floor((X - m_low.x) / m_cellsize), 0.f, static_cast<float>(m_columns - 1)));
}
int row(float const Z) const
{
return static_cast<int>(std::clamp(std::floor((Z - m_low.y) / m_cellsize), 0.f, static_cast<float>(m_rows - 1)));
}
// cells the bounds of a triangle reach: first column, first row, last column, last row
std::array<int, 4> cells(triangle const &Triangle) const
{
return {column(std::min({Triangle[0].x, Triangle[1].x, Triangle[2].x})), row(std::min({Triangle[0].z, Triangle[1].z, Triangle[2].z})), column(std::max({Triangle[0].x, Triangle[1].x, Triangle[2].x})),
row(std::max({Triangle[0].z, Triangle[1].z, Triangle[2].z}))};
}
std::vector<triangle> m_triangles;
glm::dvec3 m_origin{0.0};
glm::vec2 m_low{0.f};
float m_cellsize{8.f};
int m_columns{1};
int m_rows{1};
std::vector<std::uint32_t> m_starts; // for each cell: where its list starts; one more entry closes the last list
std::vector<std::uint32_t> m_items; // numbers of the triangles, cell after cell
mutable std::vector<std::uint32_t> m_seen; // number of the last lookup each triangle was reported in
mutable std::uint32_t m_query{0};
};