mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 03:59:19 +02:00
feat(roads): junctions, height above the ground and rounded profile following the terrain
This commit is contained in:
@@ -20,6 +20,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "imgui/imgui.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace
|
||||
@@ -108,10 +109,16 @@ std::string lower_case(std::string Text)
|
||||
double const kSnapRadius{4.0}; // an end of a road this close to the cursor is what the click is taken to mean
|
||||
double const kReach{3000.0}; // clicks landing further than this are taken for misses
|
||||
double const kLaneRange{600.0}; // lanes are drawn for the roads this close to the camera
|
||||
double const kLongestStretch{2000.0}; // a single click doesn't lead a road further than this
|
||||
// lengths of the pieces a road following the ground is tried with, from the longest; the first one to fit the ground is taken
|
||||
double const kProfileSteps[]{50.0, 25.0, 12.5};
|
||||
double const kSunkTolerance{0.05}; // how far under the height it's built at the road can get between the points it's led through
|
||||
double const kRaisedTolerance{0.5}; // and how far over it
|
||||
|
||||
ImU32 const kForwardColor{IM_COL32(70, 230, 110, 230)};
|
||||
ImU32 const kBackwardColor{IM_COL32(255, 170, 50, 230)};
|
||||
ImU32 const kTurnColor{IM_COL32(210, 210, 210, 150)};
|
||||
ImU32 const kWayColor{IM_COL32(120, 200, 255, 170)};
|
||||
ImU32 const kSelectedColor{IM_COL32(40, 220, 255, 230)};
|
||||
ImU32 const kPreviewColor{IM_COL32(255, 210, 60, 255)};
|
||||
ImU32 const kRefusedColor{IM_COL32(240, 60, 60, 255)};
|
||||
@@ -134,12 +141,29 @@ void editor_mode::update_road_tool()
|
||||
auto &tool{m_roadtool};
|
||||
if (tool.selected != nullptr && tool.selected->m_editorremoved)
|
||||
tool.selected = nullptr;
|
||||
if (tool.junction != nullptr && tool.junction->m_editorremoved)
|
||||
tool.junction = nullptr;
|
||||
tool.preview.clear();
|
||||
tool.hasjunction = false;
|
||||
tool.previewsnap = {};
|
||||
tool.previewerror.clear();
|
||||
if (false == tool.window)
|
||||
{
|
||||
tool.chain = false;
|
||||
return;
|
||||
}
|
||||
tool.mouse = Global.pCamera.Pos + GfxRenderer->Mouse_Position();
|
||||
glm::dvec3 const camera{Global.pCamera.Pos};
|
||||
tool.mouse = camera + GfxRenderer->Mouse_Position();
|
||||
if (glm::distance(tool.mouse, camera) > kReach)
|
||||
{
|
||||
tool.previewerror = "The cursor isn't over anything near enough";
|
||||
return;
|
||||
}
|
||||
// what a click would make at the moment, to be drawn over the view
|
||||
if (tool.tool == 1 && tool.chain)
|
||||
tool.preview = road_preview(tool.previewsnap, tool.previewerror, false);
|
||||
else if (tool.tool == 2)
|
||||
tool.hasjunction = junction_preview(tool.previewjunction, tool.previewsnap, tool.previewerror);
|
||||
}
|
||||
|
||||
void editor_mode::road_select(road_node *Road)
|
||||
@@ -148,6 +172,7 @@ void editor_mode::road_select(road_node *Road)
|
||||
tool.selected = Road;
|
||||
if (Road != nullptr)
|
||||
{
|
||||
tool.junction = nullptr;
|
||||
tool.settings = Road->definition();
|
||||
std::string reason;
|
||||
tool.status = "Selected: " + (Road->name().empty() ? std::string{"(unnamed road)"} : Road->name());
|
||||
@@ -159,20 +184,33 @@ void editor_mode::road_select(road_node *Road)
|
||||
copy_text(tool.sides[side], sizeof(tool.sides[side]), tool.settings.sides[side].material);
|
||||
}
|
||||
|
||||
void editor_mode::junction_select(junction_node *Junction)
|
||||
{
|
||||
auto &tool{m_roadtool};
|
||||
tool.junction = Junction;
|
||||
if (Junction == nullptr)
|
||||
return;
|
||||
tool.selected = nullptr;
|
||||
std::string reason;
|
||||
tool.status = "Selected: " + Junction->name();
|
||||
if (false == editor_road::can_edit(*Junction, &reason))
|
||||
tool.status += ". It can't be removed: " + reason;
|
||||
}
|
||||
|
||||
void editor_mode::road_cancel()
|
||||
{
|
||||
m_roadtool.chain = false;
|
||||
}
|
||||
|
||||
std::vector<segment_data> editor_mode::road_preview(editor_road::loose_end &Snap, std::string &Error) const
|
||||
std::vector<segment_data> editor_mode::road_preview(editor_road::loose_end &Snap, std::string &Error, bool const Fresh)
|
||||
{
|
||||
auto const &tool{m_roadtool};
|
||||
Snap = editor_road::find_end(tool.mouse, kSnapRadius);
|
||||
// the end the road is being drawn from isn't somewhere to lead it to
|
||||
if (Snap.road != nullptr && glm::distance(Snap.position, tool.point) < 0.5)
|
||||
if (Snap.valid() && glm::distance(Snap.position, tool.point) < 0.5)
|
||||
Snap = {};
|
||||
glm::dvec2 enddirection{0.0, 1.0};
|
||||
if (Snap.road != nullptr)
|
||||
if (Snap.valid())
|
||||
{
|
||||
glm::dvec2 const outwards{Snap.outwards.x, Snap.outwards.z};
|
||||
if (glm::length(outwards) > 1e-6)
|
||||
@@ -180,7 +218,204 @@ std::vector<segment_data> editor_mode::road_preview(editor_road::loose_end &Snap
|
||||
else
|
||||
Snap = {};
|
||||
}
|
||||
return editor_road::plan(tool.point, tool.hasdirection ? &tool.direction : nullptr, Snap.road != nullptr ? Snap.position : tool.mouse, Snap.road != nullptr ? &enddirection : nullptr, Global.ctrlState, Error);
|
||||
// in the open the road is put over what's under the cursor
|
||||
auto const target{Snap.valid() ? Snap.position : tool.mouse + glm::dvec3{0.0, tool.offset, 0.0}};
|
||||
if (glm::distance(tool.point, target) > kLongestStretch)
|
||||
{
|
||||
Error = "Too far from the last point";
|
||||
return {};
|
||||
}
|
||||
if (Snap.junction != nullptr)
|
||||
{
|
||||
// the junction has its ways through made for a set of lanes at each arm
|
||||
auto const arriving{tool.reversed ? tool.settings.backward : tool.settings.forward};
|
||||
auto const leaving{tool.reversed ? tool.settings.forward : tool.settings.backward};
|
||||
if (arriving != Snap.backward || leaving != Snap.forward || std::abs(tool.settings.lanewidth - Snap.width) > 0.01f)
|
||||
{
|
||||
char width[32];
|
||||
std::snprintf(width, sizeof(width), "%.2f", Snap.width);
|
||||
Error = "This arm of the junction takes a road with " + std::to_string(Snap.backward) + " lane(s) leading into it and " + std::to_string(Snap.forward) + " out of it, " + width + " m wide each";
|
||||
return {};
|
||||
}
|
||||
}
|
||||
auto pieces{editor_road::plan(tool.point, tool.hasdirection ? &tool.direction : nullptr, target, Snap.valid() ? &enddirection : nullptr, Global.ctrlState, Error)};
|
||||
road_profile(pieces, Snap, Fresh);
|
||||
return pieces;
|
||||
}
|
||||
|
||||
// gives the pieces laid out by the planner their heights: over the ground if the road is to follow it, with the slopes
|
||||
// of the pieces matched where they meet, and with what the road is attached to
|
||||
void editor_mode::road_profile(std::vector<segment_data> &Pieces, editor_road::loose_end const &Snap, bool const Fresh)
|
||||
{
|
||||
auto const &tool{m_roadtool};
|
||||
if (Pieces.empty())
|
||||
return;
|
||||
// an end of a road or an arm of a junction is reached with the slope it has
|
||||
double endgrade{0.0};
|
||||
if (Snap.valid())
|
||||
{
|
||||
auto const run{glm::length(glm::dvec2{Snap.outwards.x, Snap.outwards.z})};
|
||||
endgrade = (run > 1e-6 ? -Snap.outwards.y / run : 0.0);
|
||||
}
|
||||
auto const *firstgrade{tool.hasgrade ? &tool.grade : nullptr};
|
||||
auto const *lastgrade{Snap.valid() ? &endgrade : nullptr};
|
||||
if (false == tool.follow)
|
||||
{
|
||||
// straight from a point to the next one, the way a ramp or a viaduct goes
|
||||
std::vector<double> heights;
|
||||
for (auto const &piece : Pieces)
|
||||
heights.emplace_back(piece.points[segment_data::point::start].y);
|
||||
heights.emplace_back(Pieces.back().points[segment_data::point::end].y);
|
||||
editor_road::profile(Pieces, heights, firstgrade, lastgrade);
|
||||
return;
|
||||
}
|
||||
auto const planned{Pieces};
|
||||
bool fresh{Fresh};
|
||||
for (auto const step : kProfileSteps)
|
||||
{
|
||||
Pieces = editor_road::refine(planned, step);
|
||||
auto const count{Pieces.size()};
|
||||
// the ground is looked up where the pieces meet, and in the middle of each to see how the result fits it
|
||||
std::vector<glm::dvec3> points;
|
||||
points.reserve(2 * count + 1);
|
||||
for (auto const &piece : Pieces)
|
||||
points.emplace_back(piece.points[segment_data::point::start]);
|
||||
points.emplace_back(Pieces.back().points[segment_data::point::end]);
|
||||
for (auto const &piece : Pieces)
|
||||
points.emplace_back(path_point(piece, 0.5));
|
||||
// the planner leads the road between its ends, which are over the ground already
|
||||
for (auto &point : points)
|
||||
point.y -= tool.offset;
|
||||
auto ground{ground_heights(points, fresh)};
|
||||
fresh = false;
|
||||
for (auto &height : ground)
|
||||
height += tool.offset;
|
||||
std::vector<double> heights(ground.begin(), ground.begin() + count + 1);
|
||||
// the ends are where they were put
|
||||
heights.front() = Pieces.front().points[segment_data::point::start].y;
|
||||
heights.back() = Pieces.back().points[segment_data::point::end].y;
|
||||
editor_road::profile(Pieces, heights, firstgrade, lastgrade);
|
||||
// shorter pieces are called for if the ground shows through the road, or the road hangs over it.
|
||||
// the pieces attached to something are let off, as they have to get to its height one way or another
|
||||
bool fits{true};
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
if ((i == 0 && firstgrade != nullptr) || (i + 1 == count && lastgrade != nullptr))
|
||||
continue;
|
||||
auto const over{path_point(Pieces[i], 0.5).y - ground[count + 1 + i]};
|
||||
if (over < -kSunkTolerance || over > kRaisedTolerance)
|
||||
{
|
||||
fits = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fits)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// lays out the junction a click would make. returns: false if there's none to make, with the reason in Error unless
|
||||
// it's a matter of the first click being still ahead
|
||||
bool editor_mode::junction_preview(junction_node::state &State, editor_road::loose_end &Snap, std::string &Error) const
|
||||
{
|
||||
auto const &tool{m_roadtool};
|
||||
auto const kind{static_cast<editor_road::junction_kind>(std::clamp(tool.kind, 0, 3))};
|
||||
Snap = {};
|
||||
junction_node::state state;
|
||||
// the look goes after the road the junction is made for
|
||||
auto const dress = [&state](road_node::state const &Layout) {
|
||||
state.surface = Layout.surface;
|
||||
state.texturelength = Layout.texturelength;
|
||||
state.markings = Layout.markings;
|
||||
state.friction = Layout.friction;
|
||||
state.sounddistance = Layout.sounddistance;
|
||||
state.quality = Layout.quality;
|
||||
state.environment = Layout.environment;
|
||||
};
|
||||
if (false == tool.chain)
|
||||
{
|
||||
// at a loose end of a road the junction is made for that road, and put right past its end
|
||||
Snap = editor_road::find_end(tool.mouse, kSnapRadius, false);
|
||||
if (Snap.road == nullptr)
|
||||
return false;
|
||||
auto const &layout{Snap.road->definition()};
|
||||
glm::dvec2 const outwards{Snap.outwards.x, Snap.outwards.z};
|
||||
if (glm::length(outwards) < 1e-6 || layout.lanes.empty())
|
||||
{
|
||||
Error = "There's no telling which way this road goes at its end";
|
||||
return false;
|
||||
}
|
||||
// the ways through the junction start and end where the lanes of the road are expected, which takes lanes of the same width
|
||||
auto const width{layout.lanes.front().width};
|
||||
for (auto const &lane : layout.lanes)
|
||||
{
|
||||
if (std::abs(lane.width - width) > 0.01f)
|
||||
{
|
||||
Error = "Lanes of this road differ in width, and a junction takes roads with lanes of the same width";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
state = editor_road::junction(kind, Snap.position, glm::normalize(outwards), Snap.atend ? layout.forward : layout.backward, Snap.atend ? layout.backward : layout.forward, width);
|
||||
dress(layout);
|
||||
}
|
||||
else
|
||||
{
|
||||
// in the open the centre is set already, and the cursor shows where the first road comes from
|
||||
glm::dvec2 const away{tool.mouse.x - tool.point.x, tool.mouse.z - tool.point.z};
|
||||
if (glm::length(away) < 1.0)
|
||||
{
|
||||
Error = "Move the cursor away from the centre to turn the junction";
|
||||
return false;
|
||||
}
|
||||
auto const heading{-glm::normalize(away)};
|
||||
auto const &layout{tool.settings};
|
||||
auto const reach{editor_road::junction_reach(layout.forward + layout.backward, layout.lanewidth)};
|
||||
glm::dvec3 const entry{tool.point.x - heading.x * reach, tool.point.y, tool.point.z - heading.y * reach};
|
||||
state = editor_road::junction(kind, entry, heading, layout.forward, layout.backward, layout.lanewidth);
|
||||
dress(layout);
|
||||
}
|
||||
state.velocity = tool.crossingspeed;
|
||||
state.normalize();
|
||||
State = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
void editor_mode::junction_click()
|
||||
{
|
||||
auto &tool{m_roadtool};
|
||||
junction_node::state state;
|
||||
editor_road::loose_end snap;
|
||||
std::string error;
|
||||
if (false == junction_preview(state, snap, error))
|
||||
{
|
||||
if (false == error.empty())
|
||||
{
|
||||
tool.status = "Can't make the junction: " + error;
|
||||
}
|
||||
else if (false == tool.chain)
|
||||
{
|
||||
// away from the roads the first click sets the centre, the second one turns the junction
|
||||
tool.point = tool.mouse + glm::dvec3{0.0, tool.offset, 0.0};
|
||||
tool.chain = true;
|
||||
tool.status = "Centre set";
|
||||
}
|
||||
return;
|
||||
}
|
||||
std::string reason;
|
||||
if (snap.valid() && false == editor_road::can_join(snap, &reason))
|
||||
{
|
||||
tool.status = "Can't make the junction here: " + reason;
|
||||
return;
|
||||
}
|
||||
auto const created{editor_road::create(std::vector<junction_node::state>{state})};
|
||||
if (created.empty())
|
||||
{
|
||||
tool.status = "The junction wasn't created";
|
||||
return;
|
||||
}
|
||||
push_road_snapshot({}, {}, {}, created, {});
|
||||
tool.chain = false;
|
||||
tool.status = "\"" + created.front()->name() + "\" made" + (snap.road != nullptr ? " at the end of \"" + snap.road->name() + "\"" : std::string{}) + ". Lead roads out of its arms with the Build tool";
|
||||
}
|
||||
|
||||
void editor_mode::road_click()
|
||||
@@ -197,9 +432,11 @@ void editor_mode::road_click()
|
||||
if (tool.tool == 0)
|
||||
{
|
||||
auto *road{editor_road::nearest(ground, 1.5)};
|
||||
if (road == nullptr)
|
||||
auto *junction{road == nullptr ? editor_road::nearest_junction(ground) : nullptr};
|
||||
if (road == nullptr && junction == nullptr)
|
||||
tool.status = "No road here";
|
||||
road_select(road);
|
||||
junction_select(junction);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -209,30 +446,58 @@ void editor_mode::road_click()
|
||||
tool.status = reason;
|
||||
return;
|
||||
}
|
||||
if (tool.tool == 2)
|
||||
{
|
||||
junction_click();
|
||||
return;
|
||||
}
|
||||
if (false == tool.chain)
|
||||
{
|
||||
// first click sets where the road starts. started from a loose end of a road it carries that road on
|
||||
// first click sets where the road starts. started from a loose end of a road it carries that road on,
|
||||
// started from an arm of a junction it's the road the junction is made for there
|
||||
auto const end{editor_road::find_end(ground, kSnapRadius)};
|
||||
glm::dvec2 const outwards{end.outwards.x, end.outwards.z};
|
||||
if (end.road != nullptr && glm::length(outwards) > 1e-6)
|
||||
if (end.valid() && glm::length(outwards) > 1e-6)
|
||||
{
|
||||
if (false == editor_road::can_join(end, &reason))
|
||||
{
|
||||
tool.status = "Can't build from here: " + reason;
|
||||
return;
|
||||
}
|
||||
tool.point = end.position;
|
||||
tool.direction = glm::normalize(outwards);
|
||||
tool.hasdirection = true;
|
||||
// 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);
|
||||
auto const layout{end.road->definition()};
|
||||
tool.selected = nullptr;
|
||||
tool.settings = layout;
|
||||
tool.grade = end.outwards.y / glm::length(outwards);
|
||||
tool.hasgrade = true;
|
||||
tool.junction = nullptr;
|
||||
if (end.road != nullptr)
|
||||
{
|
||||
// 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();
|
||||
tool.status = "Carrying on \"" + end.road->name() + "\" with its layout";
|
||||
}
|
||||
else
|
||||
{
|
||||
tool.reversed = false;
|
||||
tool.settings.forward = end.forward;
|
||||
tool.settings.backward = end.backward;
|
||||
tool.settings.lanewidth = end.width;
|
||||
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";
|
||||
}
|
||||
road_select(nullptr);
|
||||
tool.status = "Carrying on \"" + end.road->name() + "\" with its layout";
|
||||
}
|
||||
else
|
||||
{
|
||||
tool.point = ground;
|
||||
tool.point = ground + glm::dvec3{0.0, tool.offset, 0.0};
|
||||
tool.hasdirection = false;
|
||||
tool.hasgrade = false;
|
||||
tool.reversed = false;
|
||||
tool.selected = nullptr;
|
||||
tool.junction = nullptr;
|
||||
tool.status = "Start set";
|
||||
}
|
||||
tool.chain = true;
|
||||
@@ -241,16 +506,22 @@ void editor_mode::road_click()
|
||||
|
||||
editor_road::loose_end snap;
|
||||
std::string error;
|
||||
auto pieces{road_preview(snap, error)};
|
||||
auto pieces{road_preview(snap, error, true)};
|
||||
if (pieces.empty())
|
||||
{
|
||||
tool.status = "Can't build this: " + error;
|
||||
return;
|
||||
}
|
||||
if (snap.valid() && false == editor_road::can_join(snap, &reason))
|
||||
{
|
||||
tool.status = "Can't join here: " + reason;
|
||||
return;
|
||||
}
|
||||
// where the next piece is going to carry on from
|
||||
auto const &last{pieces.back()};
|
||||
auto const endpoint{last.points[segment_data::point::end]};
|
||||
auto const tangent{last.points[segment_data::point::control2] != glm::dvec3{0.0} ? -last.points[segment_data::point::control2] : last.points[segment_data::point::end] - last.points[segment_data::point::start]};
|
||||
auto const grade{editor_road::end_grade(last)};
|
||||
if (tool.reversed)
|
||||
{
|
||||
for (auto &piece : pieces)
|
||||
@@ -274,10 +545,10 @@ void editor_mode::road_click()
|
||||
return;
|
||||
}
|
||||
push_road_snapshot({}, created, {});
|
||||
if (snap.road != nullptr)
|
||||
if (snap.valid())
|
||||
{
|
||||
tool.chain = false;
|
||||
tool.status = "Joined with \"" + snap.road->name() + "\"";
|
||||
tool.status = "Joined with \"" + (snap.road != nullptr ? snap.road->name() : snap.junction->name()) + "\"";
|
||||
return;
|
||||
}
|
||||
glm::dvec2 const planar{tangent.x, tangent.z};
|
||||
@@ -287,6 +558,8 @@ void editor_mode::road_click()
|
||||
tool.direction = glm::normalize(planar);
|
||||
tool.hasdirection = true;
|
||||
}
|
||||
tool.grade = grade;
|
||||
tool.hasgrade = true;
|
||||
tool.status = std::to_string(created.size()) + (created.size() == 1 ? " piece added" : " pieces added");
|
||||
}
|
||||
|
||||
@@ -329,16 +602,30 @@ void editor_mode::road_apply()
|
||||
bool editor_mode::road_delete()
|
||||
{
|
||||
auto &tool{m_roadtool};
|
||||
if (false == tool.window || tool.selected == nullptr)
|
||||
if (false == tool.window || (tool.selected == nullptr && tool.junction == nullptr))
|
||||
return false;
|
||||
auto *road{tool.selected};
|
||||
std::string reason;
|
||||
if (tool.junction != nullptr)
|
||||
{
|
||||
auto *junction{tool.junction};
|
||||
if (false == editor_road::can_edit(*junction, &reason))
|
||||
{
|
||||
tool.status = "Can't delete: " + reason;
|
||||
return true;
|
||||
}
|
||||
editor_road::remove(std::vector<junction_node *>{junction});
|
||||
push_road_snapshot({}, {}, {}, {}, {junction});
|
||||
tool.junction = nullptr;
|
||||
tool.status = "\"" + junction->name() + "\" deleted, Ctrl+Z brings it back";
|
||||
return true;
|
||||
}
|
||||
auto *road{tool.selected};
|
||||
if (false == editor_road::can_edit(*road, &reason))
|
||||
{
|
||||
tool.status = "Can't delete: " + reason;
|
||||
return true;
|
||||
}
|
||||
editor_road::remove({road});
|
||||
editor_road::remove(std::vector<road_node *>{road});
|
||||
push_road_snapshot({}, {}, {road});
|
||||
tool.selected = nullptr;
|
||||
tool.status = "\"" + road->name() + "\" deleted, Ctrl+Z brings it back";
|
||||
@@ -370,29 +657,43 @@ bool editor_mode::road_split()
|
||||
return true;
|
||||
}
|
||||
|
||||
void editor_mode::push_road_snapshot(std::vector<std::pair<road_node *, road_node::state>> States, std::vector<road_node *> Created, std::vector<road_node *> Removed)
|
||||
void editor_mode::push_road_snapshot(std::vector<std::pair<road_node *, road_node::state>> States, std::vector<road_node *> Created, std::vector<road_node *> Removed, std::vector<junction_node *> Junctionscreated,
|
||||
std::vector<junction_node *> Junctionsremoved)
|
||||
{
|
||||
if (States.empty() && Created.empty() && Removed.empty())
|
||||
if (States.empty() && Created.empty() && Removed.empty() && Junctionscreated.empty() && Junctionsremoved.empty())
|
||||
return;
|
||||
|
||||
if (m_max_history_size >= 0 && (int)m_history.size() >= m_max_history_size)
|
||||
m_history.erase(m_history.begin(), m_history.begin() + ((int)m_history.size() - m_max_history_size + 1));
|
||||
|
||||
auto const *road = false == States.empty() ? States.front().first : false == Created.empty() ? Created.front() : Removed.front();
|
||||
// the entry is listed under the name of the first thing it's about
|
||||
scene::basic_node const *node{nullptr};
|
||||
if (false == States.empty())
|
||||
node = States.front().first;
|
||||
else if (false == Created.empty())
|
||||
node = Created.front();
|
||||
else if (false == Removed.empty())
|
||||
node = Removed.front();
|
||||
else if (false == Junctionscreated.empty())
|
||||
node = Junctionscreated.front();
|
||||
else
|
||||
node = Junctionsremoved.front();
|
||||
EditorSnapshot snap;
|
||||
snap.action = EditorSnapshot::Action::RoadEdit;
|
||||
snap.node_name = road->name();
|
||||
snap.position = road->location();
|
||||
snap.node_name = node->name();
|
||||
snap.position = node->location();
|
||||
snap.roads = std::move(States);
|
||||
snap.roads_created = std::move(Created);
|
||||
snap.roads_removed = std::move(Removed);
|
||||
snap.junctions_created = std::move(Junctionscreated);
|
||||
snap.junctions_removed = std::move(Junctionsremoved);
|
||||
m_history.push_back(std::move(snap));
|
||||
g_redo.clear();
|
||||
}
|
||||
|
||||
void editor_mode::restore_road_snapshot(EditorSnapshot const &Snapshot, std::vector<EditorSnapshot> &Opposite, bool const Undo)
|
||||
{
|
||||
if (Snapshot.roads.empty() && Snapshot.roads_created.empty() && Snapshot.roads_removed.empty())
|
||||
if (Snapshot.roads.empty() && Snapshot.roads_created.empty() && Snapshot.roads_removed.empty() && Snapshot.junctions_created.empty() && Snapshot.junctions_removed.empty())
|
||||
return;
|
||||
|
||||
// what the roads are like now is what the opposite operation brings back
|
||||
@@ -405,11 +706,15 @@ void editor_mode::restore_road_snapshot(EditorSnapshot const &Snapshot, std::vec
|
||||
if (Undo)
|
||||
{
|
||||
editor_road::remove(Snapshot.roads_created);
|
||||
editor_road::remove(Snapshot.junctions_created);
|
||||
editor_road::revive(Snapshot.junctions_removed);
|
||||
editor_road::revive(Snapshot.roads_removed);
|
||||
}
|
||||
else
|
||||
{
|
||||
editor_road::revive(Snapshot.roads_created);
|
||||
editor_road::revive(Snapshot.junctions_created);
|
||||
editor_road::remove(Snapshot.junctions_removed);
|
||||
editor_road::remove(Snapshot.roads_removed);
|
||||
}
|
||||
editor_road::apply(Snapshot.roads);
|
||||
@@ -418,6 +723,8 @@ void editor_mode::restore_road_snapshot(EditorSnapshot const &Snapshot, std::vec
|
||||
tool.chain = false;
|
||||
if (tool.selected != nullptr)
|
||||
road_select(tool.selected->m_editorremoved ? nullptr : tool.selected);
|
||||
if (tool.junction != nullptr && tool.junction->m_editorremoved)
|
||||
tool.junction = nullptr;
|
||||
}
|
||||
|
||||
// draws controls for the layout of a road. returns: true if the layout was changed in a way which calls for the road to be made anew
|
||||
@@ -642,28 +949,71 @@ void editor_mode::render_road_window()
|
||||
ImGui::RadioButton("Select", &tool.tool, 0);
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("Build", &tool.tool, 1);
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("Junction", &tool.tool, 2);
|
||||
if (tool.tool != previoustool)
|
||||
{
|
||||
tool.chain = false;
|
||||
if (tool.tool == 1)
|
||||
if (tool.tool != 0)
|
||||
{
|
||||
tool.selected = nullptr;
|
||||
tool.junction = nullptr;
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Show lanes", &tool.lanes);
|
||||
|
||||
// how the things being built are put on the ground
|
||||
auto const placement = [&tool](bool const Road) {
|
||||
ImGui::SetNextItemWidth(120.0f);
|
||||
if (ImGui::InputFloat("Height above the ground [m]", &tool.offset, 0.05f, 0.25f, "%.2f"))
|
||||
tool.offset = std::clamp(tool.offset, -10.0f, 100.0f);
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "How far over the ground under the cursor the points are put.\nA few centimetres keep the surface from flickering where it lies on the ground.");
|
||||
if (false == Road)
|
||||
return;
|
||||
ImGui::Checkbox("Follow the ground", &tool.follow);
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "On: the road is led over the ground between the clicked points, in pieces as short as the ground calls for,\nwith the profile rounded so the slope doesn't change abruptly.\nOff: the road goes straight from a point to the next one, like a ramp or a viaduct.");
|
||||
};
|
||||
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 a loose end of a road or an arm of a junction to join it");
|
||||
if (tool.chain)
|
||||
{
|
||||
if (ImGui::Button("Finish (Esc)"))
|
||||
tool.chain = false;
|
||||
}
|
||||
placement(true);
|
||||
ImGui::Separator();
|
||||
// what's set here is what the next pieces get
|
||||
render_road_layout(tool.settings);
|
||||
}
|
||||
else if (tool.tool == 2)
|
||||
{
|
||||
ImGui::TextDisabled("LMB on a loose end of a road: junction for that road, right past its end");
|
||||
ImGui::TextDisabled("LMB elsewhere: the centre, then the side the first road comes from. Esc: start over");
|
||||
char const *kinds[]{"Crossroads", "T: the road ends, roads to both sides", "T: the road goes on, a road to the left", "T: the road goes on, a road to the right"};
|
||||
ImGui::SetNextItemWidth(280.0f);
|
||||
ImGui::Combo("Kind", &tool.kind, kinds, 4);
|
||||
ImGui::SetNextItemWidth(120.0f);
|
||||
if (ImGui::InputFloat("Speed limit on the junction [km/h]", &tool.crossingspeed, 5.0f, 10.0f, "%.0f"))
|
||||
tool.crossingspeed = std::clamp(tool.crossingspeed, 5.0f, 200.0f);
|
||||
placement(false);
|
||||
ImGui::Separator();
|
||||
ImGui::TextWrapped("Away from the roads the junction is made for roads with the lanes set in the Build tool: %d + %d, %.2f m wide. Its surface and markings go after the road it's made for.", tool.settings.forward, tool.settings.backward,
|
||||
tool.settings.lanewidth);
|
||||
ImGui::TextWrapped("Roads are led out of the arms with the Build tool, and get the lanes the junction has there.");
|
||||
}
|
||||
else if (tool.junction != nullptr)
|
||||
{
|
||||
auto const &junction{*tool.junction};
|
||||
ImGui::Text("%s, %d arms, %d ways through", junction.name().c_str(), static_cast<int>(junction.definition().arms.size()), static_cast<int>(junction.movements().size()));
|
||||
ImGui::TextDisabled("Speed limit %.0f km/h. The ways through are made for the roads attached at the moment", junction.definition().velocity);
|
||||
if (ImGui::Button("Delete (Del)"))
|
||||
road_delete();
|
||||
}
|
||||
else if (tool.selected != nullptr)
|
||||
{
|
||||
auto const &road{*tool.selected};
|
||||
@@ -681,7 +1031,7 @@ void editor_mode::render_road_window()
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::TextDisabled("LMB: select a road piece");
|
||||
ImGui::TextDisabled("LMB: select a road piece or a junction");
|
||||
}
|
||||
if (false == tool.status.empty())
|
||||
{
|
||||
@@ -748,6 +1098,14 @@ void editor_mode::draw_road_overlay() const
|
||||
drawpath(turn->m_paths.front(), kTurnColor, 1.5f, 8);
|
||||
}
|
||||
}
|
||||
// ways through the junctions
|
||||
for (auto const *junction : simulation::Junctions.sequence())
|
||||
{
|
||||
if (junction == nullptr || junction->m_editorremoved || glm::distance(junction->location(), camera) > kLaneRange)
|
||||
continue;
|
||||
for (auto const &way : junction->movements())
|
||||
drawpath(way, kWayColor, 1.5f, 8);
|
||||
}
|
||||
}
|
||||
if (false == tool.window)
|
||||
return;
|
||||
@@ -786,6 +1144,15 @@ void editor_mode::draw_road_overlay() const
|
||||
drawlist->AddCircle(screen, Radius, Color, 20, 3.0f);
|
||||
};
|
||||
|
||||
// outline of a junction: the edge of its surface, with the ends of the roads it's made for
|
||||
auto const drawjunction = [&](junction_node::state const &State, ImU32 const Color) {
|
||||
auto const outline{State.outline()};
|
||||
for (std::size_t i = 0; i < outline.size(); ++i)
|
||||
projection.line(drawlist, outline[i], outline[(i + 1) % outline.size()], Color, 2.0f);
|
||||
for (auto const &arm : State.arms)
|
||||
drawpoint(arm.position, Color, 5.0f, true);
|
||||
};
|
||||
|
||||
if (tool.selected != nullptr && false == tool.selected->m_editorremoved)
|
||||
{
|
||||
auto const &state{tool.selected->definition()};
|
||||
@@ -793,42 +1160,57 @@ void editor_mode::draw_road_overlay() const
|
||||
drawpoint(state.axis.points[segment_data::point::start], kSelectedColor, 6.0f, true);
|
||||
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);
|
||||
|
||||
std::string hint;
|
||||
if (tool.tool == 1)
|
||||
{
|
||||
if (tool.chain)
|
||||
{
|
||||
editor_road::loose_end snap;
|
||||
std::string error;
|
||||
auto const pieces{road_preview(snap, error)};
|
||||
drawpoint(tool.point, kPreviewColor, 6.0f, true);
|
||||
if (pieces.empty())
|
||||
if (tool.preview.empty())
|
||||
{
|
||||
projection.line(drawlist, tool.point, tool.mouse, kRefusedColor, 2.0f);
|
||||
hint = error + " ";
|
||||
hint = tool.previewerror + " ";
|
||||
}
|
||||
for (auto const &piece : pieces)
|
||||
for (auto const &piece : tool.preview)
|
||||
{
|
||||
auto state{tool.settings};
|
||||
state.axis = piece;
|
||||
drawroad(state, kPreviewColor);
|
||||
}
|
||||
if (snap.road != nullptr)
|
||||
drawpoint(snap.position, kSnapColor, 14.0f, false);
|
||||
if (tool.previewsnap.valid())
|
||||
drawpoint(tool.previewsnap.position, kSnapColor, 14.0f, false);
|
||||
hint += "LMB: build up to the cursor Ctrl: straight ahead Esc: finish Ctrl+Z: undo the last piece";
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const end{editor_road::find_end(tool.mouse, kSnapRadius)};
|
||||
if (end.road != nullptr)
|
||||
if (end.valid())
|
||||
drawpoint(end.position, kSnapColor, 14.0f, false);
|
||||
hint = "LMB: set where the road starts; on a loose end of a road (ring) to carry it on";
|
||||
hint = "LMB: set where the road starts; on a loose end of a road or an arm of a junction (ring) to carry on from there";
|
||||
}
|
||||
}
|
||||
else if (tool.tool == 2)
|
||||
{
|
||||
if (tool.chain)
|
||||
drawpoint(tool.point, kPreviewColor, 6.0f, true);
|
||||
if (tool.hasjunction)
|
||||
drawjunction(tool.previewjunction, kPreviewColor);
|
||||
else if (false == tool.previewerror.empty())
|
||||
hint = tool.previewerror + " ";
|
||||
if (tool.previewsnap.valid())
|
||||
drawpoint(tool.previewsnap.position, kSnapColor, 14.0f, false);
|
||||
hint += (tool.chain ? "LMB: make the junction, with the first road coming from the side of the cursor Esc: start over" :
|
||||
tool.previewsnap.valid() ? "LMB: make the junction at the end of this road" :
|
||||
"LMB: on a loose end of a road (ring) to make its junction there; elsewhere to set the centre of one");
|
||||
}
|
||||
else
|
||||
{
|
||||
hint = tool.selected != nullptr ? "LMB: select a road piece K: split at the cursor Del: delete Ctrl+Z: undo" : "LMB: select a road piece";
|
||||
hint = tool.selected != nullptr ? "LMB: select a road piece or a junction K: split at the cursor Del: delete Ctrl+Z: undo" :
|
||||
tool.junction != nullptr ? "LMB: select a road piece or a junction Del: delete Ctrl+Z: undo" :
|
||||
"LMB: select a road piece or a junction";
|
||||
}
|
||||
// shown above the place the track tools put their hints at
|
||||
ImGuiIO const &io = ImGui::GetIO();
|
||||
|
||||
Reference in New Issue
Block a user