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

feat(roads): junctions, height above the ground and rounded profile following the terrain

This commit is contained in:
2026-10-03 20:28:37 +02:00
parent e4083d507f
commit daeed40f15
13 changed files with 1963 additions and 103 deletions

View File

@@ -20,6 +20,7 @@ http://mozilla.org/MPL/2.0/.
#include "imgui/imgui.h"
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
namespace
@@ -108,10 +109,16 @@ std::string lower_case(std::string Text)
double const kSnapRadius{4.0}; // an end of a road this close to the cursor is what the click is taken to mean
double const kReach{3000.0}; // clicks landing further than this are taken for misses
double const kLaneRange{600.0}; // lanes are drawn for the roads this close to the camera
double const kLongestStretch{2000.0}; // a single click doesn't lead a road further than this
// lengths of the pieces a road following the ground is tried with, from the longest; the first one to fit the ground is taken
double const kProfileSteps[]{50.0, 25.0, 12.5};
double const kSunkTolerance{0.05}; // how far under the height it's built at the road can get between the points it's led through
double const kRaisedTolerance{0.5}; // and how far over it
ImU32 const kForwardColor{IM_COL32(70, 230, 110, 230)};
ImU32 const kBackwardColor{IM_COL32(255, 170, 50, 230)};
ImU32 const kTurnColor{IM_COL32(210, 210, 210, 150)};
ImU32 const kWayColor{IM_COL32(120, 200, 255, 170)};
ImU32 const kSelectedColor{IM_COL32(40, 220, 255, 230)};
ImU32 const kPreviewColor{IM_COL32(255, 210, 60, 255)};
ImU32 const kRefusedColor{IM_COL32(240, 60, 60, 255)};
@@ -134,12 +141,29 @@ void editor_mode::update_road_tool()
auto &tool{m_roadtool};
if (tool.selected != nullptr && tool.selected->m_editorremoved)
tool.selected = nullptr;
if (tool.junction != nullptr && tool.junction->m_editorremoved)
tool.junction = nullptr;
tool.preview.clear();
tool.hasjunction = false;
tool.previewsnap = {};
tool.previewerror.clear();
if (false == tool.window)
{
tool.chain = false;
return;
}
tool.mouse = Global.pCamera.Pos + GfxRenderer->Mouse_Position();
glm::dvec3 const camera{Global.pCamera.Pos};
tool.mouse = camera + GfxRenderer->Mouse_Position();
if (glm::distance(tool.mouse, camera) > kReach)
{
tool.previewerror = "The cursor isn't over anything near enough";
return;
}
// what a click would make at the moment, to be drawn over the view
if (tool.tool == 1 && tool.chain)
tool.preview = road_preview(tool.previewsnap, tool.previewerror, false);
else if (tool.tool == 2)
tool.hasjunction = junction_preview(tool.previewjunction, tool.previewsnap, tool.previewerror);
}
void editor_mode::road_select(road_node *Road)
@@ -148,6 +172,7 @@ void editor_mode::road_select(road_node *Road)
tool.selected = Road;
if (Road != nullptr)
{
tool.junction = nullptr;
tool.settings = Road->definition();
std::string reason;
tool.status = "Selected: " + (Road->name().empty() ? std::string{"(unnamed road)"} : Road->name());
@@ -159,20 +184,33 @@ void editor_mode::road_select(road_node *Road)
copy_text(tool.sides[side], sizeof(tool.sides[side]), tool.settings.sides[side].material);
}
void editor_mode::junction_select(junction_node *Junction)
{
auto &tool{m_roadtool};
tool.junction = Junction;
if (Junction == nullptr)
return;
tool.selected = nullptr;
std::string reason;
tool.status = "Selected: " + Junction->name();
if (false == editor_road::can_edit(*Junction, &reason))
tool.status += ". It can't be removed: " + reason;
}
void editor_mode::road_cancel()
{
m_roadtool.chain = false;
}
std::vector<segment_data> editor_mode::road_preview(editor_road::loose_end &Snap, std::string &Error) const
std::vector<segment_data> editor_mode::road_preview(editor_road::loose_end &Snap, std::string &Error, bool const Fresh)
{
auto const &tool{m_roadtool};
Snap = editor_road::find_end(tool.mouse, kSnapRadius);
// the end the road is being drawn from isn't somewhere to lead it to
if (Snap.road != nullptr && glm::distance(Snap.position, tool.point) < 0.5)
if (Snap.valid() && glm::distance(Snap.position, tool.point) < 0.5)
Snap = {};
glm::dvec2 enddirection{0.0, 1.0};
if (Snap.road != nullptr)
if (Snap.valid())
{
glm::dvec2 const outwards{Snap.outwards.x, Snap.outwards.z};
if (glm::length(outwards) > 1e-6)
@@ -180,7 +218,204 @@ std::vector<segment_data> editor_mode::road_preview(editor_road::loose_end &Snap
else
Snap = {};
}
return editor_road::plan(tool.point, tool.hasdirection ? &tool.direction : nullptr, Snap.road != nullptr ? Snap.position : tool.mouse, Snap.road != nullptr ? &enddirection : nullptr, Global.ctrlState, Error);
// in the open the road is put over what's under the cursor
auto const target{Snap.valid() ? Snap.position : tool.mouse + glm::dvec3{0.0, tool.offset, 0.0}};
if (glm::distance(tool.point, target) > kLongestStretch)
{
Error = "Too far from the last point";
return {};
}
if (Snap.junction != nullptr)
{
// the junction has its ways through made for a set of lanes at each arm
auto const arriving{tool.reversed ? tool.settings.backward : tool.settings.forward};
auto const leaving{tool.reversed ? tool.settings.forward : tool.settings.backward};
if (arriving != Snap.backward || leaving != Snap.forward || std::abs(tool.settings.lanewidth - Snap.width) > 0.01f)
{
char width[32];
std::snprintf(width, sizeof(width), "%.2f", Snap.width);
Error = "This arm of the junction takes a road with " + std::to_string(Snap.backward) + " lane(s) leading into it and " + std::to_string(Snap.forward) + " out of it, " + width + " m wide each";
return {};
}
}
auto pieces{editor_road::plan(tool.point, tool.hasdirection ? &tool.direction : nullptr, target, Snap.valid() ? &enddirection : nullptr, Global.ctrlState, Error)};
road_profile(pieces, Snap, Fresh);
return pieces;
}
// gives the pieces laid out by the planner their heights: over the ground if the road is to follow it, with the slopes
// of the pieces matched where they meet, and with what the road is attached to
void editor_mode::road_profile(std::vector<segment_data> &Pieces, editor_road::loose_end const &Snap, bool const Fresh)
{
auto const &tool{m_roadtool};
if (Pieces.empty())
return;
// an end of a road or an arm of a junction is reached with the slope it has
double endgrade{0.0};
if (Snap.valid())
{
auto const run{glm::length(glm::dvec2{Snap.outwards.x, Snap.outwards.z})};
endgrade = (run > 1e-6 ? -Snap.outwards.y / run : 0.0);
}
auto const *firstgrade{tool.hasgrade ? &tool.grade : nullptr};
auto const *lastgrade{Snap.valid() ? &endgrade : nullptr};
if (false == tool.follow)
{
// straight from a point to the next one, the way a ramp or a viaduct goes
std::vector<double> heights;
for (auto const &piece : Pieces)
heights.emplace_back(piece.points[segment_data::point::start].y);
heights.emplace_back(Pieces.back().points[segment_data::point::end].y);
editor_road::profile(Pieces, heights, firstgrade, lastgrade);
return;
}
auto const planned{Pieces};
bool fresh{Fresh};
for (auto const step : kProfileSteps)
{
Pieces = editor_road::refine(planned, step);
auto const count{Pieces.size()};
// the ground is looked up where the pieces meet, and in the middle of each to see how the result fits it
std::vector<glm::dvec3> points;
points.reserve(2 * count + 1);
for (auto const &piece : Pieces)
points.emplace_back(piece.points[segment_data::point::start]);
points.emplace_back(Pieces.back().points[segment_data::point::end]);
for (auto const &piece : Pieces)
points.emplace_back(path_point(piece, 0.5));
// the planner leads the road between its ends, which are over the ground already
for (auto &point : points)
point.y -= tool.offset;
auto ground{ground_heights(points, fresh)};
fresh = false;
for (auto &height : ground)
height += tool.offset;
std::vector<double> heights(ground.begin(), ground.begin() + count + 1);
// the ends are where they were put
heights.front() = Pieces.front().points[segment_data::point::start].y;
heights.back() = Pieces.back().points[segment_data::point::end].y;
editor_road::profile(Pieces, heights, firstgrade, lastgrade);
// shorter pieces are called for if the ground shows through the road, or the road hangs over it.
// the pieces attached to something are let off, as they have to get to its height one way or another
bool fits{true};
for (std::size_t i = 0; i < count; ++i)
{
if ((i == 0 && firstgrade != nullptr) || (i + 1 == count && lastgrade != nullptr))
continue;
auto const over{path_point(Pieces[i], 0.5).y - ground[count + 1 + i]};
if (over < -kSunkTolerance || over > kRaisedTolerance)
{
fits = false;
break;
}
}
if (fits)
break;
}
}
// lays out the junction a click would make. returns: false if there's none to make, with the reason in Error unless
// it's a matter of the first click being still ahead
bool editor_mode::junction_preview(junction_node::state &State, editor_road::loose_end &Snap, std::string &Error) const
{
auto const &tool{m_roadtool};
auto const kind{static_cast<editor_road::junction_kind>(std::clamp(tool.kind, 0, 3))};
Snap = {};
junction_node::state state;
// the look goes after the road the junction is made for
auto const dress = [&state](road_node::state const &Layout) {
state.surface = Layout.surface;
state.texturelength = Layout.texturelength;
state.markings = Layout.markings;
state.friction = Layout.friction;
state.sounddistance = Layout.sounddistance;
state.quality = Layout.quality;
state.environment = Layout.environment;
};
if (false == tool.chain)
{
// at a loose end of a road the junction is made for that road, and put right past its end
Snap = editor_road::find_end(tool.mouse, kSnapRadius, false);
if (Snap.road == nullptr)
return false;
auto const &layout{Snap.road->definition()};
glm::dvec2 const outwards{Snap.outwards.x, Snap.outwards.z};
if (glm::length(outwards) < 1e-6 || layout.lanes.empty())
{
Error = "There's no telling which way this road goes at its end";
return false;
}
// the ways through the junction start and end where the lanes of the road are expected, which takes lanes of the same width
auto const width{layout.lanes.front().width};
for (auto const &lane : layout.lanes)
{
if (std::abs(lane.width - width) > 0.01f)
{
Error = "Lanes of this road differ in width, and a junction takes roads with lanes of the same width";
return false;
}
}
state = editor_road::junction(kind, Snap.position, glm::normalize(outwards), Snap.atend ? layout.forward : layout.backward, Snap.atend ? layout.backward : layout.forward, width);
dress(layout);
}
else
{
// in the open the centre is set already, and the cursor shows where the first road comes from
glm::dvec2 const away{tool.mouse.x - tool.point.x, tool.mouse.z - tool.point.z};
if (glm::length(away) < 1.0)
{
Error = "Move the cursor away from the centre to turn the junction";
return false;
}
auto const heading{-glm::normalize(away)};
auto const &layout{tool.settings};
auto const reach{editor_road::junction_reach(layout.forward + layout.backward, layout.lanewidth)};
glm::dvec3 const entry{tool.point.x - heading.x * reach, tool.point.y, tool.point.z - heading.y * reach};
state = editor_road::junction(kind, entry, heading, layout.forward, layout.backward, layout.lanewidth);
dress(layout);
}
state.velocity = tool.crossingspeed;
state.normalize();
State = state;
return true;
}
void editor_mode::junction_click()
{
auto &tool{m_roadtool};
junction_node::state state;
editor_road::loose_end snap;
std::string error;
if (false == junction_preview(state, snap, error))
{
if (false == error.empty())
{
tool.status = "Can't make the junction: " + error;
}
else if (false == tool.chain)
{
// away from the roads the first click sets the centre, the second one turns the junction
tool.point = tool.mouse + glm::dvec3{0.0, tool.offset, 0.0};
tool.chain = true;
tool.status = "Centre set";
}
return;
}
std::string reason;
if (snap.valid() && false == editor_road::can_join(snap, &reason))
{
tool.status = "Can't make the junction here: " + reason;
return;
}
auto const created{editor_road::create(std::vector<junction_node::state>{state})};
if (created.empty())
{
tool.status = "The junction wasn't created";
return;
}
push_road_snapshot({}, {}, {}, created, {});
tool.chain = false;
tool.status = "\"" + created.front()->name() + "\" made" + (snap.road != nullptr ? " at the end of \"" + snap.road->name() + "\"" : std::string{}) + ". Lead roads out of its arms with the Build tool";
}
void editor_mode::road_click()
@@ -197,9 +432,11 @@ void editor_mode::road_click()
if (tool.tool == 0)
{
auto *road{editor_road::nearest(ground, 1.5)};
if (road == nullptr)
auto *junction{road == nullptr ? editor_road::nearest_junction(ground) : nullptr};
if (road == nullptr && junction == nullptr)
tool.status = "No road here";
road_select(road);
junction_select(junction);
return;
}
@@ -209,30 +446,58 @@ void editor_mode::road_click()
tool.status = reason;
return;
}
if (tool.tool == 2)
{
junction_click();
return;
}
if (false == tool.chain)
{
// first click sets where the road starts. started from a loose end of a road it carries that road on
// first click sets where the road starts. started from a loose end of a road it carries that road on,
// started from an arm of a junction it's the road the junction is made for there
auto const end{editor_road::find_end(ground, kSnapRadius)};
glm::dvec2 const outwards{end.outwards.x, end.outwards.z};
if (end.road != nullptr && glm::length(outwards) > 1e-6)
if (end.valid() && glm::length(outwards) > 1e-6)
{
if (false == editor_road::can_join(end, &reason))
{
tool.status = "Can't build from here: " + reason;
return;
}
tool.point = end.position;
tool.direction = glm::normalize(outwards);
tool.hasdirection = true;
// carried on from its start the road keeps the direction of its axis, so the lanes stay what they were
tool.reversed = (false == end.atend);
auto const layout{end.road->definition()};
tool.selected = nullptr;
tool.settings = layout;
tool.grade = end.outwards.y / glm::length(outwards);
tool.hasgrade = true;
tool.junction = nullptr;
if (end.road != nullptr)
{
// carried on from its start the road keeps the direction of its axis, so the lanes stay what they were
tool.reversed = (false == end.atend);
tool.settings = end.road->definition();
tool.status = "Carrying on \"" + end.road->name() + "\" with its layout";
}
else
{
tool.reversed = false;
tool.settings.forward = end.forward;
tool.settings.backward = end.backward;
tool.settings.lanewidth = end.width;
tool.settings.lanes.clear();
tool.settings.changes.clear();
tool.settings.normalize();
tool.status = "Leading a road out of \"" + end.junction->name() + "\", with the lanes the junction has there";
}
road_select(nullptr);
tool.status = "Carrying on \"" + end.road->name() + "\" with its layout";
}
else
{
tool.point = ground;
tool.point = ground + glm::dvec3{0.0, tool.offset, 0.0};
tool.hasdirection = false;
tool.hasgrade = false;
tool.reversed = false;
tool.selected = nullptr;
tool.junction = nullptr;
tool.status = "Start set";
}
tool.chain = true;
@@ -241,16 +506,22 @@ void editor_mode::road_click()
editor_road::loose_end snap;
std::string error;
auto pieces{road_preview(snap, error)};
auto pieces{road_preview(snap, error, true)};
if (pieces.empty())
{
tool.status = "Can't build this: " + error;
return;
}
if (snap.valid() && false == editor_road::can_join(snap, &reason))
{
tool.status = "Can't join here: " + reason;
return;
}
// where the next piece is going to carry on from
auto const &last{pieces.back()};
auto const endpoint{last.points[segment_data::point::end]};
auto const tangent{last.points[segment_data::point::control2] != glm::dvec3{0.0} ? -last.points[segment_data::point::control2] : last.points[segment_data::point::end] - last.points[segment_data::point::start]};
auto const grade{editor_road::end_grade(last)};
if (tool.reversed)
{
for (auto &piece : pieces)
@@ -274,10 +545,10 @@ void editor_mode::road_click()
return;
}
push_road_snapshot({}, created, {});
if (snap.road != nullptr)
if (snap.valid())
{
tool.chain = false;
tool.status = "Joined with \"" + snap.road->name() + "\"";
tool.status = "Joined with \"" + (snap.road != nullptr ? snap.road->name() : snap.junction->name()) + "\"";
return;
}
glm::dvec2 const planar{tangent.x, tangent.z};
@@ -287,6 +558,8 @@ void editor_mode::road_click()
tool.direction = glm::normalize(planar);
tool.hasdirection = true;
}
tool.grade = grade;
tool.hasgrade = true;
tool.status = std::to_string(created.size()) + (created.size() == 1 ? " piece added" : " pieces added");
}
@@ -329,16 +602,30 @@ void editor_mode::road_apply()
bool editor_mode::road_delete()
{
auto &tool{m_roadtool};
if (false == tool.window || tool.selected == nullptr)
if (false == tool.window || (tool.selected == nullptr && tool.junction == nullptr))
return false;
auto *road{tool.selected};
std::string reason;
if (tool.junction != nullptr)
{
auto *junction{tool.junction};
if (false == editor_road::can_edit(*junction, &reason))
{
tool.status = "Can't delete: " + reason;
return true;
}
editor_road::remove(std::vector<junction_node *>{junction});
push_road_snapshot({}, {}, {}, {}, {junction});
tool.junction = nullptr;
tool.status = "\"" + junction->name() + "\" deleted, Ctrl+Z brings it back";
return true;
}
auto *road{tool.selected};
if (false == editor_road::can_edit(*road, &reason))
{
tool.status = "Can't delete: " + reason;
return true;
}
editor_road::remove({road});
editor_road::remove(std::vector<road_node *>{road});
push_road_snapshot({}, {}, {road});
tool.selected = nullptr;
tool.status = "\"" + road->name() + "\" deleted, Ctrl+Z brings it back";
@@ -370,29 +657,43 @@ bool editor_mode::road_split()
return true;
}
void editor_mode::push_road_snapshot(std::vector<std::pair<road_node *, road_node::state>> States, std::vector<road_node *> Created, std::vector<road_node *> Removed)
void editor_mode::push_road_snapshot(std::vector<std::pair<road_node *, road_node::state>> States, std::vector<road_node *> Created, std::vector<road_node *> Removed, std::vector<junction_node *> Junctionscreated,
std::vector<junction_node *> Junctionsremoved)
{
if (States.empty() && Created.empty() && Removed.empty())
if (States.empty() && Created.empty() && Removed.empty() && Junctionscreated.empty() && Junctionsremoved.empty())
return;
if (m_max_history_size >= 0 && (int)m_history.size() >= m_max_history_size)
m_history.erase(m_history.begin(), m_history.begin() + ((int)m_history.size() - m_max_history_size + 1));
auto const *road = false == States.empty() ? States.front().first : false == Created.empty() ? Created.front() : Removed.front();
// the entry is listed under the name of the first thing it's about
scene::basic_node const *node{nullptr};
if (false == States.empty())
node = States.front().first;
else if (false == Created.empty())
node = Created.front();
else if (false == Removed.empty())
node = Removed.front();
else if (false == Junctionscreated.empty())
node = Junctionscreated.front();
else
node = Junctionsremoved.front();
EditorSnapshot snap;
snap.action = EditorSnapshot::Action::RoadEdit;
snap.node_name = road->name();
snap.position = road->location();
snap.node_name = node->name();
snap.position = node->location();
snap.roads = std::move(States);
snap.roads_created = std::move(Created);
snap.roads_removed = std::move(Removed);
snap.junctions_created = std::move(Junctionscreated);
snap.junctions_removed = std::move(Junctionsremoved);
m_history.push_back(std::move(snap));
g_redo.clear();
}
void editor_mode::restore_road_snapshot(EditorSnapshot const &Snapshot, std::vector<EditorSnapshot> &Opposite, bool const Undo)
{
if (Snapshot.roads.empty() && Snapshot.roads_created.empty() && Snapshot.roads_removed.empty())
if (Snapshot.roads.empty() && Snapshot.roads_created.empty() && Snapshot.roads_removed.empty() && Snapshot.junctions_created.empty() && Snapshot.junctions_removed.empty())
return;
// what the roads are like now is what the opposite operation brings back
@@ -405,11 +706,15 @@ void editor_mode::restore_road_snapshot(EditorSnapshot const &Snapshot, std::vec
if (Undo)
{
editor_road::remove(Snapshot.roads_created);
editor_road::remove(Snapshot.junctions_created);
editor_road::revive(Snapshot.junctions_removed);
editor_road::revive(Snapshot.roads_removed);
}
else
{
editor_road::revive(Snapshot.roads_created);
editor_road::revive(Snapshot.junctions_created);
editor_road::remove(Snapshot.junctions_removed);
editor_road::remove(Snapshot.roads_removed);
}
editor_road::apply(Snapshot.roads);
@@ -418,6 +723,8 @@ void editor_mode::restore_road_snapshot(EditorSnapshot const &Snapshot, std::vec
tool.chain = false;
if (tool.selected != nullptr)
road_select(tool.selected->m_editorremoved ? nullptr : tool.selected);
if (tool.junction != nullptr && tool.junction->m_editorremoved)
tool.junction = nullptr;
}
// 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
@@ -642,28 +949,71 @@ void editor_mode::render_road_window()
ImGui::RadioButton("Select", &tool.tool, 0);
ImGui::SameLine();
ImGui::RadioButton("Build", &tool.tool, 1);
ImGui::SameLine();
ImGui::RadioButton("Junction", &tool.tool, 2);
if (tool.tool != previoustool)
{
tool.chain = false;
if (tool.tool == 1)
if (tool.tool != 0)
{
tool.selected = nullptr;
tool.junction = nullptr;
}
}
ImGui::SameLine();
ImGui::Checkbox("Show lanes", &tool.lanes);
// how the things being built are put on the ground
auto const placement = [&tool](bool const Road) {
ImGui::SetNextItemWidth(120.0f);
if (ImGui::InputFloat("Height above the ground [m]", &tool.offset, 0.05f, 0.25f, "%.2f"))
tool.offset = std::clamp(tool.offset, -10.0f, 100.0f);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", "How far over the ground under the cursor the points are put.\nA few centimetres keep the surface from flickering where it lies on the ground.");
if (false == Road)
return;
ImGui::Checkbox("Follow the ground", &tool.follow);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", "On: the road is led over the ground between the clicked points, in pieces as short as the ground calls for,\nwith the profile rounded so the slope doesn't change abruptly.\nOff: the road goes straight from a point to the next one, like a ramp or a viaduct.");
};
if (tool.tool == 1)
{
ImGui::TextDisabled("LMB: start, then each next point. Ctrl: straight ahead. Esc: finish");
ImGui::TextDisabled("Start or end on a loose end of a road to join it");
ImGui::TextDisabled("Start or end on a loose end of a road or an arm of a junction to join it");
if (tool.chain)
{
if (ImGui::Button("Finish (Esc)"))
tool.chain = false;
}
placement(true);
ImGui::Separator();
// what's set here is what the next pieces get
render_road_layout(tool.settings);
}
else if (tool.tool == 2)
{
ImGui::TextDisabled("LMB on a loose end of a road: junction for that road, right past its end");
ImGui::TextDisabled("LMB elsewhere: the centre, then the side the first road comes from. Esc: start over");
char const *kinds[]{"Crossroads", "T: the road ends, roads to both sides", "T: the road goes on, a road to the left", "T: the road goes on, a road to the right"};
ImGui::SetNextItemWidth(280.0f);
ImGui::Combo("Kind", &tool.kind, kinds, 4);
ImGui::SetNextItemWidth(120.0f);
if (ImGui::InputFloat("Speed limit on the junction [km/h]", &tool.crossingspeed, 5.0f, 10.0f, "%.0f"))
tool.crossingspeed = std::clamp(tool.crossingspeed, 5.0f, 200.0f);
placement(false);
ImGui::Separator();
ImGui::TextWrapped("Away from the roads the junction is made for roads with the lanes set in the Build tool: %d + %d, %.2f m wide. Its surface and markings go after the road it's made for.", tool.settings.forward, tool.settings.backward,
tool.settings.lanewidth);
ImGui::TextWrapped("Roads are led out of the arms with the Build tool, and get the lanes the junction has there.");
}
else if (tool.junction != nullptr)
{
auto const &junction{*tool.junction};
ImGui::Text("%s, %d arms, %d ways through", junction.name().c_str(), static_cast<int>(junction.definition().arms.size()), static_cast<int>(junction.movements().size()));
ImGui::TextDisabled("Speed limit %.0f km/h. The ways through are made for the roads attached at the moment", junction.definition().velocity);
if (ImGui::Button("Delete (Del)"))
road_delete();
}
else if (tool.selected != nullptr)
{
auto const &road{*tool.selected};
@@ -681,7 +1031,7 @@ void editor_mode::render_road_window()
}
else
{
ImGui::TextDisabled("LMB: select a road piece");
ImGui::TextDisabled("LMB: select a road piece or a junction");
}
if (false == tool.status.empty())
{
@@ -748,6 +1098,14 @@ void editor_mode::draw_road_overlay() const
drawpath(turn->m_paths.front(), kTurnColor, 1.5f, 8);
}
}
// ways through the junctions
for (auto const *junction : simulation::Junctions.sequence())
{
if (junction == nullptr || junction->m_editorremoved || glm::distance(junction->location(), camera) > kLaneRange)
continue;
for (auto const &way : junction->movements())
drawpath(way, kWayColor, 1.5f, 8);
}
}
if (false == tool.window)
return;
@@ -786,6 +1144,15 @@ void editor_mode::draw_road_overlay() const
drawlist->AddCircle(screen, Radius, Color, 20, 3.0f);
};
// outline of a junction: the edge of its surface, with the ends of the roads it's made for
auto const drawjunction = [&](junction_node::state const &State, ImU32 const Color) {
auto const outline{State.outline()};
for (std::size_t i = 0; i < outline.size(); ++i)
projection.line(drawlist, outline[i], outline[(i + 1) % outline.size()], Color, 2.0f);
for (auto const &arm : State.arms)
drawpoint(arm.position, Color, 5.0f, true);
};
if (tool.selected != nullptr && false == tool.selected->m_editorremoved)
{
auto const &state{tool.selected->definition()};
@@ -793,42 +1160,57 @@ void editor_mode::draw_road_overlay() const
drawpoint(state.axis.points[segment_data::point::start], kSelectedColor, 6.0f, true);
drawpoint(state.axis.points[segment_data::point::end], kSelectedColor, 6.0f, true);
}
if (tool.junction != nullptr && false == tool.junction->m_editorremoved)
drawjunction(tool.junction->definition(), kSelectedColor);
std::string hint;
if (tool.tool == 1)
{
if (tool.chain)
{
editor_road::loose_end snap;
std::string error;
auto const pieces{road_preview(snap, error)};
drawpoint(tool.point, kPreviewColor, 6.0f, true);
if (pieces.empty())
if (tool.preview.empty())
{
projection.line(drawlist, tool.point, tool.mouse, kRefusedColor, 2.0f);
hint = error + " ";
hint = tool.previewerror + " ";
}
for (auto const &piece : pieces)
for (auto const &piece : tool.preview)
{
auto state{tool.settings};
state.axis = piece;
drawroad(state, kPreviewColor);
}
if (snap.road != nullptr)
drawpoint(snap.position, kSnapColor, 14.0f, false);
if (tool.previewsnap.valid())
drawpoint(tool.previewsnap.position, kSnapColor, 14.0f, false);
hint += "LMB: build up to the cursor Ctrl: straight ahead Esc: finish Ctrl+Z: undo the last piece";
}
else
{
auto const end{editor_road::find_end(tool.mouse, kSnapRadius)};
if (end.road != nullptr)
if (end.valid())
drawpoint(end.position, kSnapColor, 14.0f, false);
hint = "LMB: set where the road starts; on a loose end of a road (ring) to carry it on";
hint = "LMB: set where the road starts; on a loose end of a road or an arm of a junction (ring) to carry on from there";
}
}
else if (tool.tool == 2)
{
if (tool.chain)
drawpoint(tool.point, kPreviewColor, 6.0f, true);
if (tool.hasjunction)
drawjunction(tool.previewjunction, kPreviewColor);
else if (false == tool.previewerror.empty())
hint = tool.previewerror + " ";
if (tool.previewsnap.valid())
drawpoint(tool.previewsnap.position, kSnapColor, 14.0f, false);
hint += (tool.chain ? "LMB: make the junction, with the first road coming from the side of the cursor Esc: start over" :
tool.previewsnap.valid() ? "LMB: make the junction at the end of this road" :
"LMB: on a loose end of a road (ring) to make its junction there; elsewhere to set the centre of one");
}
else
{
hint = tool.selected != nullptr ? "LMB: select a road piece K: split at the cursor Del: delete Ctrl+Z: undo" : "LMB: select a road piece";
hint = tool.selected != nullptr ? "LMB: select a road piece or a junction K: split at the cursor Del: delete Ctrl+Z: undo" :
tool.junction != nullptr ? "LMB: select a road piece or a junction Del: delete Ctrl+Z: undo" :
"LMB: select a road piece or a junction";
}
// shown above the place the track tools put their hints at
ImGuiIO const &io = ImGui::GetIO();