mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 06:19:19 +02:00
fix(editor): ground lookups sorted once instead of walking every terrain model, orthophoto kept under the roads; feat(roads): junction sides, stop lines and junction options
This commit is contained in:
@@ -15,6 +15,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "editor/editorSettings.hpp"
|
||||
#include "editor/editorModelSets.hpp"
|
||||
#include "editor/editorIncludeInfo.hpp"
|
||||
#include "editor/editorGroundMesh.hpp"
|
||||
#include "utilities/Globals.h"
|
||||
#include "simulation/simulation.h"
|
||||
#include "simulation/simulationtime.h"
|
||||
@@ -230,6 +231,69 @@ namespace
|
||||
int m_rows{1};
|
||||
};
|
||||
|
||||
// meshes of the shapes and the models asked about as ground. the ground is asked about a tile at a time, by the orthophoto
|
||||
// and by the road tools, and without these every question meant going through every triangle of every terrain model in reach
|
||||
struct ground_record
|
||||
{
|
||||
ground_mesh mesh;
|
||||
// what the mesh was made of, to tell when it has to be made again
|
||||
std::size_t vertices{0};
|
||||
std::size_t indices{0};
|
||||
glm::dvec3 origin{0.0};
|
||||
TModel3d const *model{nullptr};
|
||||
glm::dmat4 matrix{0.0};
|
||||
std::uint64_t used{0}; // number of the last question it served
|
||||
};
|
||||
std::unordered_map<std::uint64_t, ground_record> ground_shapes; // by the geometry of the shape
|
||||
std::unordered_map<TAnimModel const *, ground_record> ground_models;
|
||||
std::uint64_t ground_question{0};
|
||||
std::size_t ground_held{0}; // triangles in the meshes
|
||||
std::size_t constexpr ground_limit = 8000000; // triangles kept; some 400 MB with the grids
|
||||
std::size_t constexpr ground_small = 256; // triangles; anything smaller is quicker to go through than to look up
|
||||
|
||||
// lets go of the meshes left alone the longest, once there's too much of them
|
||||
void trim_ground_meshes()
|
||||
{
|
||||
if (ground_held <= ground_limit)
|
||||
return;
|
||||
std::vector<std::uint64_t> ages;
|
||||
for (auto const &record : ground_shapes)
|
||||
ages.emplace_back(record.second.used);
|
||||
for (auto const &record : ground_models)
|
||||
ages.emplace_back(record.second.used);
|
||||
std::sort(ages.begin(), ages.end());
|
||||
// the older half goes, except for what the current question needed
|
||||
auto const threshold = std::min(ages[ages.size() / 2], ground_question - 1);
|
||||
for (auto record = ground_shapes.begin(); record != ground_shapes.end();)
|
||||
{
|
||||
if (record->second.used <= threshold)
|
||||
{
|
||||
ground_held -= record->second.mesh.size();
|
||||
record = ground_shapes.erase(record);
|
||||
}
|
||||
else
|
||||
++record;
|
||||
}
|
||||
for (auto record = ground_models.begin(); record != ground_models.end();)
|
||||
{
|
||||
if (record->second.used <= threshold)
|
||||
{
|
||||
ground_held -= record->second.mesh.size();
|
||||
record = ground_models.erase(record);
|
||||
}
|
||||
else
|
||||
++record;
|
||||
}
|
||||
}
|
||||
|
||||
// forgets the meshes of the shapes, whose geometry can be changed in place (terrain being sculpted)
|
||||
void forget_ground_shapes()
|
||||
{
|
||||
for (auto const &record : ground_shapes)
|
||||
ground_held -= record.second.mesh.size();
|
||||
ground_shapes.clear();
|
||||
}
|
||||
|
||||
// appends triangles of a shape node which overlap specified XZ rectangle
|
||||
void gather_shape_triangles(scene::shape_node const &Shape, glm::dvec2 const &Min, glm::dvec2 const &Max, std::vector<world_triangle> &Out, bool const Skiproads = false)
|
||||
{
|
||||
@@ -262,6 +326,36 @@ namespace
|
||||
return;
|
||||
auto const &verts = GfxRenderer->Vertices(data.geometry);
|
||||
auto const &indices = GfxRenderer->Indices(data.geometry);
|
||||
auto const count = (indices.empty() ? verts.size() : indices.size()) / 3;
|
||||
if (count >= ground_small)
|
||||
{
|
||||
// a large shape is sorted once, and asked for what's in the rectangle from then on
|
||||
auto &record = ground_shapes[(static_cast<std::uint64_t>(data.geometry.bank) << 32) | data.geometry.chunk];
|
||||
if (record.mesh.size() == 0 || record.vertices != verts.size() || record.indices != indices.size() || record.origin != data.origin)
|
||||
{
|
||||
std::vector<ground_mesh::triangle> triangles;
|
||||
triangles.reserve(count);
|
||||
if (false == indices.empty())
|
||||
{
|
||||
for (std::size_t i = 0; i + 2 < indices.size(); i += 3)
|
||||
triangles.push_back({verts[indices[i]].position, verts[indices[i + 1]].position, verts[indices[i + 2]].position});
|
||||
}
|
||||
else
|
||||
{
|
||||
for (std::size_t i = 0; i + 2 < verts.size(); i += 3)
|
||||
triangles.push_back({verts[i].position, verts[i + 1].position, verts[i + 2].position});
|
||||
}
|
||||
ground_held -= record.mesh.size();
|
||||
record.mesh.assign(std::move(triangles), data.origin);
|
||||
ground_held += record.mesh.size();
|
||||
record.vertices = verts.size();
|
||||
record.indices = indices.size();
|
||||
record.origin = data.origin;
|
||||
}
|
||||
record.used = ground_question;
|
||||
record.mesh.collect(Min, Max, Out);
|
||||
return;
|
||||
}
|
||||
auto const to_world = [&](gfx::basic_vertex const &v) { return data.origin + glm::dvec3(v.position); };
|
||||
if (false == indices.empty())
|
||||
{
|
||||
@@ -279,6 +373,8 @@ namespace
|
||||
// model instances at least ModelRadius large (if ModelsAsGround) the terrain tiles
|
||||
void gather_ground_triangles(glm::dvec2 const &Min, glm::dvec2 const &Max, bool const ModelsAsGround, float const ModelRadius, std::vector<world_triangle> &Out, bool const Skiproads = false)
|
||||
{
|
||||
auto const started = std::chrono::steady_clock::now();
|
||||
++ground_question;
|
||||
glm::dvec2 const center = (Min + Max) * 0.5;
|
||||
float const radius = static_cast<float>(glm::length(Max - Min) * 0.5);
|
||||
auto const sections = simulation::Region->sections(glm::dvec3(center.x, 0.0, center.y), radius); // copy, the result is a scratchpad
|
||||
@@ -303,15 +399,35 @@ namespace
|
||||
double const reach = instance->radius();
|
||||
if (instance->location().x + reach < Min.x || instance->location().x - reach > Max.x || instance->location().z + reach < Min.y || instance->location().z - reach > Max.y)
|
||||
continue;
|
||||
std::vector<world_triangle> modeltriangles;
|
||||
gather_submodel_triangles(instance->Model()->Root, instance_matrix(*instance), modeltriangles);
|
||||
for (auto const &t : modeltriangles)
|
||||
if (std::max({t[0].x, t[1].x, t[2].x}) >= Min.x && std::min({t[0].x, t[1].x, t[2].x}) <= Max.x && std::max({t[0].z, t[1].z, t[2].z}) >= Min.y &&
|
||||
std::min({t[0].z, t[1].z, t[2].z}) <= Max.y)
|
||||
Out.push_back(t);
|
||||
// the model is gone through once, and asked for what's in the rectangle from then on, for as long as it stays put
|
||||
auto const matrix = instance_matrix(*instance);
|
||||
auto &record = ground_models[instance];
|
||||
if (record.model != instance->Model() || record.matrix != matrix)
|
||||
{
|
||||
std::vector<world_triangle> modeltriangles;
|
||||
gather_submodel_triangles(instance->Model()->Root, matrix, modeltriangles);
|
||||
glm::dvec3 const origin = instance->location();
|
||||
std::vector<ground_mesh::triangle> triangles;
|
||||
triangles.reserve(modeltriangles.size());
|
||||
for (auto const &t : modeltriangles)
|
||||
triangles.push_back({glm::vec3(t[0] - origin), glm::vec3(t[1] - origin), glm::vec3(t[2] - origin)});
|
||||
ground_held -= record.mesh.size();
|
||||
record.mesh.assign(std::move(triangles), origin);
|
||||
ground_held += record.mesh.size();
|
||||
// a model which isn't loaded yet has nothing to show for itself, and is looked at again the next time
|
||||
record.model = (record.mesh.size() > 0 ? instance->Model() : nullptr);
|
||||
record.matrix = matrix;
|
||||
}
|
||||
record.used = ground_question;
|
||||
record.mesh.collect(Min, Max, Out);
|
||||
}
|
||||
}
|
||||
}
|
||||
trim_ground_meshes();
|
||||
// going through the scenery is the one thing here which can take long enough to be felt; when it does it's worth knowing
|
||||
auto const elapsed = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - started).count();
|
||||
if (elapsed > 250.0)
|
||||
WriteLog("editor: gathering the ground took " + std::to_string(static_cast<int>(elapsed)) + " ms, for " + std::to_string(Out.size()) + " triangles; " + std::to_string(ground_held) + " are kept for the next time");
|
||||
}
|
||||
|
||||
// ground kept for the road tools, which ask about the same places frame after frame. it's gathered a tile at a time:
|
||||
@@ -364,8 +480,9 @@ editor_mode::editor_mode() {
|
||||
m_roadtool.settings.normalize();
|
||||
road_select(nullptr);
|
||||
// the orthophoto is fitted onto the same ground as the area fill uses, terrain tile models included
|
||||
// the roads built in the editor aren't ground: the imagery goes under them
|
||||
m_orthophoto.ground_source([](glm::dvec2 const &Min, glm::dvec2 const &Max, std::vector<world_triangle> &Out) {
|
||||
gather_ground_triangles(Min, Max, true, 50.0f, Out);
|
||||
gather_ground_triangles(Min, Max, true, 50.0f, Out, true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1452,7 +1569,12 @@ void editor_mode::render_orthophoto_ui()
|
||||
{
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Refit"))
|
||||
{
|
||||
// the ground was edited: what's kept of it has to go as well
|
||||
ground_tiles.clear();
|
||||
forget_ground_shapes();
|
||||
m_orthophoto.refit();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("Samples the ground again, after it was edited");
|
||||
}
|
||||
@@ -1947,6 +2069,7 @@ void editor_mode::handle_terrain_sculpt(double Deltatime)
|
||||
terrain->sculpt(world.x, world.z, m_terrain_brush_radius, signedrate);
|
||||
// what the road tools know about the ground is no longer true
|
||||
ground_tiles.clear();
|
||||
forget_ground_shapes();
|
||||
}
|
||||
|
||||
void editor_mode::capture_terrain()
|
||||
|
||||
@@ -534,6 +534,9 @@ class editor_mode : public application_mode
|
||||
void render_road_menu();
|
||||
void render_road_window();
|
||||
bool render_road_layout(road_node::state &State);
|
||||
bool render_road_material(char const *Label, char *Buffer, std::size_t const Size, std::string &Value);
|
||||
void render_junction_layout(junction_node &Junction);
|
||||
void junction_apply(junction_node &Junction, junction_node::state const &State);
|
||||
void draw_road_overlay() const;
|
||||
void update_road_tool();
|
||||
void road_click();
|
||||
|
||||
@@ -284,6 +284,139 @@ void editor_mode::junction_select(junction_node *Junction)
|
||||
tool.status = "Selected: " + Junction->name();
|
||||
if (false == editor_road::can_edit(*Junction, &reason))
|
||||
tool.status += ". It can't be changed: " + reason;
|
||||
// the fields for the names of materials are shared with the roads
|
||||
copy_text(tool.surface, sizeof(tool.surface), Junction->definition().surface);
|
||||
copy_text(tool.sides[0], sizeof(tool.sides[0]), Junction->definition().side.material);
|
||||
}
|
||||
|
||||
// replaces the definition of a junction, as a step which can be taken back
|
||||
void editor_mode::junction_apply(junction_node &Junction, junction_node::state const &State)
|
||||
{
|
||||
auto &tool{m_roadtool};
|
||||
std::string reason;
|
||||
if (false == editor_road::can_edit(Junction, &reason))
|
||||
{
|
||||
tool.status = "\"" + Junction.name() + "\" can't be changed: " + reason;
|
||||
// what the window shows goes back to what the junction is like
|
||||
junction_select(&Junction);
|
||||
tool.status = "\"" + Junction.name() + "\" can't be changed: " + reason;
|
||||
return;
|
||||
}
|
||||
editor_road::record record;
|
||||
record.junctions.emplace_back(&Junction, Junction.definition());
|
||||
editor_road::apply(std::vector<std::pair<junction_node *, junction_node::state>>{{&Junction, State}});
|
||||
push_road_snapshot(std::move(record));
|
||||
tool.status = "Changed";
|
||||
}
|
||||
|
||||
// draws controls for what a junction is like, and makes the changes as they're made
|
||||
void editor_mode::render_junction_layout(junction_node &Junction)
|
||||
{
|
||||
auto &tool{m_roadtool};
|
||||
auto state{Junction.definition()};
|
||||
bool changed{false};
|
||||
// typed values are taken when confirmed, so the junction isn't made anew with each digit
|
||||
auto const number = [](char const *Label, float &Value, float const Step, char const *Format) {
|
||||
ImGui::SetNextItemWidth(120.0f);
|
||||
return ImGui::InputFloat(Label, &Value, Step, Step * 4.0f, Format, ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
};
|
||||
|
||||
auto limit{state.velocity > 0.f ? state.velocity : 0.f};
|
||||
if (number("Speed limit [km/h], 0: none", limit, 5.0f, "%.0f"))
|
||||
{
|
||||
state.velocity = (limit > 0.f ? limit : -1.f);
|
||||
changed = true;
|
||||
}
|
||||
auto centre{static_cast<float>(state.centre.y)};
|
||||
if (number("Height of the middle [m]", centre, 0.1f, "%.2f"))
|
||||
{
|
||||
state.centre.y = centre;
|
||||
changed = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "The surface is spanned between the ends of the roads and this point.\nThe ends of the roads are raised or lowered as points: select the white dot at the end of a road.");
|
||||
|
||||
// appearance
|
||||
ImGui::Separator();
|
||||
char const *colours[]{"none", "white", "orange"};
|
||||
int colour{state.markings == road_node::marking_colour::white ? 1 : state.markings == road_node::marking_colour::orange ? 2 : 0};
|
||||
ImGui::SetNextItemWidth(120.0f);
|
||||
if (ImGui::Combo("Markings", &colour, colours, 3))
|
||||
{
|
||||
state.markings = (colour == 1 ? road_node::marking_colour::white : colour == 2 ? road_node::marking_colour::orange : road_node::marking_colour::none);
|
||||
changed = true;
|
||||
}
|
||||
if (render_road_material("Surface", tool.surface, sizeof(tool.surface), state.surface))
|
||||
changed = true;
|
||||
if (number("Texture length [m]", state.texturelength, 0.5f, "%.2f"))
|
||||
{
|
||||
state.texturelength = std::max(0.1f, state.texturelength);
|
||||
changed = true;
|
||||
}
|
||||
// what the corners between the roads are lined with
|
||||
char const *sidetypes[]{"none", "shoulder", "sidewalk"};
|
||||
int type{state.side.type == road_node::side_type::shoulder ? 1 : state.side.type == road_node::side_type::sidewalk ? 2 : 0};
|
||||
ImGui::SetNextItemWidth(120.0f);
|
||||
if (ImGui::Combo("Sides of the corners", &type, sidetypes, 3))
|
||||
{
|
||||
state.side.type = (type == 1 ? road_node::side_type::shoulder : type == 2 ? road_node::side_type::sidewalk : road_node::side_type::none);
|
||||
if (state.side.type != road_node::side_type::none && state.side.width <= 0.0f)
|
||||
state.side.width = 1.5f;
|
||||
changed = true;
|
||||
}
|
||||
if (state.side.type != road_node::side_type::none)
|
||||
{
|
||||
ImGui::Indent();
|
||||
if (number("Width [m]", state.side.width, 0.25f, "%.2f"))
|
||||
{
|
||||
state.side.width = std::clamp(state.side.width, 0.1f, 20.0f);
|
||||
changed = true;
|
||||
}
|
||||
if (render_road_material("Material", tool.sides[0], sizeof(tool.sides[0]), state.side.material))
|
||||
changed = true;
|
||||
if (state.side.material.empty() || state.side.material == "none")
|
||||
ImGui::TextDisabled("Pick a material to have the sides drawn");
|
||||
if (state.side.type == road_node::side_type::sidewalk)
|
||||
{
|
||||
if (number("Kerb height [m]", state.kerbheight, 0.01f, "%.2f"))
|
||||
{
|
||||
state.kerbheight = std::clamp(state.kerbheight, 0.0f, 1.0f);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (number("Bank width [m]", state.slope.x, 0.25f, "%.2f"))
|
||||
{
|
||||
state.slope.x = std::clamp(state.slope.x, 0.0f, 20.0f);
|
||||
changed = true;
|
||||
}
|
||||
if (number("Bank drop [m]", state.slope.y, 0.1f, "%.2f"))
|
||||
{
|
||||
state.slope.y = std::clamp(state.slope.y, 0.0f, 20.0f);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
// the roads
|
||||
if (ImGui::CollapsingHeader("Roads of the junction", ImGuiTreeNodeFlags_DefaultOpen))
|
||||
{
|
||||
ImGui::TextDisabled("The lanes are taken from the roads: change them on the road, the junction follows");
|
||||
for (std::size_t arm = 0; arm < state.arms.size(); ++arm)
|
||||
{
|
||||
ImGui::PushID(static_cast<int>(arm));
|
||||
auto &data{state.arms[arm]};
|
||||
ImGui::Text("%d: %d in, %d out, %.2f m a lane", static_cast<int>(arm) + 1, data.incoming, data.outgoing, data.width);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Checkbox("Stop line", &data.stopline))
|
||||
changed = true;
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
if (changed)
|
||||
junction_apply(Junction, state);
|
||||
}
|
||||
|
||||
void editor_mode::road_cancel()
|
||||
@@ -969,8 +1102,62 @@ void editor_mode::restore_road_snapshot(EditorSnapshot const &Snapshot, std::vec
|
||||
road_cancel();
|
||||
if (tool.selected != nullptr)
|
||||
road_select(tool.selected->m_editorremoved ? nullptr : tool.selected);
|
||||
if (tool.junction != nullptr && tool.junction->m_editorremoved)
|
||||
tool.junction = nullptr;
|
||||
if (tool.junction != nullptr)
|
||||
junction_select(tool.junction->m_editorremoved ? nullptr : tool.junction);
|
||||
}
|
||||
|
||||
// a field for the name of a material, with a list of the ones in the texture folder to pick from. returns: true if the material was changed
|
||||
bool editor_mode::render_road_material(char const *Label, char *Buffer, std::size_t const Size, std::string &Value)
|
||||
{
|
||||
auto &tool{m_roadtool};
|
||||
bool picked{false};
|
||||
ImGui::PushID(Label);
|
||||
ImGui::SetNextItemWidth(200.0f);
|
||||
if (ImGui::InputText("##name", Buffer, Size, ImGuiInputTextFlags_EnterReturnsTrue) || ImGui::IsItemDeactivatedAfterEdit())
|
||||
{
|
||||
auto const name{lower_case(Buffer)};
|
||||
if (name != Value && name.find(' ') == std::string::npos)
|
||||
{
|
||||
Value = name.empty() ? std::string{"none"} : name;
|
||||
picked = true;
|
||||
}
|
||||
copy_text(Buffer, Size, Value);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("..."))
|
||||
ImGui::OpenPopup("pick");
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted(Label);
|
||||
if (ImGui::BeginPopup("pick"))
|
||||
{
|
||||
ImGui::TextDisabled("Materials and images found in the texture folder");
|
||||
ImGui::SetNextItemWidth(200.0f);
|
||||
ImGui::InputText("Filter", tool.filter, sizeof(tool.filter));
|
||||
auto const filter{lower_case(tool.filter)};
|
||||
std::vector<std::string const *> listed;
|
||||
for (auto const &name : editor_road::materials())
|
||||
if (filter.empty() || name.find(filter) != std::string::npos)
|
||||
listed.emplace_back(&name);
|
||||
ImGui::BeginChild("list", ImVec2(340.0f, 280.0f), true);
|
||||
ImGuiListClipper clipper(static_cast<int>(listed.size()));
|
||||
while (clipper.Step())
|
||||
{
|
||||
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i)
|
||||
{
|
||||
if (ImGui::Selectable(listed[i]->c_str(), *listed[i] == Value))
|
||||
{
|
||||
Value = *listed[i];
|
||||
copy_text(Buffer, Size, Value);
|
||||
picked = true;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::PopID();
|
||||
return picked;
|
||||
}
|
||||
|
||||
// draws controls for the layout of a road. returns: true if the layout was changed in a way which calls for the road to be made anew
|
||||
@@ -983,56 +1170,7 @@ bool editor_mode::render_road_layout(road_node::state &State)
|
||||
ImGui::SetNextItemWidth(120.0f);
|
||||
return ImGui::InputFloat(Label, &Value, Step, Step * 4.0f, Format, ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
};
|
||||
auto const material = [&tool](char const *Label, char *Buffer, std::size_t const Size, std::string &Value) {
|
||||
bool picked{false};
|
||||
ImGui::PushID(Label);
|
||||
ImGui::SetNextItemWidth(200.0f);
|
||||
if (ImGui::InputText("##name", Buffer, Size, ImGuiInputTextFlags_EnterReturnsTrue) || ImGui::IsItemDeactivatedAfterEdit())
|
||||
{
|
||||
auto const name{lower_case(Buffer)};
|
||||
if (name != Value && name.find(' ') == std::string::npos)
|
||||
{
|
||||
Value = name.empty() ? std::string{"none"} : name;
|
||||
picked = true;
|
||||
}
|
||||
copy_text(Buffer, Size, Value);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("..."))
|
||||
ImGui::OpenPopup("pick");
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted(Label);
|
||||
if (ImGui::BeginPopup("pick"))
|
||||
{
|
||||
ImGui::TextDisabled("Materials and images found in the texture folder");
|
||||
ImGui::SetNextItemWidth(200.0f);
|
||||
ImGui::InputText("Filter", tool.filter, sizeof(tool.filter));
|
||||
auto const filter{lower_case(tool.filter)};
|
||||
std::vector<std::string const *> listed;
|
||||
for (auto const &name : editor_road::materials())
|
||||
if (filter.empty() || name.find(filter) != std::string::npos)
|
||||
listed.emplace_back(&name);
|
||||
ImGui::BeginChild("list", ImVec2(340.0f, 280.0f), true);
|
||||
ImGuiListClipper clipper(static_cast<int>(listed.size()));
|
||||
while (clipper.Step())
|
||||
{
|
||||
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i)
|
||||
{
|
||||
if (ImGui::Selectable(listed[i]->c_str(), *listed[i] == Value))
|
||||
{
|
||||
Value = *listed[i];
|
||||
copy_text(Buffer, Size, Value);
|
||||
picked = true;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::PopID();
|
||||
return picked;
|
||||
};
|
||||
auto const material = [this](char const *Label, char *Buffer, std::size_t const Size, std::string &Value) { return render_road_material(Label, Buffer, Size, Value); };
|
||||
|
||||
// lanes
|
||||
ImGui::SetNextItemWidth(120.0f);
|
||||
@@ -1200,9 +1338,10 @@ void editor_mode::render_road_window()
|
||||
road_cancel();
|
||||
if (tool.tool != 0)
|
||||
{
|
||||
tool.selected = nullptr;
|
||||
tool.junction = nullptr;
|
||||
tool.points.clear();
|
||||
// the fields for the names of materials show the layout for the roads to be built again
|
||||
road_select(nullptr);
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
@@ -1296,44 +1435,7 @@ void editor_mode::render_road_window()
|
||||
ImGui::Text("%s, %d roads, %d ways through", junction.name().c_str(), static_cast<int>(junction.definition().arms.size()), static_cast<int>(junction.movements().size()));
|
||||
ImGui::TextDisabled("The ways through are made for the roads attached at the moment.");
|
||||
ImGui::TextDisabled("Build tool, LMB on the junction: lead another road out of it");
|
||||
auto limit{junction.definition().velocity > 0.f ? junction.definition().velocity : 0.f};
|
||||
if (number("Speed limit [km/h], 0: none", limit, 5.0f, "%.0f"))
|
||||
{
|
||||
if (editor_road::can_edit(junction, &reason))
|
||||
{
|
||||
editor_road::record record;
|
||||
record.junctions.emplace_back(&junction, junction.definition());
|
||||
auto state{junction.definition()};
|
||||
state.velocity = (limit > 0.f ? limit : -1.f);
|
||||
editor_road::apply(std::vector<std::pair<junction_node *, junction_node::state>>{{&junction, state}});
|
||||
push_road_snapshot(std::move(record));
|
||||
tool.status = "Changed";
|
||||
}
|
||||
else
|
||||
{
|
||||
tool.status = "\"" + junction.name() + "\" can't be changed: " + reason;
|
||||
}
|
||||
}
|
||||
auto centre{static_cast<float>(junction.definition().centre.y)};
|
||||
if (number("Height of the middle [m]", centre, 0.1f, "%.2f"))
|
||||
{
|
||||
if (editor_road::can_edit(junction, &reason))
|
||||
{
|
||||
editor_road::record record;
|
||||
record.junctions.emplace_back(&junction, junction.definition());
|
||||
auto state{junction.definition()};
|
||||
state.centre.y = centre;
|
||||
editor_road::apply(std::vector<std::pair<junction_node *, junction_node::state>>{{&junction, state}});
|
||||
push_road_snapshot(std::move(record));
|
||||
tool.status = "Changed";
|
||||
}
|
||||
else
|
||||
{
|
||||
tool.status = "\"" + junction.name() + "\" can't be changed: " + reason;
|
||||
}
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "The surface is spanned between the ends of the roads and this point.\nThe ends of the roads are raised or lowered as points: select the white dot at the end of a road.");
|
||||
render_junction_layout(junction);
|
||||
if (ImGui::Button("Delete (Del)"))
|
||||
road_delete();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user