17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 08:39:19 +02:00

Merge branch 'feat/modern-editor' of https://github.com/MaSzyna-EU07/maszyna into feat/modern-editor

This commit is contained in:
2026-10-04 21:58:19 +02:00
32 changed files with 7287 additions and 739 deletions

View File

@@ -9,6 +9,8 @@ http:
#include "stdafx.h"
#include "editor/editorTrack.hpp"
#include "editor/editorFormat.hpp"
#include "editor/editorGeometry.hpp"
#include "world/Track.h"
#include "vehicle/DynObj.h"
@@ -27,12 +29,20 @@ http:
#include <numeric>
#include <functional>
#include <sstream>
#include <tuple>
#include <unordered_set>
namespace
{
double const kSamePoint{0.05};
using geometry::bezier;
using geometry::chord_length;
using geometry::cross;
using geometry::plan_of;
using geometry::signed_angle;
using geometry::turned;
double const kSamePoint{geometry::same_point};
segment_data::point point_index(editor_track::point_kind const Kind)
{
@@ -45,11 +55,6 @@ segment_data::point point_index(editor_track::point_kind const Kind)
}
}
bool is_end(editor_track::point_kind const Kind)
{
return Kind == editor_track::point_kind::start || Kind == editor_track::point_kind::end;
}
bool has_switch_paths(TTrack const &Track)
{
return Track.SwitchExtension != nullptr && (Track.eType == tt_Switch || Track.eType == tt_Cross);
@@ -74,17 +79,11 @@ glm::dvec3 path_tangent(segment_data const &Path, bool const Atend)
double path_radius(segment_data const &Path, double const T)
{
auto const &p0{Path.points[segment_data::point::start]};
auto const &p3{Path.points[segment_data::point::end]};
auto const &control1{Path.points[segment_data::point::control1]};
auto const &control2{Path.points[segment_data::point::control2]};
if (control1 == glm::dvec3{} && control2 == glm::dvec3{})
if (Path.points[segment_data::point::control1] == glm::dvec3{} && Path.points[segment_data::point::control2] == glm::dvec3{})
return 0.0;
auto const p1{p0 + control1};
auto const p2{p3 + control2};
auto const u{1.0 - T};
auto const first{3.0 * u * u * (p1 - p0) + 6.0 * u * T * (p2 - p1) + 3.0 * T * T * (p3 - p2)};
auto const second{6.0 * u * (p2 - 2.0 * p1 + p0) + 6.0 * T * (p3 - 2.0 * p2 + p1)};
bezier const curve{Path};
auto const first{curve.first(T)};
auto const second{curve.second(T)};
auto const speed{std::hypot(first.x, first.z)};
auto const curvature{std::abs(first.x * second.z - first.z * second.x) / (speed * speed * speed)};
return curvature > 1e-7 ? 1.0 / curvature : 0.0;
@@ -123,8 +122,12 @@ bool adjoining_direction(TTrack const *Neighbour, glm::dvec3 const &Joint, bool
}
editor_track::observer *editor_track::s_observer{nullptr};
editor_track::state editor_track::capture(TTrack const &Track)
{
if (s_observer != nullptr)
s_observer->track_captured(Track);
state result;
result.paths = Track.m_paths;
result.velocity = Track.fVelocity;
@@ -237,6 +240,8 @@ void editor_track::commit(std::vector<TTrack *> const &Tracks)
for (auto *track : affected)
for (auto *vehicle : track->Dynamics)
vehicle->Move(0.000001);
if (s_observer != nullptr)
s_observer->tracks_committed(Tracks);
}
void editor_track::commit_parameters(TTrack &Track)
@@ -384,7 +389,7 @@ std::vector<std::pair<TTrack *, editor_track::point_ref>> editor_track::connecte
bool editor_track::is_connected(TTrack const &Track, point_ref const &Point)
{
if (false == is_end(Point.kind))
if (false == editor_track::is_end(Point.kind))
return false;
return false == connected_points(Track, point_position(Track, Point)).empty();
}
@@ -447,7 +452,7 @@ editor_track::snap_target editor_track::find_snap_target(TTrack const &Track, gl
void editor_track::snap_point(TTrack &Track, point_ref const &Point, snap_target const &Target, bool const Aligntangent)
{
if (Target.track == nullptr || false == is_end(Point.kind))
if (Target.track == nullptr || false == editor_track::is_end(Point.kind))
return;
move_point(Track, Point, Target.position);
@@ -777,23 +782,6 @@ bool editor_track::find_chain(TTrack *From, TTrack *To, chain &Chain, std::strin
namespace
{
glm::dvec2 plan_of(glm::dvec3 const &Point)
{
return {Point.x, Point.z};
}
glm::dvec2 turned(glm::dvec2 const &Vector, double const Angle)
{
auto const c{std::cos(Angle)};
auto const s{std::sin(Angle)};
return {Vector.x * c - Vector.y * s, Vector.x * s + Vector.y * c};
}
double lateral_of(glm::dvec2 const &Direction, glm::dvec2 const &Offset)
{
return Direction.x * Offset.y - Direction.y * Offset.x;
}
bool plan_straight(segment_data const &Path, editor_track::straight_tolerance const &Tolerance)
{
auto const chord{plan_of(Path.points[segment_data::point::end] - Path.points[segment_data::point::start])};
@@ -808,7 +796,7 @@ bool plan_straight(segment_data const &Path, editor_track::straight_tolerance co
return true;
auto const direction{chord / length};
for (auto const index : {segment_data::point::control1, segment_data::point::control2})
if (std::abs(lateral_of(direction, plan_of(Path.points[index]))) > Tolerance.offset)
if (std::abs(cross(direction, plan_of(Path.points[index]))) > Tolerance.offset)
return false;
auto const tangentstart{plan_of(path_tangent(Path, false))};
auto const tangentend{plan_of(path_tangent(Path, true))};
@@ -816,8 +804,8 @@ bool plan_straight(segment_data const &Path, editor_track::straight_tolerance co
return false;
auto const a{glm::normalize(tangentstart)};
auto const b{glm::normalize(tangentend)};
auto const turn{std::abs(std::atan2(lateral_of(a, b), glm::dot(a, b)))};
auto const deviation{std::max(std::abs(lateral_of(direction, a)), std::abs(lateral_of(direction, b)))};
auto const turn{std::abs(std::atan2(cross(a, b), glm::dot(a, b)))};
auto const deviation{std::max(std::abs(cross(direction, a)), std::abs(cross(direction, b)))};
return turn * Tolerance.radius <= length && deviation * Tolerance.radius <= length;
}
@@ -841,11 +829,6 @@ glm::dvec2 direction_at(TTrack const &Track, glm::dvec3 const &Joint, bool const
return {0.0, 1.0};
}
double signed_angle(glm::dvec2 const &From, glm::dvec2 const &To)
{
return std::atan2(lateral_of(From, To), glm::dot(From, To));
}
TTrack *neighbour_at(TTrack const &Track, int const Path, bool const Atend)
{
if (has_switch_paths(Track))
@@ -928,7 +911,7 @@ editor_track::straight editor_track::find_straight(TTrack &Track, straight_toler
auto const &first{Track.m_paths[firstpath]};
auto const origin{first.points[segment_data::point::start]};
auto const direction{glm::normalize(plan_of(first.points[segment_data::point::end] - origin))};
auto const lateral = [&](glm::dvec3 const &Point) { return std::abs(lateral_of(direction, plan_of(Point - origin))); };
auto const lateral = [&](glm::dvec3 const &Point) { return std::abs(cross(direction, plan_of(Point - origin))); };
auto const accepts = [&](TTrack const *Other, glm::dvec3 const &Joint) {
if (Other == nullptr || false == is_supported(*Other) || Other->m_editorremoved)
return -1;
@@ -942,7 +925,7 @@ editor_track::straight editor_track::find_straight(TTrack &Track, straight_toler
if (glm::distance(start, Joint) > kSamePoint && glm::distance(end, Joint) > kSamePoint)
continue;
auto const otherdirection{glm::normalize(plan_of(end - start))};
if (std::abs(lateral_of(direction, otherdirection)) > Tolerance.angle)
if (std::abs(cross(direction, otherdirection)) > Tolerance.angle)
continue;
if (lateral(start) > Tolerance.offset || lateral(end) > Tolerance.offset)
continue;
@@ -1182,6 +1165,538 @@ bool editor_track::find_curve(TTrack &Track, straight_tolerance const &Tolerance
return true;
}
namespace
{
// shorter remainders of a straight path are absorbed by moving the joint instead of splitting the path
double split_margin(double const Length)
{
return std::max(0.5, 0.002 * Length);
}
} // namespace
editor_track::straight_run editor_track::straight_beyond(chain const &Chain, bool const Atend, straight_tolerance const &Tolerance)
{
straight_run run;
if (Chain.tracks.empty())
return run;
auto *edge{Atend ? Chain.tracks.back() : Chain.tracks.front()};
bool const forward{Atend ? Chain.forward.back() : Chain.forward.front()};
TTrack *current{forward == Atend ? edge->trNext : edge->trPrev};
auto joint{Atend ? Chain.end : Chain.start};
auto const origin{plan_of(joint)};
auto direction{glm::normalize(plan_of(Atend ? Chain.end_direction : -Chain.start_direction))};
while (current != nullptr && run.tracks.size() < 1000)
{
std::string reason;
if (current->eType != tt_Normal || current->m_editorremoved || false == can_edit_geometry(*current, reason))
break;
if (std::find(Chain.tracks.begin(), Chain.tracks.end(), current) != Chain.tracks.end() || std::find(run.tracks.begin(), run.tracks.end(), current) != run.tracks.end())
break;
auto const &path{current->m_paths.front()};
if (false == plan_straight(path, Tolerance))
break;
bool outward;
if (glm::distance(path.points[segment_data::point::start], joint) <= kSamePoint)
outward = true;
else if (glm::distance(path.points[segment_data::point::end], joint) <= kSamePoint)
outward = false;
else
break;
auto const &opposite{path.points[outward ? segment_data::point::end : segment_data::point::start]};
if (run.tracks.empty())
{
auto const own{glm::normalize(plan_of(opposite - joint))};
if (std::abs(cross(direction, own)) > 0.01 || glm::dot(direction, own) <= 0.0)
break;
direction = own;
}
auto const offset{plan_of(opposite) - origin};
auto const along{glm::dot(offset, direction)};
if (along <= run.length + 1e-3 || std::abs(cross(direction, offset)) > Tolerance.offset)
break;
run.tracks.push_back(current);
run.outward.push_back(outward);
run.length = along;
joint = opposite;
current = outward ? current->trNext : current->trPrev;
}
return run;
}
double editor_track::run_reserve(straight_run const &Run)
{
if (Run.tracks.empty())
return 0.0;
return std::max(0.0, Run.length - split_margin(chord_length(Run.tracks.back()->m_paths.front())) - 0.01);
}
TTrack *editor_track::take_straight(straight_run const &Run, TTrack *Edge, glm::dvec3 const &Joint, glm::dvec3 const &Cut, std::vector<std::pair<TTrack *, state>> &States, std::vector<TTrack *> &Created)
{
auto const length{glm::length(plan_of(Cut - Joint))};
auto *edge{Edge};
double covered{0.0};
for (std::size_t i = 0; i < Run.tracks.size(); ++i)
{
auto *track{Run.tracks[i]};
auto const outward{Run.outward[i]};
auto const &path{track->m_paths.front()};
auto const pathlength{chord_length(path)};
auto const margin{split_margin(pathlength)};
auto const remaining{length - covered};
if (remaining <= margin)
{
if (std::abs(remaining) > 1e-4)
{
States.emplace_back(track, capture(*track));
move_point(*track, {0, outward ? point_kind::start : point_kind::end}, Cut);
commit({track});
}
break;
}
if (remaining < pathlength - margin)
{
auto const &nearpoint{path.points[outward ? segment_data::point::start : segment_data::point::end]};
auto const &farpoint{path.points[outward ? segment_data::point::end : segment_data::point::start]};
auto const t{nearest_parameter(*track, nearpoint + (farpoint - nearpoint) * (remaining / pathlength))};
auto const before{capture(*track)};
auto *created{split_path(*track, t)};
if (created == nullptr)
break;
States.emplace_back(track, before);
Created.push_back(created);
edge = outward ? track : created;
break;
}
edge = track;
covered += pathlength;
}
return edge;
}
namespace
{
using route_node = std::tuple<TTrack *, int, bool>;
std::vector<route_node> route_next(route_node const &Node)
{
std::vector<route_node> result;
auto *track{std::get<0>(Node)};
auto const path{std::get<1>(Node)};
auto const forward{std::get<2>(Node)};
auto const &exit{track->m_paths[path].points[forward ? segment_data::point::end : segment_data::point::start]};
auto *next{neighbour_at(*track, path, forward)};
if (next == nullptr || next == track || next->m_editorremoved || false == editor_track::is_supported(*next) || (next->iCategoryFlag & 15) != (track->iCategoryFlag & 15))
return result;
for (int i = 0; i < static_cast<int>(next->m_paths.size()); ++i)
{
auto const &candidate{next->m_paths[i]};
if (glm::distance(candidate.points[segment_data::point::start], exit) <= kSamePoint)
result.emplace_back(next, i, true);
else if (glm::distance(candidate.points[segment_data::point::end], exit) <= kSamePoint)
result.emplace_back(next, i, false);
}
return result;
}
} // namespace
bool editor_track::find_route(TTrack *From, TTrack *To, route &Route, std::string &Error)
{
Route = {};
if (From == nullptr || To == nullptr || false == is_supported(*From) || false == is_supported(*To))
{
Error = "Select the first and the last path of the route";
return false;
}
if (From == To)
{
Route.spans.push_back({From, 0, true});
Error.clear();
return true;
}
std::vector<route_node> nodes;
std::vector<int> parents;
std::map<route_node, int> visited;
for (int i = 0; i < static_cast<int>(From->m_paths.size()); ++i)
for (auto const forward : {true, false})
{
route_node const node{From, i, forward};
visited.emplace(node, static_cast<int>(nodes.size()));
nodes.push_back(node);
parents.push_back(-1);
}
int goal{-1};
for (std::size_t index = 0; index < nodes.size() && goal < 0 && nodes.size() < 200000; ++index)
{
for (auto const &next : route_next(nodes[index]))
{
if (std::get<0>(next) == From || visited.count(next) > 0)
continue;
visited.emplace(next, static_cast<int>(nodes.size()));
nodes.push_back(next);
parents.push_back(static_cast<int>(index));
if (std::get<0>(next) == To)
{
goal = static_cast<int>(nodes.size()) - 1;
break;
}
}
}
if (goal < 0)
{
Error = "No route connects the selected paths";
return false;
}
for (int index = goal; index >= 0; index = parents[index])
Route.spans.push_back({std::get<0>(nodes[index]), std::get<1>(nodes[index]), std::get<2>(nodes[index])});
std::reverse(Route.spans.begin(), Route.spans.end());
Error.clear();
return true;
}
editor_track::route editor_track::run_route(TTrack &Track, double const Maximum)
{
route result;
if (false == is_supported(Track))
return result;
auto const walk = [&](route_node Node) {
std::vector<route_node> nodes;
double length{0.0};
while (nodes.size() < 5000 && length < Maximum)
{
auto candidates{route_next(Node)};
if (candidates.empty())
break;
auto chosen{candidates.front()};
for (auto const &candidate : candidates)
if (std::get<1>(candidate) == 0)
chosen = candidate;
auto *track{std::get<0>(chosen)};
if (track == &Track || std::any_of(nodes.begin(), nodes.end(), [&](route_node const &Other) { return std::get<0>(Other) == track; }))
break;
nodes.push_back(chosen);
length += bezier(track->m_paths[std::get<1>(chosen)]).plan_length(8);
Node = chosen;
}
return nodes;
};
auto const ahead{walk({&Track, 0, true})};
auto const behind{walk({&Track, 0, false})};
for (auto node = behind.rbegin(); node != behind.rend(); ++node)
if (std::none_of(ahead.begin(), ahead.end(), [&](route_node const &Other) { return std::get<0>(Other) == std::get<0>(*node); }))
result.spans.push_back({std::get<0>(*node), std::get<1>(*node), false == std::get<2>(*node)});
result.spans.push_back({&Track, 0, true});
for (auto const &node : ahead)
result.spans.push_back({std::get<0>(node), std::get<1>(node), std::get<2>(node)});
return result;
}
editor_track::route editor_track::route_of(chain const &Chain)
{
route result;
for (std::size_t i = 0; i < Chain.tracks.size(); ++i)
result.spans.push_back({Chain.tracks[i], 0, Chain.forward[i]});
return result;
}
std::vector<editor_track::route_sample> editor_track::sample_route(route &Route, double const Step)
{
std::vector<route_sample> samples;
double chainage{0.0};
for (std::size_t i = 0; i < Route.spans.size(); ++i)
{
auto &span{Route.spans[i]};
auto const &path{span.track->m_paths[span.path]};
bezier const curve{path};
auto const count{std::max(2, static_cast<int>(std::ceil(curve.plan_length(16) / std::max(0.1, Step))))};
span.from = chainage;
auto previous{plan_of(curve.point(span.forward ? 0.0 : 1.0))};
for (int j = (samples.empty() ? 0 : 1); j <= count; ++j)
{
auto const t{span.forward ? static_cast<double>(j) / count : 1.0 - static_cast<double>(j) / count};
auto const position{curve.point(t)};
chainage += glm::distance(previous, plan_of(position));
previous = plan_of(position);
auto derivative{curve.first(t)};
if (false == span.forward)
derivative = -derivative;
auto const second{curve.second(t)};
auto const planar{std::hypot(derivative.x, derivative.z)};
route_sample sample;
sample.chainage = chainage;
sample.position = position;
sample.span = i;
sample.cant = 1.5 * std::sin(std::abs(glm::radians(path.rolls[0] + (path.rolls[1] - path.rolls[0]) * t)));
if (planar > 1e-9)
{
sample.direction = plan_of(derivative) / planar;
sample.grade = derivative.y / planar;
sample.curvature = (derivative.x * second.z - derivative.z * second.x) / (planar * planar * planar);
}
samples.push_back(sample);
}
span.to = chainage;
}
Route.length = chainage;
return samples;
}
namespace
{
template <typename Getter>
auto interpolate(std::vector<editor_track::route_sample> const &Samples, double const Chainage, Getter const &Get) -> decltype(Get(Samples.front()))
{
if (Samples.empty())
return {};
auto const next{std::lower_bound(Samples.begin(), Samples.end(), Chainage, [](editor_track::route_sample const &Sample, double const Value) { return Sample.chainage < Value; })};
if (next == Samples.begin())
return Get(Samples.front());
if (next == Samples.end())
return Get(Samples.back());
auto const &before{*std::prev(next)};
auto const run{next->chainage - before.chainage};
return run > 1e-9 ? Get(before) + (Get(*next) - Get(before)) * (Chainage - before.chainage) / run : Get(before);
}
void set_heights(segment_data &Path, double const Startheight, double const Endheight, double const Startgrade, double const Endgrade)
{
auto &start{Path.points[segment_data::point::start]};
auto &end{Path.points[segment_data::point::end]};
auto &control1{Path.points[segment_data::point::control1]};
auto &control2{Path.points[segment_data::point::control2]};
if (control1 == glm::dvec3{} && control2 == glm::dvec3{})
{
control1 = (end - start) / 3.0;
control2 = (start - end) / 3.0;
}
start.y = Startheight;
end.y = Endheight;
control1.y = Startgrade * std::hypot(control1.x, control1.z);
control2.y = -Endgrade * std::hypot(control2.x, control2.z);
}
// the path lies in the plane through Origin which rises with Grade along Direction (unit, in plan) and is level across it
void plane_heights(segment_data &Path, glm::dvec3 const &Origin, glm::dvec2 const &Direction, double const Grade)
{
auto &start{Path.points[segment_data::point::start]};
auto &end{Path.points[segment_data::point::end]};
auto &control1{Path.points[segment_data::point::control1]};
auto &control2{Path.points[segment_data::point::control2]};
if (control1 == glm::dvec3{} && control2 == glm::dvec3{})
{
control1 = (end - start) / 3.0;
control2 = (start - end) / 3.0;
}
auto const height = [&](glm::dvec3 const &Point) { return Origin.y + Grade * glm::dot(plan_of(Point - Origin), Direction); };
auto const first{start + control1};
auto const second{end + control2};
start.y = height(start);
end.y = height(end);
control1.y = height(first) - start.y;
control2.y = height(second) - end.y;
}
glm::dvec2 start_direction(segment_data const &Path)
{
auto const &start{Path.points[segment_data::point::start]};
auto const &control{Path.points[segment_data::point::control1]};
glm::dvec2 direction{control.x, control.z};
if (glm::length(direction) < 1e-6)
direction = {Path.points[segment_data::point::end].x - start.x, Path.points[segment_data::point::end].z - start.z};
return glm::length(direction) > 1e-9 ? glm::normalize(direction) : glm::dvec2{1.0, 0.0};
}
} // namespace
double editor_track::sampled_elevation(std::vector<route_sample> const &Samples, double const Chainage)
{
return interpolate(Samples, Chainage, [](route_sample const &Sample) { return Sample.position.y; });
}
double editor_track::sampled_grade(std::vector<route_sample> const &Samples, double const Chainage)
{
return interpolate(Samples, Chainage, [](route_sample const &Sample) { return Sample.grade; });
}
glm::dvec3 editor_track::sampled_position(std::vector<route_sample> const &Samples, double const Chainage)
{
return interpolate(Samples, Chainage, [](route_sample const &Sample) { return Sample.position; });
}
bool editor_track::adjoining_grade(route const &Route, bool const Atend, double &Grade)
{
if (Route.spans.empty())
return false;
auto const &span{Atend ? Route.spans.back() : Route.spans.front()};
auto const outward{Atend ? span.forward : false == span.forward};
auto const next{route_next({span.track, span.path, outward})};
if (next.empty())
return false;
auto const &[track, path, forward]{next.front()};
auto const tangent{path_tangent(track->m_paths[path], false == forward)};
auto const plan{std::hypot(tangent.x, tangent.z)};
if (plan < 1e-9)
return false;
auto const along{(forward ? 1.0 : -1.0) * tangent.y / plan};
Grade = Atend ? along : -along;
return true;
}
std::vector<editor_track::height_gap> editor_track::apply_profile(route &Route, std::function<double(double)> const &Elevation, std::function<double(double)> const &Grade, std::vector<double> const &Breaks, std::vector<std::pair<TTrack *, state>> &States, std::vector<TTrack *> &Created)
{
double constexpr margin{0.25};
auto const remember = [&](TTrack *Track) {
if (std::none_of(States.begin(), States.end(), [&](auto const &Entry) { return Entry.first == Track; }) && std::find(Created.begin(), Created.end(), Track) == Created.end())
States.emplace_back(Track, capture(*Track));
};
sample_route(Route, 1.0);
std::vector<route_span> spans;
for (auto const &span : Route.spans)
{
if (span.track->eType != tt_Normal)
{
spans.push_back(span);
continue;
}
std::vector<double> cuts;
for (auto const chainage : Breaks)
if (chainage > span.from + margin && chainage < span.to - margin)
cuts.push_back(chainage);
if (cuts.empty())
{
spans.push_back(span);
continue;
}
remember(span.track);
if (span.forward)
{
auto *piece{span.track};
auto origin{span.from};
spans.push_back({piece, 0, true});
for (auto const cut : cuts)
{
auto const t{bezier(piece->m_paths.front()).parameter(cut - origin)};
auto *created{split_path(*piece, t)};
if (created == nullptr)
continue;
Created.push_back(created);
spans.push_back({created, 0, true});
piece = created;
origin = cut;
}
}
else
{
std::vector<TTrack *> order;
for (auto cut = cuts.begin(); cut != cuts.end(); ++cut)
{
auto const t{bezier(span.track->m_paths.front()).parameter(span.to - *cut)};
auto *created{split_path(*span.track, t)};
if (created == nullptr)
continue;
Created.push_back(created);
order.push_back(created);
}
for (auto *piece : order)
spans.push_back({piece, 0, false});
spans.push_back({span.track, 0, false});
}
}
Route.spans = spans;
sample_route(Route, 1.0);
auto const on_route = [&](TTrack const *Track) {
return std::any_of(Route.spans.begin(), Route.spans.end(), [&](route_span const &Span) { return Span.track == Track; });
};
struct joint
{
TTrack *track;
int path;
bool atend;
TTrack *neighbour;
};
std::vector<joint> joints;
std::vector<TTrack *> changed;
for (auto const &span : Route.spans)
{
remember(span.track);
auto const startchainage{span.forward ? span.from : span.to};
auto const endchainage{span.forward ? span.to : span.from};
auto const sign{span.forward ? 1.0 : -1.0};
auto &path{span.track->m_paths[span.path]};
set_heights(path, Elevation(startchainage), Elevation(endchainage), Grade(startchainage) * sign, Grade(endchainage) * sign);
add_unique(changed, span.track);
if (span.track->eType == tt_Normal)
continue;
auto const grade{Grade((span.from + span.to) * 0.5) * sign};
for (int i = 0; i < static_cast<int>(span.track->m_paths.size()); ++i)
{
for (auto const atend : {false, true})
{
auto *neighbour{neighbour_at(*span.track, i, atend)};
if (neighbour != nullptr && false == on_route(neighbour) && std::none_of(joints.begin(), joints.end(), [&](joint const &Joint) { return Joint.track == span.track && Joint.neighbour == neighbour; }))
joints.push_back({span.track, i, atend, neighbour});
}
if (i != span.path)
plane_heights(span.track->m_paths[i], path.points[segment_data::point::start], start_direction(path), grade);
}
}
commit(changed);
sample_route(Route, 1.0);
std::vector<height_gap> gaps;
for (auto const &joint : joints)
{
auto const &end{joint.track->m_paths[joint.path].points[joint.atend ? segment_data::point::end : segment_data::point::start]};
double nearest{std::numeric_limits<double>::max()};
double gap{0.0};
for (auto const &path : joint.neighbour->m_paths)
for (auto const index : {segment_data::point::start, segment_data::point::end})
{
auto const &point{path.points[index]};
auto const distance{std::hypot(point.x - end.x, point.z - end.z)};
if (distance < nearest)
{
nearest = distance;
gap = point.y - end.y;
}
}
if (nearest <= kSamePoint && std::abs(gap) > 0.001)
gaps.push_back({joint.track, joint.neighbour, gap});
}
return gaps;
}
void editor_track::keep_heights(chain const &Chain, std::vector<segment_data> &Pieces)
{
auto existing{route_of(Chain)};
auto const samples{sample_route(existing, 1.0)};
if (samples.empty() || existing.length <= 1e-6)
return;
std::vector<double> lengths;
double total{0.0};
for (auto const &piece : Pieces)
{
lengths.push_back(bezier(piece).plan_length());
total += lengths.back();
}
if (total <= 1e-6)
return;
auto const scale{existing.length / total};
double chainage{0.0};
for (std::size_t i = 0; i < Pieces.size(); ++i)
{
auto const from{chainage * scale};
auto const to{(chainage + lengths[i]) * scale};
set_heights(Pieces[i], sampled_elevation(samples, from), sampled_elevation(samples, to), sampled_grade(samples, from) * scale, sampled_grade(samples, to) * scale);
chainage += lengths[i];
}
}
std::vector<editor_track::switch_template> editor_track::find_switch_templates()
{
std::vector<switch_template> result;
@@ -1213,7 +1728,7 @@ std::vector<editor_track::switch_template> editor_track::find_switch_templates()
local.points[segment_data::point::control1] = tolocal(path.points[segment_data::point::control1]);
local.points[segment_data::point::control2] = tolocal(path.points[segment_data::point::control2]);
}
if (glm::length(glm::dvec2{entry.local[1].points[segment_data::point::start].x, entry.local[1].points[segment_data::point::start].z}) > kSamePoint)
if (glm::length(plan_of(entry.local[1].points[segment_data::point::start])) > kSamePoint)
continue;
if (entry.local[1].points[segment_data::point::end].x < 0.0)
{
@@ -1229,8 +1744,7 @@ std::vector<editor_track::switch_template> editor_track::find_switch_templates()
auto const angle{std::atan2(std::abs(tangent.x), tangent.z)};
entry.ratio = angle > 1e-6 ? 1.0 / std::tan(angle) : 0.0;
entry.radius = path_radius(entry.local[1], 0.25);
char key[256];
std::snprintf(key, sizeof(key), "%.2f|%.2f|%.2f|%s|%s", entry.length, entry.local[1].points[segment_data::point::end].x, entry.local[1].points[segment_data::point::end].z, material_name(track->m_material1).c_str(), material_name(track->m_material2).c_str());
auto const key{format("%.2f|%.2f|%.2f|%s|%s", entry.length, entry.local[1].points[segment_data::point::end].x, entry.local[1].points[segment_data::point::end].z, material_name(track->m_material1).c_str(), material_name(track->m_material2).c_str())};
auto const found{lookup.find(key)};
if (found != lookup.end())
{
@@ -1238,9 +1752,7 @@ std::vector<editor_track::switch_template> editor_track::find_switch_templates()
continue;
}
entry.count = 1;
char label[256];
std::snprintf(label, sizeof(label), "L %.2f m 1:%.1f R %.0f m %s", entry.length, entry.ratio, entry.radius, material_name(track->m_material1).c_str());
entry.label = label;
entry.label = format("L %.2f m 1:%.1f R %.0f m %s", entry.length, entry.ratio, entry.radius, material_name(track->m_material1).c_str());
lookup.emplace(key, result.size());
result.push_back(entry);
}
@@ -1324,6 +1836,44 @@ std::vector<editor_track::switch_template> editor_track::standard_switch_templat
return result;
}
namespace
{
// definition of a path in the scenery text, up to its points: type, length, parameters, environment and textures
void write_header(std::ostream &Text, char const *Type, double const Length, double const Sound, TTrack const &Style, material_handle const Second)
{
char const *environments[] = {"flat", "mountains", "canyon", "tunnel", "bridge", "bank"};
auto const environment{Style.eEnvironment >= e_flat && Style.eEnvironment <= e_bank ? environments[Style.eEnvironment] : "flat"};
Text << Type << ' ' << Length << ' ' << Style.fTrackWidth << ' ' << Style.fFriction << ' ' << Sound << ' ' << Style.iQualityFlag << ' ' << 0 << ' ' << environment << ' ';
if (Style.m_visible)
Text << "vis " << editor_track::material_name(Style.m_material1) << ' ' << Style.fTexLength << ' ' << editor_track::material_name(Second) << ' ' << editor_track::texture_height(Style) << ' ' << Style.fTexWidth << ' ' << Style.fTexSlope << ' ';
else
Text << "unvis ";
}
void write_points(std::ostream &Text, segment_data const &Path)
{
auto const &start{Path.points[segment_data::point::start]};
auto const &control1{Path.points[segment_data::point::control1]};
auto const &control2{Path.points[segment_data::point::control2]};
auto const &end{Path.points[segment_data::point::end]};
Text << start.x << ' ' << start.y << ' ' << start.z << ' ' << Path.rolls[0] << ' ' << control1.x << ' ' << control1.y << ' ' << control1.z << ' ' << control2.x << ' ' << control2.y << ' ' << control2.z << ' ' << end.x << ' ' << end.y << ' ' << end.z << ' ' << Path.rolls[1] << ' ' << Path.radius << ' ';
}
void write_ending(std::ostream &Text, TTrack const &Style)
{
if ((Style.iCategoryFlag & 15) == 1 && false == Style.m_profile1.first.empty())
Text << "railprofile " << Style.m_profile1.first << ' ';
Text << "endtrack\n";
}
material_handle ballast_of(TTrack const &Style)
{
return Style.eType == tt_Switch ? (Style.SwitchExtension ? Style.SwitchExtension->m_material3 : null_handle) : Style.m_material2;
}
} // namespace
TTrack *editor_track::create_switch(switch_template const &Template, std::vector<segment_data> const &Paths, TTrack const &Style, std::string const &Name)
{
if (Paths.size() < 2)
@@ -1335,31 +1885,14 @@ TTrack *editor_track::create_switch(switch_template const &Template, std::vector
}
else
{
char const *environments[] = {"flat", "mountains", "canyon", "tunnel", "bridge", "bank"};
auto const environment{Style.eEnvironment >= e_flat && Style.eEnvironment <= e_bank ? environments[Style.eEnvironment] : "flat"};
std::ostringstream text;
text.precision(std::numeric_limits<double>::digits10);
text << "switch " << Template.length << ' ' << Style.fTrackWidth << ' ' << Style.fFriction << ' ' << 10.0 << ' ' << Style.iQualityFlag << ' ' << 0 << ' ' << environment << ' ';
auto const rails{Style.m_material1};
auto const trackbed{Style.eType == tt_Switch ? (Style.SwitchExtension ? Style.SwitchExtension->m_material3 : null_handle) : Style.m_material2};
if (Style.m_visible)
text << "vis " << material_name(rails) << ' ' << Style.fTexLength << ' ' << material_name(rails) << ' ' << texture_height(Style) << ' ' << Style.fTexWidth << ' ' << Style.fTexSlope << ' ';
else
text << "unvis ";
for (int i = 0; i < 2; ++i)
{
auto const &path{Paths[i]};
auto const &start{path.points[segment_data::point::start]};
auto const &control1{path.points[segment_data::point::control1]};
auto const &control2{path.points[segment_data::point::control2]};
auto const &end{path.points[segment_data::point::end]};
text << start.x << ' ' << start.y << ' ' << start.z << ' ' << path.rolls[0] << ' ' << control1.x << ' ' << control1.y << ' ' << control1.z << ' ' << control2.x << ' ' << control2.y << ' ' << control2.z << ' ' << end.x << ' ' << end.y << ' ' << end.z << ' ' << path.rolls[1] << ' ' << path.radius << ' ';
}
if (trackbed != null_handle)
write_header(text, "switch", Template.length, 10.0, Style, Style.m_material1);
write_points(text, Paths[0]);
write_points(text, Paths[1]);
if (auto const trackbed{ballast_of(Style)}; trackbed != null_handle)
text << "trackbed " << material_name(trackbed) << ' ';
if ((Style.iCategoryFlag & 15) == 1 && false == Style.m_profile1.first.empty())
text << "railprofile " << Style.m_profile1.first << ' ';
text << "endtrack\n";
write_ending(text, Style);
track = load_path(text.str(), Style, Name);
}
track->m_paths.assign(Paths.begin(), Paths.begin() + 2);
@@ -1443,7 +1976,7 @@ void editor_track::move_straight(straight const &Line, glm::dvec3 const &Start,
if (newlength < 1e-3)
return;
auto const newdirection{newplan / newlength};
auto const angle{std::atan2(lateral_of(direction, newdirection), glm::dot(direction, newdirection))};
auto const angle{std::atan2(cross(direction, newdirection), glm::dot(direction, newdirection))};
auto const along = [&](glm::dvec3 const &Point) { return glm::dot(plan_of(Point - Line.start), direction); };
struct interval
@@ -1483,7 +2016,7 @@ void editor_track::move_straight(straight const &Line, glm::dvec3 const &Start,
};
auto const transform = [&](glm::dvec3 const &Point) {
auto const a{along(Point)};
auto const offset{lateral_of(direction, plan_of(Point - Line.start))};
auto const offset{cross(direction, plan_of(Point - Line.start))};
auto const mapped{remap(a)};
auto const oldheight{Line.start.y + (Line.end.y - Line.start.y) * a / Line.length};
auto const newheight{Start.y + (End.y - Start.y) * mapped / newlength};
@@ -1613,47 +2146,35 @@ TTrack *editor_track::clone(TTrack const &Template)
TTrack *editor_track::create_path(TTrack const &Style, segment_data const &Path)
{
char const *environments[] = {"flat", "mountains", "canyon", "tunnel", "bridge", "bank"};
auto const environment{Style.eEnvironment >= e_flat && Style.eEnvironment <= e_bank ? environments[Style.eEnvironment] : "flat"};
auto const category{Style.iCategoryFlag & 15};
std::ostringstream text;
text.precision(std::numeric_limits<double>::digits10);
text << (category == 2 ? "road " : category == 4 ? "river " : "normal ") << glm::distance(Path.points[segment_data::point::start], Path.points[segment_data::point::end]) << ' ' << Style.fTrackWidth << ' ' << Style.fFriction << ' ' << Style.fSoundDistance << ' ' << Style.iQualityFlag << ' ' << 0 << ' ' << environment << ' ';
auto const ballast{Style.eType == tt_Switch ? (Style.SwitchExtension ? Style.SwitchExtension->m_material3 : null_handle) : Style.m_material2};
if (Style.m_visible)
text << "vis " << material_name(Style.m_material1) << ' ' << Style.fTexLength << ' ' << material_name(ballast) << ' ' << texture_height(Style) << ' ' << Style.fTexWidth << ' ' << Style.fTexSlope << ' ';
else
text << "unvis ";
auto const &start{Path.points[segment_data::point::start]};
auto const &control1{Path.points[segment_data::point::control1]};
auto const &control2{Path.points[segment_data::point::control2]};
auto const &end{Path.points[segment_data::point::end]};
text << start.x << ' ' << start.y << ' ' << start.z << ' ' << Path.rolls[0] << ' ' << control1.x << ' ' << control1.y << ' ' << control1.z << ' ' << control2.x << ' ' << control2.y << ' ' << control2.z << ' ' << end.x << ' ' << end.y << ' ' << end.z << ' ' << Path.rolls[1] << ' ' << Path.radius << ' ';
auto const speed{velocity(Style)};
if (speed > 0.0)
write_header(text, category == 2 ? "road" : category == 4 ? "river" : "normal", glm::distance(Path.points[segment_data::point::start], Path.points[segment_data::point::end]), Style.fSoundDistance, Style, ballast_of(Style));
write_points(text, Path);
if (auto const speed{velocity(Style)}; speed > 0.0)
text << "velocity " << speed << ' ';
if (category == 1 && false == Style.m_profile1.first.empty())
text << "railprofile " << Style.m_profile1.first << ' ';
text << "endtrack\n";
write_ending(text, Style);
auto *track{load_path(text.str(), Style)};
track->m_paths = {Path};
return track;
}
glm::dvec3 editor_track::point_at(TTrack const &Track, double const T)
{
auto const &path{Track.m_paths.front()};
auto const p0{path.points[segment_data::point::start]};
auto const p3{path.points[segment_data::point::end]};
auto const p1{p0 + path.points[segment_data::point::control1]};
auto const p2{p3 + path.points[segment_data::point::control2]};
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;
}
double editor_track::nearest_parameter(TTrack const &Track, glm::dvec3 const &Point)
{
if (Track.m_paths.empty())
return 0.5;
auto const &path{Track.m_paths.front()};
auto const p0{path.points[segment_data::point::start]};
auto const p3{path.points[segment_data::point::end]};
auto const p1{p0 + path.points[segment_data::point::control1]};
auto const p2{p3 + path.points[segment_data::point::control2]};
auto const at = [&](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 distance = [&](double const T) { auto const q{at(T)}; return glm::distance(glm::dvec2{q.x, q.z}, glm::dvec2{Point.x, Point.z}); };
auto const distance = [&](double const T) { return geometry::plan_distance(point_at(Track, T), Point); };
double best{0.5};
for (int i = 0; i <= 200; ++i)
if (distance(i / 200.0) < distance(best))
@@ -1707,6 +2228,8 @@ TTrack *editor_track::split_path(TTrack &Track, double const T)
second.rolls[0] = roll;
auto *created{create_path(Track, second)};
Track.m_paths.front() = first;
if (s_observer != nullptr)
s_observer->track_split(Track, *created, first);
commit({&Track, created});
return created;
}
@@ -1743,12 +2266,8 @@ void editor_track::retire(TTrack &Track)
update_transition(*neighbour);
rebuild_geometry(*neighbour);
}
}
void editor_track::revive(TTrack &Track)
{
Track.m_editorremoved = false;
commit({&Track});
if (s_observer != nullptr)
s_observer->track_retired(Track);
}
void editor_track::rebuild_geometry(TTrack &Track)