mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 10:59:20 +02:00
feat(roads): junctions, height above the ground and rounded profile following the terrain
This commit is contained in:
@@ -231,11 +231,13 @@ namespace
|
||||
};
|
||||
|
||||
// appends triangles of a shape node which overlap specified XZ rectangle
|
||||
void gather_shape_triangles(scene::shape_node const &Shape, glm::dvec2 const &Min, glm::dvec2 const &Max, std::vector<world_triangle> &Out)
|
||||
void gather_shape_triangles(scene::shape_node const &Shape, glm::dvec2 const &Min, glm::dvec2 const &Max, std::vector<world_triangle> &Out, bool const Skiproads = false)
|
||||
{
|
||||
auto const &data = Shape.data();
|
||||
if (editor_orthophoto::owns(data.geometry))
|
||||
return; // the orthophoto laid over the ground isn't ground itself
|
||||
if (Skiproads && owned_shapes::owns(data.geometry))
|
||||
return; // neither are the roads, for the ones which ask where to put a road
|
||||
if (data.area.radius >= 0.0f)
|
||||
{
|
||||
double const r = data.area.radius;
|
||||
@@ -275,7 +277,7 @@ namespace
|
||||
|
||||
// ground triangles overlapping an XZ rectangle (Min/Max are x,z). shapes hold the (legacy) terrain,
|
||||
// model instances at least ModelRadius large (if ModelsAsGround) the terrain tiles
|
||||
void gather_ground_triangles(glm::dvec2 const &Min, glm::dvec2 const &Max, bool const ModelsAsGround, float const ModelRadius, std::vector<world_triangle> &Out)
|
||||
void gather_ground_triangles(glm::dvec2 const &Min, glm::dvec2 const &Max, bool const ModelsAsGround, float const ModelRadius, std::vector<world_triangle> &Out, bool const Skiproads = false)
|
||||
{
|
||||
glm::dvec2 const center = (Min + Max) * 0.5;
|
||||
float const radius = static_cast<float>(glm::length(Max - Min) * 0.5);
|
||||
@@ -283,7 +285,7 @@ namespace
|
||||
for (auto *section : sections)
|
||||
{
|
||||
for (auto const &shape : section->m_shapes)
|
||||
gather_shape_triangles(shape, Min, Max, Out);
|
||||
gather_shape_triangles(shape, Min, Max, Out, Skiproads);
|
||||
for (auto &cell : section->m_cells)
|
||||
{
|
||||
double const r = cell.m_area.radius;
|
||||
@@ -349,6 +351,71 @@ editor_mode::editor_mode() {
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<double> editor_mode::ground_heights(std::vector<glm::dvec3> const &Points, bool const Fresh)
|
||||
{
|
||||
std::vector<double> heights;
|
||||
heights.reserve(Points.size());
|
||||
for (auto const &point : Points)
|
||||
heights.push_back(point.y);
|
||||
if (Points.empty() || simulation::Region == nullptr)
|
||||
return heights;
|
||||
|
||||
glm::dvec2 bmin{Points.front().x, Points.front().z};
|
||||
glm::dvec2 bmax{bmin};
|
||||
for (auto const &point : Points)
|
||||
{
|
||||
bmin = glm::min(bmin, glm::dvec2{point.x, point.z});
|
||||
bmax = glm::max(bmax, glm::dvec2{point.x, point.z});
|
||||
}
|
||||
// the road tools ask about the same place frame after frame, so the ground is gathered with room to spare and kept for a moment
|
||||
struct ground_cache
|
||||
{
|
||||
glm::dvec2 min{0.0};
|
||||
glm::dvec2 max{0.0};
|
||||
std::unique_ptr<triangle_grid> grid;
|
||||
double time{0.0};
|
||||
};
|
||||
static ground_cache cache;
|
||||
double constexpr cache_margin = 60.0;
|
||||
double constexpr cache_lifetime = 5.0;
|
||||
double const now = ImGui::GetTime();
|
||||
if (Fresh || cache.grid == nullptr || now < cache.time || now - cache.time > cache_lifetime || bmin.x < cache.min.x || bmin.y < cache.min.y || bmax.x > cache.max.x || bmax.y > cache.max.y)
|
||||
{
|
||||
cache.min = bmin - glm::dvec2{cache_margin};
|
||||
cache.max = bmax + glm::dvec2{cache_margin};
|
||||
std::vector<world_triangle> triangles;
|
||||
// the same ground as the area fill uses, terrain tile models included
|
||||
gather_ground_triangles(cache.min, cache.max, true, 50.0f, triangles, true);
|
||||
cache.grid = std::make_unique<triangle_grid>(std::move(triangles), cache.min, cache.max);
|
||||
cache.time = now;
|
||||
}
|
||||
|
||||
// surfaces much higher than the point are something it's under, rather than the ground
|
||||
double constexpr height_tolerance = 50.0;
|
||||
auto const terrains = active_terrains();
|
||||
for (std::size_t i = 0; i < Points.size(); ++i)
|
||||
{
|
||||
auto const &p = Points[i];
|
||||
double const ceiling = p.y + height_tolerance;
|
||||
double y = 0.0;
|
||||
bool found = cache.grid->height_at(p.x, p.z, ceiling, y);
|
||||
for (editor_terrain *terrain : terrains)
|
||||
{
|
||||
if (!terrain->contains(p.x, p.z))
|
||||
continue;
|
||||
double const h = terrain->height_at(p.x, p.z);
|
||||
if (h <= ceiling && (!found || h > y))
|
||||
{
|
||||
y = h;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
heights[i] = y;
|
||||
}
|
||||
return heights;
|
||||
}
|
||||
|
||||
editor_ui *editor_mode::ui() const
|
||||
{
|
||||
return static_cast<editor_ui *>(m_userinterface.get());
|
||||
|
||||
@@ -110,6 +110,8 @@ class editor_mode : public application_mode
|
||||
std::vector<std::pair<road_node *, road_node::state>> roads; // road pieces with what they were like before the change
|
||||
std::vector<road_node *> roads_created;
|
||||
std::vector<road_node *> roads_removed;
|
||||
std::vector<junction_node *> junctions_created;
|
||||
std::vector<junction_node *> junctions_removed;
|
||||
|
||||
};
|
||||
void push_snapshot(scene::basic_node *node, EditorSnapshot::Action Action = EditorSnapshot::Action::Move, std::string const &Serialized = std::string());
|
||||
@@ -491,8 +493,9 @@ class editor_mode : public application_mode
|
||||
{
|
||||
bool window{false}; // the road window is open, the left mouse button belongs to its tools
|
||||
bool lanes{true}; // lanes of the roads are drawn as trajectories
|
||||
int tool{0}; // 0: select, 1: build
|
||||
int tool{0}; // 0: select, 1: build, 2: junction
|
||||
road_node *selected{nullptr};
|
||||
junction_node *junction{nullptr}; // selected junction
|
||||
road_node::state settings; // layout shown in the window: of the selected piece, or of the pieces about to be built
|
||||
bool whole{true}; // layout changes go to every piece of the road the selected one belongs to
|
||||
bool chain{false}; // the point to build from is set
|
||||
@@ -500,6 +503,18 @@ class editor_mode : public application_mode
|
||||
glm::dvec2 direction{0.0, 1.0}; // the way it has to leave that point
|
||||
bool hasdirection{false};
|
||||
bool reversed{false}; // the pieces are laid from their ends, to carry on a road from its start
|
||||
double grade{0.0}; // slope the next piece has to start with, to carry on what it's attached to
|
||||
bool hasgrade{false};
|
||||
float offset{0.1f}; // height above the ground the roads and junctions are built at
|
||||
bool follow{true}; // built roads are led over the ground between the clicked points
|
||||
int kind{0}; // kind of the junctions being placed, an editor_road::junction_kind
|
||||
float crossingspeed{30.f}; // speed limit on the junctions being placed
|
||||
// what a click would make at the moment, worked out once a frame for display
|
||||
std::vector<segment_data> preview;
|
||||
junction_node::state previewjunction;
|
||||
bool hasjunction{false};
|
||||
editor_road::loose_end previewsnap;
|
||||
std::string previewerror;
|
||||
glm::dvec3 mouse{0.0};
|
||||
std::string status;
|
||||
char surface[128]{};
|
||||
@@ -513,12 +528,20 @@ class editor_mode : public application_mode
|
||||
void update_road_tool();
|
||||
void road_click();
|
||||
void road_select(road_node *Road);
|
||||
void junction_select(junction_node *Junction);
|
||||
void junction_click();
|
||||
bool junction_preview(junction_node::state &State, editor_road::loose_end &Snap, std::string &Error) const;
|
||||
void road_apply();
|
||||
bool road_delete();
|
||||
bool road_split();
|
||||
void road_cancel();
|
||||
std::vector<segment_data> road_preview(editor_road::loose_end &Snap, std::string &Error) const;
|
||||
void push_road_snapshot(std::vector<std::pair<road_node *, road_node::state>> States, std::vector<road_node *> Created, std::vector<road_node *> Removed);
|
||||
// Fresh: the ground is looked up anew instead of going by what was gathered for the previous frames
|
||||
std::vector<segment_data> road_preview(editor_road::loose_end &Snap, std::string &Error, bool const Fresh);
|
||||
void road_profile(std::vector<segment_data> &Pieces, editor_road::loose_end const &Snap, bool const Fresh);
|
||||
// height of the ground under each of the points, with the roads not taken for the ground. a point with nothing under it keeps its height
|
||||
std::vector<double> ground_heights(std::vector<glm::dvec3> const &Points, bool const Fresh);
|
||||
void 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 = {});
|
||||
void restore_road_snapshot(EditorSnapshot const &Snapshot, std::vector<EditorSnapshot> &Opposite, bool const Undo);
|
||||
road_tool m_roadtool;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -63,6 +63,72 @@ std::vector<road_node *> roads_at(glm::dvec3 const &Point, road_node const *Excl
|
||||
return result;
|
||||
}
|
||||
|
||||
// junctions with an arm at specified point
|
||||
std::vector<junction_node *> junctions_at(glm::dvec3 const &Point)
|
||||
{
|
||||
std::vector<junction_node *> result;
|
||||
for (auto *junction : simulation::Junctions.sequence())
|
||||
{
|
||||
if (junction == nullptr || junction->m_editorremoved)
|
||||
continue;
|
||||
for (auto const &arm : junction->definition().arms)
|
||||
{
|
||||
if (glm::distance(arm.position, Point) < kSamePoint)
|
||||
{
|
||||
result.emplace_back(junction);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void add_unique(std::vector<junction_node *> &Junctions, junction_node *Junction)
|
||||
{
|
||||
if (Junction != nullptr && std::find(Junctions.begin(), Junctions.end(), Junction) == Junctions.end())
|
||||
Junctions.emplace_back(Junction);
|
||||
}
|
||||
|
||||
// part of a path between two values of its curve parameter
|
||||
segment_data subpath(segment_data const &Path, double const From, double const To)
|
||||
{
|
||||
segment_data part{Path};
|
||||
auto const &p0{Path.points[segment_data::point::start]};
|
||||
auto const &p3{Path.points[segment_data::point::end]};
|
||||
part.rolls = {static_cast<float>(Path.rolls[0] + (Path.rolls[1] - Path.rolls[0]) * From), static_cast<float>(Path.rolls[0] + (Path.rolls[1] - Path.rolls[0]) * To)};
|
||||
if (Path.points[segment_data::point::control1] == glm::dvec3{0.0} && Path.points[segment_data::point::control2] == glm::dvec3{0.0})
|
||||
{
|
||||
part.points[segment_data::point::start] = glm::mix(p0, p3, From);
|
||||
part.points[segment_data::point::end] = glm::mix(p0, p3, To);
|
||||
return part;
|
||||
}
|
||||
auto const p1{p0 + Path.points[segment_data::point::control1]};
|
||||
auto const p2{p3 + Path.points[segment_data::point::control2]};
|
||||
auto const point = [&](double const T) {
|
||||
auto const u{1.0 - T};
|
||||
return u * u * u * p0 + 3.0 * u * u * T * p1 + 3.0 * u * T * T * p2 + T * T * T * p3;
|
||||
};
|
||||
auto const derivative = [&](double const T) {
|
||||
auto const u{1.0 - T};
|
||||
return 3.0 * u * u * (p1 - p0) + 6.0 * u * T * (p2 - p1) + 3.0 * T * T * (p3 - p2);
|
||||
};
|
||||
// a part of a cubic curve is a cubic curve, with the tangents scaled to the part of the parameter range it takes
|
||||
part.points[segment_data::point::start] = point(From);
|
||||
part.points[segment_data::point::end] = point(To);
|
||||
part.points[segment_data::point::control1] = derivative(From) * ((To - From) / 3.0);
|
||||
part.points[segment_data::point::control2] = derivative(To) * (-(To - From) / 3.0);
|
||||
return part;
|
||||
}
|
||||
|
||||
// true if there's a vehicle on any of the paths turning the traffic back at the loose ends of the road
|
||||
bool turning(road_node const &Road)
|
||||
{
|
||||
for (auto const *turn : Road.turns())
|
||||
if (turn != nullptr && false == turn->Dynamics.empty())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void retire(std::vector<TTrack *> const &Tracks)
|
||||
{
|
||||
for (auto *track : Tracks)
|
||||
@@ -138,6 +204,66 @@ bool editor_road::can_edit(road_node const &Road, std::string *Reason)
|
||||
if (turn != nullptr && false == turn->Dynamics.empty())
|
||||
reason = "there are vehicles turning back next to it";
|
||||
}
|
||||
if (reason.empty())
|
||||
{
|
||||
// so do the ways through the junctions it leads to
|
||||
for (auto const atend : {false, true})
|
||||
for (auto const *junction : junctions_at(axis_end(Road.definition(), atend)))
|
||||
if (junction->occupied())
|
||||
reason = "there are vehicles on the junction it leads to";
|
||||
}
|
||||
if (false == reason.empty() && Reason != nullptr)
|
||||
*Reason = reason;
|
||||
return reason.empty();
|
||||
}
|
||||
|
||||
bool editor_road::can_edit(junction_node const &Junction, std::string *Reason)
|
||||
{
|
||||
std::string reason;
|
||||
if (false == available(&reason))
|
||||
{
|
||||
// reason was filled by the call
|
||||
}
|
||||
else if (Junction.merged())
|
||||
{
|
||||
reason = "the scenery wasn't opened for editing (-edit), geometry of the junctions it came with can't be taken apart";
|
||||
}
|
||||
else if (false == scene::Layers.editable(&Junction, &reason))
|
||||
{
|
||||
// reason was filled by the call
|
||||
}
|
||||
else if (Junction.occupied())
|
||||
{
|
||||
reason = "there are vehicles on it";
|
||||
}
|
||||
else
|
||||
{
|
||||
// the roads attached to it get their loose ends closed anew
|
||||
for (auto const &arm : Junction.definition().arms)
|
||||
for (auto const *road : roads_at(arm.position, nullptr))
|
||||
if (turning(*road))
|
||||
reason = "there are vehicles turning back on the roads it leads to";
|
||||
}
|
||||
if (false == reason.empty() && Reason != nullptr)
|
||||
*Reason = reason;
|
||||
return reason.empty();
|
||||
}
|
||||
|
||||
bool editor_road::can_join(loose_end const &End, std::string *Reason)
|
||||
{
|
||||
// what gets attached takes the place of the paths made for the end as it was
|
||||
std::string reason;
|
||||
if (End.road != nullptr && turning(*End.road))
|
||||
reason = "there are vehicles turning back on the road";
|
||||
if (End.junction != nullptr)
|
||||
{
|
||||
if (End.junction->occupied())
|
||||
reason = "there are vehicles on the junction";
|
||||
for (auto const &arm : End.junction->definition().arms)
|
||||
for (auto const *road : roads_at(arm.position, nullptr))
|
||||
if (turning(*road))
|
||||
reason = "there are vehicles turning back on the roads the junction leads to";
|
||||
}
|
||||
if (false == reason.empty() && Reason != nullptr)
|
||||
*Reason = reason;
|
||||
return reason.empty();
|
||||
@@ -155,38 +281,64 @@ std::vector<road_node *> editor_road::neighbours(road_node const &Road)
|
||||
}
|
||||
|
||||
template <class Change_>
|
||||
void editor_road::refresh(std::vector<road_node *> const &Roads, Change_ Change)
|
||||
void editor_road::refresh(std::vector<road_node *> const &Roads, std::vector<junction_node *> const &Junctions, Change_ Change)
|
||||
{
|
||||
// the pieces next to the changed ones keep their shape, but what they're joined with changes
|
||||
// the pieces and junctions next to the changed ones keep their shape, but what they're joined with changes.
|
||||
// this takes down the paths made for the joints, so they can be made again once the change is done
|
||||
std::vector<road_node *> affected;
|
||||
for (auto *road : Roads)
|
||||
{
|
||||
add_unique(affected, road);
|
||||
for (auto *neighbour : neighbours(*road))
|
||||
add_unique(affected, neighbour);
|
||||
}
|
||||
for (auto *road : affected)
|
||||
retire(road->release_turns());
|
||||
std::vector<junction_node *> crossings;
|
||||
auto const gather = [&]() {
|
||||
std::vector<road_node *> roads{affected};
|
||||
std::vector<junction_node *> junctions{crossings};
|
||||
for (auto *road : Roads)
|
||||
{
|
||||
add_unique(roads, road);
|
||||
if (road->m_editorremoved)
|
||||
continue;
|
||||
for (auto *neighbour : neighbours(*road))
|
||||
add_unique(roads, neighbour);
|
||||
for (auto const atend : {false, true})
|
||||
for (auto *junction : junctions_at(axis_end(road->definition(), atend)))
|
||||
add_unique(junctions, junction);
|
||||
}
|
||||
for (auto *junction : Junctions)
|
||||
add_unique(junctions, junction);
|
||||
// a junction changes what the roads attached to it do at their ends
|
||||
for (auto *junction : junctions)
|
||||
if (false == junction->m_editorremoved)
|
||||
for (auto const &arm : junction->definition().arms)
|
||||
for (auto *road : roads_at(arm.position, nullptr))
|
||||
add_unique(roads, road);
|
||||
for (auto *road : roads)
|
||||
{
|
||||
if (std::find(affected.begin(), affected.end(), road) == affected.end())
|
||||
{
|
||||
retire(road->release_turns());
|
||||
affected.emplace_back(road);
|
||||
}
|
||||
}
|
||||
for (auto *junction : junctions)
|
||||
{
|
||||
if (std::find(crossings.begin(), crossings.end(), junction) == crossings.end())
|
||||
{
|
||||
retire(junction->release_links());
|
||||
crossings.emplace_back(junction);
|
||||
}
|
||||
}
|
||||
};
|
||||
gather();
|
||||
for (auto *road : Roads)
|
||||
{
|
||||
road->hide();
|
||||
retire(road->release_lanes());
|
||||
}
|
||||
for (auto *junction : Junctions)
|
||||
junction->hide();
|
||||
|
||||
Change();
|
||||
|
||||
// the change could have moved the pieces next to other ones
|
||||
for (auto *road : Roads)
|
||||
{
|
||||
for (auto *neighbour : neighbours(*road))
|
||||
{
|
||||
if (std::find(affected.begin(), affected.end(), neighbour) == affected.end())
|
||||
{
|
||||
retire(neighbour->release_turns());
|
||||
affected.emplace_back(neighbour);
|
||||
}
|
||||
}
|
||||
}
|
||||
// the change could have moved things next to other ones
|
||||
gather();
|
||||
std::vector<TTrack *> lanes;
|
||||
for (auto *road : Roads)
|
||||
{
|
||||
@@ -198,6 +350,13 @@ void editor_road::refresh(std::vector<road_node *> const &Roads, Change_ Change)
|
||||
}
|
||||
// joins the lanes with the lanes of the neighbours, by the ends they share
|
||||
editor_track::commit(lanes);
|
||||
for (auto *junction : Junctions)
|
||||
if (false == junction->m_editorremoved)
|
||||
junction->show();
|
||||
// the junctions take the lanes which end at them, what's left loose after that is closed by the roads
|
||||
for (auto *junction : crossings)
|
||||
if (false == junction->m_editorremoved)
|
||||
junction->create_links();
|
||||
for (auto *road : affected)
|
||||
if (false == road->m_editorremoved)
|
||||
road->close_ends();
|
||||
@@ -227,7 +386,7 @@ std::vector<road_node *> editor_road::create(std::vector<road_node::state> const
|
||||
scene::Layers.count(road->layer(), scene::layer_item::track);
|
||||
created.emplace_back(road);
|
||||
}
|
||||
refresh(created, []() {});
|
||||
refresh(created, {}, []() {});
|
||||
return created;
|
||||
}
|
||||
|
||||
@@ -238,7 +397,7 @@ void editor_road::apply(std::vector<std::pair<road_node *, road_node::state>> co
|
||||
add_unique(roads, change.first);
|
||||
if (roads.empty())
|
||||
return;
|
||||
refresh(roads, [&]() {
|
||||
refresh(roads, {}, [&]() {
|
||||
for (auto const &change : Changes)
|
||||
{
|
||||
change.first->define(change.second);
|
||||
@@ -255,7 +414,7 @@ void editor_road::remove(std::vector<road_node *> const &Roads)
|
||||
add_unique(roads, road);
|
||||
if (roads.empty())
|
||||
return;
|
||||
refresh(roads, [&]() {
|
||||
refresh(roads, {}, [&]() {
|
||||
for (auto *road : roads)
|
||||
{
|
||||
road->m_editorremoved = true;
|
||||
@@ -272,7 +431,7 @@ void editor_road::revive(std::vector<road_node *> const &Roads)
|
||||
add_unique(roads, road);
|
||||
if (roads.empty())
|
||||
return;
|
||||
refresh(roads, [&]() {
|
||||
refresh(roads, {}, [&]() {
|
||||
for (auto *road : roads)
|
||||
{
|
||||
road->m_editorremoved = false;
|
||||
@@ -282,6 +441,68 @@ void editor_road::revive(std::vector<road_node *> const &Roads)
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<junction_node *> editor_road::create(std::vector<junction_node::state> const &States)
|
||||
{
|
||||
static int counter{0};
|
||||
std::vector<junction_node *> created;
|
||||
if (false == available())
|
||||
return created;
|
||||
for (auto const &state : States)
|
||||
{
|
||||
scene::node_data data;
|
||||
data.type = "junction";
|
||||
data.range_max = -1.0;
|
||||
data.layer = scene::Layers.active(); // null_handle unless the scenery was opened for editing
|
||||
do
|
||||
{
|
||||
data.name = "junction_" + std::to_string(++counter);
|
||||
} while (simulation::Junctions.find(data.name) != nullptr);
|
||||
auto *junction{new junction_node(data)};
|
||||
junction->define(state);
|
||||
junction->mark_dirty();
|
||||
simulation::Junctions.insert(junction);
|
||||
scene::Layers.count(junction->layer(), scene::layer_item::track);
|
||||
created.emplace_back(junction);
|
||||
}
|
||||
refresh({}, created, []() {});
|
||||
return created;
|
||||
}
|
||||
|
||||
void editor_road::remove(std::vector<junction_node *> const &Junctions)
|
||||
{
|
||||
std::vector<junction_node *> junctions;
|
||||
for (auto *junction : Junctions)
|
||||
if (junction != nullptr && false == junction->m_editorremoved)
|
||||
add_unique(junctions, junction);
|
||||
if (junctions.empty())
|
||||
return;
|
||||
refresh({}, junctions, [&]() {
|
||||
for (auto *junction : junctions)
|
||||
{
|
||||
junction->m_editorremoved = true;
|
||||
scene::Layers.count(junction->layer(), scene::layer_item::track, -1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void editor_road::revive(std::vector<junction_node *> const &Junctions)
|
||||
{
|
||||
std::vector<junction_node *> junctions;
|
||||
for (auto *junction : Junctions)
|
||||
if (junction != nullptr && junction->m_editorremoved)
|
||||
add_unique(junctions, junction);
|
||||
if (junctions.empty())
|
||||
return;
|
||||
refresh({}, junctions, [&]() {
|
||||
for (auto *junction : junctions)
|
||||
{
|
||||
junction->m_editorremoved = false;
|
||||
junction->mark_dirty();
|
||||
scene::Layers.count(junction->layer(), scene::layer_item::track);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
road_node *editor_road::split(road_node &Road, double const T)
|
||||
{
|
||||
if (T <= 0.001 || T >= 0.999)
|
||||
@@ -386,7 +607,29 @@ road_node *editor_road::nearest(glm::dvec3 const &Point, double const Margin)
|
||||
return result;
|
||||
}
|
||||
|
||||
editor_road::loose_end editor_road::find_end(glm::dvec3 const &Point, double const Radius)
|
||||
junction_node *editor_road::nearest_junction(glm::dvec3 const &Point)
|
||||
{
|
||||
junction_node *result{nullptr};
|
||||
double best{std::numeric_limits<double>::max()};
|
||||
for (auto *junction : simulation::Junctions.sequence())
|
||||
{
|
||||
if (junction == nullptr || junction->m_editorremoved)
|
||||
continue;
|
||||
auto const &state{junction->definition()};
|
||||
// the junction takes the ground up to the ends of the roads it's made for
|
||||
double reach{0.0};
|
||||
for (auto const &arm : state.arms)
|
||||
reach = std::max(reach, glm::distance(glm::dvec2{arm.position.x, arm.position.z}, glm::dvec2{state.centre.x, state.centre.z}));
|
||||
auto const distance{glm::distance(glm::dvec2{state.centre.x, state.centre.z}, glm::dvec2{Point.x, Point.z})};
|
||||
if (distance > reach || std::abs(state.centre.y - Point.y) > 3.0 || distance > best)
|
||||
continue;
|
||||
best = distance;
|
||||
result = junction;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
editor_road::loose_end editor_road::find_end(glm::dvec3 const &Point, double const Radius, bool const Junctions)
|
||||
{
|
||||
loose_end result;
|
||||
double best{Radius};
|
||||
@@ -399,15 +642,38 @@ editor_road::loose_end editor_road::find_end(glm::dvec3 const &Point, double con
|
||||
{
|
||||
auto const position{axis_end(state, atend)};
|
||||
auto const distance{glm::distance(glm::dvec2{position.x, position.z}, glm::dvec2{Point.x, Point.z})};
|
||||
if (distance > best || std::abs(position.y - Point.y) > 3.0 || false == roads_at(position, road).empty())
|
||||
if (distance > best || std::abs(position.y - Point.y) > 3.0 || false == roads_at(position, road).empty() || false == junctions_at(position).empty())
|
||||
continue;
|
||||
best = distance;
|
||||
result = loose_end{};
|
||||
result.road = road;
|
||||
result.atend = atend;
|
||||
result.position = position;
|
||||
result.outwards = (atend ? state.tangent(1.0) : -state.tangent(0.0));
|
||||
}
|
||||
}
|
||||
if (false == Junctions)
|
||||
return result;
|
||||
for (auto *junction : simulation::Junctions.sequence())
|
||||
{
|
||||
if (junction == nullptr || junction->m_editorremoved)
|
||||
continue;
|
||||
for (auto const &arm : junction->definition().arms)
|
||||
{
|
||||
auto const distance{glm::distance(glm::dvec2{arm.position.x, arm.position.z}, glm::dvec2{Point.x, Point.z})};
|
||||
if (distance > best || std::abs(arm.position.y - Point.y) > 3.0 || false == roads_at(arm.position, nullptr).empty())
|
||||
continue;
|
||||
best = distance;
|
||||
result = loose_end{};
|
||||
result.junction = junction;
|
||||
result.position = arm.position;
|
||||
result.outwards = glm::dvec3{arm.direction.x, 0.0, arm.direction.y};
|
||||
// a road leading away from the junction has the lanes going out of it as the ones going along its axis
|
||||
result.forward = arm.outgoing;
|
||||
result.backward = arm.incoming;
|
||||
result.width = arm.width;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -535,6 +801,147 @@ std::vector<segment_data> editor_road::plan(glm::dvec3 const &Start, glm::dvec2
|
||||
return pieces;
|
||||
}
|
||||
|
||||
double editor_road::planar_length(segment_data const &Path)
|
||||
{
|
||||
auto const whole{subpath(Path, 0.0, 1.0)};
|
||||
auto const &p0{whole.points[segment_data::point::start]};
|
||||
auto const &p3{whole.points[segment_data::point::end]};
|
||||
if (whole.points[segment_data::point::control1] == glm::dvec3{0.0} && whole.points[segment_data::point::control2] == glm::dvec3{0.0})
|
||||
return glm::distance(glm::dvec2{p0.x, p0.z}, glm::dvec2{p3.x, p3.z});
|
||||
auto const p1{p0 + whole.points[segment_data::point::control1]};
|
||||
auto const p2{p3 + whole.points[segment_data::point::control2]};
|
||||
double length{0.0};
|
||||
glm::dvec2 previous{p0.x, p0.z};
|
||||
for (int i = 1; i <= 32; ++i)
|
||||
{
|
||||
auto const t{i / 32.0};
|
||||
auto const u{1.0 - t};
|
||||
auto const point{u * u * u * p0 + 3.0 * u * u * t * p1 + 3.0 * u * t * t * p2 + t * t * t * p3};
|
||||
glm::dvec2 const current{point.x, point.z};
|
||||
length += glm::distance(previous, current);
|
||||
previous = current;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
double editor_road::end_grade(segment_data const &Path)
|
||||
{
|
||||
auto const &control{Path.points[segment_data::point::control2]};
|
||||
if (control != glm::dvec3{0.0})
|
||||
{
|
||||
auto const run{glm::length(glm::dvec2{control.x, control.z})};
|
||||
return run > 1e-6 ? -control.y / run : 0.0;
|
||||
}
|
||||
auto const length{planar_length(Path)};
|
||||
return length > 1e-6 ? (Path.points[segment_data::point::end].y - Path.points[segment_data::point::start].y) / length : 0.0;
|
||||
}
|
||||
|
||||
std::vector<segment_data> editor_road::refine(std::vector<segment_data> const &Pieces, double const Length)
|
||||
{
|
||||
std::vector<segment_data> result;
|
||||
for (auto const &piece : Pieces)
|
||||
{
|
||||
auto const count{std::max(1, static_cast<int>(std::ceil(planar_length(piece) / std::max(1.0, Length) - 0.01)))};
|
||||
for (int i = 0; i < count; ++i)
|
||||
result.emplace_back(count == 1 ? piece : subpath(piece, static_cast<double>(i) / count, static_cast<double>(i + 1) / count));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void editor_road::profile(std::vector<segment_data> &Pieces, std::vector<double> Heights, double const *Startgrade, double const *Endgrade)
|
||||
{
|
||||
auto const count{Pieces.size()};
|
||||
if (count == 0 || Heights.size() != count + 1)
|
||||
return;
|
||||
std::vector<double> lengths(count);
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
lengths[i] = std::max(0.01, planar_length(Pieces[i]));
|
||||
// the heights in between are evened out, so the road doesn't ride every dip. it's never lowered though,
|
||||
// as that would let the ground the heights were taken from come through the surface
|
||||
{
|
||||
auto const sampled{Heights};
|
||||
for (std::size_t i = 1; i < count; ++i)
|
||||
Heights[i] = std::max(sampled[i], 0.25 * sampled[i - 1] + 0.5 * sampled[i] + 0.25 * sampled[i + 1]);
|
||||
}
|
||||
std::vector<double> slopes(count);
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
slopes[i] = (Heights[i + 1] - Heights[i]) / lengths[i];
|
||||
// slope at each joint is shared by the pieces on both its sides, which is what rounds the profile
|
||||
std::vector<double> grades(count + 1);
|
||||
grades.front() = (Startgrade != nullptr ? *Startgrade : slopes.front());
|
||||
grades.back() = (Endgrade != nullptr ? *Endgrade : slopes.back());
|
||||
for (std::size_t i = 1; i < count; ++i)
|
||||
{
|
||||
auto const before{slopes[i - 1]};
|
||||
auto const after{slopes[i]};
|
||||
if (before * after <= 0.0)
|
||||
{
|
||||
// a crest or a bottom
|
||||
grades[i] = 0.0;
|
||||
continue;
|
||||
}
|
||||
auto grade{(before * lengths[i] + after * lengths[i - 1]) / (lengths[i - 1] + lengths[i])};
|
||||
// kept from overshooting the heights the road is led through
|
||||
auto const limit{3.0 * std::min(std::abs(before), std::abs(after))};
|
||||
grades[i] = std::clamp(grade, -limit, limit);
|
||||
}
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
auto &piece{Pieces[i]};
|
||||
auto &start{piece.points[segment_data::point::start]};
|
||||
auto &end{piece.points[segment_data::point::end]};
|
||||
auto &control1{piece.points[segment_data::point::control1]};
|
||||
auto &control2{piece.points[segment_data::point::control2]};
|
||||
start.y = Heights[i];
|
||||
end.y = Heights[i + 1];
|
||||
if (control1 == glm::dvec3{0.0} && control2 == glm::dvec3{0.0})
|
||||
{
|
||||
if (std::abs(grades[i] - slopes[i]) < 1e-4 && std::abs(grades[i + 1] - slopes[i]) < 1e-4)
|
||||
continue; // even slope, the piece stays a plain straight
|
||||
// a straight needs its control points spelled out to bend up or down
|
||||
control1 = (end - start) / 3.0;
|
||||
control2 = (start - end) / 3.0;
|
||||
}
|
||||
control1.y = grades[i] * glm::length(glm::dvec2{control1.x, control1.z});
|
||||
control2.y = -grades[i + 1] * glm::length(glm::dvec2{control2.x, control2.z});
|
||||
}
|
||||
}
|
||||
|
||||
double editor_road::junction_reach(int const Lanes, float const Width)
|
||||
{
|
||||
// half of the road, and room for the corners
|
||||
return 0.5 * std::max(1, Lanes) * Width + 6.0;
|
||||
}
|
||||
|
||||
junction_node::state editor_road::junction(junction_kind const Kind, glm::dvec3 const &Entry, glm::dvec2 const &Heading, int const Incoming, int const Outgoing, float const Width)
|
||||
{
|
||||
junction_node::state state;
|
||||
auto const heading{glm::length(Heading) > 1e-6 ? glm::normalize(Heading) : glm::dvec2{0.0, 1.0}};
|
||||
glm::dvec2 const left{heading.y, -heading.x};
|
||||
auto const reach{junction_reach(Incoming + Outgoing, Width)};
|
||||
auto const spot = [&](glm::dvec2 const &Direction, double const Distance) { return glm::dvec3{Entry.x + Direction.x * Distance, Entry.y, Entry.z + Direction.y * Distance}; };
|
||||
state.centre = spot(heading, reach);
|
||||
auto const arm = [&](glm::dvec2 const &Direction, int const In, int const Out) {
|
||||
junction_node::arm_data data;
|
||||
data.position = glm::dvec3{state.centre.x + Direction.x * reach, Entry.y, state.centre.z + Direction.y * reach};
|
||||
data.direction = Direction;
|
||||
data.incoming = In;
|
||||
data.outgoing = Out;
|
||||
data.width = Width;
|
||||
state.arms.emplace_back(data);
|
||||
};
|
||||
// the road the junction is entered by, then the others: what comes in by one leaves by them
|
||||
arm(-heading, Incoming, Outgoing);
|
||||
if (Kind == junction_kind::cross || Kind == junction_kind::tee_left || Kind == junction_kind::tee_right)
|
||||
arm(heading, Outgoing, Incoming);
|
||||
if (Kind == junction_kind::cross || Kind == junction_kind::tee_sides || Kind == junction_kind::tee_left)
|
||||
arm(left, Outgoing, Incoming);
|
||||
if (Kind == junction_kind::cross || Kind == junction_kind::tee_sides || Kind == junction_kind::tee_right)
|
||||
arm(-left, Outgoing, Incoming);
|
||||
state.normalize();
|
||||
return state;
|
||||
}
|
||||
|
||||
std::vector<std::string> const &editor_road::materials()
|
||||
{
|
||||
static std::vector<std::string> names;
|
||||
|
||||
@@ -17,22 +17,42 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "world/Road.h"
|
||||
|
||||
// editor operations on the roads. a road in the scenery is a string of pieces joined end to end,
|
||||
// each piece being a road node with lanes and geometry of its own
|
||||
// each piece being a road node with lanes and geometry of its own; roads meet at junctions
|
||||
class editor_road
|
||||
{
|
||||
public:
|
||||
// an end of a road piece nothing is attached to
|
||||
// an end of a road piece, or an arm of a junction, nothing is attached to
|
||||
struct loose_end
|
||||
{
|
||||
road_node *road{nullptr};
|
||||
bool atend{false};
|
||||
junction_node *junction{nullptr};
|
||||
bool atend{false}; // of a road: the end of its axis rather than the start
|
||||
glm::dvec3 position{0.0};
|
||||
glm::dvec3 outwards{0.0}; // direction the road would continue in
|
||||
glm::dvec3 outwards{0.0}; // direction a road would continue in
|
||||
// of a junction arm: lanes of a road leading away from it
|
||||
int forward{0};
|
||||
int backward{0};
|
||||
float width{0.f};
|
||||
|
||||
bool valid() const
|
||||
{
|
||||
return road != nullptr || junction != nullptr;
|
||||
}
|
||||
};
|
||||
enum class junction_kind
|
||||
{
|
||||
cross, // four roads
|
||||
tee_sides, // the road ends, with roads to both sides
|
||||
tee_left, // the road goes on, with a road to the left
|
||||
tee_right // the road goes on, with a road to the right
|
||||
};
|
||||
// true if the roads can be built and changed at all. optionally explains why they can't
|
||||
static bool available(std::string *Reason = nullptr);
|
||||
// true if specified road piece can be changed or removed. optionally explains why it can't
|
||||
// true if specified road piece or junction can be changed or removed. optionally explains why it can't
|
||||
static bool can_edit(road_node const &Road, std::string *Reason = nullptr);
|
||||
static bool can_edit(junction_node const &Junction, std::string *Reason = nullptr);
|
||||
// true if a road or a junction can be attached to specified loose end. optionally explains why it can't
|
||||
static bool can_join(loose_end const &End, std::string *Reason = nullptr);
|
||||
// makes road pieces out of provided definitions, with their lanes joined to the neighbours and geometry in the scene
|
||||
static std::vector<road_node *> create(std::vector<road_node::state> const &States);
|
||||
// replaces definitions of the pieces and makes their lanes and geometry anew
|
||||
@@ -40,26 +60,48 @@ class editor_road
|
||||
// takes the pieces out of the scenery. they're kept around, so they can be brought back
|
||||
static void remove(std::vector<road_node *> const &Roads);
|
||||
static void revive(std::vector<road_node *> const &Roads);
|
||||
// the same for the junctions
|
||||
static std::vector<junction_node *> create(std::vector<junction_node::state> const &States);
|
||||
static void remove(std::vector<junction_node *> const &Junctions);
|
||||
static void revive(std::vector<junction_node *> const &Junctions);
|
||||
// cuts the piece in two at specified point of its axis. returns: the new piece, holding the part past the cut
|
||||
static road_node *split(road_node &Road, double const T);
|
||||
// the pieces which make a single road together with specified one, in no particular order
|
||||
static std::vector<road_node *> chain(road_node &Road);
|
||||
// the piece under specified point, or within specified distance from it
|
||||
static road_node *nearest(glm::dvec3 const &Point, double const Margin);
|
||||
// the junction specified point lies on
|
||||
static junction_node *nearest_junction(glm::dvec3 const &Point);
|
||||
// value of the curve parameter for the point of the axis nearest to specified point
|
||||
static double nearest_parameter(road_node::state const &State, glm::dvec3 const &Point);
|
||||
// end of a piece with nothing attached, within specified distance from specified point
|
||||
static loose_end find_end(glm::dvec3 const &Point, double const Radius);
|
||||
// end of a road with nothing attached, within specified distance from specified point; optionally arms of junctions too
|
||||
static loose_end find_end(glm::dvec3 const &Point, double const Radius, bool const Junctions = true);
|
||||
// lays out axes of the pieces for a road leading from a point to another. the directions are optional requirements
|
||||
// for the road at its ends; without them it starts straight at the target, with them it bends to meet them.
|
||||
// returns: the axes in order, or nothing if there's no reasonable road to make, with the reason in Error
|
||||
static std::vector<segment_data> plan(glm::dvec3 const &Start, glm::dvec2 const *Direction, glm::dvec3 const &Target, glm::dvec2 const *Enddirection, bool const Straight, std::string &Error);
|
||||
// cuts the pieces so none is longer than specified
|
||||
static std::vector<segment_data> refine(std::vector<segment_data> const &Pieces, double const Length);
|
||||
// gives a string of pieces a smooth profile running through provided heights.
|
||||
// Heights: one for the start of each piece and one for the end of the last. these of the ends are kept, the others are evened out:
|
||||
// the dips are filled while the tops stay, so the ground the heights were taken from doesn't come through the road.
|
||||
// Startgrade, Endgrade: slopes to keep at the ends, to carry on what the road joins; nullptr for none
|
||||
static void profile(std::vector<segment_data> &Pieces, std::vector<double> Heights, double const *Startgrade, double const *Endgrade);
|
||||
// length of the path seen from above
|
||||
static double planar_length(segment_data const &Path);
|
||||
// slope of the path at its end, in the direction it's laid out
|
||||
static double end_grade(segment_data const &Path);
|
||||
// distance from the centre of a junction to the ends of the roads it's made for
|
||||
static double junction_reach(int const Lanes, float const Width);
|
||||
// lays out a junction entered by a road. Entry: middle of the end of that road, Heading: the way the road goes there,
|
||||
// Incoming, Outgoing: lanes of that road leading into the junction and out of it
|
||||
static junction_node::state junction(junction_kind const Kind, glm::dvec3 const &Entry, glm::dvec2 const &Heading, int const Incoming, int const Outgoing, float const Width);
|
||||
// names of materials and images present in the texture folder, to pick from
|
||||
static std::vector<std::string> const &materials();
|
||||
|
||||
private:
|
||||
// takes down what depends on shape of the listed pieces, lets the caller change them, then builds it all again
|
||||
// takes down what depends on the listed pieces and junctions, lets the caller change them, then builds it all again
|
||||
template <class Change_>
|
||||
static void refresh(std::vector<road_node *> const &Roads, Change_ Change);
|
||||
static void refresh(std::vector<road_node *> const &Roads, std::vector<junction_node *> const &Junctions, Change_ Change);
|
||||
static std::vector<road_node *> neighbours(road_node const &Road);
|
||||
};
|
||||
|
||||
@@ -857,6 +857,72 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
|
||||
state.patches[source.layer].edits.emplace_back(std::move(edit));
|
||||
rewritten.emplace(road);
|
||||
}
|
||||
// road junctions, likewise
|
||||
std::vector<junction_node *> savedjunctions;
|
||||
std::vector<junction_node const *> droppedjunctions;
|
||||
auto const junction_text = [](junction_node &Junction, glm::dvec3 const &Offset) {
|
||||
auto const definition{Junction.definition()};
|
||||
auto local{definition};
|
||||
local.centre -= Offset;
|
||||
for (auto &arm : local.arms)
|
||||
{
|
||||
arm.position -= Offset;
|
||||
}
|
||||
Junction.define(local);
|
||||
std::string text;
|
||||
Junction.export_as_text(text);
|
||||
Junction.define(definition);
|
||||
text.erase(text.find_last_not_of(" \t\r\n") + 1);
|
||||
return text;
|
||||
};
|
||||
for (auto *junction : simulation::Junctions.sequence())
|
||||
{
|
||||
if (junction == nullptr || junction->from_template())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto const nodelayer{resolve(junction->layer())};
|
||||
if (false == is_output(nodelayer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto const lookup{m_sources.find(junction)};
|
||||
if (lookup == m_sources.end())
|
||||
{
|
||||
if (junction->m_editorremoved || false == junction->dirty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
created[nodelayer].emplace_back(junction, junction_text(*junction, layer(nodelayer).context_insert().offset));
|
||||
savedjunctions.push_back(junction);
|
||||
continue;
|
||||
}
|
||||
auto const &source{lookup->second};
|
||||
if ((false == junction->m_editorremoved && false == junction->dirty()) || false == writable(source.layer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (false == load(source.layer))
|
||||
{
|
||||
return fail(state.error);
|
||||
}
|
||||
file_patch::edit edit;
|
||||
edit.begin = source.span.begin;
|
||||
edit.end = source.span.end;
|
||||
if (junction->m_editorremoved)
|
||||
{
|
||||
droppedjunctions.push_back(junction);
|
||||
}
|
||||
else
|
||||
{
|
||||
edit.text = junction_text(*junction, source.context.offset);
|
||||
edit.length = edit.text.size();
|
||||
edit.node = junction;
|
||||
savedjunctions.push_back(junction);
|
||||
}
|
||||
state.patches[source.layer].edits.emplace_back(std::move(edit));
|
||||
rewritten.emplace(junction);
|
||||
}
|
||||
for (auto const &erased : m_erased)
|
||||
{
|
||||
file_patch::edit edit;
|
||||
@@ -1308,6 +1374,14 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
|
||||
{
|
||||
m_sources.erase(road);
|
||||
}
|
||||
for (auto *junction : savedjunctions)
|
||||
{
|
||||
junction->m_dirty = false;
|
||||
}
|
||||
for (auto const *junction : droppedjunctions)
|
||||
{
|
||||
m_sources.erase(junction);
|
||||
}
|
||||
if (removedterrain)
|
||||
{
|
||||
// binary terrain file still holds geometry of the dropped layer, have it rebuilt on the next load
|
||||
|
||||
@@ -38,6 +38,7 @@ event_manager Events;
|
||||
memory_table Memory;
|
||||
path_table Paths;
|
||||
road_table Roads;
|
||||
junction_table Junctions;
|
||||
traction_table Traction;
|
||||
powergridsource_table Powergrid;
|
||||
instance_table Instances;
|
||||
|
||||
@@ -83,6 +83,7 @@ extern event_manager Events;
|
||||
extern memory_table Memory;
|
||||
extern path_table Paths;
|
||||
extern road_table Roads;
|
||||
extern junction_table Junctions;
|
||||
extern traction_table Traction;
|
||||
extern powergridsource_table Powergrid;
|
||||
extern instance_table Instances;
|
||||
|
||||
@@ -181,6 +181,7 @@ state_serializer::deserialize_continue(std::shared_ptr<deserializer_state> state
|
||||
// geometry of the roads is generated on each load instead of being kept in the binary terrain file,
|
||||
// so it's inserted in the region only after that file had its chance to be written
|
||||
simulation::Roads.create_geometry( Scratchpad );
|
||||
simulation::Junctions.create_geometry( Scratchpad );
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -398,6 +399,8 @@ state_serializer::deserialize_firstinit( cParser &Input, scene::scratch_data &Sc
|
||||
}
|
||||
|
||||
simulation::Paths.InitTracks();
|
||||
// the junctions tie the lanes of the roads together, what's left loose after that gets closed by the roads
|
||||
simulation::Junctions.InitJunctions();
|
||||
simulation::Roads.InitRoads();
|
||||
simulation::Traction.InitTraction();
|
||||
simulation::Events.InitEvents();
|
||||
@@ -521,6 +524,20 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
|
||||
road->create_lanes();
|
||||
scene::Layers.track( road, { sourcebegin, Input.TokenEnd() } );
|
||||
}
|
||||
else if( nodedata.type == "junction" ) {
|
||||
|
||||
auto *junction { new junction_node( nodedata ) };
|
||||
junction->import(
|
||||
Input,
|
||||
( Scratchpad.location.offset.empty() ?
|
||||
glm::dvec3 { 0.0 } :
|
||||
glm::dvec3 { Scratchpad.location.offset.top() } ) );
|
||||
if( false == simulation::Junctions.insert( junction ) ) {
|
||||
ErrorLog( "Bad scenario: duplicate junction name \"" + junction->name() + "\" defined in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" );
|
||||
}
|
||||
scene::Groups.insert( scene::Groups.handle(), junction );
|
||||
scene::Layers.track( junction, { sourcebegin, Input.TokenEnd() } );
|
||||
}
|
||||
else if( nodedata.type == "traction" ) {
|
||||
|
||||
auto *traction { deserialize_traction( Input, Scratchpad, nodedata ) };
|
||||
@@ -695,6 +712,7 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
|
||||
{ "dynamic", scene::layer_item::vehicle },
|
||||
{ "track", scene::layer_item::track },
|
||||
{ "road", scene::layer_item::track },
|
||||
{ "junction", scene::layer_item::track },
|
||||
{ "traction", scene::layer_item::traction },
|
||||
{ "tractionpowersource", scene::layer_item::powersource },
|
||||
{ "model", scene::layer_item::model },
|
||||
@@ -1404,6 +1422,11 @@ state_serializer::export_nodes_to_stream(std::ostream &scmfile, bool Dirty) cons
|
||||
road->export_as_text( scmfile );
|
||||
}
|
||||
}
|
||||
for( auto const *junction : Junctions.sequence() ) {
|
||||
if( junction != nullptr && false == junction->m_editorremoved && junction->dirty() == Dirty && junction->group() == null_handle ) {
|
||||
junction->export_as_text( scmfile );
|
||||
}
|
||||
}
|
||||
// traction
|
||||
scmfile << "// traction\n";
|
||||
for( auto const *traction : Traction.sequence() ) {
|
||||
@@ -1459,7 +1482,7 @@ TAnimModel *state_serializer::create_model(const std::string &src, const std::st
|
||||
std::pair<int, int> state_serializer::preview_include(std::string const &Directive, scene::layer_context const &Context, scene::layer_handle Layer, scene::instance_handle Instance) {
|
||||
// statements which take more than a single token, with the tokens ending them
|
||||
static std::unordered_map<std::string, std::string> const nodeends {
|
||||
{ "dynamic", "enddynamic" }, { "track", "endtrack" }, { "road", "endroad" }, { "traction", "endtraction" }, { "tractionpowersource", "end" }, { "model", "endmodel" },
|
||||
{ "dynamic", "enddynamic" }, { "track", "endtrack" }, { "road", "endroad" }, { "junction", "endjunction" }, { "traction", "endtraction" }, { "tractionpowersource", "end" }, { "model", "endmodel" },
|
||||
{ "triangles", "endtri" }, { "triangle_strip", "endtri" }, { "triangle_fan", "endtri" }, { "lines", "endline" }, { "line_strip", "endline" }, { "line_loop", "endline" },
|
||||
{ "memcell", "endmemcell" }, { "eventlauncher", "end" }, { "sound", "endsound" } };
|
||||
static std::unordered_map<std::string, std::string> const statementends {
|
||||
|
||||
@@ -45,6 +45,8 @@ struct light_array;
|
||||
class particle_manager;
|
||||
class road_node; // kawałek drogi, z którego powstają pasy ruchu
|
||||
class road_table;
|
||||
class junction_node;
|
||||
class junction_table;
|
||||
struct dictionary_source;
|
||||
class trainset_desc;
|
||||
class scenery_desc;
|
||||
|
||||
734
world/Road.cpp
734
world/Road.cpp
@@ -907,26 +907,52 @@ std::vector<scene::shape_node> road_node::create_shapes() const
|
||||
return shapes;
|
||||
}
|
||||
|
||||
// puts geometry of the road in the scene as shapes of its own, which can be taken back
|
||||
void road_node::show()
|
||||
namespace
|
||||
{
|
||||
if (m_section != nullptr || m_merged || simulation::Region == nullptr || false == simulation::Region->point_inside(location()))
|
||||
|
||||
// banks holding geometry put in the scene by the owned shapes
|
||||
std::unordered_set<std::uint32_t> ownedbanks;
|
||||
|
||||
double const junction_reach{0.25}; // an end of a road this close to an arm is attached to it
|
||||
double const junction_fit{0.05}; // a lane has to end this close to where the junction expects it
|
||||
int const corner_steps{6}; // pieces a rounded corner is made of
|
||||
|
||||
// direction on the ground as a vector in space
|
||||
glm::dvec3 flat(glm::dvec2 const &Direction)
|
||||
{
|
||||
return glm::dvec3{Direction.x, 0.0, Direction.y};
|
||||
}
|
||||
|
||||
// direction to the left of the provided one, on the ground
|
||||
glm::dvec2 left_of(glm::dvec2 const &Direction)
|
||||
{
|
||||
return glm::dvec2{Direction.y, -Direction.x};
|
||||
}
|
||||
|
||||
// angle of the turn from one direction to another, positive to the left
|
||||
double turn_angle(glm::dvec2 const &From, glm::dvec2 const &To)
|
||||
{
|
||||
return std::atan2(glm::dot(To, left_of(From)), glm::dot(To, From));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// puts provided shapes in the section of the scene holding specified point
|
||||
void owned_shapes::show(std::vector<scene::shape_node> Shapes, glm::dvec3 const &Location)
|
||||
{
|
||||
if (m_section != nullptr || Shapes.empty() || simulation::Region == nullptr || false == simulation::Region->point_inside(Location))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto shapes{create_shapes()};
|
||||
if (shapes.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto §ion{simulation::Region->section(location())};
|
||||
auto §ion{simulation::Region->section(Location)};
|
||||
// the rest of the section has to be built by now, or it'd count our shapes as waiting for it too
|
||||
section.create_geometry();
|
||||
if (m_bank.bank == 0 && m_bank.chunk == 0)
|
||||
{
|
||||
m_bank = GfxRenderer->Create_Bank();
|
||||
ownedbanks.emplace(m_bank.bank);
|
||||
}
|
||||
for (auto &shape : shapes)
|
||||
for (auto &shape : Shapes)
|
||||
{
|
||||
// shapes held by a section are drawn relative to its centre
|
||||
shape.origin(section.m_area.center);
|
||||
@@ -935,14 +961,14 @@ void road_node::show()
|
||||
shape.create_geometry(m_bank);
|
||||
m_geometry.emplace_back(shape.data().geometry);
|
||||
section.m_shapes.emplace_back(std::move(shape));
|
||||
// the section may need to reach further to keep the road from being culled at its edges
|
||||
// the section may need to reach further to keep the shape from being culled at its edges
|
||||
section.m_area.radius = std::max(section.m_area.radius, static_cast<float>(glm::length(section.m_area.center - centre) + radius));
|
||||
}
|
||||
m_section = §ion;
|
||||
}
|
||||
|
||||
// takes back geometry put in the scene by show()
|
||||
void road_node::hide()
|
||||
// takes the shapes back
|
||||
void owned_shapes::hide()
|
||||
{
|
||||
if (m_section == nullptr)
|
||||
{
|
||||
@@ -960,6 +986,650 @@ void road_node::hide()
|
||||
m_section = nullptr;
|
||||
}
|
||||
|
||||
// true if specified geometry was put in the scene by any object of this class
|
||||
bool owned_shapes::owns(gfx::geometry_handle const &Geometry)
|
||||
{
|
||||
return Geometry.bank != 0 && ownedbanks.count(Geometry.bank) != 0;
|
||||
}
|
||||
|
||||
// puts geometry of the road in the scene as shapes of its own, which can be taken back
|
||||
void road_node::show()
|
||||
{
|
||||
if (m_merged || m_shapes.shown())
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_shapes.show(create_shapes(), location());
|
||||
}
|
||||
|
||||
// takes back geometry put in the scene by show()
|
||||
void road_node::hide()
|
||||
{
|
||||
m_shapes.hide();
|
||||
}
|
||||
|
||||
// brings the content to a usable form
|
||||
void junction_node::state::normalize()
|
||||
{
|
||||
for (auto &arm : arms)
|
||||
{
|
||||
arm.incoming = std::clamp(arm.incoming, 0, 8);
|
||||
arm.outgoing = std::clamp(arm.outgoing, 0, 8);
|
||||
arm.width = std::max(1.f, arm.width);
|
||||
arm.direction = (glm::length(arm.direction) > 1e-6 ? glm::normalize(arm.direction) : glm::dvec2{0.0, 1.0});
|
||||
}
|
||||
if (texturelength < 0.01f)
|
||||
{
|
||||
texturelength = 4.f;
|
||||
}
|
||||
if (surface.empty())
|
||||
{
|
||||
surface = "none";
|
||||
}
|
||||
static std::array<std::string, 7> const environments{"flat", "mountains", "mountain", "canyon", "tunnel", "bridge", "bank"};
|
||||
if (std::find(environments.begin(), environments.end(), environment) == environments.end())
|
||||
{
|
||||
environment = "flat";
|
||||
}
|
||||
}
|
||||
|
||||
// width of the road at specified arm
|
||||
double junction_node::state::arm_width(std::size_t const Arm) const
|
||||
{
|
||||
return Arm < arms.size() ? static_cast<double>(arms[Arm].incoming + arms[Arm].outgoing) * arms[Arm].width : 0.0;
|
||||
}
|
||||
|
||||
// where a lane of an arm meets the junction; the lanes of each kind are counted from the middle of the road, starting with 1
|
||||
glm::dvec3 junction_node::state::lane_point(std::size_t const Arm, bool const Incoming, int const Lane) const
|
||||
{
|
||||
auto const &arm{arms[Arm]};
|
||||
// seen from the junction the traffic coming in is on the left side of the road, and the one going out on the right
|
||||
auto const divide{0.5 * arm_width(Arm) - static_cast<double>(arm.incoming) * arm.width};
|
||||
auto const offset{Incoming ? divide + (Lane - 0.5) * arm.width : divide - (Lane - 0.5) * arm.width};
|
||||
return arm.position + flat(left_of(arm.direction)) * offset;
|
||||
}
|
||||
|
||||
// arms in the order they're met going around the junction
|
||||
std::vector<std::size_t> junction_node::state::arm_order() const
|
||||
{
|
||||
std::vector<std::size_t> order(arms.size());
|
||||
for (std::size_t idx = 0; idx < order.size(); ++idx)
|
||||
{
|
||||
order[idx] = idx;
|
||||
}
|
||||
// the order goes from the z axis towards the x axis, which makes triangles spanned from the centre face up
|
||||
std::sort(order.begin(), order.end(), [this](std::size_t const Left, std::size_t const Right) { return std::atan2(arms[Left].direction.x, arms[Left].direction.y) < std::atan2(arms[Right].direction.x, arms[Right].direction.y); });
|
||||
return order;
|
||||
}
|
||||
|
||||
// edge of the surface: ends of the arms joined with rounded corners, going around the junction
|
||||
std::vector<glm::dvec3> junction_node::state::outline(std::vector<std::vector<glm::dvec3>> *Corners) const
|
||||
{
|
||||
std::vector<glm::dvec3> points;
|
||||
auto const order{arm_order()};
|
||||
for (std::size_t idx = 0; idx < order.size(); ++idx)
|
||||
{
|
||||
auto const &arm{arms[order[idx]]};
|
||||
auto const &next{arms[order[(idx + 1) % order.size()]]};
|
||||
auto const halfwidth{0.5 * arm_width(order[idx])};
|
||||
auto const nexthalfwidth{0.5 * arm_width(order[(idx + 1) % order.size()])};
|
||||
// the end of the arm, then the corner leading to the next one
|
||||
auto const right{arm.position - flat(left_of(arm.direction)) * halfwidth};
|
||||
auto const left{arm.position + flat(left_of(arm.direction)) * halfwidth};
|
||||
auto const nextright{next.position - flat(left_of(next.direction)) * nexthalfwidth};
|
||||
points.emplace_back(right);
|
||||
points.emplace_back(left);
|
||||
// the corner is bent towards the point where the edges of both roads would meet, if there's a reasonable one
|
||||
auto control{0.5 * (left + nextright)};
|
||||
auto const cross{arm.direction.x * next.direction.y - arm.direction.y * next.direction.x};
|
||||
if (std::abs(cross) > 0.05)
|
||||
{
|
||||
// left - arm.direction * t == nextright - next.direction * s
|
||||
glm::dvec2 const delta{nextright.x - left.x, nextright.z - left.z};
|
||||
auto const t{-(delta.x * next.direction.y - delta.y * next.direction.x) / cross};
|
||||
auto const s{-(delta.x * arm.direction.y - delta.y * arm.direction.x) / cross};
|
||||
if (t > 0.0 && s > 0.0 && t < 100.0 && s < 100.0)
|
||||
{
|
||||
control = left - flat(arm.direction) * t;
|
||||
control.y = 0.5 * (left.y + nextright.y);
|
||||
}
|
||||
}
|
||||
std::vector<glm::dvec3> corner{left};
|
||||
for (int step = 1; step < corner_steps; ++step)
|
||||
{
|
||||
auto const t{static_cast<double>(step) / corner_steps};
|
||||
auto const point{(1.0 - t) * (1.0 - t) * left + 2.0 * (1.0 - t) * t * control + t * t * nextright};
|
||||
points.emplace_back(point);
|
||||
corner.emplace_back(point);
|
||||
}
|
||||
corner.emplace_back(nextright);
|
||||
if (Corners != nullptr)
|
||||
{
|
||||
Corners->emplace_back(std::move(corner));
|
||||
}
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
junction_node::junction_node(scene::node_data const &Nodedata) : basic_node(Nodedata) {}
|
||||
|
||||
// restores content of the node from provided input stream; reads up to and including the closing 'endjunction'
|
||||
void junction_node::import(cParser &Input, glm::dvec3 const &Offset)
|
||||
{
|
||||
auto const label{m_name.empty() ? std::string{"unnamed junction"} : "junction \"" + m_name + "\""};
|
||||
|
||||
m_state = state{};
|
||||
Input.getTokens(3);
|
||||
Input >> m_state.centre.x >> m_state.centre.y >> m_state.centre.z;
|
||||
m_state.centre += Offset;
|
||||
|
||||
auto token{Input.getToken<std::string>()};
|
||||
while (false == token.empty() && token != "endjunction")
|
||||
{
|
||||
if (token == "arm")
|
||||
{
|
||||
// arm <position> <direction x z> <lanes in> <lanes out> <lane width>
|
||||
arm_data arm;
|
||||
Input.getTokens(3);
|
||||
Input >> arm.position.x >> arm.position.y >> arm.position.z;
|
||||
arm.position += Offset;
|
||||
Input.getTokens(2);
|
||||
Input >> arm.direction.x >> arm.direction.y;
|
||||
Input.getTokens(3);
|
||||
Input >> arm.incoming >> arm.outgoing >> arm.width;
|
||||
m_state.arms.emplace_back(arm);
|
||||
}
|
||||
else if (token == "surface")
|
||||
{
|
||||
m_state.surface = Input.getToken<std::string>();
|
||||
replace_slashes(m_state.surface);
|
||||
}
|
||||
else if (token == "texlength")
|
||||
{
|
||||
Input.getTokens();
|
||||
Input >> m_state.texturelength;
|
||||
}
|
||||
else if (token == "markings")
|
||||
{
|
||||
auto const colour{Input.getToken<std::string>()};
|
||||
m_state.markings = (colour == "white" ? road_node::marking_colour::white : colour == "orange" ? road_node::marking_colour::orange : road_node::marking_colour::none);
|
||||
}
|
||||
else if (token == "velocity")
|
||||
{
|
||||
Input.getTokens();
|
||||
Input >> m_state.velocity;
|
||||
}
|
||||
else if (token == "friction")
|
||||
{
|
||||
Input.getTokens();
|
||||
Input >> m_state.friction;
|
||||
}
|
||||
else if (token == "environment")
|
||||
{
|
||||
m_state.environment = Input.getToken<std::string>();
|
||||
}
|
||||
else
|
||||
{
|
||||
ErrorLog("Bad junction: unknown property: \"" + token + "\" defined for " + label);
|
||||
}
|
||||
token = Input.getToken<std::string>();
|
||||
}
|
||||
if (m_state.arms.size() < 2 || m_state.arms.size() > 4)
|
||||
{
|
||||
ErrorLog("Bad junction: " + label + " has " + std::to_string(m_state.arms.size()) + " arms, it takes 2 to 4");
|
||||
if (m_state.arms.size() > 4)
|
||||
{
|
||||
m_state.arms.resize(4);
|
||||
}
|
||||
}
|
||||
m_state.normalize();
|
||||
location(m_state.centre);
|
||||
}
|
||||
|
||||
void junction_node::define(state const &State)
|
||||
{
|
||||
m_state = State;
|
||||
m_state.normalize();
|
||||
location(m_state.centre);
|
||||
m_area.radius = -1.f;
|
||||
}
|
||||
|
||||
// makes paths leading through the junction, between the lanes of the roads attached to it at the moment
|
||||
void junction_node::create_links()
|
||||
{
|
||||
m_movements.clear();
|
||||
auto const &arms{m_state.arms};
|
||||
// lanes of the roads attached to the arms; a road is attached if one of its ends is where the arm is
|
||||
struct arm_lanes
|
||||
{
|
||||
std::vector<TTrack *> incoming;
|
||||
std::vector<TTrack *> outgoing;
|
||||
};
|
||||
std::vector<arm_lanes> lanes(arms.size());
|
||||
for (std::size_t arm = 0; arm < arms.size(); ++arm)
|
||||
{
|
||||
lanes[arm].incoming.assign(static_cast<std::size_t>(arms[arm].incoming), nullptr);
|
||||
lanes[arm].outgoing.assign(static_cast<std::size_t>(arms[arm].outgoing), nullptr);
|
||||
for (auto const *road : simulation::Roads.sequence())
|
||||
{
|
||||
if (road == nullptr || road->m_editorremoved || road->tracks().size() != road->definition().lanes.size())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto const &layout{road->definition()};
|
||||
for (auto const atend : {false, true})
|
||||
{
|
||||
if (glm::distance(layout.axis.points[atend ? segment_data::point::end : segment_data::point::start], arms[arm].position) > junction_reach)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// the lanes heading for this end of the road lead into the junction, the others lead out of it
|
||||
for (int lane = 1; lane <= arms[arm].incoming; ++lane)
|
||||
{
|
||||
auto const index{layout.lane_index((atend ? "f" : "b") + std::to_string(lane))};
|
||||
if (index >= 0 && road->tracks()[index] != nullptr && glm::distance(glm::dvec3{road->tracks()[index]->CurrentSegment()->FastGetPoint_1()}, m_state.lane_point(arm, true, lane)) < junction_fit)
|
||||
{
|
||||
lanes[arm].incoming[lane - 1] = road->tracks()[index];
|
||||
}
|
||||
}
|
||||
for (int lane = 1; lane <= arms[arm].outgoing; ++lane)
|
||||
{
|
||||
auto const index{layout.lane_index((atend ? "b" : "f") + std::to_string(lane))};
|
||||
if (index >= 0 && road->tracks()[index] != nullptr && glm::distance(glm::dvec3{road->tracks()[index]->CurrentSegment()->FastGetPoint_0()}, m_state.lane_point(arm, false, lane)) < junction_fit)
|
||||
{
|
||||
lanes[arm].outgoing[lane - 1] = road->tracks()[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class way
|
||||
{
|
||||
left,
|
||||
straight,
|
||||
right
|
||||
};
|
||||
struct exit_data
|
||||
{
|
||||
std::size_t arm;
|
||||
double turn;
|
||||
way kind;
|
||||
TTrack *track;
|
||||
};
|
||||
std::ostringstream text;
|
||||
text.precision(std::numeric_limits<double>::digits10);
|
||||
auto const write_point = [&text](glm::dvec3 const &Point) { text << Point.x << ' ' << Point.y << ' ' << Point.z << ' '; };
|
||||
|
||||
for (std::size_t arm = 0; arm < arms.size(); ++arm)
|
||||
{
|
||||
auto const heading{-arms[arm].direction};
|
||||
// the other arms with a road to leave by, from the leftmost to the rightmost
|
||||
std::vector<exit_data> exits;
|
||||
for (std::size_t other = 0; other < arms.size(); ++other)
|
||||
{
|
||||
if (other == arm || std::all_of(lanes[other].outgoing.begin(), lanes[other].outgoing.end(), [](TTrack const *Track) { return Track == nullptr; }))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
exits.push_back({other, turn_angle(heading, arms[other].direction), way::straight, nullptr});
|
||||
}
|
||||
std::sort(exits.begin(), exits.end(), [](exit_data const &Left, exit_data const &Right) { return Left.turn > Right.turn; });
|
||||
if (exits.size() == 3)
|
||||
{
|
||||
exits[0].kind = way::left;
|
||||
exits[2].kind = way::right;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const sideways{glm::radians(50.0)};
|
||||
for (auto &exit : exits)
|
||||
{
|
||||
exit.kind = (exit.turn > sideways ? way::left : exit.turn < -sideways ? way::right : way::straight);
|
||||
}
|
||||
if (exits.size() == 2 && exits[0].kind == exits[1].kind)
|
||||
{
|
||||
// two ways of the same kind are told apart by which one is more to the left
|
||||
if (exits[0].kind == way::right)
|
||||
{
|
||||
exits[0].kind = way::straight;
|
||||
}
|
||||
else if (exits[0].kind == way::left)
|
||||
{
|
||||
exits[1].kind = way::straight;
|
||||
}
|
||||
else
|
||||
{
|
||||
exits[0].kind = way::left;
|
||||
exits[1].kind = way::right;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto const lanecount{static_cast<int>(lanes[arm].incoming.size())};
|
||||
for (int lane = 1; lane <= lanecount; ++lane)
|
||||
{
|
||||
auto *entry{lanes[arm].incoming[lane - 1]};
|
||||
if (entry == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// with more lanes than one the inner lane takes the left turns, the outer one the right turns, and all go straight
|
||||
std::vector<exit_data> ways;
|
||||
for (auto const &exit : exits)
|
||||
{
|
||||
auto const allowed{lanecount == 1 || exit.kind == way::straight || (exit.kind == way::left && lane == 1) || (exit.kind == way::right && lane == lanecount)};
|
||||
if (allowed)
|
||||
{
|
||||
ways.emplace_back(exit);
|
||||
}
|
||||
}
|
||||
if (ways.empty())
|
||||
{
|
||||
ways = exits;
|
||||
}
|
||||
for (auto &exit : ways)
|
||||
{
|
||||
// the lane of the same number on the road to leave by, or the nearest one there is
|
||||
auto const &candidates{lanes[exit.arm].outgoing};
|
||||
for (int shift = 0; shift < static_cast<int>(candidates.size()) && exit.track == nullptr; ++shift)
|
||||
{
|
||||
for (auto const index : {lane - 1 - shift, lane - 1 + shift})
|
||||
{
|
||||
if (index >= 0 && index < static_cast<int>(candidates.size()) && candidates[index] != nullptr)
|
||||
{
|
||||
exit.track = candidates[index];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (exit.track == nullptr)
|
||||
{
|
||||
exit.track = candidates.back();
|
||||
}
|
||||
}
|
||||
ways.erase(std::remove_if(ways.begin(), ways.end(), [](exit_data const &Exit) { return Exit.track == nullptr; }), ways.end());
|
||||
if (ways.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (ways.size() == 3)
|
||||
{
|
||||
// the legacy crossroads has the way straight ahead at its second point, the left one at the third and the right one at the fourth
|
||||
std::swap(ways[0], ways[1]);
|
||||
}
|
||||
|
||||
// shape of the ways: each leaves the lane coming in the way that lane goes, and joins the lane going out the way that one goes
|
||||
glm::dvec3 const start{entry->CurrentSegment()->FastGetPoint_1()};
|
||||
std::vector<glm::dvec3> ends;
|
||||
std::vector<glm::dvec3> endcontrols;
|
||||
double reach{0.0};
|
||||
for (auto const &exit : ways)
|
||||
{
|
||||
glm::dvec3 const end{exit.track->CurrentSegment()->FastGetPoint_0()};
|
||||
auto const span{glm::distance(start, end)};
|
||||
ends.emplace_back(end);
|
||||
endcontrols.emplace_back(-flat(arms[exit.arm].direction) * (0.39 * span));
|
||||
reach += 0.39 * span / ways.size();
|
||||
}
|
||||
auto const startcontrol{flat(heading) * reach};
|
||||
for (std::size_t idx = 0; idx < ways.size(); ++idx)
|
||||
{
|
||||
segment_data movement;
|
||||
movement.points[segment_data::point::start] = start;
|
||||
movement.points[segment_data::point::control1] = startcontrol;
|
||||
movement.points[segment_data::point::control2] = endcontrols[idx];
|
||||
movement.points[segment_data::point::end] = ends[idx];
|
||||
m_movements.emplace_back(movement);
|
||||
}
|
||||
|
||||
text.str("");
|
||||
text << (ways.size() == 1 ? "road " : "cross ") << glm::distance(start, ends[0]) << ' ' << arms[arm].width << ' ' << m_state.friction << ' ' << m_state.sounddistance << ' ' << m_state.quality << " 0 " << m_state.environment << " unvis ";
|
||||
// first path: from the lane coming in to the first way out
|
||||
write_point(start);
|
||||
text << "0 ";
|
||||
write_point(startcontrol);
|
||||
write_point(endcontrols[0]);
|
||||
write_point(ends[0]);
|
||||
text << "0 0 ";
|
||||
if (ways.size() == 2)
|
||||
{
|
||||
// second path of a crossroads of three roads starts where the first one does
|
||||
write_point(start);
|
||||
text << "0 ";
|
||||
write_point(startcontrol);
|
||||
write_point(endcontrols[1]);
|
||||
write_point(ends[1]);
|
||||
text << "0 0 ";
|
||||
}
|
||||
else if (ways.size() == 3)
|
||||
{
|
||||
// second path of a crossroads of four roads runs between its third and fourth point
|
||||
write_point(ends[1]);
|
||||
text << "0 ";
|
||||
write_point(endcontrols[1]);
|
||||
write_point(endcontrols[2]);
|
||||
write_point(ends[2]);
|
||||
text << "0 0 ";
|
||||
}
|
||||
if (m_state.velocity > 0.f)
|
||||
{
|
||||
text << "velocity " << m_state.velocity << ' ';
|
||||
}
|
||||
text << "endtrack";
|
||||
auto *link{create_track(text.str(), m_links.size())};
|
||||
|
||||
// the path is tied to the lanes by hand: the lanes going out are shared by the paths from all the other arms,
|
||||
// which is more than the automatic joining of path ends can express
|
||||
if (ways.size() == 1)
|
||||
{
|
||||
entry->ConnectNextPrev(link, 0);
|
||||
link->trNext = ways[0].track;
|
||||
link->iNextDirection = 0;
|
||||
if (ways[0].track->trPrev == nullptr)
|
||||
{
|
||||
ways[0].track->trPrev = link;
|
||||
ways[0].track->iPrevDirection = 1;
|
||||
}
|
||||
}
|
||||
else if (link->SwitchExtension != nullptr)
|
||||
{
|
||||
auto &extension{*link->SwitchExtension};
|
||||
// the points of the crossroads are stored as: first, second, fourth, third
|
||||
extension.pPrevs[0] = entry;
|
||||
extension.iPrevDirection[0] = 1;
|
||||
extension.pNexts[0] = ways[0].track;
|
||||
extension.iNextDirection[0] = 0;
|
||||
extension.pPrevs[1] = (ways.size() == 3 ? ways[2].track : ways[1].track);
|
||||
extension.iPrevDirection[1] = 0;
|
||||
extension.pNexts[1] = (ways.size() == 3 ? ways[1].track : nullptr);
|
||||
extension.iNextDirection[1] = 0;
|
||||
link->Switch(0);
|
||||
entry->trNext = link;
|
||||
entry->iNextDirection = 0;
|
||||
// the lanes going out get to know one of the paths leading to them, for whatever goes backwards
|
||||
int const ends2[]{1, 2};
|
||||
int const ends3[]{1, 3, 2};
|
||||
for (std::size_t idx = 0; idx < ways.size(); ++idx)
|
||||
{
|
||||
if (ways[idx].track->trPrev == nullptr)
|
||||
{
|
||||
ways[idx].track->trPrev = link;
|
||||
ways[idx].track->iPrevDirection = (ways.size() == 3 ? ends3[idx] : ends2[idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_links.emplace_back(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// gives up the paths leading through the junction
|
||||
std::vector<TTrack *> junction_node::release_links()
|
||||
{
|
||||
std::vector<TTrack *> released;
|
||||
released.swap(m_links);
|
||||
m_movements.clear();
|
||||
return released;
|
||||
}
|
||||
|
||||
// true if there's a vehicle on any path of the junction
|
||||
bool junction_node::occupied() const
|
||||
{
|
||||
return std::any_of(m_links.begin(), m_links.end(), [](TTrack const *Track) { return Track != nullptr && false == Track->Dynamics.empty(); });
|
||||
}
|
||||
|
||||
// generates geometry of the surface
|
||||
std::vector<scene::shape_node> junction_node::create_shapes() const
|
||||
{
|
||||
std::vector<scene::shape_node> shapes;
|
||||
std::vector<std::vector<glm::dvec3>> corners;
|
||||
auto const points{m_state.outline(&corners)};
|
||||
if (points.size() < 3)
|
||||
{
|
||||
return shapes;
|
||||
}
|
||||
auto const ¢re{m_state.centre};
|
||||
if (m_state.surface != "none")
|
||||
{
|
||||
// a fan of triangles spanned from the centre, with the image laid out flat over the ground
|
||||
auto const tile{static_cast<double>(texture_ratio(m_state.surface)) * m_state.texturelength};
|
||||
auto const vertex = [&](glm::dvec3 const &Point, glm::vec3 const &Normal) {
|
||||
return world_vertex{Point, Normal, {static_cast<float>(0.5 + (Point.x - centre.x) / tile), static_cast<float>(0.5 + (Point.z - centre.z) / m_state.texturelength)}};
|
||||
};
|
||||
std::vector<world_vertex> vertices;
|
||||
for (std::size_t idx = 0; idx < points.size(); ++idx)
|
||||
{
|
||||
auto const ¤t{points[idx]};
|
||||
auto const &next{points[(idx + 1) % points.size()]};
|
||||
auto normal{glm::cross(current - centre, next - centre)};
|
||||
if (glm::length2(normal) < 1e-12)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
glm::vec3 const facing{glm::normalize(normal)};
|
||||
vertices.emplace_back(vertex(centre, facing));
|
||||
vertices.emplace_back(vertex(current, facing));
|
||||
vertices.emplace_back(vertex(next, facing));
|
||||
}
|
||||
if (false == vertices.empty())
|
||||
{
|
||||
scene::shape_node shape;
|
||||
shape.make_terrain(GfxRenderer->Fetch_Material(m_state.surface), std::move(vertices), glm::dvec3{0.0});
|
||||
shapes.emplace_back(std::move(shape));
|
||||
}
|
||||
}
|
||||
if (m_state.markings != road_node::marking_colour::none)
|
||||
{
|
||||
// edge lines carried around the corners
|
||||
std::vector<world_vertex> vertices;
|
||||
glm::dvec3 const lift{0.0, line_lift, 0.0};
|
||||
glm::vec3 const upwards{0.f, 1.f, 0.f};
|
||||
for (auto const &corner : corners)
|
||||
{
|
||||
for (std::size_t idx = 0; idx + 1 < corner.size(); ++idx)
|
||||
{
|
||||
// the line keeps its distance from the edge, on the side of the centre
|
||||
auto const inwards = [&](glm::dvec3 const &Point) {
|
||||
auto direction{centre - Point};
|
||||
direction.y = 0.0;
|
||||
return glm::length2(direction) > 1e-12 ? glm::normalize(direction) : glm::dvec3{0.0};
|
||||
};
|
||||
auto const outer0{corner[idx] + inwards(corner[idx]) * edge_inset + lift};
|
||||
auto const inner0{corner[idx] + inwards(corner[idx]) * (edge_inset + line_width) + lift};
|
||||
auto const outer1{corner[idx + 1] + inwards(corner[idx + 1]) * edge_inset + lift};
|
||||
auto const inner1{corner[idx + 1] + inwards(corner[idx + 1]) * (edge_inset + line_width) + lift};
|
||||
// the corners are listed going around the junction the way which makes these face up
|
||||
vertices.push_back({inner0, upwards, {0.f, 0.f}});
|
||||
vertices.push_back({outer0, upwards, {1.f, 0.f}});
|
||||
vertices.push_back({outer1, upwards, {1.f, 1.f}});
|
||||
vertices.push_back({inner0, upwards, {0.f, 0.f}});
|
||||
vertices.push_back({outer1, upwards, {1.f, 1.f}});
|
||||
vertices.push_back({inner1, upwards, {0.f, 1.f}});
|
||||
}
|
||||
}
|
||||
if (false == vertices.empty())
|
||||
{
|
||||
lighting_data paint;
|
||||
paint.diffuse = (m_state.markings == road_node::marking_colour::white ? glm::vec4{0.92f, 0.92f, 0.92f, 1.f} : glm::vec4{0.95f, 0.5f, 0.08f, 1.f});
|
||||
paint.ambient = paint.diffuse;
|
||||
scene::shape_node shape;
|
||||
shape.make_terrain(GfxRenderer->Fetch_Material("colored"), std::move(vertices), glm::dvec3{0.0});
|
||||
shape.lighting(paint);
|
||||
shapes.emplace_back(std::move(shape));
|
||||
}
|
||||
}
|
||||
return shapes;
|
||||
}
|
||||
|
||||
void junction_node::show()
|
||||
{
|
||||
if (m_merged || m_shapes.shown())
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_shapes.show(create_shapes(), location());
|
||||
}
|
||||
|
||||
void junction_node::hide()
|
||||
{
|
||||
m_shapes.hide();
|
||||
}
|
||||
|
||||
// creates a path through the junction from provided definition and registers it with the simulation
|
||||
TTrack *junction_node::create_track(std::string const &Definition, std::size_t const Index)
|
||||
{
|
||||
cParser parser(Definition, cParser::buffer_TEXT);
|
||||
scene::node_data nodedata;
|
||||
nodedata.range_max = -1.0;
|
||||
nodedata.name = (m_name.empty() ? std::string{} : m_name + ":way" + std::to_string(Index + 1));
|
||||
nodedata.type = "track";
|
||||
auto *track{new TTrack(nodedata)};
|
||||
track->Load(&parser, glm::dvec3{0.0});
|
||||
track->m_road = this;
|
||||
// NOTE: the table points the name at the newest path, which is what a junction made anew in the editor needs
|
||||
simulation::Paths.insert(track);
|
||||
simulation::Region->insert_and_register(track);
|
||||
return track;
|
||||
}
|
||||
|
||||
float junction_node::radius_()
|
||||
{
|
||||
float radius{0.f};
|
||||
for (std::size_t arm = 0; arm < m_state.arms.size(); ++arm)
|
||||
{
|
||||
radius = std::max(radius, static_cast<float>(glm::distance(m_state.arms[arm].position, m_state.centre) + 0.5 * m_state.arm_width(arm)));
|
||||
}
|
||||
return radius;
|
||||
}
|
||||
|
||||
void junction_node::serialize_(std::ostream &Output) const
|
||||
{
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
void junction_node::deserialize_(std::istream &Input)
|
||||
{
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
// export() subclass details, sends basic content of the class in legacy (text) format to provided stream
|
||||
void junction_node::export_as_text_(std::ostream &Output) const
|
||||
{
|
||||
Output << "junction ";
|
||||
auto const precision{Output.precision(std::numeric_limits<double>::digits10)};
|
||||
Output << m_state.centre.x << ' ' << m_state.centre.y << ' ' << m_state.centre.z << ' ';
|
||||
for (auto const &arm : m_state.arms)
|
||||
{
|
||||
Output << "arm " << arm.position.x << ' ' << arm.position.y << ' ' << arm.position.z << ' ' << arm.direction.x << ' ' << arm.direction.y << ' ' << arm.incoming << ' ' << arm.outgoing << ' ' << arm.width << ' ';
|
||||
}
|
||||
Output.precision(precision);
|
||||
Output << "surface " << m_state.surface << ' ' << "texlength " << m_state.texturelength << ' ' << "markings "
|
||||
<< (m_state.markings == road_node::marking_colour::white ? "white" : m_state.markings == road_node::marking_colour::orange ? "orange" : "none") << ' ' << "velocity " << m_state.velocity << ' ' << "friction " << m_state.friction << ' '
|
||||
<< "environment " << m_state.environment << ' ';
|
||||
Output << "endjunction"
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
// creates a path for the vehicles and registers it with the simulation
|
||||
TTrack *road_node::create_track(std::string const &Name, segment_data const &Path, float const Width, float const Velocity)
|
||||
{
|
||||
@@ -1116,4 +1786,42 @@ void road_table::create_geometry(scene::scratch_data &Scratchpad)
|
||||
}
|
||||
}
|
||||
|
||||
// legacy style initialization, to be performed when the tracks are joined, ahead of the roads
|
||||
void junction_table::InitJunctions()
|
||||
{
|
||||
for (auto *junction : m_items)
|
||||
{
|
||||
if (junction != nullptr)
|
||||
{
|
||||
junction->create_links();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generates geometry of the junctions and puts it in the scene
|
||||
void junction_table::create_geometry(scene::scratch_data &Scratchpad)
|
||||
{
|
||||
if (simulation::Region == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (auto *junction : m_items)
|
||||
{
|
||||
if (junction == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (Global.editor_session && false == Global.NvRenderer)
|
||||
{
|
||||
junction->show();
|
||||
continue;
|
||||
}
|
||||
for (auto &shape : junction->create_shapes())
|
||||
{
|
||||
simulation::Region->insert(shape, Scratchpad, false);
|
||||
}
|
||||
junction->merged(true);
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
138
world/Road.h
138
world/Road.h
@@ -26,6 +26,27 @@ struct scratch_data;
|
||||
class basic_section;
|
||||
}
|
||||
|
||||
// geometry a node puts in the scene as shapes of its own, apart from the shapes of the scenery, so it can take it back
|
||||
class owned_shapes
|
||||
{
|
||||
public:
|
||||
// puts provided shapes in the section of the scene holding specified point
|
||||
void show(std::vector<scene::shape_node> Shapes, glm::dvec3 const &Location);
|
||||
// takes the shapes back
|
||||
void hide();
|
||||
bool shown() const
|
||||
{
|
||||
return m_section != nullptr;
|
||||
}
|
||||
// true if specified geometry was put in the scene by any object of this class
|
||||
static bool owns(gfx::geometry_handle const &Geometry);
|
||||
|
||||
private:
|
||||
gfx::geometrybank_handle m_bank{0, 0};
|
||||
std::vector<gfx::geometry_handle> m_geometry;
|
||||
scene::basic_section *m_section{nullptr};
|
||||
};
|
||||
|
||||
// a piece of a road for wheeled traffic: an axis, and a cross-section which stays the same along it.
|
||||
// the node itself is neither driven on nor drawn. it produces the lanes, which are ordinary invisible one-way tracks,
|
||||
// and the surface, the sides and the markings, which are ordinary shapes. this way the vehicles, the ai and the renderers
|
||||
@@ -176,10 +197,108 @@ class road_node : public scene::basic_node
|
||||
std::vector<TTrack *> m_tracks;
|
||||
std::vector<TTrack *> m_turns;
|
||||
bool m_merged{false};
|
||||
// geometry put in the scene by show()
|
||||
gfx::geometrybank_handle m_bank{0, 0};
|
||||
std::vector<gfx::geometry_handle> m_geometry;
|
||||
scene::basic_section *m_section{nullptr};
|
||||
owned_shapes m_shapes; // geometry put in the scene by show()
|
||||
};
|
||||
|
||||
// a place where roads meet. each arm of the junction is where a road ends: a point in the middle of that end, the way
|
||||
// the road leaves, and the lanes it has there. the junction produces the surface between the arms, and paths leading
|
||||
// the vehicles from each lane coming in to the lanes going out of the other arms. a lane with more than one way out
|
||||
// gets a legacy crossroads path, which is what the vehicles and their drivers already know how to pick a way through.
|
||||
// scenery entry:
|
||||
// node <max> <min> <name> junction <centre> arm <position> <direction x z> <lanes in> <lanes out> <lane width> ... [<property> <values>]... endjunction
|
||||
class junction_node : public scene::basic_node
|
||||
{
|
||||
|
||||
public:
|
||||
// types
|
||||
struct arm_data
|
||||
{
|
||||
glm::dvec3 position{0.0}; // middle of the end of the road
|
||||
glm::dvec2 direction{0.0, 1.0}; // the way out of the junction, seen from above (x, z)
|
||||
int incoming{1}; // lanes leading into the junction
|
||||
int outgoing{1}; // lanes leading out of it
|
||||
float width{3.5f}; // width of a lane
|
||||
};
|
||||
struct state
|
||||
{
|
||||
glm::dvec3 centre{0.0};
|
||||
std::vector<arm_data> arms;
|
||||
std::string surface{"asphaltdark1"};
|
||||
float texturelength{4.f};
|
||||
road_node::marking_colour markings{road_node::marking_colour::white};
|
||||
float velocity{30.f}; // speed limit on the way through
|
||||
float friction{0.85f};
|
||||
float sounddistance{25.f};
|
||||
int quality{15};
|
||||
std::string environment{"flat"};
|
||||
|
||||
// brings the content to a usable form
|
||||
void normalize();
|
||||
// width of the road at specified arm
|
||||
double arm_width(std::size_t const Arm) const;
|
||||
// where a lane of an arm meets the junction; the lanes of each kind are counted from the middle of the road, starting with 1
|
||||
glm::dvec3 lane_point(std::size_t const Arm, bool const Incoming, int const Lane) const;
|
||||
// arms in the order they're met going around the junction
|
||||
std::vector<std::size_t> arm_order() const;
|
||||
// edge of the surface: ends of the arms joined with rounded corners, going around the junction.
|
||||
// Corners, if provided, receives the points of each corner apart
|
||||
std::vector<glm::dvec3> outline(std::vector<std::vector<glm::dvec3>> *Corners = nullptr) const;
|
||||
};
|
||||
// constructors
|
||||
explicit junction_node(scene::node_data const &Nodedata);
|
||||
// methods
|
||||
// restores content of the node from provided input stream; reads up to and including the closing 'endjunction'
|
||||
void import(cParser &Input, glm::dvec3 const &Offset);
|
||||
state const &definition() const
|
||||
{
|
||||
return m_state;
|
||||
}
|
||||
void define(state const &State);
|
||||
// makes paths leading through the junction, between the lanes of the roads attached to it at the moment
|
||||
void create_links();
|
||||
// gives up the paths leading through the junction; the caller is expected to take them out of use
|
||||
std::vector<TTrack *> release_links();
|
||||
std::vector<TTrack *> const &links() const
|
||||
{
|
||||
return m_links;
|
||||
}
|
||||
// shapes of the ways through the junction, for display
|
||||
std::vector<segment_data> const &movements() const
|
||||
{
|
||||
return m_movements;
|
||||
}
|
||||
// true if there's a vehicle on any path of the junction
|
||||
bool occupied() const;
|
||||
// generates geometry of the surface
|
||||
std::vector<scene::shape_node> create_shapes() const;
|
||||
// puts geometry of the junction in the scene as shapes of its own, or takes it back
|
||||
void show();
|
||||
void hide();
|
||||
void merged(bool const Merged)
|
||||
{
|
||||
m_merged = Merged;
|
||||
}
|
||||
bool merged() const
|
||||
{
|
||||
return m_merged;
|
||||
}
|
||||
// members
|
||||
bool m_editorremoved{false};
|
||||
|
||||
private:
|
||||
// methods
|
||||
float radius_() override;
|
||||
void serialize_(std::ostream &Output) const override;
|
||||
void deserialize_(std::istream &Input) 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
|
||||
TTrack *create_track(std::string const &Definition, std::size_t const Index);
|
||||
// members
|
||||
state m_state;
|
||||
std::vector<TTrack *> m_links;
|
||||
std::vector<segment_data> m_movements;
|
||||
bool m_merged{false};
|
||||
owned_shapes m_shapes;
|
||||
};
|
||||
|
||||
// collection of roads present in the scene
|
||||
@@ -193,4 +312,15 @@ class road_table : public basic_table<road_node>
|
||||
void create_geometry(scene::scratch_data &Scratchpad);
|
||||
};
|
||||
|
||||
// collection of road junctions present in the scene
|
||||
class junction_table : public basic_table<junction_node>
|
||||
{
|
||||
|
||||
public:
|
||||
// legacy style initialization, to be performed when the tracks are joined, ahead of the roads
|
||||
void InitJunctions();
|
||||
// generates geometry of the junctions and puts it in the scene
|
||||
void create_geometry(scene::scratch_data &Scratchpad);
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@@ -186,7 +186,7 @@ public:
|
||||
|
||||
std::vector<segment_data> m_paths; // source data for owned paths
|
||||
bool m_editorremoved { false };
|
||||
road_node *m_road { nullptr }; // road which generated this path as one of its lanes. such path is one-way, and isn't saved nor edited on its own
|
||||
scene::basic_node *m_road { nullptr }; // road or road junction which generated this path. such path is one-way, and isn't saved nor edited on its own
|
||||
int iterate_stamp = 0;
|
||||
|
||||
// sleepermodel optional parameter -------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user