mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 05:09:20 +02:00
feat(roads): right of way and lane turns at junctions, road weights, kerbs, medians and forking roads, banks to the ground, roundabouts, pedestrian crossings, spawn sets by count; fix(editor): model skin export, placement on the ground under the orthophoto
This commit is contained in:
@@ -159,15 +159,21 @@ ImU32 const kSnapColor{IM_COL32(255, 60, 255, 255)};
|
||||
ImU32 const kCrossingColor{IM_COL32(255, 220, 60, 230)};
|
||||
ImU32 const kSpawnColor{IM_COL32(90, 230, 120, 230)};
|
||||
ImU32 const kDespawnColor{IM_COL32(255, 110, 110, 230)};
|
||||
ImU32 const kCrosswalkColor{IM_COL32(235, 235, 235, 230)};
|
||||
double const kBankReach{30.0}; // how far from the road the ground is looked for, for a bank to be led to it
|
||||
double const kBankStep{0.5};
|
||||
|
||||
roadpoint_node::kind_type point_kind(int const Index)
|
||||
{
|
||||
return Index == 1 ? roadpoint_node::kind_type::spawn : Index == 2 ? roadpoint_node::kind_type::despawn : roadpoint_node::kind_type::crossing;
|
||||
return Index == 1 ? roadpoint_node::kind_type::spawn : Index == 2 ? roadpoint_node::kind_type::despawn : Index == 3 ? roadpoint_node::kind_type::crosswalk : roadpoint_node::kind_type::crossing;
|
||||
}
|
||||
|
||||
char const *point_label(roadpoint_node::kind_type const Kind)
|
||||
{
|
||||
return Kind == roadpoint_node::kind_type::spawn ? "spawn point" : Kind == roadpoint_node::kind_type::despawn ? "removal point" : "level crossing";
|
||||
return Kind == roadpoint_node::kind_type::spawn ? "spawn point" :
|
||||
Kind == roadpoint_node::kind_type::despawn ? "removal point" :
|
||||
Kind == roadpoint_node::kind_type::crosswalk ? "pedestrian crossing" :
|
||||
"level crossing";
|
||||
}
|
||||
|
||||
// gathers new definitions for the pieces which meet or end at provided points.
|
||||
@@ -267,6 +273,12 @@ void editor_mode::update_road_tool()
|
||||
}
|
||||
if (tool.tool != 1)
|
||||
return;
|
||||
if (tool.ring)
|
||||
{
|
||||
// a click puts a roundabout around the cursor, there's nothing to lead from a point to another
|
||||
tool.chain = false;
|
||||
return;
|
||||
}
|
||||
if (tool.chain)
|
||||
{
|
||||
// what a click would make at the moment, to be drawn over the view
|
||||
@@ -304,6 +316,9 @@ void editor_mode::road_select(road_node *Road)
|
||||
copy_text(tool.surface, sizeof(tool.surface), tool.settings.surface);
|
||||
for (int side = 0; side < 2; ++side)
|
||||
copy_text(tool.sides[side], sizeof(tool.sides[side]), tool.settings.sides[side].material);
|
||||
copy_text(tool.kerbtext, sizeof(tool.kerbtext), tool.settings.kerbmaterial);
|
||||
copy_text(tool.mediantext, sizeof(tool.mediantext), tool.settings.median.material);
|
||||
copy_text(tool.banktext, sizeof(tool.banktext), tool.settings.bankmaterial);
|
||||
}
|
||||
|
||||
void editor_mode::junction_select(junction_node *Junction)
|
||||
@@ -320,6 +335,7 @@ void editor_mode::junction_select(junction_node *Junction)
|
||||
// the fields for the names of materials are shared with the roads
|
||||
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.kerbtext, sizeof(tool.kerbtext), Junction->definition().kerbmaterial);
|
||||
}
|
||||
|
||||
void editor_mode::roadpoint_select(roadpoint_node *Point)
|
||||
@@ -351,6 +367,21 @@ void editor_mode::roadpoint_aim(bool const Fresh)
|
||||
tool.hastarget = false;
|
||||
tool.targetnote.clear();
|
||||
tool.targetdirection = glm::dvec3{0.0};
|
||||
if (kind == roadpoint_node::kind_type::crosswalk)
|
||||
{
|
||||
auto const *road{editor_road::nearest(tool.mouse, kSideMargin)};
|
||||
if (road == nullptr)
|
||||
{
|
||||
tool.targetnote = "Point at a road";
|
||||
return;
|
||||
}
|
||||
// the stripes go across the road, whichever part of it the cursor is over
|
||||
auto const t{editor_road::nearest_parameter(road->definition(), tool.mouse)};
|
||||
tool.target.position = road->definition().point(t);
|
||||
tool.targetdirection = road->definition().tangent(t);
|
||||
tool.hastarget = true;
|
||||
return;
|
||||
}
|
||||
if (kind == roadpoint_node::kind_type::crossing)
|
||||
{
|
||||
auto const *road{editor_road::nearest(tool.mouse, kSideMargin)};
|
||||
@@ -418,7 +449,7 @@ void editor_mode::roadpoint_place()
|
||||
case roadpoint_node::kind_type::spawn:
|
||||
{
|
||||
tool.status = (state.vehicles.empty() ? std::string{"Spawn point made, with no vehicles: nothing appears there until it's given some (Select, click the point)."} :
|
||||
"Spawn point made, with " + std::to_string(state.vehicles.size() * state.copies) + " vehicles. They appear while the simulation runs.");
|
||||
"Spawn point made, with up to " + std::to_string(state.count) + " vehicles on the roads at a time. They appear while the simulation runs.");
|
||||
break;
|
||||
}
|
||||
case roadpoint_node::kind_type::despawn:
|
||||
@@ -426,6 +457,11 @@ void editor_mode::roadpoint_place()
|
||||
tool.status = "Removal point made, it takes vehicles from " + std::to_string(point->lanes().size()) + (point->lanes().size() == 1 ? " lane." : " lanes.");
|
||||
break;
|
||||
}
|
||||
case roadpoint_node::kind_type::crosswalk:
|
||||
{
|
||||
tool.status = "Pedestrian crossing painted.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (false == tool.targetnote.empty())
|
||||
tool.status += " " + tool.targetnote + ".";
|
||||
@@ -520,12 +556,13 @@ bool editor_mode::render_roadpoint_layout(roadpoint_node::state &State, roadpoin
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "The speed the vehicles appear with. From there on the drivers go by the speed limits of the lanes.");
|
||||
ImGui::SetNextItemWidth(120.0f);
|
||||
if (ImGui::InputInt("Copies of each vehicle", &State.copies, 1, 1, flags))
|
||||
if (ImGui::InputInt("Vehicles on the roads at a time", &State.count, 1, 5, flags))
|
||||
changed = true;
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "How many of each vehicle of the set are made. A vehicle can't be in two places at once,\n"
|
||||
"so this is how many of a kind can be on the road together. All of them are loaded along with the scenery,\n"
|
||||
"and a vehicle taken by a removal point goes back to wait for its next turn.");
|
||||
ImGui::SetTooltip("%s", "How many vehicles of this point can be on the scenery at once. That many are made along with the scenery,\n"
|
||||
"drawn from the listed ones in turn; one of those which wait is put on the road each time its turn comes,\n"
|
||||
"until all of them are out. A vehicle taken by a removal point goes back to wait for its next turn,\n"
|
||||
"and so does one which stood still for 15 seconds, other than at a closed level crossing, or went backwards.");
|
||||
// the set
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Vehicles of the set: %d", static_cast<int>(State.vehicles.size()));
|
||||
@@ -590,6 +627,14 @@ bool editor_mode::render_roadpoint_layout(roadpoint_node::state &State, roadpoin
|
||||
"The distance it starts with takes in a single lane.");
|
||||
break;
|
||||
}
|
||||
case roadpoint_node::kind_type::crosswalk:
|
||||
{
|
||||
if (number("Length along the road [m]", State.length, 0.5f, "%.1f"))
|
||||
changed = true;
|
||||
ImGui::TextDisabled("Stripes painted from one edge of the road to the other.");
|
||||
ImGui::TextDisabled("The drivers don't stop for it: there's nobody to cross.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (changed && Point != nullptr)
|
||||
State.normalize();
|
||||
@@ -661,6 +706,25 @@ void editor_mode::render_junction_layout(junction_node &Junction)
|
||||
changed = true;
|
||||
}
|
||||
// what the corners between the roads are lined with
|
||||
if (ImGui::Checkbox("Kerb around the corners", &state.kerbs))
|
||||
changed = true;
|
||||
if (state.kerbs)
|
||||
{
|
||||
ImGui::Indent();
|
||||
if (number("Kerb width [m]", state.kerbwidth, 0.05f, "%.2f"))
|
||||
{
|
||||
state.kerbwidth = std::clamp(state.kerbwidth, 0.05f, 2.0f);
|
||||
changed = true;
|
||||
}
|
||||
if (render_road_material("Kerb material", tool.kerbtext, sizeof(tool.kerbtext), state.kerbmaterial))
|
||||
changed = true;
|
||||
if (state.side.type != road_node::side_type::sidewalk && number("Kerb height [m]", state.kerbheight, 0.01f, "%.2f"))
|
||||
{
|
||||
state.kerbheight = std::clamp(state.kerbheight, 0.0f, 1.0f);
|
||||
changed = true;
|
||||
}
|
||||
ImGui::Unindent();
|
||||
}
|
||||
char const *sidetypes[]{"none", "shoulder", "sidewalk"};
|
||||
int type{state.side.type == road_node::side_type::shoulder ? 1 : state.side.type == road_node::side_type::sidewalk ? 2 : 0};
|
||||
ImGui::SetNextItemWidth(120.0f);
|
||||
@@ -710,7 +774,9 @@ void editor_mode::render_junction_layout(junction_node &Junction)
|
||||
// the roads
|
||||
if (ImGui::CollapsingHeader("Roads of the junction", ImGuiTreeNodeFlags_DefaultOpen))
|
||||
{
|
||||
ImGui::TextDisabled("The lanes are taken from the roads: change them on the road, the junction follows");
|
||||
ImGui::TextDisabled("The lanes are taken from the roads: change them on the road, the junction follows.");
|
||||
ImGui::TextDisabled("The roads are numbered in the view. Lanes are counted from the middle of the road;");
|
||||
ImGui::TextDisabled("automatic: the inner lane turns left, the outer one right, all go straight.");
|
||||
for (std::size_t arm = 0; arm < state.arms.size(); ++arm)
|
||||
{
|
||||
ImGui::PushID(static_cast<int>(arm));
|
||||
@@ -719,6 +785,36 @@ void editor_mode::render_junction_layout(junction_node &Junction)
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Checkbox("Stop line", &data.stopline))
|
||||
changed = true;
|
||||
if (data.incoming > 0 && state.arms.size() > 2)
|
||||
{
|
||||
ImGui::Indent();
|
||||
// the entries are in the order of the stored values
|
||||
char const *priorities[]{"no sign: gives way to the right", "has the right of way", "gives way", "stops, then gives way"};
|
||||
int priority{static_cast<int>(data.priority)};
|
||||
ImGui::SetNextItemWidth(220.0f);
|
||||
if (ImGui::Combo("##priority", &priority, priorities, 4))
|
||||
{
|
||||
data.priority = static_cast<junction_node::right_of_way>(std::clamp(priority, 0, 3));
|
||||
changed = true;
|
||||
}
|
||||
// ways each lane can be left by
|
||||
char const *ways[]{"automatic", "left", "straight", "left, straight", "right", "left, right", "straight, right", "any"};
|
||||
for (int lane = 0; lane < data.incoming; ++lane)
|
||||
{
|
||||
ImGui::PushID(100 + lane);
|
||||
int turns{static_cast<std::size_t>(lane) < data.turns.size() ? data.turns[lane] & 7 : 0};
|
||||
auto const label{"lane " + std::to_string(lane + 1) + " goes"};
|
||||
ImGui::SetNextItemWidth(140.0f);
|
||||
if (ImGui::Combo(label.c_str(), &turns, ways, 8))
|
||||
{
|
||||
data.turns.resize(std::max(data.turns.size(), static_cast<std::size_t>(lane) + 1), 0);
|
||||
data.turns[lane] = turns;
|
||||
changed = true;
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::Unindent();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
@@ -771,14 +867,15 @@ editor_mode::road_plan editor_mode::road_preview(bool const Fresh)
|
||||
plan.snap = {};
|
||||
}
|
||||
}
|
||||
if (plan.snap.junction != nullptr)
|
||||
if (plan.snap.junction != nullptr || plan.snap.half != 0)
|
||||
{
|
||||
// the junction has its ways through made for a set of lanes at each arm
|
||||
// the junction has its ways through made for a set of lanes at each arm; a single roadway of a road takes the lanes it has
|
||||
if (false == evenlanes || arriving != plan.snap.backward || leaving != plan.snap.forward || std::abs(lanewidth - plan.snap.width) > 0.01f)
|
||||
{
|
||||
char width[32];
|
||||
std::snprintf(width, sizeof(width), "%.2f", plan.snap.width);
|
||||
plan.error = "This arm of the junction takes a road with " + std::to_string(plan.snap.backward) + " lane(s) leading into it and " + std::to_string(plan.snap.forward) + " out of it, " + width + " m wide each";
|
||||
plan.error = std::string{plan.snap.junction != nullptr ? "This arm of the junction" : "This roadway"} + " takes a road with ";
|
||||
plan.error += std::to_string(plan.snap.backward) + " lane(s) leading to it and " + std::to_string(plan.snap.forward) + " away from it, " + width + " m wide each";
|
||||
return plan;
|
||||
}
|
||||
}
|
||||
@@ -992,6 +1089,11 @@ void editor_mode::road_click()
|
||||
tool.status = reason;
|
||||
return;
|
||||
}
|
||||
if (tool.ring)
|
||||
{
|
||||
road_roundabout(ground);
|
||||
return;
|
||||
}
|
||||
if (false == tool.chain)
|
||||
{
|
||||
// first click sets where the road starts
|
||||
@@ -1015,27 +1117,40 @@ void editor_mode::road_click()
|
||||
tool.hasdirection = true;
|
||||
tool.grade = end.outwards.y / glm::length(outwards);
|
||||
tool.hasgrade = true;
|
||||
if (end.road != nullptr)
|
||||
if (end.road != nullptr && end.half == 0)
|
||||
{
|
||||
// carried on from its start the road keeps the direction of its axis, so the lanes stay what they were
|
||||
tool.reversed = (false == end.atend);
|
||||
tool.settings = end.road->definition();
|
||||
// and it stays as wide as it is at that end
|
||||
// and it stays as wide as it is at that end, with its directions as far apart
|
||||
auto const scale{tool.settings.taper[end.atend ? 1 : 0]};
|
||||
tool.settings.taper = {scale, scale};
|
||||
auto const gap{tool.settings.median.width[end.atend ? 1 : 0]};
|
||||
tool.settings.median.width = {gap, gap};
|
||||
// the banks were made for the ground where they are
|
||||
tool.settings.banks = {};
|
||||
tool.status = "Carrying on \"" + end.road->name() + "\" with its layout";
|
||||
}
|
||||
else
|
||||
{
|
||||
tool.reversed = false;
|
||||
if (end.road != nullptr)
|
||||
{
|
||||
// one of the roadways of a road which keeps its directions apart is carried on as a road of its own, which looks like the road it leaves
|
||||
tool.settings = end.road->definition();
|
||||
tool.settings.sides[end.half > 0 ? 0 : 1] = road_node::side_data{};
|
||||
tool.settings.banks = {};
|
||||
}
|
||||
tool.settings.forward = end.forward;
|
||||
tool.settings.backward = end.backward;
|
||||
tool.settings.lanewidth = end.width;
|
||||
tool.settings.taper = {1.f, 1.f};
|
||||
tool.settings.median.width = {0.f, 0.f};
|
||||
tool.settings.lanes.clear();
|
||||
tool.settings.changes.clear();
|
||||
tool.settings.normalize();
|
||||
tool.status = "Leading a road out of \"" + end.junction->name() + "\", with the lanes the junction has there";
|
||||
tool.status = (end.junction != nullptr ? "Leading a road out of \"" + end.junction->name() + "\", with the lanes the junction has there" :
|
||||
"Carrying on one of the roadways of \"" + end.road->name() + "\" as a one-way road");
|
||||
}
|
||||
road_select(nullptr);
|
||||
}
|
||||
@@ -1178,6 +1293,12 @@ void editor_mode::road_apply()
|
||||
state.axis = road->definition().axis;
|
||||
// how wide the road is at its points is set at these points, for each piece on its own
|
||||
state.taper = road->definition().taper;
|
||||
if (road != tool.selected)
|
||||
{
|
||||
// and so are the banks, and how far apart the two directions are led
|
||||
state.median.width = road->definition().median.width;
|
||||
state.banks = road->definition().banks;
|
||||
}
|
||||
if (road != tool.selected && road->definition().changes.size() == state.changes.size())
|
||||
{
|
||||
// where the lanes can be changed is set for each piece on its own
|
||||
@@ -1257,6 +1378,122 @@ bool editor_mode::road_delete()
|
||||
return true;
|
||||
}
|
||||
|
||||
// puts a roundabout around specified point
|
||||
void editor_mode::road_roundabout(glm::dvec3 const &Ground)
|
||||
{
|
||||
auto &tool{m_roadtool};
|
||||
std::string reason;
|
||||
if (false == editor_road::available(&reason))
|
||||
{
|
||||
tool.status = reason;
|
||||
return;
|
||||
}
|
||||
auto const states{editor_road::roundabout(Ground + glm::dvec3{0.0, tool.offset, 0.0}, tool.ringradius, tool.settings, tool.island)};
|
||||
if (states.empty())
|
||||
{
|
||||
tool.status = "The roundabout is too small for its lanes: give it a larger radius";
|
||||
return;
|
||||
}
|
||||
for (auto const &state : states)
|
||||
{
|
||||
for (auto const t : {0.0, 0.5})
|
||||
{
|
||||
if (editor_road::nearest(state.point(t), 0.0) != nullptr || editor_road::nearest_junction(state.point(t)) != nullptr)
|
||||
{
|
||||
tool.status = "There's a road in the way. Make the roundabout next to it, then lead the road into its side";
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto const created{editor_road::create(states)};
|
||||
if (created.empty())
|
||||
{
|
||||
tool.status = "The roundabout wasn't created";
|
||||
return;
|
||||
}
|
||||
editor_road::record record;
|
||||
record.roads_created = created;
|
||||
push_road_snapshot(std::move(record));
|
||||
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.";
|
||||
}
|
||||
|
||||
// leads the banks of the selected piece, or of the whole road, to the ground beside it
|
||||
void editor_mode::road_bank_to_ground()
|
||||
{
|
||||
auto &tool{m_roadtool};
|
||||
if (tool.selected == nullptr)
|
||||
return;
|
||||
auto const pieces{tool.whole ? editor_road::chain(*tool.selected) : std::vector<road_node *>{tool.selected}};
|
||||
std::string reason;
|
||||
for (auto const *road : pieces)
|
||||
{
|
||||
if (false == editor_road::can_edit(*road, &reason))
|
||||
{
|
||||
tool.status = "\"" + road->name() + "\" can't be changed: " + reason;
|
||||
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;
|
||||
for (auto *road : pieces)
|
||||
{
|
||||
auto state{road->definition()};
|
||||
for (int side = 0; side < 2; ++side)
|
||||
{
|
||||
auto const &data{state.sides[side]};
|
||||
auto const sided{data.type != road_node::side_type::none && data.width > 0.f};
|
||||
for (int end = 0; end < 2; ++end)
|
||||
{
|
||||
auto const t{static_cast<double>(end)};
|
||||
// where the bank starts: past the roadway, the kerb and whatever lies beside it
|
||||
auto const tangent{state.tangent(t)};
|
||||
glm::dvec3 across{tangent.z, 0.0, -tangent.x};
|
||||
if (glm::length(across) < 1e-9)
|
||||
continue;
|
||||
across = glm::normalize(across) * (side == 0 ? 1.0 : -1.0);
|
||||
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)
|
||||
edge.y += state.kerbheight;
|
||||
// the ground further and further from the road, without the roads themselves
|
||||
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].width[end] = static_cast<float>(width);
|
||||
state.banks[side].drop[end] = static_cast<float>(drop);
|
||||
}
|
||||
}
|
||||
changes.emplace_back(road, state);
|
||||
}
|
||||
editor_road::record record;
|
||||
for (auto const &change : changes)
|
||||
record.roads.emplace_back(change.first, change.first->definition());
|
||||
editor_road::apply(changes);
|
||||
push_road_snapshot(std::move(record));
|
||||
road_select(tool.selected);
|
||||
tool.status = "Banks of " + std::to_string(changes.size()) + (changes.size() == 1 ? " piece" : " pieces") + " led to the ground.";
|
||||
auto const &layout{tool.selected->definition()};
|
||||
if ((layout.sides[0].type != road_node::side_type::shoulder || layout.sides[1].type != road_node::side_type::shoulder) && (layout.bankmaterial.empty() || layout.bankmaterial == "none"))
|
||||
tool.status += " Pick a bank material to have them drawn where the road has no shoulder.";
|
||||
}
|
||||
|
||||
// takes the selected points out of their roads
|
||||
bool editor_mode::road_points_delete()
|
||||
{
|
||||
@@ -1628,7 +1865,28 @@ bool editor_mode::render_road_layout(road_node::state &State)
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
if (State.sides[0].type == road_node::side_type::sidewalk || State.sides[1].type == road_node::side_type::sidewalk)
|
||||
// kerbs
|
||||
if (ImGui::Checkbox("Kerb on the left", &State.kerbs[0]))
|
||||
changed = true;
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Checkbox("on the right", &State.kerbs[1]))
|
||||
changed = true;
|
||||
auto const island{State.divided() && State.median.type == road_node::median_type::island};
|
||||
if (State.kerbs[0] || State.kerbs[1] || island)
|
||||
{
|
||||
ImGui::Indent();
|
||||
if (number("Kerb width [m]", State.kerbwidth, 0.05f, "%.2f"))
|
||||
{
|
||||
State.kerbwidth = std::clamp(State.kerbwidth, 0.05f, 2.0f);
|
||||
changed = true;
|
||||
}
|
||||
if (material("Kerb material", tool.kerbtext, sizeof(tool.kerbtext), State.kerbmaterial))
|
||||
changed = true;
|
||||
if ((State.kerbs[0] || State.kerbs[1]) && (State.kerbmaterial.empty() || State.kerbmaterial == "none"))
|
||||
ImGui::TextDisabled("Pick a material to have the kerbs drawn");
|
||||
ImGui::Unindent();
|
||||
}
|
||||
if (State.kerbs[0] || State.kerbs[1] || island || State.sides[0].type == road_node::side_type::sidewalk || State.sides[1].type == road_node::side_type::sidewalk)
|
||||
{
|
||||
if (number("Kerb height [m]", State.kerbheight, 0.01f, "%.2f"))
|
||||
{
|
||||
@@ -1643,6 +1901,8 @@ bool editor_mode::render_road_layout(road_node::state &State)
|
||||
State.slope.x = std::clamp(State.slope.x, 0.0f, 20.0f);
|
||||
changed = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "The bank every shoulder of the road is closed with, unless the side has a bank of its own set below.");
|
||||
if (number("Bank drop [m]", State.slope.y, 0.1f, "%.2f"))
|
||||
{
|
||||
State.slope.y = std::clamp(State.slope.y, 0.0f, 20.0f);
|
||||
@@ -1650,6 +1910,105 @@ bool editor_mode::render_road_layout(road_node::state &State)
|
||||
}
|
||||
}
|
||||
|
||||
// banks leading 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("Set for each piece on its own, at its start and at its end.");
|
||||
char const *banknames[]{"Bank on the left", "Bank on the right"};
|
||||
for (int side = 0; side < 2; ++side)
|
||||
{
|
||||
ImGui::PushID(100 + side);
|
||||
auto &bank{State.banks[side]};
|
||||
if (ImGui::Checkbox(banknames[side], &bank.set))
|
||||
{
|
||||
if (bank.set && bank.width[0] <= 0.f && bank.width[1] <= 0.f)
|
||||
{
|
||||
bank.width = {State.slope.x, State.slope.x};
|
||||
bank.drop = {State.slope.y, State.slope.y};
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
if (bank.set)
|
||||
{
|
||||
ImGui::Indent();
|
||||
if (number("Width at the start [m]", bank.width[0], 0.5f, "%.2f"))
|
||||
changed = true;
|
||||
if (number("Drop at the start [m]", bank.drop[0], 0.25f, "%.2f"))
|
||||
changed = true;
|
||||
if (number("Width at the end [m]", bank.width[1], 0.5f, "%.2f"))
|
||||
changed = true;
|
||||
if (number("Drop at the end [m]", bank.drop[1], 0.25f, "%.2f"))
|
||||
changed = true;
|
||||
ImGui::Unindent();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
if (material("Bank material", tool.banktext, sizeof(tool.banktext), State.bankmaterial))
|
||||
changed = true;
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "What a bank is covered with next to a sidewalk, a kerb or the bare edge of the road.\nA bank of a shoulder is covered with the material of the shoulder.");
|
||||
if (tool.selected != nullptr)
|
||||
{
|
||||
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"))
|
||||
{
|
||||
road_bank_to_ground();
|
||||
// the layout shown was replaced with what the road is like now
|
||||
return false;
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "Looks for the ground on both sides of the road, at the start and at the end of each piece,\n"
|
||||
"and sets the banks to reach it with the slope given above: down where the road is over the ground, up where it's cut into it.\n"
|
||||
"Done for the selected piece, or for every piece of the road if the changes go to the whole road.");
|
||||
}
|
||||
}
|
||||
|
||||
// the two directions kept apart
|
||||
if (State.forward > 0 && State.backward > 0 && ImGui::CollapsingHeader("Between the directions"))
|
||||
{
|
||||
char const *mediantypes[]{"nothing, the ground shows", "surface closed to the traffic", "island"};
|
||||
int type{State.median.type == road_node::median_type::gap ? 0 : State.median.type == road_node::median_type::island ? 2 : 1};
|
||||
ImGui::SetNextItemWidth(220.0f);
|
||||
if (ImGui::Combo("##median", &type, mediantypes, 3))
|
||||
{
|
||||
State.median.type = (type == 0 ? road_node::median_type::gap : type == 2 ? road_node::median_type::island : road_node::median_type::painted);
|
||||
changed = true;
|
||||
}
|
||||
if (number("Apart at the start [m]", State.median.width[0], 0.5f, "%.2f"))
|
||||
changed = true;
|
||||
if (number("Apart at the end [m]", State.median.width[1], 0.5f, "%.2f"))
|
||||
changed = true;
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "How far apart the two directions are led at each end of the piece; 0 at one end and more at the other makes them part along it.\n"
|
||||
"Set for each piece on its own. The pieces next to it follow it where they meet, and so do the junctions.\n"
|
||||
"A road with its directions apart can be carried on as two one-way roads: start the Build tool\n"
|
||||
"on the middle of one of its roadways, at an end of the road with nothing attached.");
|
||||
if (State.median.type == road_node::median_type::island)
|
||||
{
|
||||
if (material("Island material", tool.mediantext, sizeof(tool.mediantext), State.median.material))
|
||||
changed = true;
|
||||
ImGui::TextDisabled("The island is as high as the kerbs; with a kerb material set it gets kerbs of its own.");
|
||||
}
|
||||
}
|
||||
|
||||
// traffic
|
||||
if (number("Traffic share, 100: like any other", State.weight, 10.0f, "%.0f"))
|
||||
{
|
||||
State.weight = std::clamp(State.weight, 0.0f, 10000.0f);
|
||||
changed = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "How willing the drivers are to take this road when they leave a junction, next to the other roads they can take there.\n"
|
||||
"With 100 for the main road and 20 for a dirt road leaving it, one vehicle in six turns into the dirt road.\n"
|
||||
"0: nobody takes it, unless there's no other way.");
|
||||
if (ImGui::Checkbox("Part of a roundabout", &State.roundabout))
|
||||
changed = true;
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "A junction made on this road gives the right of way to the traffic going along it,\nand tells the traffic of the road joining it to give way.");
|
||||
|
||||
// single lanes, and the rules for changing between them
|
||||
if (ImGui::CollapsingHeader("Lanes and lane changes", ImGuiTreeNodeFlags_DefaultOpen))
|
||||
{
|
||||
@@ -1737,10 +2096,27 @@ void editor_mode::render_road_window()
|
||||
};
|
||||
if (tool.tool == 1)
|
||||
{
|
||||
ImGui::TextDisabled("LMB: start, then each next point. Ctrl: straight ahead. Esc: finish");
|
||||
ImGui::TextDisabled("Start or end on a loose end of a road to join it.");
|
||||
ImGui::TextDisabled("Start or end on the side of a road to make a junction there,");
|
||||
ImGui::TextDisabled("or on a junction to give it another road.");
|
||||
if (ImGui::Checkbox("Roundabout", &tool.ring))
|
||||
road_cancel();
|
||||
if (tool.ring)
|
||||
{
|
||||
ImGui::TextDisabled("LMB: put a roundabout around the cursor. It's a one-way road going around,");
|
||||
ImGui::TextDisabled("with the lanes set below as the lanes along the axis.");
|
||||
ImGui::TextDisabled("Then untick this and lead the roads out of its side, or into it.");
|
||||
ImGui::SetNextItemWidth(120.0f);
|
||||
if (ImGui::InputFloat("Radius of the middle of its road [m]", &tool.ringradius, 1.0f, 5.0f, "%.1f"))
|
||||
tool.ringradius = std::clamp(tool.ringradius, 8.0f, 200.0f);
|
||||
render_road_material("Island in the middle", tool.islandtext, sizeof(tool.islandtext), tool.island);
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "What the middle is covered with; it's raised on a kerb as high as the kerbs of the road.\nnone: the middle is left as it is.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::TextDisabled("LMB: start, then each next point. Ctrl: straight ahead. Esc: finish");
|
||||
ImGui::TextDisabled("Start or end on a loose end of a road to join it.");
|
||||
ImGui::TextDisabled("Start or end on the side of a road to make a junction there,");
|
||||
ImGui::TextDisabled("or on a junction to give it another road.");
|
||||
}
|
||||
if (tool.chain)
|
||||
{
|
||||
if (ImGui::Button("Finish (Esc)"))
|
||||
@@ -1769,7 +2145,8 @@ void editor_mode::render_road_window()
|
||||
ImGui::RadioButton("Spawn point", &tool.placekind, 1);
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("Removal point", &tool.placekind, 2);
|
||||
tool.placekind = std::clamp(tool.placekind, 0, 2);
|
||||
ImGui::RadioButton("Pedestrian crossing", &tool.placekind, 3);
|
||||
tool.placekind = std::clamp(tool.placekind, 0, 3);
|
||||
switch (tool.placekind)
|
||||
{
|
||||
case 0:
|
||||
@@ -1780,6 +2157,9 @@ void editor_mode::render_road_window()
|
||||
ImGui::TextDisabled("LMB on a lane. The vehicles of the set appear there one by one");
|
||||
ImGui::TextDisabled("and drive on the way the lane goes.");
|
||||
break;
|
||||
case 3:
|
||||
ImGui::TextDisabled("LMB on a road: stripes are painted across it there.");
|
||||
break;
|
||||
default:
|
||||
ImGui::TextDisabled("LMB on a lane. The vehicles which get there are taken off the road;");
|
||||
ImGui::TextDisabled("put one where the road leaves the scenery.");
|
||||
@@ -1986,7 +2366,7 @@ void editor_mode::draw_road_overlay() const
|
||||
glm::dvec3 across{tangent.z, 0.0, -tangent.x};
|
||||
if (glm::length(across) > 1e-6)
|
||||
across = glm::normalize(across);
|
||||
auto const halfwidth{0.5 * State.width() * State.scale(t)};
|
||||
auto const halfwidth{0.5 * State.span(t)};
|
||||
glm::dvec3 const edges[2]{position + across * halfwidth, position - across * halfwidth};
|
||||
if (i > 0)
|
||||
{
|
||||
@@ -2074,6 +2454,13 @@ void editor_mode::draw_road_overlay() const
|
||||
drawlabel(State.position, Color, "removal");
|
||||
break;
|
||||
}
|
||||
case roadpoint_node::kind_type::crosswalk:
|
||||
{
|
||||
drawpoint(State.position, Color, 11.0f, false);
|
||||
if (labelled)
|
||||
drawlabel(State.position, Color, "pedestrians");
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
for (auto const *point : simulation::Roadpoints.sequence())
|
||||
@@ -2082,7 +2469,11 @@ void editor_mode::draw_road_overlay() const
|
||||
continue;
|
||||
auto const kind{point->definition().kind};
|
||||
drawmarker(point->definition(), point,
|
||||
point == tool.marker ? kSelectedColor : kind == roadpoint_node::kind_type::spawn ? kSpawnColor : kind == roadpoint_node::kind_type::despawn ? kDespawnColor : kCrossingColor);
|
||||
point == tool.marker ? kSelectedColor :
|
||||
kind == roadpoint_node::kind_type::spawn ? kSpawnColor :
|
||||
kind == roadpoint_node::kind_type::despawn ? kDespawnColor :
|
||||
kind == roadpoint_node::kind_type::crosswalk ? kCrosswalkColor :
|
||||
kCrossingColor);
|
||||
if (point == tool.marker)
|
||||
drawpoint(point->definition().position, kSelectedColor, 15.0f, false);
|
||||
}
|
||||
@@ -2095,10 +2486,29 @@ void editor_mode::draw_road_overlay() const
|
||||
drawpoint(state.axis.points[segment_data::point::end], kSelectedColor, 6.0f, true);
|
||||
}
|
||||
if (tool.junction != nullptr && false == tool.junction->m_editorremoved)
|
||||
{
|
||||
drawjunction(tool.junction->definition(), kSelectedColor);
|
||||
// the roads are numbered the way the window lists them
|
||||
auto const &arms{tool.junction->definition().arms};
|
||||
for (std::size_t arm = 0; arm < arms.size(); ++arm)
|
||||
drawlabel(arms[arm].position, kSelectedColor, std::to_string(arm + 1).c_str());
|
||||
}
|
||||
|
||||
std::string hint;
|
||||
if (tool.tool == 1)
|
||||
if (tool.tool == 1 && tool.ring)
|
||||
{
|
||||
// the road going around, as wide as its lanes
|
||||
auto layout{tool.settings};
|
||||
layout.backward = 0;
|
||||
layout.forward = std::max(1, layout.forward);
|
||||
layout.lanes.clear();
|
||||
layout.normalize();
|
||||
auto const centre{tool.mouse + glm::dvec3{0.0, tool.offset, 0.0}};
|
||||
drawring(centre, tool.ringradius + 0.5 * layout.width(), kPreviewColor);
|
||||
drawring(centre, std::max(0.0, tool.ringradius - 0.5 * layout.width()), kPreviewColor);
|
||||
hint = "LMB: put a roundabout here";
|
||||
}
|
||||
else if (tool.tool == 1)
|
||||
{
|
||||
if (tool.chain)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user