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

feat(roads): lanes change within a road piece (lanesto), junctions of two roads from earlier sceneries are loaded as such pieces and saved as roads; the editor leads the lanes of neighbouring pieces with them

This commit is contained in:
2026-10-04 05:00:52 +02:00
parent ceae93eec5
commit 444e89558b
8 changed files with 1114 additions and 225 deletions

View File

@@ -512,6 +512,7 @@ class editor_mode : public application_mode
std::vector<glm::dvec3> points; // selected points where the pieces meet or end
road_node::state settings; // layout shown in the window: of the selected piece, or of the pieces about to be built
bool whole{true}; // layout changes go to every piece of the road the selected one belongs to
bool varied{false}; // the selected piece is shown with lanes of its own for its end
bool chain{false}; // the point to build from is set
glm::dvec3 point{0.0}; // where the next piece starts
glm::dvec2 direction{0.0, 1.0}; // the way it has to leave that point

View File

@@ -313,6 +313,12 @@ void editor_mode::road_select(road_node *Road)
if (false == editor_road::can_edit(*Road, &reason))
tool.status += ". It can't be changed: " + reason;
}
else
{
// the layout stays for the build tool, which makes pieces with the same lanes all the way
tool.settings.missing = {};
}
tool.varied = tool.settings.changing();
copy_text(tool.surface, sizeof(tool.surface), tool.settings.surface);
for (int side = 0; side < 2; ++side)
copy_text(tool.sides[side], sizeof(tool.sides[side]), tool.settings.sides[side].material);
@@ -392,6 +398,11 @@ void editor_mode::roadpoint_aim(bool const Fresh)
tool.targetnote = "Point at the place a road crosses the rails";
return;
}
if (road->definition().changing())
{
tool.targetnote = "The road gains or loses lanes on this piece; a level crossing goes on a piece which keeps its lanes";
return;
}
glm::dvec3 middle{0.0};
double halfspan{0.0};
if (editor_road::rails_across(*road, tool.mouse, kRailReach, middle, halfspan))
@@ -1197,7 +1208,8 @@ void editor_mode::road_click()
{
// 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();
// with the lanes it has at that end
tool.settings = end.road->definition().section(end.atend ? 1 : 0);
// and it stays as wide as it is at that end, with its directions as far apart
auto const scale{tool.settings.taper[end.atend ? 1 : 0]};
tool.settings.taper = {scale, scale};
@@ -1216,6 +1228,7 @@ void editor_mode::road_click()
tool.settings = end.road->definition();
tool.settings.sides[end.half > 0 ? 0 : 1] = road_node::side_data{};
tool.settings.banks = {};
tool.settings.missing = {};
}
tool.settings.forward = end.forward;
tool.settings.backward = end.backward;
@@ -1320,6 +1333,7 @@ void editor_mode::road_click()
{
auto state{tool.settings};
state.axis = pieces[index];
state.missing = {};
auto const from{along(index)};
auto const to{along(index + 1)};
auto const &median{tool.settings.median};
@@ -1373,7 +1387,8 @@ void editor_mode::road_apply()
if (tool.selected == nullptr)
return;
tool.settings.normalize();
auto const pieces{tool.whole ? editor_road::chain(*tool.selected) : std::vector<road_node *>{tool.selected}};
// other lanes at the end are something the selected piece has for itself; the rest of the road keeps the lanes it has
auto const pieces{tool.whole && false == tool.settings.changing() ? editor_road::chain(*tool.selected) : std::vector<road_node *>{tool.selected}};
editor_road::record record;
std::vector<std::pair<road_node *, road_node::state>> changes;
// the banks are kept for each piece as they are, unless it's the banks which were just changed
@@ -1674,6 +1689,11 @@ bool editor_mode::road_split()
tool.status = "Can't split: " + reason;
return true;
}
if (road->definition().changing())
{
tool.status = "Can't split a piece which gains or loses lanes; give it the same lanes at both ends first";
return true;
}
editor_road::record record;
record.roads.emplace_back(road, road->definition());
auto *created{editor_road::split(*road, editor_road::nearest_parameter(road->definition(), tool.mouse))};
@@ -1735,7 +1755,7 @@ void editor_mode::road_point_width(float const Width)
{
// the pieces on both sides of a point get to that width there, and from there go back to their own along their length
auto const changes{point_changes(m_roadtool.points, [Width](road_node::state &State, bool const Atend, std::size_t const) {
auto const width{State.width()};
auto const width{State.width(Atend ? 1 : 0)};
if (width > 0.0)
State.taper[Atend ? 1 : 0] = std::clamp(static_cast<float>(Width / width), 0.25f, 4.f);
})};
@@ -1931,19 +1951,52 @@ bool editor_mode::render_road_layout(road_node::state &State)
};
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);
if (ImGui::InputInt("Lanes along the axis", &State.forward))
changed = true;
ImGui::SetNextItemWidth(120.0f);
if (ImGui::InputInt("Lanes against the axis", &State.backward))
changed = true;
if (changed)
// lanes. a piece of a road which is there already can end with other lanes than it starts with
{
// a different set of lanes starts with the common width and speed limit, and the default rules for changing lanes
State.lanes.clear();
State.changes.clear();
State.normalize();
auto const selecting{tool.tool == 0 && tool.selected != nullptr};
int counts[2][2]{{State.lanes_at(0, true), State.lanes_at(0, false)}, {State.lanes_at(1, true), State.lanes_at(1, false)}};
bool relaned{false};
ImGui::SetNextItemWidth(120.0f);
if (ImGui::InputInt(selecting && tool.varied ? "Lanes along the axis, at the start" : "Lanes along the axis", &counts[0][0]))
relaned = true;
ImGui::SetNextItemWidth(120.0f);
if (ImGui::InputInt(selecting && tool.varied ? "Lanes against the axis, at the start" : "Lanes against the axis", &counts[0][1]))
relaned = true;
if (selecting)
{
// ticked it only shows the lanes of the end to be set; unticked it gives the end the lanes of the start
if (ImGui::Checkbox("Other lanes at the end of the piece", &tool.varied) && false == tool.varied && State.changing())
relaned = true;
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", "The road gains or loses lanes along this piece: it gets wider or narrower, a lane it gains branches off the outermost lane\n"
"of its direction (two at most), a lane it loses joins the outermost one left.\n"
"What's next to the piece is fitted to the lanes the piece has at that end.\n"
"The axis goes the way the lanes drawn in green do: the start of the piece is where these come from.\n"
"Split the piece (K) first to have the lanes change over a part of it.");
if (tool.varied)
{
ImGui::SetNextItemWidth(120.0f);
if (ImGui::InputInt("Lanes along the axis, at the end", &counts[1][0]))
relaned = true;
ImGui::SetNextItemWidth(120.0f);
if (ImGui::InputInt("Lanes against the axis, at the end", &counts[1][1]))
relaned = true;
}
}
// the traffic going along the axis gets on the piece at its start, the one going against it at its end
if (State.lanes_at(1, true) - State.lanes_at(0, true) > 2 || State.lanes_at(0, false) - State.lanes_at(1, false) > 2)
ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.3f, 1.0f), "A lane forks into three at most: lanes past the second one gained can't be got on");
if (relaned)
{
if (false == selecting || false == tool.varied)
{
counts[1][0] = counts[0][0];
counts[1][1] = counts[0][1];
}
// a different set of lanes starts with the common width and speed limit, and the default rules for changing lanes
State.lanes_between(counts[0][0], counts[0][1], counts[1][0], counts[1][1]);
changed = true;
}
}
if (number("Lane width [m]", State.lanewidth, 0.25f, "%.2f"))
{
@@ -2342,7 +2395,7 @@ void editor_mode::render_road_window()
if (false == ends.empty())
{
auto const &layout{ends.front().road->definition()};
auto width{static_cast<float>(layout.width() * layout.taper[ends.front().atend ? 1 : 0])};
auto width{static_cast<float>(layout.width(ends.front().atend ? 1 : 0) * layout.taper[ends.front().atend ? 1 : 0])};
if (number("Road width here [m]", width, 0.25f, "%.2f"))
road_point_width(std::clamp(width, 1.0f, 100.0f));
if (ImGui::IsItemHovered())
@@ -2410,12 +2463,20 @@ void editor_mode::render_road_window()
else if (tool.selected != nullptr)
{
auto const &road{*tool.selected};
ImGui::Text("%s, %.1f m, %d + %d lanes", road.name().c_str(), road.definition().length(), road.definition().forward, road.definition().backward);
auto const &layout{road.definition()};
if (layout.changing())
ImGui::Text("%s, %.1f m, %d + %d to %d + %d lanes", road.name().c_str(), layout.length(), layout.lanes_at(0, true), layout.lanes_at(0, false), layout.lanes_at(1, true),
layout.lanes_at(1, false));
else
ImGui::Text("%s, %.1f m, %d + %d lanes", road.name().c_str(), layout.length(), layout.forward, layout.backward);
if (ImGui::Button("Delete (Del)"))
road_delete();
ImGui::SameLine();
ImGui::TextDisabled("K: split at the cursor");
ImGui::Checkbox("Changes go to the whole road", &tool.whole);
if (tool.settings.changing())
ImGui::TextDisabled("This piece has lanes of its own at its end, so it's changed alone");
else
ImGui::Checkbox("Changes go to the whole road", &tool.whole);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", "A road is a string of pieces. With this on a change of the layout is made to all of them,\nexcept for the lane change rules, which are always set for the selected piece alone.\nWith this off the selected piece alone is changed, and the road next to it is fitted to it:\nmade wider or narrower towards it, or given a stretch where the lanes change if their number differs.\nSplit a piece (K) to get a piece of the length you need.");
ImGui::Separator();
@@ -2474,15 +2535,18 @@ void editor_mode::draw_road_overlay() const
if (false == projection.room(drawlist))
break;
auto const against{static_cast<std::size_t>(road->definition().backward)};
auto const &tracks{road->tracks()};
for (std::size_t lane = 0; lane < tracks.size(); ++lane)
// a lane of a piece which gains or loses lanes is a path at each end of the piece; the ways between these are drawn below
auto const lanecount{road->definition().lanes.size()};
for (std::size_t slot = 0; slot < 2 * lanecount; ++slot)
{
if (tracks[lane] == nullptr || tracks[lane]->m_paths.empty())
auto const lane{slot / 2};
auto const *track{road->lane_track(lane, static_cast<int>(slot % 2))};
if (track == nullptr || track->m_paths.empty() || (slot % 2 == 1 && track == road->lane_track(lane, 0)))
continue;
auto const &path{tracks[lane]->m_paths.front()};
auto const &path{track->m_paths.front()};
auto const color{lane >= against ? kForwardColor : kBackwardColor};
drawpath(path, color, 2.0f, is_curved(path) ? pieces(path, road->location()) : 1);
if (range > 300.0)
if (range > 300.0 || road->definition().changing())
continue;
auto const middle{path_point(path, 0.5)};
auto direction{path_point(path, 0.52) - middle};
@@ -2499,6 +2563,9 @@ void editor_mode::draw_road_overlay() const
if (turn != nullptr && false == turn->m_paths.empty() && range < 300.0)
drawpath(turn->m_paths.front(), kTurnColor, 1.5f, 8);
}
// ways from the lanes at one end to the lanes at the other
for (auto const &way : road->ways())
drawpath(way, kWayColor, 1.5f, pieces(way, road->location()));
}
// ways through the junctions
for (auto const *junction : simulation::Junctions.sequence())