17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-30 08:30:28 +01: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

@@ -920,6 +920,7 @@ void editor_mode::restore_terrain(EditorSnapshot &Snapshot)
Snapshot.terrain = std::make_shared<terrain_streamer::change const>(std::move(opposite));
forget_ground();
m_water_selected = -1;
m_water_material_of = -1;
}
void editor_mode::undo_last()
@@ -1607,7 +1608,9 @@ void editor_mode::render_terrain_ui()
ImGui::SameLine();
radio(STR_C("Chunks"), terrain_tool::chunks, STR_C("LMB adds a chunk shaped as the ground under it, next to the clicked one; Shift+LMB removes the clicked chunk"));
ImGui::SameLine();
radio(STR_C("Spacing"), terrain_tool::spacing, STR_C("LMB gives the clicked chunk the point spacing chosen below, the ground resampled"));
radio(STR_C("Spacing"), terrain_tool::spacing, STR_C("LMB selects the clicked chunk, Ctrl+LMB adds it to the selection or takes it away;\nthe point spacing of the selected chunks is converted in \"Chunk resolution\" below"));
ImGui::SameLine();
radio(STR_C("Restore"), terrain_tool::restore, STR_C("LMB takes back the touch-ups made over the tracks under the brush:\nthe terrain returns to the shape the modifiers (tracks) give it"));
radio(STR_C("Sculpt"), terrain_tool::sculpt, STR_C("LMB raises the terrain under the brush, Shift+LMB lowers it"));
ImGui::SameLine();
radio(STR_C("Smooth"), terrain_tool::smooth, STR_C("LMB evens the terrain out under the brush"));
@@ -1625,7 +1628,7 @@ void editor_mode::render_terrain_ui()
if (spacing_combo(STR_C("Point spacing"), m_terrain_spacing))
m_streamer.default_spacing(m_terrain_spacing);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("Spacing of the grid points of the chunks made, and the one the spacing tool gives the clicked chunk.\n"
ImGui::SetTooltip("%s", STR_C("Spacing of the grid points of the chunks made.\n"
"A chunk is 250 x 250 m; finer grids take more memory and time to draw."));
ImGui::SetNextItemWidth(120.0f);
ImGui::InputFloat(STR_C("Base height (m)"), &m_terrain_baseheight, 0.0f, 0.0f, "%.2f");
@@ -1649,6 +1652,56 @@ void editor_mode::render_terrain_ui()
}
}
if (ImGui::CollapsingHeader(STR_C("Chunk resolution"), m_terrain_tool == terrain_tool::spacing ? ImGuiTreeNodeFlags_DefaultOpen : 0))
{
// chunks removed since are no longer selected
for (auto it = m_terrain_selection.begin(); it != m_terrain_selection.end();)
it = m_streamer.exists(*it) ? std::next(it) : m_terrain_selection.erase(it);
if (m_terrain_selection.empty())
ImGui::TextWrapped("%s", STR_C("No chunk selected. With the Spacing tool LMB selects the clicked chunk, Ctrl+LMB adds one or takes it away."));
else
{
bool coarser = false;
std::size_t points = 0, after = 0;
int const cells = heightmap::cells_for(m_terrain_convert_spacing);
ImGui::BeginChild("##chunkselection", ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * std::min<float>(6.0f, static_cast<float>(m_terrain_selection.size()) + 0.5f)), true);
for (auto const &key : m_terrain_selection)
{
auto const centre = heightmap::chunk_corner(key) + glm::dvec2{heightmap::chunk_size * 0.5};
auto const *chunk = m_streamer.terrain_at(centre.x, centre.y);
if (chunk == nullptr)
{
ImGui::TextDisabled(STR_C("%d,%d: not loaded"), key.first, key.second);
continue;
}
auto const &data = chunk->data();
coarser |= cells < data.cells;
points += static_cast<std::size_t>(data.side()) * data.side();
after += static_cast<std::size_t>(cells + 1) * (cells + 1);
auto const paint = data.paint > 0 ? format(STR_C(", paint every %.2f m"), heightmap::chunk_size / data.paint) : std::string(STR_C(", one material"));
auto const touchups = data.adjust.empty() ? std::string() : std::string(STR_C(", touch-ups"));
ImGui::Text("%d,%d: %s%s%s", key.first, key.second, spacing_label(static_cast<float>(data.spacing())), paint.c_str(), touchups.c_str());
}
ImGui::EndChild();
spacing_combo(STR_C("Convert to"), m_terrain_convert_spacing);
ImGui::SameLine();
if (ImGui::Button(STR_C("Convert")))
convert_selected_chunks();
ImGui::SameLine();
if (ImGui::Button(STR_C("Clear the selection")))
m_terrain_selection.clear();
ImGui::Checkbox(STR_C("Paint every 0.5 m with 0.5 m spacing"), &m_terrain_convert_paint);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("Chunks converted to 0.5 m get their paint samples every 0.5 m as well (every metre otherwise),\n"
"so the materials can be painted as finely as the ground is shaped"));
if (points > 0)
ImGui::TextDisabled(STR_C("Grid points: %zu -> %zu"), points, after);
if (coarser)
ImGui::TextWrapped("%s", STR_C("A coarser grid can't hold the shapes smaller than its spacing: they're averaged out. "
"The edges of the chunks keep their heights, so the neighbours still meet them. Ctrl+Z takes it back."));
}
}
if (terrain_brush() && ImGui::CollapsingHeader(STR_C("Brush"), ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::SetNextItemWidth(120.0f);
@@ -1658,7 +1711,18 @@ void editor_mode::render_terrain_ui()
ImGui::DragFloat(STR_C("Strength"), &m_terrain_brush_strength, 0.05f, 0.05f, 50.0f, "%.2f");
m_terrain_brush_strength = std::max(0.05f, m_terrain_brush_strength);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("Sculpting: metres per second at the middle of the brush; smoothing, levelling and painting: how fast they work"));
ImGui::SetTooltip("%s", STR_C("Sculpting: metres per second at the middle of the brush; smoothing, levelling, painting and restoring: how fast they work"));
if (m_terrain_tool == terrain_tool::sculpt || m_terrain_tool == terrain_tool::smooth || m_terrain_tool == terrain_tool::level)
{
auto touchup = m_streamer.touch_up();
if (ImGui::Checkbox(STR_C("Touch up over the tracks"), &touchup))
m_streamer.touch_up(touchup);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("On: the brush shapes the terrain as it's seen, also where the tracks (modifiers) shape it;\n"
"the changes there are kept as touch-ups over the modifiers, and stay when the track changes.\n"
"The Restore tool takes them back.\n"
"Off: the brush shapes only the ground under the modifiers, the tracks keep their formation and slopes."));
}
if (m_terrain_tool == terrain_tool::level)
{
ImGui::SetNextItemWidth(120.0f);
@@ -1750,13 +1814,27 @@ void editor_mode::render_terrain_ui()
}
auto const &water{m_streamer.water()};
std::optional<std::size_t> removed;
std::optional<std::pair<std::size_t, std::string>> dropped;
for (std::size_t i = 0; i < water.size(); ++i)
{
ImGui::PushID(static_cast<int>(i));
if (ImGui::Selectable(format("%s, %.2f m, %zu points", water[i].name.c_str(), water[i].level, water[i].outline.size()).c_str(), static_cast<int>(i) == m_water_selected))
auto const material = water[i].material.empty() ? std::string(STR_C("default material")) : water[i].material;
if (ImGui::Selectable(format("%s, %.2f m, %zu points, %s", water[i].name.c_str(), water[i].level, water[i].outline.size(), material.c_str()).c_str(), static_cast<int>(i) == m_water_selected))
m_water_selected = static_cast<int>(i);
// a texture dropped on an entry becomes the material of that water
std::string name;
if (material_drop(name))
dropped = std::make_pair(i, name);
ImGui::PopID();
}
if (dropped)
{
auto changed{water};
changed[dropped->first].material = dropped->second;
m_streamer.water(std::move(changed));
m_water_selected = static_cast<int>(dropped->first);
m_water_material_of = -1;
}
if (m_water_selected >= 0 && m_water_selected < static_cast<int>(water.size()))
{
// the level is changed in the water once the dragging ends
@@ -1771,6 +1849,33 @@ void editor_mode::render_terrain_ui()
changed[m_water_selected].level = m_water_selected_level;
m_streamer.water(std::move(changed));
}
// the material and the repeat of its texture, changed once the field is left (or a texture dropped on it)
if (m_water_material_of != m_water_selected)
{
std::snprintf(m_water_selected_material, sizeof(m_water_selected_material), "%s", water[m_water_selected].material.c_str());
m_water_selected_size = water[m_water_selected].size;
m_water_material_of = m_water_selected;
}
bool retexture = false;
ImGui::SetNextItemWidth(160.0f);
ImGui::InputText(STR_C("Material of the chosen"), m_water_selected_material, IM_ARRAYSIZE(m_water_selected_material));
retexture |= ImGui::IsItemDeactivatedAfterEdit();
retexture |= material_drop(m_water_selected_material, IM_ARRAYSIZE(m_water_selected_material));
if (ImGui::IsItemHovered())
ImGui::SetTooltip(STR_C("A material or a texture (drag one from the texture browser).\nEmpty: %s, the water material of the terrain"), m_streamer.manifest().water_material.c_str());
ImGui::SetNextItemWidth(120.0f);
ImGui::DragFloat(STR_C("Texture repeats every (m)##water"), &m_water_selected_size, 0.1f, 0.0f, 500.0f, "%.1f");
m_water_selected_size = std::max(0.0f, m_water_selected_size);
retexture |= ImGui::IsItemDeactivatedAfterEdit();
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("Metres the texture of the water repeats at; 0: as the material gives it, or 8 m"));
if (retexture)
{
auto changed{water};
changed[m_water_selected].material = m_water_selected_material;
changed[m_water_selected].size = m_water_selected_size;
m_streamer.water(std::move(changed));
}
if (ImGui::Button(STR_C("Remove the chosen water")))
removed = static_cast<std::size_t>(m_water_selected);
}
@@ -1780,7 +1885,20 @@ void editor_mode::render_terrain_ui()
changed.erase(changed.begin() + *removed);
m_streamer.water(std::move(changed));
m_water_selected = -1;
m_water_material_of = -1;
}
// the material of the bodies of water which don't name their own
if (false == m_water_default_edited)
std::snprintf(m_water_default_material, sizeof(m_water_default_material), "%s", m_streamer.manifest().water_material.c_str());
ImGui::SetNextItemWidth(160.0f);
ImGui::InputText(STR_C("Default water material"), m_water_default_material, IM_ARRAYSIZE(m_water_default_material));
m_water_default_edited = ImGui::IsItemActive();
bool redefault = ImGui::IsItemDeactivatedAfterEdit();
redefault |= material_drop(m_water_default_material, IM_ARRAYSIZE(m_water_default_material));
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("Material of the bodies of water which don't name their own"));
if (redefault)
m_streamer.water_material(m_water_default_material);
}
if (ImGui::CollapsingHeader(STR_C("Modifiers")))
@@ -1788,16 +1906,30 @@ void editor_mode::render_terrain_ui()
auto const &modifiers{m_streamer.modifiers()};
if (modifiers.empty())
ImGui::TextWrapped("%s", STR_C("None. The vertical profile of the tracks makes them: the terrain follows the formation and the slopes of the route."));
std::optional<std::string> removed;
std::optional<std::string> removed, baked;
for (auto const &modifier : modifiers)
{
ImGui::PushID(modifier.name.c_str());
ImGui::TextUnformatted(format("%s, %zu points", modifier.name.c_str(), modifier.points.size()).c_str());
ImGui::SameLine();
if (ImGui::SmallButton(STR_C("Bake")))
baked = modifier.name;
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("The ground keeps the shape the modifier gives it, and the modifier is removed:\n"
"from then on the terrain there is shaped by hand like anywhere else, and no longer follows the track"));
ImGui::SameLine();
if (ImGui::SmallButton(STR_C("Remove")))
removed = modifier.name;
ImGui::PopID();
}
if (auto const touchups = m_streamer.touched_up(); touchups > 0)
ImGui::TextDisabled(STR_C("Touch-ups over the modifiers: %zu points (of the chunks in memory)"), touchups);
if (baked)
{
m_streamer.bake_modifier(*baked);
ground_tiles.clear();
forget_ground_shapes();
}
if (removed)
{
m_streamer.remove_modifier(*removed);
@@ -1856,7 +1988,10 @@ void editor_mode::draw_terrain_overlay()
auto const z = cursor.z + std::sin(angle) * m_terrain_brush_radius;
circle.emplace_back(x, ground(x, z, cursor.y) + 0.1, z);
}
auto const color = m_terrain_tool == terrain_tool::paint ? IM_COL32(120, 230, 120, 220) : m_terrain_tool == terrain_tool::smooth ? IM_COL32(120, 200, 255, 220) : IM_COL32(255, 180, 80, 220);
auto const color = m_terrain_tool == terrain_tool::paint ? IM_COL32(120, 230, 120, 220)
: m_terrain_tool == terrain_tool::smooth ? IM_COL32(120, 200, 255, 220)
: m_terrain_tool == terrain_tool::restore ? IM_COL32(230, 120, 230, 220)
: IM_COL32(255, 180, 80, 220);
polyline(circle, color, true, 2.0f);
if (m_terrain_tool == terrain_tool::level)
{
@@ -1883,17 +2018,15 @@ void editor_mode::draw_terrain_overlay()
}
}
}
if ((m_terrain_tool == terrain_tool::chunks || m_terrain_tool == terrain_tool::spacing) && hovering)
{
auto const key = heightmap::chunk_at(cursor.x, cursor.z);
auto const exists = m_streamer.exists(key);
auto const low = heightmap::chunk_corner(key);
// outline of a chunk, laid over the ground
auto const chunk_outline = [&](heightmap::chunk_key const &Key, double const Fallback, ImU32 const Color, float const Thickness) {
auto const low = heightmap::chunk_corner(Key);
std::vector<glm::dvec3> square;
auto const edge = [&](glm::dvec2 const &From, glm::dvec2 const &To) {
for (int i = 0; i < 25; ++i)
{
auto const point = From + (To - From) * (i / 25.0);
square.emplace_back(point.x, ground(point.x, point.y, cursor.y) + 0.2, point.y);
square.emplace_back(point.x, ground(point.x, point.y, Fallback) + 0.2, point.y);
}
};
auto const size = heightmap::chunk_size;
@@ -1901,15 +2034,31 @@ void editor_mode::draw_terrain_overlay()
edge(low + glm::dvec2{size, 0.0}, low + glm::dvec2{size});
edge(low + glm::dvec2{size}, low + glm::dvec2{0.0, size});
edge(low + glm::dvec2{0.0, size}, low);
polyline(square, exists ? IM_COL32(255, 255, 255, 200) : IM_COL32(255, 230, 90, 200), true, 2.0f);
polyline(square, Color, true, Thickness);
};
if (m_terrain_tool == terrain_tool::spacing)
for (auto const &key : m_terrain_selection)
chunk_outline(key, cursor.y, IM_COL32(90, 220, 255, 230), 3.0f);
if ((m_terrain_tool == terrain_tool::chunks || m_terrain_tool == terrain_tool::spacing) && hovering)
{
auto const key = heightmap::chunk_at(cursor.x, cursor.z);
auto const exists = m_streamer.exists(key);
chunk_outline(key, cursor.y, exists ? IM_COL32(255, 255, 255, 200) : IM_COL32(255, 230, 90, 200), 2.0f);
ImVec2 label;
if (exists && projection.project(cursor, label))
{
auto const *chunk = m_streamer.terrain_at(cursor.x, cursor.z);
if (chunk != nullptr)
{
auto const text = (m_terrain_tool == terrain_tool::spacing ? format("%s -> %s", spacing_label(static_cast<float>(chunk->data().spacing())), spacing_label(m_terrain_spacing))
: std::string(spacing_label(static_cast<float>(chunk->data().spacing()))));
auto const &data = chunk->data();
auto text = std::string(spacing_label(static_cast<float>(data.spacing())));
if (m_terrain_tool == terrain_tool::spacing)
{
if (data.paint > 0)
text += format(STR_C(", paint every %.2f m"), heightmap::chunk_size / data.paint);
if (m_terrain_selection.count(key) != 0)
text += STR(" (selected)");
}
drawlist->AddText(ImVec2(label.x + 14.0f, label.y - 14.0f), IM_COL32(255, 255, 255, 230), text.c_str());
}
}
@@ -2346,13 +2495,19 @@ void editor_mode::handle_terrain_click(bool const Shift)
}
case terrain_tool::spacing:
{
// a click selects the chunk, Ctrl adds it to the selection or takes it away
auto const key = heightmap::chunk_at(world.x, world.z);
if (false == m_streamer.exists(key))
{
if (false == Global.ctrlState)
m_terrain_selection.clear();
return;
m_streamer.spacing(key.first, key.second, m_terrain_spacing);
ground_tiles.clear();
forget_ground_shapes();
m_terrain_status = format(STR_C("Chunk %d,%d has points every %s now"), key.first, key.second, spacing_label(m_terrain_spacing));
}
if (false == Global.ctrlState)
m_terrain_selection = {key};
else if (m_terrain_selection.erase(key) == 0)
m_terrain_selection.insert(key);
m_terrain_status = format(STR_C("Chunks selected: %zu"), m_terrain_selection.size());
break;
}
case terrain_tool::water:
@@ -2951,6 +3106,9 @@ void editor_mode::handle_terrain_brush(double Deltatime)
case terrain_tool::paint:
m_streamer.paint(world.x, world.z, m_terrain_brush_radius, std::min(1.0, rate * 0.25), Global.shiftState ? std::uint16_t{0} : static_cast<std::uint16_t>(m_terrain_layer));
break;
case terrain_tool::restore:
shaped = m_streamer.restore_stitching(world.x, world.z, m_terrain_brush_radius, std::min(1.0, rate * 0.5));
break;
default:
break;
}
@@ -2962,6 +3120,22 @@ void editor_mode::handle_terrain_brush(double Deltatime)
}
}
void editor_mode::convert_selected_chunks()
{
// with 0.5 m spacing the paint can be as fine, the other spacings keep a sample per metre
auto const paint = m_terrain_convert_paint ? (m_terrain_convert_spacing < 0.75f ? heightmap::default_paint_samples * 2 : heightmap::default_paint_samples) : 0;
std::size_t converted = 0;
for (auto const &key : m_terrain_selection)
if (m_streamer.spacing(key.first, key.second, m_terrain_convert_spacing, paint))
++converted;
if (converted > 0)
{
ground_tiles.clear();
forget_ground_shapes();
}
m_terrain_status = format(STR_C("Chunks converted to %s: %zu of %zu"), spacing_label(m_terrain_convert_spacing), converted, m_terrain_selection.size());
}
bool editor_mode::pick_terrain_target()
{
glm::dvec3 const offset = GfxRenderer->Mouse_Position();

View File

@@ -311,11 +311,18 @@ class editor_mode : public application_mode, private editor_track::observer
smooth,
level, // leads the ground to the target height, Ctrl: picks the target height under the cursor
paint, // paints the chosen material, Shift: the first material of the palette
spacing, // gives the clicked chunk the chosen point spacing
water // outlines a body of water
spacing, // selects chunks (Ctrl: adds or takes away one), whose point spacing is then converted
water, // outlines a body of water
restore // takes the touch-ups over the modifiers back
};
terrain_tool m_terrain_tool{terrain_tool::none};
bool terrain_brush() const { return m_terrain_tool == terrain_tool::sculpt || m_terrain_tool == terrain_tool::smooth || m_terrain_tool == terrain_tool::level || m_terrain_tool == terrain_tool::paint; }
bool terrain_brush() const
{
return m_terrain_tool == terrain_tool::sculpt || m_terrain_tool == terrain_tool::smooth || m_terrain_tool == terrain_tool::level || m_terrain_tool == terrain_tool::paint ||
m_terrain_tool == terrain_tool::restore;
}
// converts the point spacing of the selected chunks to m_terrain_convert_spacing
void convert_selected_chunks();
// takes the height of whatever is under the cursor (terrain, track, model) for the target of the level brush. returns: false if there's nothing
bool pick_terrain_target();
bool terrain_clicking() const { return m_terrain_tool == terrain_tool::chunks || m_terrain_tool == terrain_tool::spacing || m_terrain_tool == terrain_tool::water; }
@@ -427,6 +434,14 @@ class editor_mode : public application_mode, private editor_track::observer
bool m_water_level_edited{false}; // the level was given by hand, the points clicked don't change it
char m_water_material[128]{""};
int m_water_selected{-1};
char m_water_selected_material[128]{""}; // material of the chosen water, while it's edited
int m_water_material_of{-1}; // the water whose material is in the field above
float m_water_selected_size{0.0f}; // metres its texture repeats at, 0: given by the material
char m_water_default_material[128]{""}; // of the waters which don't name their own, while it's edited
bool m_water_default_edited{false};
std::set<heightmap::chunk_key> m_terrain_selection; // chunks chosen with the spacing tool
float m_terrain_convert_spacing{1.0f}; // point spacing the selected chunks are converted to
bool m_terrain_convert_paint{true}; // chunks converted to 0.5 m get paint samples every 0.5 m too
float m_water_selected_level{0.0f}; // level of the chosen water, while it's edited
bool m_water_level_dragged{false};
std::string m_terrain_status;

View File

@@ -78,6 +78,13 @@ http://mozilla.org/MPL/2.0/.
// terrainadd <count> <spacing> chunks made around the camera, count x count, as the button of the chunks panel does it
// terrainprobe <x> <z> logs the height of the heightmap terrain and the materials of the chunk
// terrainconvert converts every material of the terrain files of the scenery
// terrainmodifier <name> <x0> <z0> <x1> <z1> <formation> <half width> a straight modifier, as the vertical profile makes them
// terraintouchup <0|1> the brushes touch up over the modifiers, or shape the ground under them
// terrainrestore <x> <z> <radius> a stroke of the restore brush
// terrainbake <name> the modifier baked into the ground
// terrainselect <cx> <cz> [add] the chunk selected with the spacing tool (add: Ctrl)
// terrainresolution <spacing> [paint 0|1] the selected chunks converted, as the button of the resolution panel does it
// terrainwater <index> <material> [size] material and repeat of a body of water; terrainwaterdefault <material>
// orthophoto <north> <east> <radius> <fit 0|1> [in scene 0|1] the imagery layer on, placed and fitted as given
// screenshot
// save
@@ -1168,6 +1175,90 @@ void editor_mode::selftest_step()
forget_ground();
WriteLog(format("SELFTEST %s %.1f %.1f: %s", command.c_str(), x, z, changed ? "changed" : "nothing changed"));
}
else if (command == "terrainmodifier")
{
heightmap::modifier modifier;
heightmap::corridor_point a, b;
double formation{0.0}, halfwidth{3.5};
words >> modifier.name >> a.position.x >> a.position.y >> b.position.x >> b.position.y >> formation >> halfwidth;
a.formation = b.formation = formation;
a.half_width = b.half_width = halfwidth;
modifier.points = {a, b};
m_streamer.modifier(std::move(modifier));
forget_ground();
WriteLog(format("SELFTEST terrainmodifier: %zu modifiers", m_streamer.modifiers().size()));
}
else if (command == "terraintouchup")
{
int state{1};
words >> state;
m_streamer.touch_up(state != 0);
WriteLog(format("SELFTEST terraintouchup %d", state));
}
else if (command == "terrainrestore")
{
double x{0.0}, z{0.0}, radius{10.0};
words >> x >> z >> radius;
auto const changed{m_streamer.restore_stitching(x, z, radius, 1.0)};
forget_ground();
WriteLog(format("SELFTEST terrainrestore %.1f %.1f: %s, touch-ups left %zu", x, z, changed ? "changed" : "nothing changed", m_streamer.touched_up()));
}
else if (command == "terrainbake")
{
std::string name;
words >> name;
auto const baked{m_streamer.bake_modifier(name)};
forget_ground();
WriteLog(format("SELFTEST terrainbake %s: %s, %zu modifiers left", name.c_str(), baked ? "baked" : "not found", m_streamer.modifiers().size()));
}
else if (command == "terrainselect")
{
int cx{0}, cz{0};
std::string add;
words >> cx >> cz >> add;
if (add != "add")
m_terrain_selection.clear();
if (m_streamer.exists({cx, cz}))
m_terrain_selection.insert({cx, cz});
WriteLog(format("SELFTEST terrainselect: %zu selected", m_terrain_selection.size()));
}
else if (command == "terrainresolution")
{
float spacing{1.f};
int paint{1};
words >> spacing >> paint;
m_terrain_convert_spacing = heightmap::valid_spacing(spacing);
m_terrain_convert_paint = paint != 0;
convert_selected_chunks();
WriteLog("SELFTEST terrainresolution: " + m_terrain_status);
}
else if (command == "terrainwater" || command == "terrainwaterdefault")
{
if (command == "terrainwaterdefault")
{
std::string material;
words >> material;
m_streamer.water_material(material);
}
else
{
std::size_t index{0};
std::string material;
float size{0.f};
words >> index >> material >> size;
auto water{m_streamer.water()};
if (index < water.size())
{
water[index].material = material == "-" ? std::string{} : material;
water[index].size = size;
m_streamer.water(std::move(water));
}
}
std::string list;
for (auto const &body : m_streamer.water())
list += format(" [%s: %s, %.1f m]", body.name.c_str(), body.material.empty() ? "default" : body.material.c_str(), body.size);
WriteLog("SELFTEST " + command + ": default " + m_streamer.manifest().water_material + list);
}
else if (command == "terrainadd")
{
int count{1};
@@ -1191,7 +1282,18 @@ void editor_mode::selftest_step()
materials += (layer < palette.size() ? palette[layer].material : std::string{"?"}) + " ";
}
auto const found{m_streamer.height_at(x, z, height)};
WriteLog(format("SELFTEST terrainprobe %.1f %.1f: %s %.3f, %zu chunks, materials %s", x, z, found ? "height" : "no terrain", height, m_streamer.resident(), materials.c_str()));
std::string grid;
if (chunk != nullptr)
{
auto const &data{chunk->data()};
double base{0.0}, adjust{0.0};
if (false == data.base.empty())
base = data.height_in(data.base, x, z);
if (false == data.adjust.empty())
adjust = data.height_in(data.adjust, x, z);
grid = format(", spacing %.1f, paint %d, base %s %.3f, touch-up %.3f", data.spacing(), data.paint, data.base.empty() ? "none" : "", base, adjust);
}
WriteLog(format("SELFTEST terrainprobe %.1f %.1f: %s %.3f, %zu chunks, materials %s%s", x, z, found ? "height" : "no terrain", height, m_streamer.resident(), materials.c_str(), grid.c_str()));
}
else if (command == "terrainconvert")
{

View File

@@ -213,8 +213,11 @@ void editor_mode::work_area_tools(std::vector<tool_button> &Buttons)
chosen(terrain_tool::level), choose(terrain_tool::level)});
Buttons.push_back({icon::brush, STR_C("Paint"), nullptr, STR_C("LMB paints the material chosen in the palette, Shift+LMB the first material of the palette"), chosen(terrain_tool::paint), choose(terrain_tool::paint)});
Buttons.push_back({icon::chunks, STR_C("Chunks"), nullptr, STR_C("LMB adds a chunk next to the clicked one, Shift+LMB removes it"), chosen(terrain_tool::chunks), choose(terrain_tool::chunks)});
Buttons.push_back({icon::count, STR_C("Point spacing"), nullptr, STR_C("LMB gives the clicked chunk the point spacing chosen in the tool options"), chosen(terrain_tool::spacing), choose(terrain_tool::spacing)});
Buttons.push_back({icon::count, STR_C("Point spacing"), nullptr, STR_C("LMB selects the clicked chunk, Ctrl+LMB adds one or takes it away; the tool options convert the point spacing of the selected"),
chosen(terrain_tool::spacing), choose(terrain_tool::spacing)});
Buttons.push_back({icon::area_fill, STR_C("Water"), nullptr, STR_C("LMB adds a point of the outline of a body of water, Shift+LMB takes the last one back"), chosen(terrain_tool::water), choose(terrain_tool::water)});
Buttons.push_back({icon::joints, STR_C("Restore"), nullptr, STR_C("LMB takes back the touch-ups made over the tracks: the terrain returns to the shape the tracks give it"), chosen(terrain_tool::restore),
choose(terrain_tool::restore)});
auto const orthophoto{m_orthophoto.enabled()};
Buttons.push_back({icon::orthophoto, STR_C("Orthophoto"), nullptr, STR_C("Aerial imagery of geoportal.gov.pl under the scenery, laid out by the origin of the scenery"), orthophoto,
[this, orthophoto]() { m_orthophoto.enabled(false == orthophoto); }, true});