mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 06:19:19 +02:00
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
This commit is contained in:
@@ -136,6 +136,8 @@ void terrain_streamer::close()
|
||||
m_loading.clear();
|
||||
m_removed.clear();
|
||||
m_palette.clear();
|
||||
m_recording = 0;
|
||||
m_change = change();
|
||||
m_active = false;
|
||||
m_name.clear();
|
||||
m_directory.clear();
|
||||
@@ -159,6 +161,8 @@ void terrain_streamer::reset()
|
||||
m_removed.clear();
|
||||
m_palette.clear();
|
||||
clear_water(false);
|
||||
m_recording = 0;
|
||||
m_change = change();
|
||||
m_active = false;
|
||||
m_name.clear();
|
||||
m_directory.clear();
|
||||
@@ -898,6 +902,24 @@ bool terrain_streamer::add_chunk(int Cx, int Cz, float Spacing, std::function<fl
|
||||
for (int iz = 0; iz <= data->cells; ++iz)
|
||||
for (int ix = 0; ix <= data->cells; ++ix)
|
||||
data->heights[static_cast<std::size_t>(iz) * data->side() + ix] = Height(low.x + ix * step, low.y + iz * step);
|
||||
return add_chunk(std::move(data));
|
||||
}
|
||||
|
||||
bool terrain_streamer::add_chunk(std::shared_ptr<heightmap::chunk_data> Data)
|
||||
{
|
||||
if (Data == nullptr || false == Data->valid())
|
||||
return false;
|
||||
auto const key{Data->key};
|
||||
if (false == m_active || load_now(key) != nullptr || m_chunks.count(key) > 0)
|
||||
return false;
|
||||
if (false == in_region(glm::dvec2{chunk_centre(key).x, chunk_centre(key).z}))
|
||||
return false;
|
||||
for (auto &layer : Data->layers)
|
||||
if (layer != heightmap::no_layer && layer >= m_manifest.layers.size())
|
||||
layer = 0;
|
||||
auto data{std::move(Data)};
|
||||
auto const low{data->corner()};
|
||||
remember(key, nullptr);
|
||||
auto chunk{std::make_unique<editor_terrain>(std::move(data))};
|
||||
chunk->m_modified = true;
|
||||
auto const [lowest, highest]{std::minmax_element(chunk->heights().begin(), chunk->heights().end())};
|
||||
@@ -915,6 +937,12 @@ bool terrain_streamer::add_chunk(int Cx, int Cz, float Spacing, std::function<fl
|
||||
void terrain_streamer::remove_chunk(int Cx, int Cz)
|
||||
{
|
||||
chunk_key const key{Cx, Cz};
|
||||
if (recording())
|
||||
{
|
||||
// the data is needed to bring the chunk back
|
||||
if (auto const *chunk{load_now(key)})
|
||||
remember(*chunk);
|
||||
}
|
||||
auto const lookup{m_chunks.find(key)};
|
||||
if (lookup != m_chunks.end())
|
||||
{
|
||||
@@ -931,6 +959,7 @@ void terrain_streamer::spacing(int Cx, int Cz, float Spacing)
|
||||
auto *chunk{load_now({Cx, Cz})};
|
||||
if (chunk == nullptr || heightmap::cells_for(Spacing) == chunk->cells())
|
||||
return;
|
||||
remember(*chunk);
|
||||
chunk->replace(chunk->data().resampled(Spacing), true, false);
|
||||
auto const low{chunk->data().corner()};
|
||||
apply_modifiers(*chunk, low, low + glm::dvec2{heightmap::chunk_size});
|
||||
@@ -957,6 +986,7 @@ bool terrain_streamer::reshape(glm::dvec2 const &Min, glm::dvec2 const &Max, std
|
||||
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())
|
||||
@@ -1081,6 +1111,7 @@ bool terrain_streamer::paint(double X, double Z, double Radius, double Strength,
|
||||
if (false == painted)
|
||||
continue;
|
||||
data->compact_layers();
|
||||
remember(*chunk);
|
||||
chunk->replace(std::move(data), false, true);
|
||||
changed = true;
|
||||
}
|
||||
@@ -1092,6 +1123,7 @@ std::uint16_t terrain_streamer::layer(std::string const &Material, float const S
|
||||
for (std::size_t i = 0; i < m_manifest.layers.size(); ++i)
|
||||
if (m_manifest.layers[i].material == Material && (Size <= 0.f || m_manifest.layers[i].size == Size))
|
||||
return static_cast<std::uint16_t>(i);
|
||||
remember_manifest();
|
||||
m_manifest.layers.push_back({Material, std::max(0.f, Size)});
|
||||
m_manifestchanged = true;
|
||||
return static_cast<std::uint16_t>(m_manifest.layers.size() - 1);
|
||||
@@ -1101,6 +1133,7 @@ void terrain_streamer::layer_size(std::uint16_t const Layer, float const Size)
|
||||
{
|
||||
if (Layer >= m_manifest.layers.size() || m_manifest.layers[Layer].size == Size)
|
||||
return;
|
||||
remember_manifest();
|
||||
m_manifest.layers[Layer].size = std::max(0.f, Size);
|
||||
m_manifestchanged = true;
|
||||
// the materials of the chunks using it are made again
|
||||
@@ -1109,6 +1142,19 @@ void terrain_streamer::layer_size(std::uint16_t const Layer, float const Size)
|
||||
entry.second->m_paintedversion = 0;
|
||||
}
|
||||
|
||||
void terrain_streamer::layer_material(std::uint16_t const Layer, std::string const &Material)
|
||||
{
|
||||
if (Layer >= m_manifest.layers.size() || Material.empty() || m_manifest.layers[Layer].material == Material)
|
||||
return;
|
||||
remember_manifest();
|
||||
m_manifest.layers[Layer].material = Material;
|
||||
m_manifestchanged = true;
|
||||
m_palette.erase(Layer);
|
||||
for (auto &entry : m_chunks)
|
||||
if (entry.second->data().slot_of(Layer) >= 0 || (Layer == 0 && entry.second->data().layer_count() == 0))
|
||||
entry.second->m_paintedversion = 0;
|
||||
}
|
||||
|
||||
void terrain_streamer::compress(bool const State)
|
||||
{
|
||||
if (m_manifest.compress == State)
|
||||
@@ -1128,6 +1174,7 @@ void terrain_streamer::default_spacing(float const Spacing)
|
||||
|
||||
void terrain_streamer::water(std::vector<heightmap::water_body> Water)
|
||||
{
|
||||
remember_manifest();
|
||||
m_manifest.water = std::move(Water);
|
||||
m_manifestchanged = true;
|
||||
m_waterchanged = true;
|
||||
@@ -1155,12 +1202,14 @@ void terrain_streamer::apply_modifiers(editor_terrain &Chunk, glm::dvec2 const &
|
||||
// no modifier reaches the chunk (any more): its own heights are what's left
|
||||
if (false == current.base.empty())
|
||||
{
|
||||
remember(Chunk);
|
||||
auto &data{Chunk.edit(true, false)};
|
||||
data.heights = std::move(data.base);
|
||||
data.base.clear();
|
||||
}
|
||||
return;
|
||||
}
|
||||
remember(Chunk);
|
||||
auto &data{Chunk.edit(true, false)};
|
||||
if (data.base.empty())
|
||||
data.base = data.heights;
|
||||
@@ -1187,6 +1236,7 @@ void terrain_streamer::apply_modifiers(glm::dvec2 const &Min, glm::dvec2 const &
|
||||
|
||||
void terrain_streamer::modifier(heightmap::modifier Modifier)
|
||||
{
|
||||
remember_manifest();
|
||||
auto bounds{Modifier.bounds()};
|
||||
auto const existing{std::find_if(m_manifest.modifiers.begin(), m_manifest.modifiers.end(), [&](heightmap::modifier const &Item) { return Item.name == Modifier.name; })};
|
||||
if (existing != m_manifest.modifiers.end())
|
||||
@@ -1208,6 +1258,7 @@ bool terrain_streamer::remove_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 bounds{existing->bounds()};
|
||||
m_manifest.modifiers.erase(existing);
|
||||
m_manifestchanged = true;
|
||||
@@ -1222,6 +1273,103 @@ bool terrain_streamer::modified() const
|
||||
return std::any_of(m_chunks.begin(), m_chunks.end(), [](auto const &Entry) { return Entry.second->modified(); });
|
||||
}
|
||||
|
||||
// undo
|
||||
|
||||
void terrain_streamer::begin_change()
|
||||
{
|
||||
if (m_recording++ == 0)
|
||||
m_change = change();
|
||||
}
|
||||
|
||||
terrain_streamer::change terrain_streamer::end_change()
|
||||
{
|
||||
if (m_recording == 0)
|
||||
return change();
|
||||
if (--m_recording > 0)
|
||||
return change(); // the outer change goes on
|
||||
change result{std::move(m_change)};
|
||||
m_change = change();
|
||||
return result;
|
||||
}
|
||||
|
||||
void terrain_streamer::remember(chunk_key const &Key, heightmap::chunk_ptr const &Data)
|
||||
{
|
||||
if (m_recording > 0)
|
||||
m_change.chunks.emplace(Key, Data); // only the first state counts
|
||||
}
|
||||
|
||||
void terrain_streamer::remember_manifest()
|
||||
{
|
||||
if (m_recording == 0 || m_change.manifest)
|
||||
return;
|
||||
m_change.manifest = true;
|
||||
m_change.layers = m_manifest.layers;
|
||||
m_change.water = m_manifest.water;
|
||||
m_change.modifiers = m_manifest.modifiers;
|
||||
}
|
||||
|
||||
terrain_streamer::change terrain_streamer::restore(change const &Change)
|
||||
{
|
||||
change opposite;
|
||||
if (false == m_active)
|
||||
return opposite;
|
||||
for (auto const &[key, data] : Change.chunks)
|
||||
{
|
||||
auto *current{load_now(key)};
|
||||
opposite.chunks.emplace(key, current != nullptr ? current->shared() : nullptr);
|
||||
if (data == nullptr)
|
||||
{
|
||||
if (current != nullptr)
|
||||
{
|
||||
undraw(*current);
|
||||
m_chunks.erase(key);
|
||||
m_removed.insert(key);
|
||||
touch_neighbours(key);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (current != nullptr)
|
||||
{
|
||||
current->replace(data, true, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto chunk{std::make_unique<editor_terrain>(data)};
|
||||
chunk->m_modified = true;
|
||||
auto const [low, high]{std::minmax_element(chunk->heights().begin(), chunk->heights().end())};
|
||||
auto const corner{data->corner()};
|
||||
chunk->m_low = {corner.x, *low, corner.y};
|
||||
chunk->m_high = {corner.x + heightmap::chunk_size, *high, corner.y + heightmap::chunk_size};
|
||||
m_chunks.emplace(key, std::move(chunk));
|
||||
m_removed.erase(key);
|
||||
m_loading.erase(key);
|
||||
}
|
||||
touch_neighbours(key);
|
||||
}
|
||||
if (Change.manifest)
|
||||
{
|
||||
opposite.manifest = true;
|
||||
opposite.layers = m_manifest.layers;
|
||||
opposite.water = m_manifest.water;
|
||||
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;
|
||||
m_manifestchanged = true;
|
||||
m_waterchanged = true;
|
||||
if (false == samelayers)
|
||||
{
|
||||
// the materials of the chunks are made anew from the palette as it was
|
||||
m_palette.clear();
|
||||
for (auto &entry : m_chunks)
|
||||
entry.second->m_paintedversion = 0;
|
||||
}
|
||||
}
|
||||
return opposite;
|
||||
}
|
||||
|
||||
bool terrain_streamer::save(std::string *Error)
|
||||
{
|
||||
if (false == m_active)
|
||||
|
||||
Reference in New Issue
Block a user