17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 03:59:19 +02:00

Editor terrain: soft stitching to tracks, chunk resolution conversion, water materials

This commit is contained in:
2026-10-10 16:08:12 +02:00
parent 243ce636a7
commit 63302c1ace
8 changed files with 613 additions and 61 deletions

View File

@@ -954,16 +954,21 @@ void terrain_streamer::remove_chunk(int Cx, int Cz)
touch_neighbours(key);
}
void terrain_streamer::spacing(int Cx, int Cz, float Spacing)
bool terrain_streamer::spacing(int Cx, int Cz, float Spacing, int Paint)
{
auto *chunk{load_now({Cx, Cz})};
if (chunk == nullptr || heightmap::cells_for(Spacing) == chunk->cells())
return;
if (chunk == nullptr)
return false;
auto const &current{chunk->data()};
auto const repaint{Paint > 0 && current.paint > 0 && Paint != current.paint};
if (heightmap::cells_for(Spacing) == current.cells && false == repaint)
return false;
remember(*chunk);
chunk->replace(chunk->data().resampled(Spacing), true, false);
chunk->replace(current.resampled(Spacing, Paint), true, repaint);
auto const low{chunk->data().corner()};
apply_modifiers(*chunk, low, low + glm::dvec2{heightmap::chunk_size});
touch_neighbours(chunk->key());
return true;
}
bool terrain_streamer::reshape(glm::dvec2 const &Min, glm::dvec2 const &Max, std::function<bool(double X, double Z, float &Height)> const &Shaper)
@@ -978,24 +983,119 @@ bool terrain_streamer::reshape(glm::dvec2 const &Min, glm::dvec2 const &Max, std
auto const zs{index_range(low.y, step, current.side(), Min.y, Max.y)};
if (xs.first > xs.second || zs.first > zs.second)
continue;
// a copy is shaped, and taken only if anything changed
auto heights{current.base.empty() ? current.heights : current.base};
bool shaped{false};
if (current.base.empty() || false == m_touchup)
{
// a copy is shaped, and taken only if anything changed
auto heights{current.base.empty() ? current.heights : current.base};
bool shaped{false};
for (int iz = zs.first; iz <= zs.second; ++iz)
for (int ix = xs.first; ix <= xs.second; ++ix)
shaped |= Shaper(low.x + ix * step, low.y + iz * step, heights[static_cast<std::size_t>(iz) * current.side() + ix]);
if (false == shaped)
continue;
remember(*chunk);
auto &data{chunk->edit(true, false)};
(data.base.empty() ? data.heights : data.base) = std::move(heights);
if (false == data.base.empty())
apply_modifiers(*chunk, Min, Max);
changed = true;
continue;
}
// the terrain as it's seen is shaped. where a modifier shapes the ground (or a touch-up is there already) the change
// is a touch-up over it, elsewhere it's the ground under the modifiers, which may then reach further
auto heights{current.heights};
auto base{current.base};
auto adjust{current.adjust.empty() ? std::vector<float>(heights.size(), 0.f) : current.adjust};
bool shaped{false}, touched{false};
for (int iz = zs.first; iz <= zs.second; ++iz)
for (int ix = xs.first; ix <= xs.second; ++ix)
shaped |= Shaper(low.x + ix * step, low.y + iz * step, heights[static_cast<std::size_t>(iz) * current.side() + ix]);
{
auto const index{static_cast<std::size_t>(iz) * current.side() + ix};
auto height{heights[index]};
if (false == Shaper(low.x + ix * step, low.y + iz * step, height))
continue;
shaped = true;
auto const change{height - heights[index]};
auto const shapedbymodifier{std::abs(heights[index] - adjust[index] - base[index]) > 0.0005f};
if (shapedbymodifier || adjust[index] != 0.f)
{
adjust[index] += change;
touched = true;
}
else
base[index] += change;
heights[index] = height;
}
if (false == shaped)
continue;
remember(*chunk);
auto &data{chunk->edit(true, false)};
(data.base.empty() ? data.heights : data.base) = std::move(heights);
if (false == data.base.empty())
apply_modifiers(*chunk, Min, Max);
data.base = std::move(base);
if (touched)
data.adjust = std::move(adjust);
data.heights = std::move(heights);
apply_modifiers(*chunk, Min, Max);
changed = true;
}
return changed;
}
bool terrain_streamer::restore_stitching(double X, double Z, double Radius, double Amount)
{
if (Radius <= 0.0 || Amount <= 0.0)
return false;
glm::dvec2 const low{X - Radius, Z - Radius}, high{X + Radius, Z + Radius};
bool changed{false};
for (auto *chunk : chunks_in(low, high, false))
{
auto const &current{chunk->data()};
if (current.adjust.empty())
continue;
auto const corner{current.corner()};
auto const step{current.spacing()};
auto const xs{index_range(corner.x, step, current.side(), low.x, high.x)};
auto const zs{index_range(corner.y, step, current.side(), low.y, high.y)};
auto adjust{current.adjust};
bool restored{false};
for (int iz = zs.first; iz <= zs.second; ++iz)
for (int ix = xs.first; ix <= xs.second; ++ix)
{
auto const vx{corner.x + ix * step}, vz{corner.y + iz * step};
auto const distance{std::sqrt((vx - X) * (vx - X) + (vz - Z) * (vz - Z))};
auto &value{adjust[static_cast<std::size_t>(iz) * current.side() + ix]};
if (distance > Radius || value == 0.f)
continue;
value *= static_cast<float>(1.0 - std::min(1.0, Amount * falloff(distance, Radius)));
// what's below the step of the stored heights is gone
if (std::abs(value) < heightmap::height_step * 0.5)
value = 0.f;
restored = true;
}
if (false == restored)
continue;
remember(*chunk);
auto &data{chunk->edit(true, false)};
if (std::all_of(adjust.begin(), adjust.end(), [](float const Value) { return Value == 0.f; }))
data.adjust.clear();
else
data.adjust = std::move(adjust);
apply_modifiers(*chunk, low, high);
changed = true;
}
return changed;
}
std::size_t terrain_streamer::touched_up() const
{
std::size_t count{0};
for (auto const &entry : m_chunks)
{
auto const &adjust{entry.second->data().adjust};
count += static_cast<std::size_t>(std::count_if(adjust.begin(), adjust.end(), [](float const Value) { return Value != 0.f; }));
}
return count;
}
bool terrain_streamer::sculpt(double X, double Z, double Radius, double Strength)
{
if (Radius <= 0.0)
@@ -1017,7 +1117,7 @@ bool terrain_streamer::smooth(double X, double Z, double Radius, double Amount)
// every chunk the brush touches is smoothed from the same ground, so the edges they share stay together
std::vector<std::pair<editor_terrain const *, std::vector<float>>> before;
for (auto *chunk : chunks_in(low - glm::dvec2{6.0}, high + glm::dvec2{6.0}, false))
before.emplace_back(chunk, chunk->data().base.empty() ? chunk->heights() : chunk->data().base);
before.emplace_back(chunk, (chunk->data().base.empty() || m_touchup) ? chunk->heights() : chunk->data().base);
auto const ground = [&](double const Gx, double const Gz, double const Fallback) {
for (auto const &entry : before)
if (entry.first->contains(Gx, Gz))
@@ -1201,6 +1301,16 @@ void terrain_streamer::water(std::vector<heightmap::water_body> Water)
m_waterchanged = true;
}
void terrain_streamer::water_material(std::string const &Material)
{
if (Material.empty() || Material == m_manifest.water_material)
return;
remember_manifest();
m_manifest.water_material = Material;
m_manifestchanged = true;
m_waterchanged = true;
}
void terrain_streamer::apply_modifiers(editor_terrain &Chunk, glm::dvec2 const &Min, glm::dvec2 const &Max)
{
auto const &current{Chunk.data()};
@@ -1220,20 +1330,28 @@ void terrain_streamer::apply_modifiers(editor_terrain &Chunk, glm::dvec2 const &
}
if (false == reachesany)
{
// no modifier reaches the chunk (any more): its own heights are what's left
// no modifier reaches the chunk (any more): its own heights are what's left, with the touch-ups made over the modifiers
if (false == current.base.empty())
{
remember(Chunk);
auto &data{Chunk.edit(true, false)};
data.heights = std::move(data.base);
if (data.adjust.size() == data.heights.size())
for (std::size_t index = 0; index < data.heights.size(); ++index)
data.heights[index] += data.adjust[index];
data.base.clear();
data.adjust.clear();
}
return;
}
remember(Chunk);
auto &data{Chunk.edit(true, false)};
if (data.base.empty())
{
data.base = data.heights;
data.adjust.clear();
}
auto const touchups{data.adjust.size() == data.heights.size()};
auto const step{data.spacing()};
auto const xs{index_range(low.x, step, data.side(), std::max(Min.x, low.x), std::min(Max.x, high.x))};
auto const zs{index_range(low.y, step, data.side(), std::max(Min.y, low.y), std::min(Max.y, high.y))};
@@ -1245,7 +1363,7 @@ void terrain_streamer::apply_modifiers(editor_terrain &Chunk, glm::dvec2 const &
auto const x{low.x + ix * step}, z{low.y + iz * step};
for (auto const &shape : shapes)
shape.apply(x, z, height);
data.heights[index] = height;
data.heights[index] = touchups ? height + data.adjust[index] : height;
}
}
@@ -1287,6 +1405,41 @@ bool terrain_streamer::remove_modifier(std::string const &Name)
return true;
}
bool terrain_streamer::bake_modifier(std::string const &Name)
{
auto const existing{std::find_if(m_manifest.modifiers.begin(), m_manifest.modifiers.end(), [&](heightmap::modifier const &Item) { return Item.name == Name; })};
if (existing == m_manifest.modifiers.end())
return false;
remember_manifest();
auto const baked{*existing};
auto const bounds{baked.bounds()};
heightmap::modifier_shape const shape{baked};
// the ground under the modifiers takes the shape this one gives it; the other modifiers and the touch-ups go on top
for (auto *chunk : chunks_in(bounds.first, bounds.second, true))
{
auto const &current{chunk->data()};
if (current.base.empty())
continue;
auto const low{current.corner()};
auto const step{current.spacing()};
auto const xs{index_range(low.x, step, current.side(), bounds.first.x, bounds.second.x)};
auto const zs{index_range(low.y, step, current.side(), bounds.first.y, bounds.second.y)};
auto base{current.base};
bool shaped{false};
for (int iz = zs.first; iz <= zs.second; ++iz)
for (int ix = xs.first; ix <= xs.second; ++ix)
shaped |= shape.apply(low.x + ix * step, low.y + iz * step, base[static_cast<std::size_t>(iz) * current.side() + ix]);
if (false == shaped)
continue;
remember(*chunk);
chunk->edit(true, false).base = std::move(base);
}
m_manifest.modifiers.erase(std::find_if(m_manifest.modifiers.begin(), m_manifest.modifiers.end(), [&](heightmap::modifier const &Item) { return Item.name == Name; }));
m_manifestchanged = true;
apply_modifiers(bounds.first, bounds.second);
return true;
}
bool terrain_streamer::modified() const
{
if (m_manifestchanged || false == m_removed.empty())
@@ -1326,6 +1479,7 @@ void terrain_streamer::remember_manifest()
m_change.manifest = true;
m_change.layers = m_manifest.layers;
m_change.water = m_manifest.water;
m_change.water_material = m_manifest.water_material;
m_change.modifiers = m_manifest.modifiers;
}
@@ -1372,12 +1526,15 @@ terrain_streamer::change terrain_streamer::restore(change const &Change)
opposite.manifest = true;
opposite.layers = m_manifest.layers;
opposite.water = m_manifest.water;
opposite.water_material = m_manifest.water_material;
opposite.modifiers = m_manifest.modifiers;
auto const samelayers{std::equal(Change.layers.begin(), Change.layers.end(), m_manifest.layers.begin(), m_manifest.layers.end(),
[](heightmap::layer_def const &A, heightmap::layer_def const &B) { return A.material == B.material && A.size == B.size; })};
m_manifest.layers = Change.layers;
m_manifest.modifiers = Change.modifiers;
m_manifest.water = Change.water;
if (false == Change.water_material.empty())
m_manifest.water_material = Change.water_material;
m_manifestchanged = true;
m_waterchanged = true;
if (false == samelayers)
@@ -1523,7 +1680,7 @@ void terrain_streamer::build_water()
continue;
auto const material{GfxRenderer->Fetch_Material(body.material.empty() ? m_manifest.water_material : body.material)};
auto const *materialdata{GfxRenderer->Material(material)};
auto const repeat{(materialdata != nullptr && materialdata->GetSize().x > 0.f) ? static_cast<double>(materialdata->GetSize().x) : 8.0};
auto const repeat{body.size > 0.f ? static_cast<double>(body.size) : (materialdata != nullptr && materialdata->GetSize().x > 0.f) ? static_cast<double>(materialdata->GetSize().x) : 8.0};
glm::dvec3 const centre{centroid.x, body.level, centroid.y};
auto &section{simulation::Region->section(centre)};
// the section makes the geometry of its own shapes once, before it's drawn; done first, it leaves ours alone

View File

@@ -87,8 +87,9 @@ class terrain_streamer
// a chunk made elsewhere, at the place its key says; its layers are indices of the palette
bool add_chunk(std::shared_ptr<heightmap::chunk_data> Data);
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);
// changes the point spacing of the chunk at (Cx, Cz), heights resampled; Paint: paint samples along a side less one
// the chunk gets, 0 keeps the ones it has. returns: true if the chunk changed
bool spacing(int Cx, int Cz, float Spacing, int Paint = 0);
// 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
@@ -96,9 +97,19 @@ class terrain_streamer
// leads the ground within Radius of (X,Z) towards the height Target, Amount (0..1) of the way at the centre;
// Mode: 0 up and down, 1 only raises it, 2 only lowers it
bool level(double X, double Z, double Radius, double Target, double Amount, int Mode = 0);
// 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
// gives every grid point within the rectangle to Shaper, with its world position and height; Shaper returns true if it
// changed the height. where the modifiers shape the ground the change is a touch-up kept over them (see touch_up()),
// elsewhere it changes the ground under them
bool reshape(glm::dvec2 const &Min, glm::dvec2 const &Max, std::function<bool(double X, double Z, float &Height)> const &Shaper);
// true: the brushes shape the terrain as it's seen, also where the modifiers (tracks) shape it, the changes kept there
// as touch-ups over the modifiers; false: the brushes shape only the ground under the modifiers, which keep their shape
void touch_up(bool const State) { m_touchup = State; }
bool touch_up() const { return m_touchup; }
// takes the touch-ups within Radius of (X,Z) back, Amount (0..1) of the way at the centre: the terrain goes back to the
// shape the modifiers give it
bool restore_stitching(double X, double Z, double Radius, double Amount);
// grid points with touch-ups over the modifiers, in the chunks in memory
std::size_t touched_up() const;
// 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
@@ -111,9 +122,14 @@ class terrain_streamer
// bodies of water
std::vector<heightmap::water_body> const &water() const { return m_manifest.water; }
void water(std::vector<heightmap::water_body> Water);
// material of the bodies of water which don't name their own
void water_material(std::string const &Material);
// modifiers: added or replaced by name
void modifier(heightmap::modifier Modifier);
bool remove_modifier(std::string const &Name);
// the ground takes the shape the modifier gives it for good, and the modifier is removed: from then on the
// terrain there is shaped by hand like anywhere else
bool bake_modifier(std::string const &Name);
std::vector<heightmap::modifier> const &modifiers() const { return m_manifest.modifiers; }
// changed since the last save
bool modified() const;
@@ -122,9 +138,10 @@ class terrain_streamer
struct change
{
std::map<chunk_key, heightmap::chunk_ptr> chunks; // the data the chunks had; null: there was no chunk
bool manifest{false}; // the palette, the water and the modifiers below were changed
bool manifest{false}; // the palette, the water (and its material) and the modifiers below were changed
std::vector<heightmap::layer_def> layers;
std::vector<heightmap::water_body> water;
std::string water_material;
std::vector<heightmap::modifier> modifiers;
bool empty() const { return chunks.empty() && false == manifest; }
@@ -238,6 +255,7 @@ class terrain_streamer
heightmap::manifest m_manifest;
bool m_manifestchanged{false};
float m_detail{120.f};
bool m_touchup{true};
int m_radius{2};
std::map<heightmap::pack_key, pack_state> m_packs;