17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-03 17:50:21 +01:00

fix(roads): lane transitions drawn as road with an island or a closed zone, end faces and rounded island ends, closed zone stripes on curves, banks applied to the whole road; junction banks to the ground and pedestrian crossings

This commit is contained in:
2026-10-04 03:25:32 +02:00
parent 27a94509c4
commit ceae93eec5
5 changed files with 995 additions and 132 deletions

View File

@@ -562,6 +562,11 @@ class editor_mode : public application_mode
bool render_road_material(char const *Label, char *Buffer, std::size_t const Size, std::string &Value); bool render_road_material(char const *Label, char *Buffer, std::size_t const Size, std::string &Value);
void render_junction_layout(junction_node &Junction); void render_junction_layout(junction_node &Junction);
void junction_apply(junction_node &Junction, junction_node::state const &State); void junction_apply(junction_node &Junction, junction_node::state const &State);
// leads the banks of the corners of a junction to the ground beside it
void junction_bank_to_ground(junction_node &Junction);
// how far from specified point, and how far down, a slope of the grade set for the banks gets to the ground.
// Outwards: the way the slope leads, seen from above
std::pair<float, float> bank_reach(glm::dvec3 const &Edge, glm::dvec3 const &Outwards);
void draw_road_overlay() const; void draw_road_overlay() const;
void update_road_tool(); void update_road_tool();
void road_click(); void road_click();

View File

@@ -336,6 +336,8 @@ void editor_mode::junction_select(junction_node *Junction)
copy_text(tool.surface, sizeof(tool.surface), Junction->definition().surface); copy_text(tool.surface, sizeof(tool.surface), Junction->definition().surface);
copy_text(tool.sides[0], sizeof(tool.sides[0]), Junction->definition().side.material); copy_text(tool.sides[0], sizeof(tool.sides[0]), Junction->definition().side.material);
copy_text(tool.kerbtext, sizeof(tool.kerbtext), Junction->definition().kerbmaterial); copy_text(tool.kerbtext, sizeof(tool.kerbtext), Junction->definition().kerbmaterial);
copy_text(tool.banktext, sizeof(tool.banktext), Junction->definition().bankmaterial);
copy_text(tool.mediantext, sizeof(tool.mediantext), Junction->definition().medianmaterial);
} }
void editor_mode::roadpoint_select(roadpoint_node *Point) void editor_mode::roadpoint_select(roadpoint_node *Point)
@@ -771,6 +773,59 @@ void editor_mode::render_junction_layout(junction_node &Junction)
ImGui::Unindent(); ImGui::Unindent();
} }
// banks leading from the corners to the ground
if (ImGui::CollapsingHeader("Banks to the ground"))
{
if (render_road_material("Bank material", tool.banktext, sizeof(tool.banktext), state.bankmaterial))
changed = true;
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", "What the banks are covered with next to a sidewalk, a kerb or the bare edge.\nA bank of a shoulder is covered with the material of the shoulder.");
ImGui::SetNextItemWidth(120.0f);
if (ImGui::InputFloat("Run for a metre of drop [m]", &tool.bankgrade, 0.25f, 0.5f, "%.2f"))
tool.bankgrade = std::clamp(tool.bankgrade, 0.25f, 10.0f);
if (ImGui::Button("Lead the banks to the ground"))
{
if (changed)
junction_apply(Junction, state);
junction_bank_to_ground(Junction);
return;
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", "Looks for the ground past each corner, at both roads the corner runs between,\n"
"and sets the bank of the corner to reach it. Done for the roads too, the banks meet at the ends of the roads.");
auto banked{false};
for (auto const &arm : state.arms)
banked = banked || arm.bank.set;
if (banked)
{
ImGui::SameLine();
if (ImGui::Button("Remove them"))
{
for (auto &arm : state.arms)
arm.bank = road_node::bank_data{};
changed = true;
}
}
}
if (state.arms.size() == 2)
{
// where a road changes its lanes: what's between its two directions, if the roads keep them apart
ImGui::Separator();
char const *mediantypes[]{"nothing, the ground shows", "surface closed to the traffic", "island"};
int median{state.median == road_node::median_type::gap ? 0 : state.median == road_node::median_type::island ? 2 : 1};
ImGui::SetNextItemWidth(220.0f);
if (ImGui::Combo("Between the directions", &median, mediantypes, 3))
{
state.median = (median == 0 ? road_node::median_type::gap : median == 2 ? road_node::median_type::island : road_node::median_type::painted);
changed = true;
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", "Shows where a road which keeps its two directions apart meets this stretch:\n"
"what's between the directions is led on to the other road, where it closes or carries on.");
if (state.median == road_node::median_type::island && render_road_material("Island material", tool.mediantext, sizeof(tool.mediantext), state.medianmaterial))
changed = true;
}
// the roads // the roads
if (ImGui::CollapsingHeader("Roads of the junction", ImGuiTreeNodeFlags_DefaultOpen)) if (ImGui::CollapsingHeader("Roads of the junction", ImGuiTreeNodeFlags_DefaultOpen))
{ {
@@ -782,9 +837,30 @@ void editor_mode::render_junction_layout(junction_node &Junction)
ImGui::PushID(static_cast<int>(arm)); ImGui::PushID(static_cast<int>(arm));
auto &data{state.arms[arm]}; auto &data{state.arms[arm]};
ImGui::Text("%d: %d in, %d out, %.2f m a lane", static_cast<int>(arm) + 1, data.incoming, data.outgoing, data.width); ImGui::Text("%d: %d in, %d out, %.2f m a lane", static_cast<int>(arm) + 1, data.incoming, data.outgoing, data.width);
ImGui::SameLine(); if (state.arms.size() > 2)
if (ImGui::Checkbox("Stop line", &data.stopline)) {
changed = true; ImGui::SameLine();
if (ImGui::Checkbox("Stop line", &data.stopline))
changed = true;
// stripes for the pedestrians, across the whole road where it meets the junction
ImGui::SameLine();
bool zebra{data.crosswalk > 0.f};
if (ImGui::Checkbox("Pedestrian crossing", &zebra))
{
data.crosswalk = (zebra ? 4.f : 0.f);
changed = true;
}
if (zebra)
{
ImGui::SameLine();
ImGui::SetNextItemWidth(90.0f);
if (ImGui::InputFloat("m##crosswalk", &data.crosswalk, 0.5f, 1.0f, "%.1f", ImGuiInputTextFlags_EnterReturnsTrue))
{
data.crosswalk = std::clamp(data.crosswalk, 1.0f, 20.0f);
changed = true;
}
}
}
if (data.incoming > 0 && state.arms.size() > 2) if (data.incoming > 0 && state.arms.size() > 2)
{ {
ImGui::Indent(); ImGui::Indent();
@@ -1231,10 +1307,31 @@ void editor_mode::road_click()
editor_road::carry_out(plan.end, record); editor_road::carry_out(plan.end, record);
} }
std::vector<road_node::state> states; std::vector<road_node::state> states;
// what changes from the start of a piece to its end is spread over the whole stretch being built,
// so the pieces don't each go through all of it
std::vector<double> stations{0.0};
for (auto const &piece : pieces) for (auto const &piece : pieces)
stations.emplace_back(stations.back() + editor_road::planar_length(piece));
auto const along = [&stations](std::size_t const Index) {
auto const fraction{stations.back() > 0.0 ? stations[Index] / stations.back() : 0.0};
return static_cast<float>(fraction * fraction * (3.0 - 2.0 * fraction));
};
for (std::size_t index = 0; index < pieces.size(); ++index)
{ {
auto state{tool.settings}; auto state{tool.settings};
state.axis = piece; state.axis = pieces[index];
auto const from{along(index)};
auto const to{along(index + 1)};
auto const &median{tool.settings.median};
state.median.width = {glm::mix(median.width[0], median.width[1], from), glm::mix(median.width[0], median.width[1], to)};
// an island is rounded where the stretch starts and ends
state.median.round = {index == 0 ? median.round[0] : 0.f, index + 1 == pieces.size() ? median.round[1] : 0.f};
for (std::size_t side = 0; side < state.banks.size(); ++side)
{
auto const &bank{tool.settings.banks[side]};
state.banks[side].width = {glm::mix(bank.width[0], bank.width[1], from), glm::mix(bank.width[0], bank.width[1], to)};
state.banks[side].drop = {glm::mix(bank.drop[0], bank.drop[1], from), glm::mix(bank.drop[0], bank.drop[1], to)};
}
states.emplace_back(state); states.emplace_back(state);
} }
auto const created{editor_road::create(states)}; auto const created{editor_road::create(states)};
@@ -1279,6 +1376,8 @@ void editor_mode::road_apply()
auto const pieces{tool.whole ? editor_road::chain(*tool.selected) : std::vector<road_node *>{tool.selected}}; auto const pieces{tool.whole ? 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
auto const banked{tool.settings.banks != tool.selected->definition().banks};
for (auto *road : pieces) for (auto *road : pieces)
{ {
std::string reason; std::string reason;
@@ -1295,9 +1394,12 @@ void editor_mode::road_apply()
state.taper = road->definition().taper; state.taper = road->definition().taper;
if (road != tool.selected) if (road != tool.selected)
{ {
// and so are the banks, and how far apart the two directions are led // and so is how far apart the two directions are led, and the rounded ends of what's between them
state.median.width = road->definition().median.width; state.median.width = road->definition().median.width;
state.banks = road->definition().banks; state.median.round = road->definition().median.round;
// banks led to the ground differ from piece to piece, so a change of something else leaves them alone
if (false == banked)
state.banks = road->definition().banks;
} }
if (road != tool.selected && road->definition().changes.size() == state.changes.size()) if (road != tool.selected && road->definition().changes.size() == state.changes.size())
{ {
@@ -1417,6 +1519,69 @@ void editor_mode::road_roundabout(glm::dvec3 const &Ground)
tool.status = "Roundabout made. Untick Roundabout and lead roads out of its side, or into it: the traffic joining it gives way to the traffic going around."; tool.status = "Roundabout made. Untick Roundabout and lead roads out of its side, or into it: the traffic joining it gives way to the traffic going around.";
} }
// how far from specified point, and how far down, a slope of the grade set for the banks gets to the ground
std::pair<float, float> editor_mode::bank_reach(glm::dvec3 const &Edge, glm::dvec3 const &Outwards)
{
auto const grade{std::clamp(static_cast<double>(m_roadtool.bankgrade), 0.25, 10.0)};
auto const steps{static_cast<int>(kBankReach / kBankStep)};
// the ground further and further from the edge, without the roads themselves
std::vector<glm::dvec3> probes;
for (int step = 1; step <= steps; ++step)
probes.emplace_back(Edge + Outwards * (step * kBankStep));
auto const ground{ground_heights(probes, true)};
// the bank ends where a slope of the set grade, going down or up, gets to the ground
auto const rising{ground.front() > Edge.y};
auto width{kBankReach};
auto drop{Edge.y - ground.back()};
for (int step = 1; step <= steps; ++step)
{
auto const reach{step * kBankStep};
auto const height{ground[step - 1]};
if (rising ? height <= Edge.y + reach / grade : height >= Edge.y - reach / grade)
{
width = reach;
drop = Edge.y - height;
break;
}
}
return {static_cast<float>(width), static_cast<float>(drop)};
}
// leads the banks of the corners of a junction to the ground beside it
void editor_mode::junction_bank_to_ground(junction_node &Junction)
{
auto state{Junction.definition()};
auto const order{state.arm_order()};
auto const sided{state.side.type != road_node::side_type::none && state.side.width > 0.f};
// where the banks start: past the kerb and whatever lines the corners
auto const lining{(state.kerbs ? state.kerbwidth : 0.f) + (sided ? state.side.width : 0.f)};
auto const raised{sided && state.side.type == road_node::side_type::sidewalk ? state.kerbheight : 0.f};
for (std::size_t idx = 0; idx < order.size(); ++idx)
{
// a corner starts at the left edge of a road, seen from the junction, and ends at the right edge of the next one
auto &arm{state.arms[order[idx]]};
auto const &next{state.arms[order[(idx + 1) % order.size()]]};
glm::dvec3 const outwards[2]{glm::dvec3{arm.direction.y, 0.0, -arm.direction.x}, -glm::dvec3{next.direction.y, 0.0, -next.direction.x}};
glm::dvec3 const edges[2]{arm.position + outwards[0] * (0.5 * state.arm_width(order[idx]) + lining) + glm::dvec3{0.0, raised, 0.0},
next.position + outwards[1] * (0.5 * state.arm_width(order[(idx + 1) % order.size()]) + lining) + glm::dvec3{0.0, raised, 0.0}};
arm.bank.set = true;
for (int end = 0; end < 2; ++end)
{
auto const reach{bank_reach(edges[end], outwards[end])};
arm.bank.width[end] = reach.first;
arm.bank.drop[end] = reach.second;
}
}
junction_apply(Junction, state);
auto &tool{m_roadtool};
if (tool.status == "Changed")
{
tool.status = "Banks of the junction led to the ground.";
if ((false == sided || state.side.type != road_node::side_type::shoulder) && (state.bankmaterial.empty() || state.bankmaterial == "none"))
tool.status += " Pick a bank material to have them drawn.";
}
}
// leads the banks of the selected piece, or of the whole road, to the ground beside it // leads the banks of the selected piece, or of the whole road, to the ground beside it
void editor_mode::road_bank_to_ground() void editor_mode::road_bank_to_ground()
{ {
@@ -1433,8 +1598,6 @@ void editor_mode::road_bank_to_ground()
return; return;
} }
} }
auto const grade{std::clamp(static_cast<double>(tool.bankgrade), 0.25, 10.0)};
auto const steps{static_cast<int>(kBankReach / kBankStep)};
std::vector<std::pair<road_node *, road_node::state>> changes; std::vector<std::pair<road_node *, road_node::state>> changes;
for (auto *road : pieces) for (auto *road : pieces)
{ {
@@ -1455,29 +1618,10 @@ void editor_mode::road_bank_to_ground()
auto edge{state.point(t) + across * (0.5 * state.span(t) + (state.kerbs[side] ? state.kerbwidth : 0.f) + (sided ? data.width : 0.f))}; auto edge{state.point(t) + across * (0.5 * state.span(t) + (state.kerbs[side] ? state.kerbwidth : 0.f) + (sided ? data.width : 0.f))};
if (sided && data.type == road_node::side_type::sidewalk) if (sided && data.type == road_node::side_type::sidewalk)
edge.y += state.kerbheight; edge.y += state.kerbheight;
// the ground further and further from the road, without the roads themselves auto const reach{bank_reach(edge, across)};
std::vector<glm::dvec3> probes;
for (int step = 1; step <= steps; ++step)
probes.emplace_back(edge + across * (step * kBankStep));
auto const ground{ground_heights(probes, true)};
// the bank ends where a slope of the set grade, going down or up, gets to the ground
auto const rising{ground.front() > edge.y};
auto width{kBankReach};
auto drop{edge.y - ground.back()};
for (int step = 1; step <= steps; ++step)
{
auto const reach{step * kBankStep};
auto const height{ground[step - 1]};
if (rising ? height <= edge.y + reach / grade : height >= edge.y - reach / grade)
{
width = reach;
drop = edge.y - height;
break;
}
}
state.banks[side].set = true; state.banks[side].set = true;
state.banks[side].width[end] = static_cast<float>(width); state.banks[side].width[end] = reach.first;
state.banks[side].drop[end] = static_cast<float>(drop); state.banks[side].drop[end] = reach.second;
} }
} }
changes.emplace_back(road, state); changes.emplace_back(road, state);
@@ -1914,7 +2058,8 @@ bool editor_mode::render_road_layout(road_node::state &State)
if (ImGui::CollapsingHeader("Banks to the ground")) if (ImGui::CollapsingHeader("Banks to the ground"))
{ {
ImGui::TextDisabled("A slope from the edge of the road down, or up, to the ground."); ImGui::TextDisabled("A slope from the edge of the road down, or up, to the ground.");
ImGui::TextDisabled("Set for each piece on its own, at its start and at its end."); ImGui::TextDisabled("Set here it goes to every piece the changes go to; the button below");
ImGui::TextDisabled("works out each piece on its own.");
char const *banknames[]{"Bank on the left", "Bank on the right"}; char const *banknames[]{"Bank on the left", "Bank on the right"};
for (int side = 0; side < 2; ++side) for (int side = 0; side < 2; ++side)
{ {
@@ -1991,6 +2136,13 @@ bool editor_mode::render_road_layout(road_node::state &State)
if (material("Island material", tool.mediantext, sizeof(tool.mediantext), State.median.material)) if (material("Island material", tool.mediantext, sizeof(tool.mediantext), State.median.material))
changed = true; changed = true;
ImGui::TextDisabled("The island is as high as the kerbs; with a kerb material set it gets kerbs of its own."); ImGui::TextDisabled("The island is as high as the kerbs; with a kerb material set it gets kerbs of its own.");
if (number("Rounded at the start [m]", State.median.round[0], 0.5f, "%.2f"))
changed = true;
if (number("Rounded at the end [m]", State.median.round[1], 0.5f, "%.2f"))
changed = true;
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", "Length of the rounded end of the island at that end of the piece; half of the width of the island makes it a half circle.\n"
"0: the island is cut straight, with a face. Use it where the next piece has no island.");
} }
} }
@@ -2240,9 +2392,17 @@ void editor_mode::render_road_window()
else if (tool.junction != nullptr) else if (tool.junction != nullptr)
{ {
auto &junction{*tool.junction}; auto &junction{*tool.junction};
ImGui::Text("%s, %d roads, %d ways through", junction.name().c_str(), static_cast<int>(junction.definition().arms.size()), static_cast<int>(junction.movements().size())); if (junction.definition().arms.size() == 2)
ImGui::TextDisabled("The ways through are made for the roads attached at the moment."); {
ImGui::TextDisabled("Build tool, LMB on the junction: lead another road out of it"); ImGui::Text("%s, a stretch where the road changes its lanes", junction.name().c_str());
ImGui::TextDisabled("It's drawn as a part of the road, and leads the lanes of one side to the lanes of the other.");
}
else
{
ImGui::Text("%s, %d roads, %d ways through", junction.name().c_str(), static_cast<int>(junction.definition().arms.size()), static_cast<int>(junction.movements().size()));
ImGui::TextDisabled("The ways through are made for the roads attached at the moment.");
ImGui::TextDisabled("Build tool, LMB on the junction: lead another road out of it");
}
render_junction_layout(junction); render_junction_layout(junction);
if (ImGui::Button("Delete (Del)")) if (ImGui::Button("Delete (Del)"))
road_delete(); road_delete();

View File

@@ -141,6 +141,8 @@ road_node::state part(road_node::state const &State, double const From, double c
result.taper = {static_cast<float>(State.scale(From)), static_cast<float>(State.scale(To))}; result.taper = {static_cast<float>(State.scale(From)), static_cast<float>(State.scale(To))};
if (State.divided()) if (State.divided())
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
result.median.round = {From <= 0.0 ? State.median.round[0] : 0.f, To >= 1.0 ? State.median.round[1] : 0.f};
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]};
@@ -337,6 +339,9 @@ void dress(junction_node::state &Junction, road_node::state const &Road)
Junction.kerbs = (Road.kerbs[0] || Road.kerbs[1]); Junction.kerbs = (Road.kerbs[0] || Road.kerbs[1]);
Junction.kerbwidth = Road.kerbwidth; Junction.kerbwidth = Road.kerbwidth;
Junction.kerbmaterial = Road.kerbmaterial; Junction.kerbmaterial = Road.kerbmaterial;
Junction.bankmaterial = Road.bankmaterial;
Junction.median = Road.median.type;
Junction.medianmaterial = Road.median.material;
Junction.slope = Road.slope; Junction.slope = Road.slope;
Junction.friction = Road.friction; Junction.friction = Road.friction;
Junction.sounddistance = Road.sounddistance; Junction.sounddistance = Road.sounddistance;
@@ -409,6 +414,7 @@ road_node::state flipped(road_node::state const &State)
std::swap(result.sides[0], result.sides[1]); std::swap(result.sides[0], result.sides[1]);
std::swap(result.taper[0], result.taper[1]); std::swap(result.taper[0], result.taper[1]);
std::swap(result.median.width[0], result.median.width[1]); std::swap(result.median.width[0], result.median.width[1]);
std::swap(result.median.round[0], result.median.round[1]);
std::swap(result.kerbs[0], result.kerbs[1]); std::swap(result.kerbs[0], result.kerbs[1]);
std::swap(result.banks[0], result.banks[1]); std::swap(result.banks[0], result.banks[1]);
for (auto &bank : result.banks) for (auto &bank : result.banks)
@@ -1110,6 +1116,7 @@ bool editor_road::dissolve(glm::dvec3 const &Joint, record &Record, std::string
} }
merged.median.width[1] = (after.divided() ? after.median.width[1] : 0.f); merged.median.width[1] = (after.divided() ? after.median.width[1] : 0.f);
} }
merged.median.round[1] = after.median.round[1];
for (std::size_t side = 0; side < merged.banks.size(); ++side) for (std::size_t side = 0; side < merged.banks.size(); ++side)
{ {
if (merged.banks[side].set && after.banks[side].set) if (merged.banks[side].set && after.banks[side].set)
@@ -1171,6 +1178,8 @@ road_node *editor_road::split(road_node &Road, double const T)
first.median.width[1] = gap; first.median.width[1] = gap;
second.median.width[0] = gap; second.median.width[0] = gap;
} }
first.median.round[1] = 0.f;
second.median.round[0] = 0.f;
for (std::size_t side = 0; side < first.banks.size(); ++side) for (std::size_t side = 0; side < first.banks.size(); ++side)
{ {
auto const &bank{Road.definition().banks[side]}; auto const &bank{Road.definition().banks[side]};

File diff suppressed because it is too large Load Diff

View File

@@ -56,6 +56,7 @@ class owned_shapes
// scenery entry: // scenery entry:
// node <max> <min> <name> road <axis, laid out like a path of a track> [<property> <values>]... endroad // node <max> <min> <name> road <axis, laid out like a path of a track> [<property> <values>]... endroad
// 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>,
// 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
class road_node : public scene::basic_node class road_node : public scene::basic_node
@@ -106,6 +107,7 @@ class road_node : public scene::basic_node
median_type type{median_type::painted}; median_type type{median_type::painted};
std::array<float, 2> width{0.f, 0.f}; // at the start and at the end of the axis std::array<float, 2> width{0.f, 0.f}; // at the start and at the end of the axis
std::string material{"none"}; // what an island is covered with std::string material{"none"}; // what an island is covered with
std::array<float, 2> round{0.f, 0.f}; // length of the rounded end of an island, at the start and at the end of the axis. 0: the island is cut straight
}; };
// slope leading from the edge of the road down, or up, to the ground // slope leading from the edge of the road down, or up, to the ground
struct bank_data struct bank_data
@@ -113,6 +115,11 @@ class road_node : public scene::basic_node
bool set{false}; // false: a shoulder gets the bank the road gives to all its shoulders, anything else gets none bool set{false}; // false: a shoulder gets the bank the road gives to all its shoulders, anything else gets none
std::array<float, 2> width{0.f, 0.f}; // at the start and at the end of the axis std::array<float, 2> width{0.f, 0.f}; // at the start and at the end of the axis
std::array<float, 2> drop{0.f, 0.f}; // how far below the edge of the road it ends; negative if it rises std::array<float, 2> drop{0.f, 0.f}; // how far below the edge of the road it ends; negative if it rises
bool operator==(bank_data const &Other) const
{
return set == Other.set && width == Other.width && drop == Other.drop;
}
}; };
// everything the scenery says about the road. the lanes go left to right when facing along the axis: // everything the scenery says about the road. the lanes go left to right when facing along the axis:
// the ones going against it, outermost first, then the ones going along it // the ones going against it, outermost first, then the ones going along it
@@ -253,7 +260,10 @@ class road_node : public scene::basic_node
// properties: surface <material>, texlength <m>, markings <white|orange|none>, side <none|shoulder|sidewalk> <width> <material>, kerb <height>, // properties: surface <material>, texlength <m>, markings <white|orange|none>, side <none|shoulder|sidewalk> <width> <material>, kerb <height>,
// slope <width> <drop>, stopline <number of an arm, counted from 1>, velocity <km/h>, friction <value>, environment <name>, // slope <width> <drop>, stopline <number of an arm, counted from 1>, velocity <km/h>, friction <value>, environment <name>,
// priority <arm> <none|main|yield|stop>, turns <arm> <lane leading in, counted from the middle of the road> <letters out of l, s, r>, // priority <arm> <none|main|yield|stop>, turns <arm> <lane leading in, counted from the middle of the road> <letters out of l, s, r>,
// 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>,
// 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
// 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
@@ -286,6 +296,10 @@ class junction_node : public scene::basic_node
float width{3.5f}; // width of a lane float width{3.5f}; // width of a lane
bool stopline{false}; // a line is painted across the lanes leading into the junction bool stopline{false}; // a line is painted across the lanes leading into the junction
float median{0.f}; // how far apart the two directions of the road are there float median{0.f}; // how far apart the two directions of the road are there
float crosswalk{0.f}; // length of the pedestrian crossing painted across the road where it meets the junction; 0: none
// slope leading to the ground from the corner which starts at the left edge of this road, seen from the junction, and goes on
// to the next road. it starts at this road and ends at the next one
road_node::bank_data bank;
right_of_way priority{right_of_way::none}; right_of_way priority{right_of_way::none};
// ways each lane leading into the junction can be left by, a sum of turn_flags; the lanes are counted from the middle of the road. // ways each lane leading into the junction can be left by, a sum of turn_flags; the lanes are counted from the middle of the road.
// a lane with no entry, or with 0, gets what the junction works out: the inner lane takes the left turns, the outer one the right turns // a lane with no entry, or with 0, gets what the junction works out: the inner lane takes the left turns, the outer one the right turns
@@ -339,6 +353,10 @@ class junction_node : public scene::basic_node
std::string kerbmaterial{"none"}; std::string kerbmaterial{"none"};
float kerbheight{0.12f}; float kerbheight{0.12f};
glm::vec2 slope{1.f, 0.4f}; // width and drop of the bank which closes a shoulder glm::vec2 slope{1.f, 0.4f}; // width and drop of the bank which closes a shoulder
std::string bankmaterial{"none"}; // what a bank of a corner is covered with, other than one of a shoulder
// a junction of two roads, where a road changes its lanes: what's between the two directions where they're apart
road_node::median_type median{road_node::median_type::painted};
std::string medianmaterial{"none"}; // what an island is covered with
float velocity{30.f}; // speed limit on the way through float velocity{30.f}; // speed limit on the way through
float friction{0.85f}; float friction{0.85f};
float sounddistance{25.f}; float sounddistance{25.f};
@@ -413,6 +431,8 @@ class junction_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 through the junction from provided definition and registers it with the simulation // creates a path through the junction from provided definition and registers it with the simulation
TTrack *create_track(std::string const &Definition, std::size_t const Index); TTrack *create_track(std::string const &Definition, std::size_t const Index);
// geometry of a junction of two roads
std::vector<scene::shape_node> create_transition_shapes() const;
// sets up what the traffic of each lane leading in is told, once the ways through are made // sets up what the traffic of each lane leading in is told, once the ways through are made
void create_gates(); void create_gates();
// members // members