17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-04 23:35:43 +01: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 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 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 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 bool chain{false}; // the point to build from is set
glm::dvec3 point{0.0}; // where the next piece starts 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 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)) if (false == editor_road::can_edit(*Road, &reason))
tool.status += ". It can't be changed: " + 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); copy_text(tool.surface, sizeof(tool.surface), tool.settings.surface);
for (int side = 0; side < 2; ++side) for (int side = 0; side < 2; ++side)
copy_text(tool.sides[side], sizeof(tool.sides[side]), tool.settings.sides[side].material); 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"; tool.targetnote = "Point at the place a road crosses the rails";
return; 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}; glm::dvec3 middle{0.0};
double halfspan{0.0}; double halfspan{0.0};
if (editor_road::rails_across(*road, tool.mouse, kRailReach, middle, halfspan)) 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 // 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.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 // 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]}; auto const scale{tool.settings.taper[end.atend ? 1 : 0]};
tool.settings.taper = {scale, scale}; tool.settings.taper = {scale, scale};
@@ -1216,6 +1228,7 @@ void editor_mode::road_click()
tool.settings = end.road->definition(); tool.settings = end.road->definition();
tool.settings.sides[end.half > 0 ? 0 : 1] = road_node::side_data{}; tool.settings.sides[end.half > 0 ? 0 : 1] = road_node::side_data{};
tool.settings.banks = {}; tool.settings.banks = {};
tool.settings.missing = {};
} }
tool.settings.forward = end.forward; tool.settings.forward = end.forward;
tool.settings.backward = end.backward; tool.settings.backward = end.backward;
@@ -1320,6 +1333,7 @@ void editor_mode::road_click()
{ {
auto state{tool.settings}; auto state{tool.settings};
state.axis = pieces[index]; state.axis = pieces[index];
state.missing = {};
auto const from{along(index)}; auto const from{along(index)};
auto const to{along(index + 1)}; auto const to{along(index + 1)};
auto const &median{tool.settings.median}; auto const &median{tool.settings.median};
@@ -1373,7 +1387,8 @@ void editor_mode::road_apply()
if (tool.selected == nullptr) if (tool.selected == nullptr)
return; return;
tool.settings.normalize(); 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; editor_road::record record;
std::vector<std::pair<road_node *, road_node::state>> changes; 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 // 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; tool.status = "Can't split: " + reason;
return true; 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; editor_road::record record;
record.roads.emplace_back(road, road->definition()); record.roads.emplace_back(road, road->definition());
auto *created{editor_road::split(*road, editor_road::nearest_parameter(road->definition(), tool.mouse))}; 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 // 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 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) if (width > 0.0)
State.taper[Atend ? 1 : 0] = std::clamp(static_cast<float>(Width / width), 0.25f, 4.f); 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); }; 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 // lanes. a piece of a road which is there already can end with other lanes than it starts with
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)
{ {
// a different set of lanes starts with the common width and speed limit, and the default rules for changing lanes auto const selecting{tool.tool == 0 && tool.selected != nullptr};
State.lanes.clear(); int counts[2][2]{{State.lanes_at(0, true), State.lanes_at(0, false)}, {State.lanes_at(1, true), State.lanes_at(1, false)}};
State.changes.clear(); bool relaned{false};
State.normalize(); 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")) if (number("Lane width [m]", State.lanewidth, 0.25f, "%.2f"))
{ {
@@ -2342,7 +2395,7 @@ void editor_mode::render_road_window()
if (false == ends.empty()) if (false == ends.empty())
{ {
auto const &layout{ends.front().road->definition()}; 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")) if (number("Road width here [m]", width, 0.25f, "%.2f"))
road_point_width(std::clamp(width, 1.0f, 100.0f)); road_point_width(std::clamp(width, 1.0f, 100.0f));
if (ImGui::IsItemHovered()) if (ImGui::IsItemHovered())
@@ -2410,12 +2463,20 @@ void editor_mode::render_road_window()
else if (tool.selected != nullptr) else if (tool.selected != nullptr)
{ {
auto const &road{*tool.selected}; 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)")) if (ImGui::Button("Delete (Del)"))
road_delete(); road_delete();
ImGui::SameLine(); ImGui::SameLine();
ImGui::TextDisabled("K: split at the cursor"); 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()) 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::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(); ImGui::Separator();
@@ -2474,15 +2535,18 @@ void editor_mode::draw_road_overlay() const
if (false == projection.room(drawlist)) if (false == projection.room(drawlist))
break; break;
auto const against{static_cast<std::size_t>(road->definition().backward)}; auto const against{static_cast<std::size_t>(road->definition().backward)};
auto const &tracks{road->tracks()}; // 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
for (std::size_t lane = 0; lane < tracks.size(); ++lane) 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; continue;
auto const &path{tracks[lane]->m_paths.front()}; auto const &path{track->m_paths.front()};
auto const color{lane >= against ? kForwardColor : kBackwardColor}; auto const color{lane >= against ? kForwardColor : kBackwardColor};
drawpath(path, color, 2.0f, is_curved(path) ? pieces(path, road->location()) : 1); 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; continue;
auto const middle{path_point(path, 0.5)}; auto const middle{path_point(path, 0.5)};
auto direction{path_point(path, 0.52) - middle}; 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) if (turn != nullptr && false == turn->m_paths.empty() && range < 300.0)
drawpath(turn->m_paths.front(), kTurnColor, 1.5f, 8); 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 // ways through the junctions
for (auto const *junction : simulation::Junctions.sequence()) for (auto const *junction : simulation::Junctions.sequence())

View File

@@ -143,6 +143,11 @@ road_node::state part(road_node::state const &State, double const From, double c
result.median.width = {static_cast<float>(State.median_width(From)), static_cast<float>(State.median_width(To))}; result.median.width = {static_cast<float>(State.median_width(From)), static_cast<float>(State.median_width(To))};
// an island is rounded at the ends of the piece, not where the piece is cut // an island is rounded at the ends of the piece, not where the piece is cut
result.median.round = {From <= 0.0 ? State.median.round[0] : 0.f, To >= 1.0 ? State.median.round[1] : 0.f}; result.median.round = {From <= 0.0 ? State.median.round[0] : 0.f, To >= 1.0 ? State.median.round[1] : 0.f};
// and it's at its ends a piece is short of lanes
if (From > 0.0)
result.missing[0] = {0, 0};
if (To < 1.0)
result.missing[1] = {0, 0};
for (std::size_t side = 0; side < result.banks.size(); ++side) for (std::size_t side = 0; side < result.banks.size(); ++side)
{ {
auto const &bank{State.banks[side]}; auto const &bank{State.banks[side]};
@@ -166,8 +171,8 @@ std::vector<glm::dvec3> end_points(road_node::state const &State, bool const Ate
if (glm::length(left) < 1e-9) if (glm::length(left) < 1e-9)
return result; return result;
left = glm::normalize(left); left = glm::normalize(left);
auto const halfwidth{0.5 * State.width()}; auto const halfwidth{0.5 * State.width(Atend ? 1 : 0)};
auto const divide{State.divide()}; auto const divide{State.divide(Atend ? 1 : 0)};
// lanes going against the axis are on its left // lanes going against the axis are on its left
result.emplace_back(result.front() + left * (0.5 * (halfwidth + divide) * State.scale(t) + 0.5 * gap)); result.emplace_back(result.front() + left * (0.5 * (halfwidth + divide) * State.scale(t) + 0.5 * gap));
result.emplace_back(result.front() + left * (0.5 * (divide - halfwidth) * State.scale(t) - 0.5 * gap)); result.emplace_back(result.front() + left * (0.5 * (divide - halfwidth) * State.scale(t) - 0.5 * gap));
@@ -275,6 +280,12 @@ bool follow(road_node &Road, double const T, bool const Forward, double const Di
Cut.road = piece; Cut.road = piece;
Cut.t = (remaining <= 0.0 ? from : parameter_at(lengths, forward ? here + remaining : here - remaining)); Cut.t = (remaining <= 0.0 ? from : parameter_at(lengths, forward ? here + remaining : here - remaining));
Cut.keepstart = (false == forward); Cut.keepstart = (false == forward);
if (state.changing() && Cut.t > 0.001 && Cut.t < 0.999)
{
// partway along such a piece the road has no whole number of lanes to make an arm for
Error = "Too close to a place where the road changes its lanes";
return false;
}
return true; return true;
} }
// the junction takes this piece up to its end, as what would be left of it is too short to be a road // the junction takes this piece up to its end, as what would be left of it is too short to be a road
@@ -313,9 +324,10 @@ bool cut_arm(road_cut const &Cut, junction_node::arm_data &Arm)
Arm.position = state.point(Cut.t); Arm.position = state.point(Cut.t);
// the way out of the junction is the way the kept part of the piece goes from the cut // the way out of the junction is the way the kept part of the piece goes from the cut
Arm.direction = planar(state.tangent(Cut.t)) * (Cut.keepstart ? -1.0 : 1.0); Arm.direction = planar(state.tangent(Cut.t)) * (Cut.keepstart ? -1.0 : 1.0);
// lanes heading for the cut lead into the junction // lanes heading for the cut lead into the junction. a piece which gains or loses lanes is only cut at one of its ends
Arm.incoming = (Cut.keepstart ? state.forward : state.backward); auto const end{Cut.t > 0.5 ? 1 : 0};
Arm.outgoing = (Cut.keepstart ? state.backward : state.forward); Arm.incoming = state.lanes_at(end, Cut.keepstart);
Arm.outgoing = state.lanes_at(end, false == Cut.keepstart);
Arm.median = static_cast<float>(state.median_width(Cut.t)); Arm.median = static_cast<float>(state.median_width(Cut.t));
return editor_road::lane_width(state, Cut.t, Arm.width); return editor_road::lane_width(state, Cut.t, Arm.width);
} }
@@ -406,6 +418,8 @@ road_node::state flipped(road_node::state const &State)
std::swap(result.axis.points[segment_data::point::control1], result.axis.points[segment_data::point::control2]); std::swap(result.axis.points[segment_data::point::control1], result.axis.points[segment_data::point::control2]);
result.axis.rolls = {-State.axis.rolls[1], -State.axis.rolls[0]}; result.axis.rolls = {-State.axis.rolls[1], -State.axis.rolls[0]};
std::swap(result.forward, result.backward); std::swap(result.forward, result.backward);
result.missing[0] = {State.missing[1][1], State.missing[1][0]};
result.missing[1] = {State.missing[0][1], State.missing[0][0]};
// the lanes are listed left to right for someone facing along the axis, who's now looking the other way // the lanes are listed left to right for someone facing along the axis, who's now looking the other way
std::reverse(result.lanes.begin(), result.lanes.end()); std::reverse(result.lanes.begin(), result.lanes.end());
std::reverse(result.changes.begin(), result.changes.end()); std::reverse(result.changes.begin(), result.changes.end());
@@ -660,6 +674,10 @@ void editor_road::refresh(std::vector<road_node *> const &Roads, std::vector<jun
} }
// joins the lanes with the lanes of the neighbours, by the ends they share // joins the lanes with the lanes of the neighbours, by the ends they share
editor_track::commit(lanes); editor_track::commit(lanes);
// the pieces which gain or lose lanes lead the ones they have at one end to the ones they have at the other
for (auto *road : Roads)
if (false == road->m_editorremoved)
road->create_links();
for (auto *junction : Junctions) for (auto *junction : Junctions)
if (false == junction->m_editorremoved) if (false == junction->m_editorremoved)
junction->show(); junction->show();
@@ -1071,6 +1089,11 @@ bool editor_road::dissolve(glm::dvec3 const &Joint, record &Record, std::string
before = flipped(before); before = flipped(before);
if (dropped.atend) if (dropped.atend)
after = flipped(after); after = flipped(after);
if (before.changing() || after.changing())
{
Error = "the road changes its lanes next to the point. Give that piece the same lanes at both its ends first";
return false;
}
if (before.forward != after.forward || before.backward != after.backward) if (before.forward != after.forward || before.backward != after.backward)
{ {
Error = "the road has other lanes on each side of the point. Give both sides the same lanes first"; Error = "the road has other lanes on each side of the point. Give both sides the same lanes first";
@@ -1134,7 +1157,8 @@ bool editor_road::dissolve(glm::dvec3 const &Joint, record &Record, std::string
road_node *editor_road::split(road_node &Road, double const T) road_node *editor_road::split(road_node &Road, double const T)
{ {
if (T <= 0.001 || T >= 0.999) // partway along a piece which gains or loses lanes the road has no whole number of them
if (T <= 0.001 || T >= 0.999 || Road.definition().changing())
return nullptr; return nullptr;
auto first{Road.definition()}; auto first{Road.definition()};
auto second{first}; auto second{first};
@@ -1194,13 +1218,16 @@ road_node *editor_road::split(road_node &Road, double const T)
std::vector<road_node *> editor_road::chain(road_node &Road) std::vector<road_node *> editor_road::chain(road_node &Road)
{ {
std::vector<road_node *> result{&Road}; std::vector<road_node *> result{&Road};
// a piece which gains or loses lanes is where a road of one layout ends and one of another begins
if (Road.definition().changing())
return result;
for (std::size_t idx = 0; idx < result.size(); ++idx) for (std::size_t idx = 0; idx < result.size(); ++idx)
{ {
for (auto const atend : {false, true}) for (auto const atend : {false, true})
{ {
// a road goes on only where exactly two pieces meet // a road goes on only where exactly two pieces meet
auto const next{roads_at(axis_end(result[idx]->definition(), atend), result[idx])}; auto const next{roads_at(axis_end(result[idx]->definition(), atend), result[idx])};
if (next.size() == 1) if (next.size() == 1 && false == next.front()->definition().changing())
add_unique(result, next.front()); add_unique(result, next.front());
} }
} }
@@ -1308,7 +1335,7 @@ editor_road::loose_end editor_road::find_end(glm::dvec3 const &Point, double con
result.outwards = (atend ? state.tangent(1.0) : -state.tangent(0.0)); result.outwards = (atend ? state.tangent(1.0) : -state.tangent(0.0));
// the first of the points is the roadway of the lanes going against the axis // the first of the points is the roadway of the lanes going against the axis
result.half = (half == 1 ? -1 : 1); result.half = (half == 1 ? -1 : 1);
auto const lanes{half == 1 ? state.backward : state.forward}; auto const lanes{state.lanes_at(atend ? 1 : 0, half != 1)};
// lanes which leave the road there go along the axis of the road carrying them on, the ones which come to it go against that axis // lanes which leave the road there go along the axis of the road carrying them on, the ones which come to it go against that axis
auto const leaving{(half == 2) == atend}; auto const leaving{(half == 2) == atend};
result.forward = (leaving ? lanes : 0); result.forward = (leaving ? lanes : 0);
@@ -1632,8 +1659,8 @@ void editor_road::settle(road_node &Road, record &Record, std::string &Notes)
auto const &layout{Road.definition()}; auto const &layout{Road.definition()};
auto const position{axis_end(layout, atend)}; auto const position{axis_end(layout, atend)};
// lanes heading for this end of the piece, and the ones leading away from it // lanes heading for this end of the piece, and the ones leading away from it
auto const arriving{atend ? layout.forward : layout.backward}; auto const arriving{layout.lanes_at(atend ? 1 : 0, atend)};
auto const leaving{atend ? layout.backward : layout.forward}; auto const leaving{layout.lanes_at(atend ? 1 : 0, false == atend)};
float lanewidth{0.f}; float lanewidth{0.f};
auto const evenlanes{lane_width(layout, atend ? 1.0 : 0.0, lanewidth)}; auto const evenlanes{lane_width(layout, atend ? 1.0 : 0.0, lanewidth)};
auto const gap{static_cast<float>(layout.median_width(atend ? 1.0 : 0.0))}; auto const gap{static_cast<float>(layout.median_width(atend ? 1.0 : 0.0))};
@@ -1678,14 +1705,15 @@ void editor_road::settle(road_node &Road, record &Record, std::string &Notes)
auto const otherlayout{other->definition()}; auto const otherlayout{other->definition()};
auto const otheratend{glm::distance(axis_end(otherlayout, true), position) < kSamePoint}; auto const otheratend{glm::distance(axis_end(otherlayout, true), position) < kSamePoint};
// lanes of the neighbour heading for the place they meet, and the ones leading away from it // lanes of the neighbour heading for the place they meet, and the ones leading away from it
auto const otherarriving{otheratend ? otherlayout.forward : otherlayout.backward}; auto const otherarriving{otherlayout.lanes_at(otheratend ? 1 : 0, otheratend)};
auto const otherleaving{otheratend ? otherlayout.backward : otherlayout.forward}; auto const otherleaving{otherlayout.lanes_at(otheratend ? 1 : 0, false == otheratend)};
auto const othergap{static_cast<float>(otherlayout.median_width(otheratend ? 1.0 : 0.0))};
if (otherarriving == leaving && otherleaving == arriving) if (otherarriving == leaving && otherleaving == arriving)
{ {
// the same lanes: the neighbour is made as wide as the piece where they meet, and gets to that width along its length // the same lanes: the neighbour is made as wide as the piece where they meet, and gets to that width along its length
auto const width{layout.width() * layout.taper[atend ? 1 : 0]}; auto const width{layout.width(atend ? 1 : 0) * layout.taper[atend ? 1 : 0]};
auto const othergap{static_cast<float>(otherlayout.median_width(otheratend ? 1.0 : 0.0))}; auto const otherwidth{otherlayout.width(otheratend ? 1 : 0)};
if (std::abs(width - otherlayout.width() * otherlayout.taper[otheratend ? 1 : 0]) < 0.001 && std::abs(gap - othergap) < 0.001f) if (std::abs(width - otherwidth * otherlayout.taper[otheratend ? 1 : 0]) < 0.001 && std::abs(gap - othergap) < 0.001f)
continue; continue;
if (false == can_edit(*other, &reason)) if (false == can_edit(*other, &reason))
{ {
@@ -1693,7 +1721,7 @@ void editor_road::settle(road_node &Road, record &Record, std::string &Notes)
continue; continue;
} }
auto state{otherlayout}; auto state{otherlayout};
state.taper[otheratend ? 1 : 0] = std::clamp(static_cast<float>(width / otherlayout.width()), 0.25f, 4.f); state.taper[otheratend ? 1 : 0] = std::clamp(static_cast<float>(width / otherwidth), 0.25f, 4.f);
// and it leads its two directions as far apart as the piece has them there // and it leads its two directions as far apart as the piece has them there
if (std::abs(gap - othergap) >= 0.001f) if (std::abs(gap - othergap) >= 0.001f)
{ {
@@ -1710,80 +1738,67 @@ void editor_road::settle(road_node &Road, record &Record, std::string &Notes)
continue; continue;
} }
// other lanes: the end of the neighbour is given up for a junction of the two, where the lanes of one are led to the lanes of the other // other lanes: the neighbour is led from the lanes it has to the lanes of the piece. a long one gives up its end for that,
if (false == evenlanes) // which is made a piece of its own; a short one, or one which changes its lanes already, does it along its whole length
float otherlanewidth{0.f};
if (false == evenlanes || false == lane_width(otherlayout, otheratend ? 1.0 : 0.0, otherlanewidth))
{ {
Notes += "Lanes of \"" + Road.name() + "\"" + unevenlanes; Notes += "Lanes of \"" + (evenlanes ? other->name() : Road.name()) + "\" differ in width, and a road changes the number of its lanes only if they're all as wide. ";
continue; continue;
} }
road_cut cut; if (false == can_edit(*other, &reason))
std::vector<road_node *> consumed;
std::string error;
bool room{false};
for (auto const length : {kTransitionLength, 0.5 * kTransitionLength})
{
consumed.clear();
if (follow(*other, otheratend ? 1.0 : 0.0, false == otheratend, length, cut, consumed, error))
{
room = true;
break;
}
}
if (false == room)
{
Notes += "There's no room next to \"" + Road.name() + "\" for the lanes to change (" + error + "), so they don't lead on there. ";
continue;
}
if (cut.road == &Road)
{
Notes += "The road is too short for its lanes to change. ";
continue;
}
std::vector<road_node *> removed{consumed};
if (cut.road != other)
removed.emplace_back(other);
// a cut at the very end the piece is kept from leaves it whole
auto const trimmed{cut.keepstart ? cut.t < 0.999 : cut.t > 0.001};
bool allowed{false == trimmed || can_edit(*cut.road, &reason)};
for (auto const *piece : removed)
allowed = allowed && can_edit(*piece, &reason);
junction_node::state junction;
junction_node::arm_data arms[2];
if (false == allowed)
{ {
Notes += "The lanes next to \"" + Road.name() + "\" weren't fitted: " + reason + ". "; Notes += "The lanes next to \"" + Road.name() + "\" weren't fitted: " + reason + ". ";
continue; continue;
} }
if (false == cut_arm(cut, arms[0])) auto *stretch{other};
if (false == otherlayout.changing() && otherlayout.length() > 1.5 * kTransitionLength)
{ {
Notes += "Lanes of \"" + cut.road->name() + "\"" + unevenlanes; auto const lengths{stations(otherlayout)};
auto *rest{split(*other, parameter_at(lengths, otheratend ? lengths.back() - kTransitionLength : kTransitionLength))};
if (rest == nullptr)
{
Notes += "\"" + other->name() + "\" couldn't be cut for the lanes to change. ";
continue;
}
// the neighbour goes on record the way it was before the cut
if (std::none_of(Record.roads.begin(), Record.roads.end(), [other](std::pair<road_node *, road_node::state> const &Entry) { return Entry.first == other; }))
Record.roads.emplace_back(other, otherlayout);
Record.roads_created.emplace_back(rest);
// the part past the cut is the one at the end of the neighbour
if (otheratend)
stretch = rest;
}
auto state{stretch->definition()};
// the end the stretch shares with the piece gets the lanes of the piece, the other one keeps what it has
auto const end{otheratend ? 1 : 0};
int counts[2][2]{{state.lanes_at(0, true), state.lanes_at(0, false)}, {state.lanes_at(1, true), state.lanes_at(1, false)}};
// the lanes of the piece leading away from where they meet are the ones of the stretch heading for it
counts[end][0] = (otheratend ? leaving : arriving);
counts[end][1] = (otheratend ? arriving : leaving);
if (false == state.lanes.empty())
state.lanewidth = state.lanes.front().width;
state.lanes_between(counts[0][0], counts[0][1], counts[1][0], counts[1][1]);
if (state.lanes_at(end, true) != counts[end][0] || state.lanes_at(end, false) != counts[end][1])
{
Notes += "The lanes of \"" + Road.name() + "\" can't be led on to the road next to it. ";
continue; continue;
} }
arms[1].position = position; // and it's as wide there as the piece is, with its directions as far apart
arms[1].direction = planar(layout.tangent(atend ? 1.0 : 0.0)) * (atend ? -1.0 : 1.0); state.taper[end] = std::clamp(lanewidth / state.lanes.front().width, 0.25f, 4.f);
arms[1].incoming = arriving; if (std::abs(gap - othergap) >= 0.001f)
arms[1].outgoing = leaving;
arms[1].width = lanewidth;
arms[1].median = gap;
junction.arms.assign(std::begin(arms), std::end(arms));
junction.centre = 0.5 * (arms[0].position + arms[1].position);
dress(junction, layout);
// it's a stretch of the road, with the speed limit the road has
junction.velocity = std::min(layout.velocity > 0.f ? layout.velocity : std::numeric_limits<float>::max(), otherlayout.velocity > 0.f ? otherlayout.velocity : std::numeric_limits<float>::max());
if (junction.velocity == std::numeric_limits<float>::max())
junction.velocity = -1.f;
junction.normalize();
// room is made first, so nothing gets joined with what's about to go
remove(removed);
Record.roads_removed.insert(Record.roads_removed.end(), removed.begin(), removed.end());
if (trimmed)
{ {
remember(Record, cut.road); if (false == state.divided())
apply(std::vector<std::pair<road_node *, road_node::state>>{{cut.road, cut.keepstart ? part(cut.road->definition(), 0.0, cut.t) : part(cut.road->definition(), cut.t, 1.0)}}); {
state.median.type = layout.median.type;
state.median.material = layout.median.material;
state.median.width = {0.f, 0.f};
}
state.median.width[end] = gap;
} }
auto const created{create(std::vector<junction_node::state>{junction})}; if (stretch == other)
Record.junctions_created.insert(Record.junctions_created.end(), created.begin(), created.end()); remember(Record, other);
apply(std::vector<std::pair<road_node *, road_node::state>>{{stretch, state}});
} }
} }
@@ -1812,6 +1827,11 @@ bool editor_road::branch_from(road_node &Road, double const T, glm::dvec2 const
Error = "The road would leave at too sharp an angle"; Error = "The road would leave at too sharp an angle";
return false; return false;
} }
if (layout.changing())
{
Error = "\"" + Road.name() + "\" changes its lanes along the way, which leaves no place for a junction. Lead the road out of a piece next to it";
return false;
}
float lanewidth{0.f}; float lanewidth{0.f};
if (false == lane_width(layout, T, lanewidth)) if (false == lane_width(layout, T, lanewidth))
{ {
@@ -1988,6 +2008,7 @@ std::vector<road_node::state> editor_road::roundabout(glm::dvec3 const &Centre,
if (layout.forward < 1) if (layout.forward < 1)
layout.forward = std::max(1, layout.backward); layout.forward = std::max(1, layout.backward);
layout.backward = 0; layout.backward = 0;
layout.missing = {};
layout.lanes.clear(); layout.lanes.clear();
layout.changes.clear(); layout.changes.clear();
layout.taper = {1.f, 1.f}; layout.taper = {1.f, 1.f};

View File

@@ -134,8 +134,8 @@ class editor_road
static std::vector<std::pair<junction_node *, std::size_t>> arms_at(glm::dvec3 const &Point); static std::vector<std::pair<junction_node *, std::size_t>> arms_at(glm::dvec3 const &Point);
// brings what's next to the ends of a piece in line with the piece, after its lanes or its width were changed: // brings what's next to the ends of a piece in line with the piece, after its lanes or its width were changed:
// a junction gets its arm made for the lanes the piece has now, a neighbouring piece with the same lanes is made as wide // a junction gets its arm made for the lanes the piece has now, a neighbouring piece with the same lanes is made as wide
// as the piece where they meet, and one with other lanes gets a transition cut out of its end - a junction of two roads, // as the piece where they meet, and one with other lanes is led to the lanes of the piece: its end, or the whole of it if it's short,
// which leads the lanes of one to the lanes of the other. what gets done is added to Record, what couldn't be done to Notes // becomes a piece which gains or loses lanes along the way. what gets done is added to Record, what couldn't be done to Notes
static void settle(road_node &Road, record &Record, std::string &Notes); static void settle(road_node &Road, record &Record, std::string &Notes);
// width shared by all lanes of the road at specified value of the curve parameter of its axis. returns: false if the lanes differ in width // width shared by all lanes of the road at specified value of the curve parameter of its axis. returns: false if the lanes differ in width
static bool lane_width(road_node::state const &State, double const T, float &Width); static bool lane_width(road_node::state const &State, double const T, float &Width);

View File

@@ -401,7 +401,9 @@ state_serializer::deserialize_firstinit( cParser &Input, scene::scratch_data &Sc
} }
simulation::Paths.InitTracks(); simulation::Paths.InitTracks();
// the junctions tie the lanes of the roads together, what's left loose after that gets closed by the roads // the roads tie up their own lanes where they gain or lose some, the junctions tie the lanes of the roads together,
// what's left loose after that gets closed by the roads
simulation::Roads.InitLanes();
simulation::Junctions.InitJunctions(); simulation::Junctions.InitJunctions();
simulation::Roads.InitRoads(); simulation::Roads.InitRoads();
// crossings and traffic points go by the lanes, which are complete at this point // crossings and traffic points go by the lanes, which are complete at this point
@@ -536,11 +538,29 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
( Scratchpad.location.offset.empty() ? ( Scratchpad.location.offset.empty() ?
glm::dvec3 { 0.0 } : glm::dvec3 { 0.0 } :
glm::dvec3 { Scratchpad.location.offset.top() } ) ); glm::dvec3 { Scratchpad.location.offset.top() } ) );
if( false == simulation::Junctions.insert( junction ) ) { road_node::state stretch;
ErrorLog( "Bad scenario: duplicate junction name \"" + junction->name() + "\" defined in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" ); if( junction->as_road( stretch ) ) {
// a junction of two roads is where a road changes its lanes, which used to take a junction and is done by a road now.
// it's loaded as the road it would be, and marked as changed so it's written as one when the scenery is saved
delete junction;
nodedata.type = "road";
auto *road { new road_node( nodedata ) };
road->define( stretch );
if( false == simulation::Roads.insert( road ) ) {
ErrorLog( "Bad scenario: duplicate road name \"" + road->name() + "\" defined in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" );
}
scene::Groups.insert( scene::Groups.handle(), road );
road->create_lanes();
road->mark_dirty();
scene::Layers.track( road, { sourcebegin, Input.TokenEnd() } );
}
else {
if( false == simulation::Junctions.insert( junction ) ) {
ErrorLog( "Bad scenario: duplicate junction name \"" + junction->name() + "\" defined in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" );
}
scene::Groups.insert( scene::Groups.handle(), junction );
scene::Layers.track( junction, { sourcebegin, Input.TokenEnd() } );
} }
scene::Groups.insert( scene::Groups.handle(), junction );
scene::Layers.track( junction, { sourcebegin, Input.TokenEnd() } );
} }
else if( roadpoint_node::is_keyword( nodedata.type ) ) { else if( roadpoint_node::is_keyword( nodedata.type ) ) {
// level crossing, or a point where road vehicles appear or are taken away // level crossing, or a point where road vehicles appear or are taken away

File diff suppressed because it is too large Load Diff

View File

@@ -58,7 +58,8 @@ class owned_shapes
// properties, past the ones of the lanes and the look: median <gap|painted|island> <width at the start> <width at the end> <material>, // properties, past the ones of the lanes and the look: median <gap|painted|island> <width at the start> <width at the end> <material>,
// medianround <length of the rounded end of an island at the start> <at the end>, // medianround <length of the rounded end of an island at the start> <at the end>,
// kerbs <left|right|both> <width> <material>, bank <left|right|both> <width at the start> <drop at the start> <width at the end> <drop at the end>, // kerbs <left|right|both> <width> <material>, bank <left|right|both> <width at the start> <drop at the start> <width at the end> <drop at the end>,
// bankmaterial <material>, weight <value>, roundabout // bankmaterial <material>, weight <value>, roundabout,
// lanesto <along the axis> <against the axis>: lanes the piece has at the end of its axis, where these aren't the ones it starts with
class road_node : public scene::basic_node class road_node : public scene::basic_node
{ {
@@ -151,9 +152,23 @@ class road_node : public scene::basic_node
std::array<bank_data, 2> banks; // left, right std::array<bank_data, 2> banks; // left, right
std::string bankmaterial{"none"}; // what a bank is covered with, other than one of a shoulder, which goes with the shoulder std::string bankmaterial{"none"}; // what a bank is covered with, other than one of a shoulder, which goes with the shoulder
bool roundabout{false}; // the piece is a part of a roundabout: traffic on it goes ahead of the traffic joining it bool roundabout{false}; // the piece is a part of a roundabout: traffic on it goes ahead of the traffic joining it
// lanes the piece is short of at an end of its axis: [end][0: the ones going along the axis, 1: the ones going against it].
// these are the outermost lanes of the direction, which the road gains or loses along the piece;
// forward and backward say how many lanes there are where the piece has the most of them
std::array<std::array<int, 2>, 2> missing{};
// brings the content to a usable form: values within their limits, an entry for each lane and for each pair of neighbours // brings the content to a usable form: values within their limits, an entry for each lane and for each pair of neighbours
void normalize(); void normalize();
// true if the piece has other lanes at its end than at its start
bool changing() const;
// sets the lanes the piece has at the start and at the end of its axis
void lanes_between(int const Startforward, int const Startbackward, int const Endforward, int const Endbackward);
// number of lanes at an end of the axis (0: the start, 1: the end): of the ones going along it, or of the ones going against it
int lanes_at(int const End, bool const Along) const;
// true if the piece has the lane at specified end of the axis
bool lane_at(std::size_t const Lane, int const End) const;
// the same road with the lanes it has at specified end of the axis all the way
state section(int const End) const;
// identifier of a lane: f1..fn go along the axis and b1..bn against it, both counted outwards from where the directions meet // identifier of a lane: f1..fn go along the axis and b1..bn against it, both counted outwards from where the directions meet
std::string lane_id(std::size_t const Lane) const; std::string lane_id(std::size_t const Lane) const;
// number of a lane in the left to right order, -1 if there's no lane with such identifier // number of a lane in the left to right order, -1 if there's no lane with such identifier
@@ -162,8 +177,17 @@ class road_node : public scene::basic_node
int default_change(std::size_t const Boundary) const; int default_change(std::size_t const Boundary) const;
// distance of the middle of a lane from the axis, positive to the left when facing along the axis // distance of the middle of a lane from the axis, positive to the left when facing along the axis
double lane_offset(std::size_t const Lane) const; double lane_offset(std::size_t const Lane) const;
// the same at an end of the axis, where the road is centred on the axis with the lanes it has there.
// a lane the road is short of at that end is where the lane it comes out of, or goes into, is
double lane_offset(std::size_t const Lane, int const End) const;
// distance of the middle of a lane from the axis at specified part of the length of the axis, with the taper and what's between the directions counted in
double lane_position(std::size_t const Lane, double const Fraction) const;
// combined width of the lanes, before the taper // combined width of the lanes, before the taper
double width() const; double width() const;
// the same for the lanes the piece has at an end of the axis
double width(int const End) const;
// combined width of the lanes at specified part of the length of the axis, with the taper counted in
double breadth(double const Fraction) const;
// what the widths of the lanes are multiplied by at specified value of the curve parameter // what the widths of the lanes are multiplied by at specified value of the curve parameter
double scale(double const T) const; double scale(double const T) const;
// true if the two directions of the road are kept apart anywhere along it // true if the two directions of the road are kept apart anywhere along it
@@ -172,10 +196,20 @@ class road_node : public scene::basic_node
double median_width(double const T) const; double median_width(double const T) const;
// distance from the axis of the line the two directions meet at, on a road which doesn't keep them apart // distance from the axis of the line the two directions meet at, on a road which doesn't keep them apart
double divide() const; double divide() const;
// the same for the lanes the piece has at an end of the axis
double divide(int const End) const;
// the same at specified part of the length of the axis, with the taper counted in
double middle(double const Fraction) const;
// width of the whole roadway at specified value of the curve parameter: the lanes and what's between the directions // width of the whole roadway at specified value of the curve parameter: the lanes and what's between the directions
double span(double const T) const; double span(double const T) const;
// shape of the middle of a lane, laid out in the direction of travel // shape of the middle of a lane, laid out in the direction of travel
segment_data lane_path(std::size_t const Lane) const; segment_data lane_path(std::size_t const Lane) const;
// shape of the way from the middle of a lane to the middle of another over a part of the axis, laid out in the direction of travel.
// From, To: values of the curve parameter of the axis, in the order of the axis
segment_data lane_path(std::size_t const Lane, std::size_t const Tolane, double const From, double const To) const;
// part of the axis, as values of its curve parameter, over which a piece which gains or loses lanes leads the lanes it has
// at one end to the lanes it has at the other. before it and past it there's a short path for each lane the piece has at that end
std::array<double, 2> change_range() const;
// length of the axis // length of the axis
double length() const; double length() const;
// position on the axis and direction of the axis, for specified value of the curve parameter // position on the axis and direction of the axis, for specified value of the curve parameter
@@ -196,17 +230,36 @@ class road_node : public scene::basic_node
void define(state const &State); void define(state const &State);
// creates paths of the lanes and hands them over to the simulation // creates paths of the lanes and hands them over to the simulation
void create_lanes(); void create_lanes();
// makes the paths which lead the lanes the piece has at one end to the lanes it has at the other, where these differ.
// to be called once the lanes are joined with the lanes of the neighbours
void create_links();
// links lanes of opposite directions at an end of the road which leads nowhere, so the vehicles can turn back // links lanes of opposite directions at an end of the road which leads nowhere, so the vehicles can turn back
void close_ends(); void close_ends();
// gives up the paths of the lanes, or the ones made to close the loose ends. the paths belong to the path table, // gives up the paths of the lanes, or the ones made to close the loose ends. the paths belong to the path table,
// the caller is expected to take them out of use // the caller is expected to take them out of use
std::vector<TTrack *> release_lanes(); std::vector<TTrack *> release_lanes();
std::vector<TTrack *> release_turns(); std::vector<TTrack *> release_turns();
// paths of the lanes, in the order of the lanes, and the ones made to close the loose ends // paths of the lanes, and the ones made to close the loose ends. the former go in the order of the lanes;
// on a piece which gains or loses lanes these are the short paths left of each such lane at the ends of the piece
std::vector<TTrack *> const &tracks() const std::vector<TTrack *> const &tracks() const
{ {
return m_tracks; return m_tracks;
} }
// path of a lane at an end of the axis (0: the start, 1: the end); nullptr if the piece doesn't have the lane there
TTrack *lane_track(std::size_t const Lane, int const End) const
{
return Lane < m_lanetracks[End].size() ? m_lanetracks[End][Lane] : nullptr;
}
// paths leading from the lanes at one end to the lanes at the other, on a piece which gains or loses lanes
std::vector<TTrack *> const &links() const
{
return m_links;
}
// shapes of the ways these make, for display
std::vector<segment_data> const &ways() const
{
return m_ways;
}
std::vector<TTrack *> const &turns() const std::vector<TTrack *> const &turns() const
{ {
return m_turns; return m_turns;
@@ -243,9 +296,14 @@ class road_node : public scene::basic_node
void export_as_text_(std::ostream &Output) const override; void export_as_text_(std::ostream &Output) const override;
// creates a path for the vehicles and registers it with the simulation // creates a path for the vehicles and registers it with the simulation
TTrack *create_track(std::string const &Name, segment_data const &Path, float const Width, float const Velocity); TTrack *create_track(std::string const &Name, segment_data const &Path, float const Width, float const Velocity);
// the same for a path put together by the caller, the way the scenery defines one
TTrack *create_track(std::string const &Name, std::string const &Definition);
// members // members
state m_state; state m_state;
std::vector<TTrack *> m_tracks; std::vector<TTrack *> m_tracks;
std::array<std::vector<TTrack *>, 2> m_lanetracks; // path of each lane at the start and at the end of the axis
std::vector<TTrack *> m_links;
std::vector<segment_data> m_ways;
std::vector<TTrack *> m_turns; std::vector<TTrack *> m_turns;
bool m_merged{false}; bool m_merged{false};
owned_shapes m_shapes; // geometry put in the scene by show() owned_shapes m_shapes; // geometry put in the scene by show()
@@ -263,7 +321,8 @@ class road_node : public scene::basic_node
// armmedian <arm> <width of what keeps the two directions of the road apart there>, kerbs <width> <material>, // armmedian <arm> <width of what keeps the two directions of the road apart there>, kerbs <width> <material>,
// cornerbank <arm> <width> <drop> <width> <drop> (at that road, then at the next one), bankmaterial <material>, // cornerbank <arm> <width> <drop> <width> <drop> (at that road, then at the next one), bankmaterial <material>,
// crosswalk <arm> <length>, median <gap|painted|island> <material> // crosswalk <arm> <length>, median <gap|painted|island> <material>
// a junction of two roads is drawn as a stretch of road, on which the lanes of one are led to the lanes of the other // a junction of two roads is drawn as a stretch of road, on which the lanes of one are led to the lanes of the other.
// that's what a road which changed its lanes used to take; such a junction found in a scenery is loaded as a piece of road instead, see as_road()
// the arms don't have to be level with each other: the surface is spanned between their ends and the centre. // the arms don't have to be level with each other: the surface is spanned between their ends and the centre.
// the junction also tells the vehicles when to wait: the ones which have to give way wait for the ones with the right of way, // the junction also tells the vehicles when to wait: the ones which have to give way wait for the ones with the right of way,
// and where neither has it, for the ones coming from their right, or from the opposite side when turning left across their way // and where neither has it, for the ones coming from their right, or from the opposite side when turning left across their way
@@ -324,6 +383,9 @@ class junction_node : public scene::basic_node
TTrack *link{nullptr}; // path, or crossroads of paths, leading on from it TTrack *link{nullptr}; // path, or crossroads of paths, leading on from it
std::vector<way_data> ways; std::vector<way_data> ways;
std::vector<TTrack *> approach; // lanes the vehicles heading for the junction are looked for on, nearest first std::vector<TTrack *> approach; // lanes the vehicles heading for the junction are looked for on, nearest first
// for each of these: the way a vehicle on it has to have picked to be heading for the junction; 0 if there's no choice there.
// it's set for a path on which a road forks into more lanes
std::vector<int> wishes;
glm::dvec3 line{0.0}; // where the lane ends glm::dvec3 line{0.0}; // where the lane ends
road_order *order{nullptr}; road_order *order{nullptr};
// what's going on at the moment // what's going on at the moment
@@ -385,6 +447,9 @@ class junction_node : public scene::basic_node
return m_state; return m_state;
} }
void define(state const &State); void define(state const &State);
// a junction of two roads is a stretch where a road changes its lanes, which a road can do by itself.
// Road: receives the piece of road to take the place of the junction. returns: false if the junction isn't of that kind
bool as_road(road_node::state &Road) const;
// makes paths leading through the junction, between the lanes of the roads attached to it at the moment // makes paths leading through the junction, between the lanes of the roads attached to it at the moment
void create_links(); void create_links();
// gives up the paths leading through the junction; the caller is expected to take them out of use // gives up the paths leading through the junction; the caller is expected to take them out of use
@@ -452,7 +517,10 @@ class road_table : public basic_table<road_node>
{ {
public: public:
// legacy style initialization, to be performed when the tracks are already joined // legacy style initialization, to be performed when the tracks are already joined: ties up the lanes of the pieces
// which gain or lose them. the junctions go by these, so it comes ahead of their initialization
void InitLanes();
// the same, to be performed once the junctions are done: closes the ends of the roads which lead nowhere
void InitRoads(); void InitRoads();
// generates geometry of the roads and puts it in the scene // generates geometry of the roads and puts it in the scene
void create_geometry(scene::scratch_data &Scratchpad); void create_geometry(scene::scratch_data &Scratchpad);

View File

@@ -545,6 +545,29 @@ void roadpoint_node::bind()
m_lanes.emplace_back(lane); m_lanes.emplace_back(lane);
} }
} }
// and from the paths which lead the lanes of a piece which gains or loses them from one of its ends to the other
for (auto *link : road->links())
{
if (link == nullptr)
{
continue;
}
for (auto const &way : link->m_paths)
{
if (out_of_reach(way, m_state.position, m_state.radius))
{
continue;
}
path_curve const curve{way};
double distance{0.0};
auto const t{curve.nearest(m_state.position, distance)};
if (distance <= m_state.radius && std::abs(curve.point(t).y - m_state.position.y) <= 3.0)
{
m_lanes.emplace_back(link);
break;
}
}
}
} }
break; break;
} }
@@ -559,10 +582,16 @@ void roadpoint_node::bind_crossing()
auto const *crossed{road_under(m_state.position, where)}; auto const *crossed{road_under(m_state.position, where)};
if (crossed != nullptr) if (crossed != nullptr)
{ {
auto const &lanes{crossed->tracks()}; auto const &layout{crossed->definition()};
for (std::size_t idx = 0; idx < lanes.size(); ++idx) if (layout.changing())
{ {
auto *lane{lanes[idx]}; // NOTE: on such a piece a lane is a string of paths, and may fork; the vehicles are stopped ahead of all that
ErrorLog("Bad road point: crossing \"" + m_name + "\" lies on a piece of road which gains or loses lanes, the vehicles are stopped before that piece");
}
for (std::size_t idx = 0; idx < layout.lanes.size(); ++idx)
{
// the path the traffic of the lane gets on the piece by; on a piece which keeps its lanes it's the lane itself
auto *lane{crossed->lane_track(idx, idx >= static_cast<std::size_t>(layout.backward) ? 0 : 1)};
if (lane == nullptr || lane->m_paths.empty()) if (lane == nullptr || lane->m_paths.empty())
{ {
continue; continue;