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:
@@ -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
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
903
world/Road.cpp
903
world/Road.cpp
File diff suppressed because it is too large
Load Diff
76
world/Road.h
76
world/Road.h
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user