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:
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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")
|
||||
{
|
||||
|
||||
@@ -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});
|
||||
|
||||
Reference in New Issue
Block a user