17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-05 05:14:27 +01:00

refactor(editor): split the track mode into its gizmo, kept records, switch drive, switch slide and track bed files; path geometry shared in editorGeometry instead of four copies of the reversed path, track shape rules (stretching, rolls at joints, middle, switch axis, drive spot) in editor_track, fitting the track to the ground in ground_follow with the terrain query passed in, the record layer and the switch classification in the stores; long gizmo, record watch and switch slide functions broken up, out-parameters replaced by optional results

This commit is contained in:
maj00r
2026-10-11 22:03:59 +02:00
parent f34056ff5a
commit c5ac698702
21 changed files with 2614 additions and 2095 deletions

View File

@@ -745,4 +745,15 @@ void restore(std::vector<record> Records)
settle();
}
scene::layer_handle layer_of(TTrack const &Track)
{
auto const usable = [](scene::layer_handle const Layer) { return scene::Layers.valid(Layer) && scene::Layers.accepts(Layer) && false == scene::Layers.layer(Layer).name.ends_with(".txtf"); };
auto layer{scene::Layers.resolve(Track.layer())};
if (false == usable(layer))
layer = scene::Layers.active();
if (false == usable(layer))
layer = 1;
return layer;
}
} // namespace alignment_store

View File

@@ -101,5 +101,8 @@ std::optional<std::vector<record>> take_changes();
void settle();
// puts the records back as they were, for undo and redo
void restore(std::vector<record> Records);
// layer the editor keeps the records of the track in: its own, unless it's a terrain file (.txtf), whose binary version
// (.btf) the editor doesn't read the records from
scene::layer_handle layer_of(TTrack const &Track);
} // namespace alignment_store

View File

@@ -159,4 +159,72 @@ inline double chord_length(segment_data const &Path)
return glm::length(plan_of(Path.points[segment_data::point::end] - Path.points[segment_data::point::start]));
}
inline bool has_controls(segment_data const &Path)
{
return Path.points[segment_data::point::control1] != glm::dvec3{} || Path.points[segment_data::point::control2] != glm::dvec3{};
}
inline segment_data reversed(segment_data const &Path)
{
segment_data result{Path};
result.points[segment_data::point::start] = Path.points[segment_data::point::end];
result.points[segment_data::point::end] = Path.points[segment_data::point::start];
result.points[segment_data::point::control1] = Path.points[segment_data::point::control2];
result.points[segment_data::point::control2] = Path.points[segment_data::point::control1];
result.rolls = {-Path.rolls[1], -Path.rolls[0]};
return result;
}
inline std::vector<segment_data> straight_piece(glm::dvec3 const &Point, glm::dvec2 const &Direction, double const Grade, double const Length)
{
if (Length < 0.5)
return {};
segment_data path;
path.points[segment_data::point::start] = Point;
path.points[segment_data::point::end] = {Point.x + Direction.x * Length, Point.y + Grade * Length, Point.z + Direction.y * Length};
return {path};
}
inline segment_data part_of(segment_data const &Piece, double const From, double const To)
{
bezier const curve{Piece};
segment_data result{Piece};
result.points[segment_data::point::start] = curve.point(From);
result.points[segment_data::point::end] = curve.point(To);
if (has_controls(Piece))
{
result.points[segment_data::point::control1] = curve.first(From) * ((To - From) / 3.0);
result.points[segment_data::point::control2] = -curve.first(To) * ((To - From) / 3.0);
}
result.rolls = {glm::mix(Piece.rolls[0], Piece.rolls[1], static_cast<float>(From)), glm::mix(Piece.rolls[0], Piece.rolls[1], static_cast<float>(To))};
return result;
}
inline std::vector<segment_data> arc_through(glm::dvec3 const &Point, glm::dvec2 const &Direction, double const Grade, glm::dvec2 const &Offset)
{
auto const along{glm::dot(Offset, Direction)};
auto const lateral{cross(Direction, Offset)};
auto const chord{glm::length(Offset)};
if (along <= 0.0 || chord < 0.5)
return {};
if (std::abs(lateral) < 0.005 * chord + 0.05)
return straight_piece(Point, Direction, Grade, along);
auto const angle{2.0 * std::atan2(std::abs(lateral), along)};
return arc_pieces(Point, Direction, Grade, chord / (2.0 * std::sin(angle * 0.5)), angle, lateral > 0.0 ? 1 : -1);
}
inline double hermite(double const Length, double const Height0, double const Grade0, double const Height1, double const Grade1, double const T, double &Grade)
{
auto const t2{T * T};
auto const t3{t2 * T};
Grade = (6.0 * t2 - 6.0 * T) * (Height0 - Height1) / Length + (3.0 * t2 - 4.0 * T + 1.0) * Grade0 + (3.0 * t2 - 2.0 * T) * Grade1;
return (2.0 * t3 - 3.0 * t2 + 1.0) * Height0 + (t3 - 2.0 * t2 + T) * Length * Grade0 + (3.0 * t2 - 2.0 * t3) * Height1 + (t3 - t2) * Length * Grade1;
}
inline glm::dvec3 level(glm::dvec3 const &Vector)
{
glm::dvec3 const flat{Vector.x, 0.0, Vector.z};
return glm::length(flat) > 1e-9 ? glm::normalize(flat) : glm::dvec3{0.0, 0.0, 1.0};
}
} // namespace geometry

View File

@@ -0,0 +1,201 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "editor/editorGroundFollow.hpp"
#include "editor/editorGeometry.hpp"
#include <algorithm>
#include <cmath>
#include <iterator>
namespace ground_follow
{
namespace
{
using geometry::bezier;
using geometry::has_controls;
using geometry::part_of;
using geometry::plan_of;
double constexpr kStep{2.0};
double constexpr kTolerance{0.1};
double constexpr kSpacing{10.0};
struct samples
{
std::vector<double> chainages;
std::vector<glm::dvec3> points;
std::vector<glm::dvec2> directions;
};
samples sample(std::vector<segment_data> const &Pieces, std::vector<double> const &Lengths, double const Total, ends const &Ends)
{
samples result;
double reached{0.0};
for (std::size_t i = 0; i < Pieces.size(); ++i)
{
bezier const curve{Pieces[i]};
auto const count{std::max(1, static_cast<int>(std::ceil(Lengths[i] / kStep)))};
for (int k = (i == 0 ? 1 : 0); k < count; ++k)
{
auto const along{Lengths[i] * k / count};
auto const t{curve.parameter(along)};
auto const chainage{reached + along};
auto point{curve.point(t)};
point.y = Ends.height0 + (Ends.height1 - Ends.height0) * chainage / Total + 50.0;
result.chainages.push_back(chainage);
result.points.push_back(point);
result.directions.push_back(plan_of(curve.first(t)));
}
reached += Lengths[i];
}
return result;
}
void fit(knot_line &Line, std::vector<double> const &Chainages, std::vector<double> const &Targets)
{
for (std::size_t pass = 0; pass < Chainages.size(); ++pass)
{
auto worst{kTolerance};
auto pick{Chainages.size()};
for (std::size_t j = 0; j < Chainages.size(); ++j)
{
if (std::isnan(Targets[j]))
continue;
double grade{0.0};
auto const miss{std::abs(Line.at(Chainages[j], grade) - Targets[j])};
if (miss <= worst || false == Line.room(Chainages[j], kSpacing))
continue;
worst = miss;
pick = j;
}
if (pick == Chainages.size())
return;
Line.insert(Chainages[pick], Targets[pick]);
}
}
std::vector<segment_data> cut(std::vector<segment_data> const &Pieces, std::vector<double> const &Lengths, std::vector<double> const &Knots, std::vector<std::pair<double, double>> &Spans)
{
std::vector<segment_data> result;
double from{0.0};
for (std::size_t i = 0; i < Pieces.size(); ++i)
{
bezier const curve{Pieces[i]};
auto const to{from + Lengths[i]};
double cut{from};
double parameter{0.0};
for (auto const knot : Knots)
{
if (knot < cut + 0.5 || knot > to - 0.5)
continue;
auto const next{curve.parameter(knot - from)};
result.push_back(part_of(Pieces[i], parameter, next));
Spans.emplace_back(cut, knot);
cut = knot;
parameter = next;
}
result.push_back(parameter > 0.0 ? part_of(Pieces[i], parameter, 1.0) : Pieces[i]);
Spans.emplace_back(cut, to);
from = to;
}
return result;
}
void lift(segment_data &Piece, std::pair<double, double> const &Span, knot_line const &Line)
{
auto &points{Piece.points};
auto const [a, b]{Span};
auto const length{b - a};
double begin{0.0};
double end{0.0};
auto &start{points[segment_data::point::start]};
auto &finish{points[segment_data::point::end]};
auto &control1{points[segment_data::point::control1]};
auto &control2{points[segment_data::point::control2]};
start.y = Line.at(a, begin);
finish.y = Line.at(b, end);
if (has_controls(Piece))
{
control1.y = begin * glm::length(plan_of(control1));
control2.y = -end * glm::length(plan_of(control2));
return;
}
auto const linear{(finish.y - start.y) / std::max(length, 1e-6)};
if (length < 1e-3 || (std::abs(begin - linear) < 1e-6 && std::abs(end - linear) < 1e-6))
return;
auto const along{glm::normalize(plan_of(finish - start)) * (length / 3.0)};
control1 = {along.x, begin * length / 3.0, along.y};
control2 = {-along.x, -end * length / 3.0, -along.y};
}
} // namespace
void knot_line::refresh()
{
auto const last{knots.size() - 1};
slopes.assign(knots.size(), 0.0);
for (std::size_t i = 0; i <= last; ++i)
{
auto const a{i == 0 ? 0 : i - 1};
auto const b{i == last ? last : i + 1};
slopes[i] = (heights[b] - heights[a]) / (knots[b] - knots[a]);
}
if (false == std::isnan(grade0))
slopes.front() = grade0;
if (false == std::isnan(grade1))
slopes.back() = grade1;
}
void knot_line::insert(double const Chainage, double const Height)
{
auto const where{std::upper_bound(knots.begin(), knots.end(), Chainage) - knots.begin()};
knots.insert(knots.begin() + where, Chainage);
heights.insert(heights.begin() + where, Height);
refresh();
}
double knot_line::at(double const Chainage, double &Grade) const
{
auto const next{static_cast<std::size_t>(std::upper_bound(knots.begin(), knots.end(), Chainage) - knots.begin())};
auto const i{std::clamp<std::size_t>(next, 1, knots.size() - 1) - 1};
auto const length{knots[i + 1] - knots[i]};
return geometry::hermite(length, heights[i], slopes[i], heights[i + 1], slopes[i + 1], std::clamp((Chainage - knots[i]) / length, 0.0, 1.0), Grade);
}
bool knot_line::room(double const Chainage, double const Spacing) const
{
auto const next{std::upper_bound(knots.begin(), knots.end(), Chainage)};
return next != knots.end() && *next - Chainage >= Spacing && Chainage - *std::prev(next) >= Spacing;
}
void follow(std::vector<segment_data> &Pieces, ends const &Ends, bed_query const &Bed)
{
std::vector<double> lengths;
double total{0.0};
for (auto const &piece : Pieces)
total += lengths.emplace_back(bezier{piece}.plan_length(256));
if (total < 1e-3)
return;
auto const points{sample(Pieces, lengths, total, Ends)};
auto const targets{Bed(points.points, points.directions)};
knot_line line{{0.0, total}, {Ends.height0, Ends.height1}, {}, Ends.grade0, Ends.grade1};
line.refresh();
fit(line, points.chainages, targets);
std::vector<std::pair<double, double>> spans;
auto result{cut(Pieces, lengths, line.knots, spans)};
for (std::size_t i = 0; i < result.size(); ++i)
lift(result[i], spans[i], line);
Pieces = std::move(result);
}
} // namespace ground_follow

View File

@@ -0,0 +1,47 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <functional>
#include <vector>
#include <glm/glm.hpp>
#include "world/Segment.h"
namespace ground_follow
{
using bed_query = std::function<std::vector<double>(std::vector<glm::dvec3> const &, std::vector<glm::dvec2> const &)>;
struct ends
{
double height0{0.0};
double grade0{0.0};
double height1{0.0};
double grade1{0.0};
};
struct knot_line
{
std::vector<double> knots;
std::vector<double> heights;
std::vector<double> slopes;
double grade0{0.0};
double grade1{0.0};
void refresh();
void insert(double const Chainage, double const Height);
double at(double const Chainage, double &Grade) const;
bool room(double const Chainage, double const Spacing) const;
};
void follow(std::vector<segment_data> &Pieces, ends const &Ends, bed_query const &Bed);
} // namespace ground_follow

View File

@@ -363,4 +363,29 @@ void restore(std::vector<record> Records)
settle();
}
void place(record &Entry, TTrack const &First)
{
auto const &main{First.m_paths.front()};
geometry::bezier const curve{main};
Entry.start = main.points[segment_data::point::start];
auto const direction{geometry::plan_of(curve.first(0.0))};
Entry.direction = glm::length(direction) > 1e-12 ? glm::normalize(direction) : glm::dvec2{0.0, 1.0};
Entry.km = alignment_store::chainage_at(Entry.start);
}
void classify(record &Entry, TTrack const &Switch)
{
geometry::bezier const main{Switch.m_paths[0]};
auto const &diverging{Switch.m_paths[1]};
Entry.hand = geometry::cross(Entry.direction, geometry::plan_of(diverging.points[segment_data::point::end] - Entry.start)) > 0.0 ? 'R' : 'L';
auto const first{geometry::plan_of(main.first(0.0))};
auto const last{geometry::plan_of(main.first(1.0))};
auto const turn{glm::length(first) > 1e-12 && glm::length(last) > 1e-12 ? geometry::signed_angle(glm::normalize(first), glm::normalize(last)) : 0.0};
Entry.main_radius = std::abs(turn) > 1e-6 ? main.plan_length() / turn : 0.0;
if (std::abs(Entry.main_radius) > 20000.0)
Entry.main_radius = 0.0;
if (Entry.type == "Rz" || Entry.type == "Rlj" || Entry.type == "Rld")
Entry.type = Entry.main_radius == 0.0 ? "Rz" : (Entry.main_radius > 0.0) == (Entry.hand == 'R') ? "Rlj" : "Rld";
}
} // namespace switch_store

View File

@@ -60,6 +60,8 @@ std::vector<std::string> follow();
double branch_radius(record const &Record);
// keeps the record in the file of the layer, in place of the one with the same name
void put(record Entry, scene::layer_handle const Layer);
void place(record &Entry, TTrack const &First);
void classify(record &Entry, TTrack const &Switch);
void forget(std::string const &Name);
// on save: the records follow the renamed switches, the ones whose paths are gone are dropped
void tidy();

View File

@@ -44,6 +44,7 @@ using geometry::bezier;
using geometry::chord_length;
using geometry::cross;
using geometry::plan_of;
using geometry::reversed;
using geometry::signed_angle;
using geometry::turned;
@@ -94,17 +95,6 @@ double path_radius(segment_data const &Path, double const T)
return curvature > 1e-7 ? 1.0 / curvature : 0.0;
}
segment_data reversed(segment_data const &Path)
{
segment_data result{Path};
result.points[segment_data::point::start] = Path.points[segment_data::point::end];
result.points[segment_data::point::end] = Path.points[segment_data::point::start];
result.points[segment_data::point::control1] = Path.points[segment_data::point::control2];
result.points[segment_data::point::control2] = Path.points[segment_data::point::control1];
result.rolls = {-Path.rolls[1], -Path.rolls[0]};
return result;
}
bool adjoining_direction(TTrack const *Neighbour, glm::dvec3 const &Joint, bool const Leaving, glm::dvec3 &Direction, double &Radius)
{
if (Neighbour == nullptr)

View File

@@ -126,6 +126,60 @@ class editor_track
static std::vector<std::pair<TTrack *, point_ref>> partners(TTrack &Track, point_ref const &End);
static void align_controls(TTrack const &Track, point_ref const &Control, std::vector<std::pair<TTrack *, point_ref>> const &Partners);
static point_ref end_of(point_ref const &Control);
static glm::dvec3 control_vector(TTrack const &Track, point_ref const &Control);
static glm::dvec3 outward(TTrack const &Track, point_ref const &Point);
static point_ref switch_main(TTrack const &Track, point_ref Point);
static std::size_t driven_paths(TTrack const &Track);
static double joint_roll(TTrack const &Track, point_ref const &Point);
static void roll_joined(TTrack &Other, point_ref const &Theirs, float const Roll);
static void match_rolls(TTrack const &Track, std::vector<joint> const &Joints);
struct path_end
{
int path{0};
bool atend{true};
};
static std::optional<path_end> stretch_end(TTrack const &Track, point_ref const &Point);
static std::vector<segment_data> stretched(segment_data const &Path, bool const Atend, double const Length);
static std::pair<glm::dvec3, glm::dvec3> end_axis(segment_data const &Path, bool const Atend);
static point_ref stretch_remap(point_ref Point, int const Path, std::size_t const Pieces);
struct middle_point
{
glm::dvec3 point{0.0};
glm::dvec3 tangent{0.0, 0.0, 1.0};
double chainage{0.0};
};
static middle_point middle(TTrack const &Track);
struct edit
{
std::vector<std::pair<TTrack *, state>> states;
std::vector<TTrack *> created;
std::vector<TTrack *> removed;
std::vector<TTrack *> changed;
void capture(TTrack &Track);
};
struct drive_spot
{
glm::dvec3 location{0.0};
double yaw{0.0};
glm::dvec2 tilt{0.0};
bool rightswitch{false};
};
static std::optional<drive_spot> drive_spot_of(TTrack const &Switch);
struct straight_axis
{
glm::dvec3 origin{0.0};
glm::dvec2 direction{0.0, 1.0};
double length{0.0};
double along(glm::dvec3 const &Point) const;
double lateral(glm::dvec3 const &Point) const;
};
static std::optional<straight_axis> main_axis(TTrack const &Switch);
static std::vector<segment_data> slid(TTrack const &Switch, double const Distance);
struct snap_target
{

286
editor/editorTrackShape.cpp Normal file
View File

@@ -0,0 +1,286 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "editor/editorTrack.hpp"
#include "editor/editorGeometry.hpp"
#include "world/Track.h"
#include "utilities/Globals.h"
#include <algorithm>
#include <cmath>
namespace
{
using geometry::arc_pieces;
using geometry::bezier;
using geometry::cross;
using geometry::grade_of;
using geometry::plan_distance;
using geometry::plan_of;
using geometry::reversed;
using geometry::signed_angle;
using geometry::straight_piece;
std::vector<segment_data> scaled(segment_data const &Path, bool const Atend, double const Length, double const Original)
{
auto const &start{Path.points[segment_data::point::start]};
auto const &end{Path.points[segment_data::point::end]};
auto const scale{Length / std::max(Original, 1e-6)};
segment_data piece{Path};
piece.points[segment_data::point::end] = start + (end - start) * scale;
piece.points[segment_data::point::control1] *= scale;
piece.points[segment_data::point::control2] *= scale;
piece.radius *= static_cast<float>(scale);
return {Atend ? piece : reversed(piece)};
}
void spread_rolls(std::vector<segment_data> &Pieces, segment_data const &Path, double const Original, float const Radius)
{
double chainage{0.0};
for (auto &piece : Pieces)
{
auto const piecelength{bezier{piece}.plan_length()};
auto const from{static_cast<float>(std::clamp(chainage / Original, 0.0, 1.0))};
auto const to{static_cast<float>(std::clamp((chainage + piecelength) / Original, 0.0, 1.0))};
piece.rolls = {glm::mix(Path.rolls[0], Path.rolls[1], from), glm::mix(Path.rolls[0], Path.rolls[1], to)};
piece.radius = Radius;
chainage += piecelength;
}
}
} // namespace
std::vector<segment_data> editor_track::stretched(segment_data const &Path, bool const Atend, double const Length)
{
auto const path{Atend ? Path : reversed(Path)};
bezier const curve{path};
auto const &start{path.points[segment_data::point::start]};
auto const &end{path.points[segment_data::point::end]};
auto const first{plan_of(curve.first(0.0))};
auto const last{plan_of(curve.first(1.0))};
if (glm::length(first) < 1e-9 || glm::length(last) < 1e-9)
return {};
glm::dvec2 const direction{glm::normalize(first)};
auto const grade{grade_of(curve.first(0.0))};
auto const original{curve.plan_length()};
auto const turn{signed_angle(direction, glm::normalize(last))};
auto const lateral{cross(direction, plan_of(end - start))};
std::vector<segment_data> pieces;
float radius{0.0f};
if (std::abs(turn) < 1e-4 && std::abs(lateral) < 0.01)
pieces = straight_piece(start, direction, grade, Length);
else
{
auto const arcradius{original / std::abs(turn)};
int const side{turn > 0.0 ? 1 : -1};
auto const arc{arc_pieces(start, direction, grade, arcradius, std::abs(turn), side)};
bool const circular{std::abs(turn) > 1e-4 && plan_distance(arc.back().points[segment_data::point::end], end) < 0.05 && plan_distance(arc.front().points[segment_data::point::start], start) < 1e-6};
if (false == circular)
return scaled(path, Atend, Length, original);
pieces = arc_pieces(start, direction, grade, arcradius, std::min(Length / arcradius, glm::radians(330.0)), side);
radius = path.radius;
}
spread_rolls(pieces, path, original, radius);
if (false == Atend)
{
std::reverse(pieces.begin(), pieces.end());
for (auto &piece : pieces)
piece = reversed(piece);
}
return pieces;
}
std::optional<editor_track::path_end> editor_track::stretch_end(TTrack const &Track, point_ref const &Point)
{
if (Track.eType != tt_Normal || false == is_supported(Track) || Track.m_paths.empty())
return std::nullopt;
auto const last{static_cast<int>(Track.m_paths.size()) - 1};
if (false == Point.valid())
{
bool const startfree{false == is_connected(Track, {0, point_kind::start})};
bool const endfree{false == is_connected(Track, {last, point_kind::end})};
bool const atend{endfree || false == startfree};
return path_end{atend ? last : 0, atend};
}
if (false == is_end(Point.kind) || Point.path > last)
return std::nullopt;
auto const position{point_position(Track, Point)};
for (int i = 0; i <= last; ++i)
if (i != Point.path && (plan_distance(Track.m_paths[i].points[segment_data::point::start], position) < geometry::same_point || plan_distance(Track.m_paths[i].points[segment_data::point::end], position) < geometry::same_point))
return std::nullopt;
return path_end{Point.path, Point.kind == point_kind::end};
}
std::pair<glm::dvec3, glm::dvec3> editor_track::end_axis(segment_data const &Path, bool const Atend)
{
bezier const curve{Path};
auto const tangent{Atend ? curve.first(1.0) : -curve.first(0.0)};
return {Path.points[Atend ? segment_data::point::end : segment_data::point::start], glm::length(tangent) > 1e-9 ? glm::normalize(tangent) : glm::dvec3{}};
}
editor_track::point_ref editor_track::stretch_remap(point_ref Point, int const Path, std::size_t const Pieces)
{
if (false == Point.valid() || Point.path < Path)
return Point;
auto const extra{static_cast<int>(Pieces) - 1};
if (Point.path > Path || Point.kind == point_kind::end || Point.kind == point_kind::control2)
Point.path += extra;
return Point;
}
std::size_t editor_track::driven_paths(TTrack const &Track)
{
return Track.eType == tt_Switch ? std::min<std::size_t>(1, Track.m_paths.size()) : Track.m_paths.size();
}
editor_track::middle_point editor_track::middle(TTrack const &Track)
{
auto const count{driven_paths(Track)};
double total{0.0};
for (std::size_t i = 0; i < count; ++i)
total += bezier{Track.m_paths[i]}.plan_length();
middle_point result{Track.location(), {0.0, 0.0, 1.0}, total * 0.5};
double along{0.0};
for (std::size_t i = 0; i < count; ++i)
{
bezier const curve{Track.m_paths[i]};
auto const length{curve.plan_length()};
if (along + length >= result.chainage || i + 1 == count)
{
auto const t{curve.parameter(result.chainage - along)};
result.point = curve.point(t);
result.tangent = curve.first(t);
return result;
}
along += length;
}
return result;
}
double editor_track::joint_roll(TTrack const &Track, point_ref const &Point)
{
if (Point.path < 0 || Point.path >= static_cast<int>(Track.m_paths.size()))
return 0.0;
auto const &path{Track.m_paths[Point.path]};
return Point.kind == point_kind::start ? path.rolls[0] : -path.rolls[1];
}
void editor_track::roll_joined(TTrack &Other, point_ref const &Theirs, float const Roll)
{
auto &theirs{Other.m_paths[Theirs.path]};
if (Theirs.kind == point_kind::start)
theirs.rolls[0] = -Roll;
else
theirs.rolls[1] = Roll;
}
void editor_track::match_rolls(TTrack const &Track, std::vector<joint> const &Joints)
{
for (auto const &joint : Joints)
if (joint.own.path < static_cast<int>(Track.m_paths.size()) && joint.theirs.path < static_cast<int>(joint.other->m_paths.size()))
roll_joined(*joint.other, joint.theirs, static_cast<float>(joint_roll(Track, joint.own)));
}
glm::dvec3 editor_track::outward(TTrack const &Track, point_ref const &Point)
{
bezier const curve{Track.m_paths[Point.path]};
return Point.kind == point_kind::start ? -curve.first(0.0) : curve.first(1.0);
}
editor_track::point_ref editor_track::switch_main(TTrack const &Track, point_ref Point)
{
if (Track.eType == tt_Switch && Point.valid() && Point.path > 0)
Point.path = 0;
return Point;
}
glm::dvec3 editor_track::control_vector(TTrack const &Track, point_ref const &Control)
{
return point_position(Track, Control) - point_position(Track, end_of(Control));
}
std::optional<editor_track::drive_spot> editor_track::drive_spot_of(TTrack const &Switch)
{
if (Switch.eType != tt_Switch || Switch.m_paths.size() < 2)
return std::nullopt;
auto const &main{Switch.m_paths[0]};
auto const &diverging{Switch.m_paths[1]};
auto const start{main.points[segment_data::point::start]};
auto const &control{main.points[segment_data::point::control1]};
auto const along{control != glm::dvec3{} ? control : main.points[segment_data::point::end] - start};
auto heading{plan_of(along)};
if (glm::length(heading) < 1e-9)
return std::nullopt;
heading = glm::normalize(heading);
glm::dvec2 const right{-heading.y, heading.x};
drive_spot spot;
spot.rightswitch = glm::dot(plan_of(diverging.points[segment_data::point::end] - start), right) > 0.0;
spot.location = start;
auto const yaw{glm::degrees(std::atan2(heading.x, heading.y))};
spot.yaw = yaw < 0.0 ? yaw + 360.0 : yaw;
auto const run{glm::length(plan_of(along))};
auto const roll{static_cast<double>(main.rolls[0])};
spot.tilt = {-glm::degrees(std::atan(along.y / run)), roll};
if (Global.bRollFix)
spot.location.y += 0.75 * std::abs(std::sin(glm::radians(roll)));
return spot;
}
double editor_track::straight_axis::along(glm::dvec3 const &Point) const
{
return glm::dot(plan_of(Point - origin), direction);
}
double editor_track::straight_axis::lateral(glm::dvec3 const &Point) const
{
return std::abs(cross(direction, plan_of(Point - origin)));
}
std::optional<editor_track::straight_axis> editor_track::main_axis(TTrack const &Switch)
{
if (Switch.m_paths.empty())
return std::nullopt;
auto const &main{Switch.m_paths.front()};
auto const origin{main.points[segment_data::point::start]};
auto const axis{plan_of(main.points[segment_data::point::end] - origin)};
auto const length{glm::length(axis)};
if (length < 1e-3)
return std::nullopt;
return straight_axis{origin, axis / length, length};
}
std::vector<segment_data> editor_track::slid(TTrack const &Switch, double const Distance)
{
if (Switch.m_paths.empty())
return {};
auto const &main{Switch.m_paths.front()};
auto const run{main.points[segment_data::point::end] - main.points[segment_data::point::start]};
auto const planlength{glm::length(plan_of(run))};
if (planlength < 1e-3)
return {};
auto const shift{run * (Distance / planlength)};
auto paths{Switch.m_paths};
for (auto &path : paths)
{
path.points[segment_data::point::start] += shift;
path.points[segment_data::point::end] += shift;
}
return paths;
}
void editor_track::edit::capture(TTrack &Track)
{
if (std::find(changed.begin(), changed.end(), &Track) != changed.end())
return;
states.emplace_back(&Track, editor_track::capture(Track));
changed.push_back(&Track);
}