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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
201
editor/editorGroundFollow.cpp
Normal file
201
editor/editorGroundFollow.cpp
Normal 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
|
||||
47
editor/editorGroundFollow.hpp
Normal file
47
editor/editorGroundFollow.hpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
286
editor/editorTrackShape.cpp
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user