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

refactor(editor): shared plan geometry and formatting helpers

This commit is contained in:
maj00r
2026-10-04 00:33:44 +02:00
parent 8778353b22
commit e5c91d8a1a
10 changed files with 392 additions and 487 deletions

View File

@@ -9,11 +9,12 @@ http:
#include "stdafx.h"
#include "editor/editorAlignment.hpp"
#include "editor/editorFormat.hpp"
#include "editor/editorGeometry.hpp"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdarg>
#include <cstdio>
#include <functional>
@@ -26,43 +27,20 @@ namespace
double const kGravity{9.81};
double const kPi{3.14159265358979323846};
double cross(glm::dvec2 const &A, glm::dvec2 const &B)
{
return A.x * B.y - A.y * B.x;
}
using geometry::cross;
using geometry::plan_of;
using geometry::turned;
glm::dvec2 perpendicular(glm::dvec2 const &Vector)
{
return {-Vector.y, Vector.x};
}
glm::dvec2 rotate(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};
}
glm::dvec2 plan(glm::dvec3 const &Point)
{
return {Point.x, Point.z};
}
double ceil_to(double const Value, double const Step)
{
return std::ceil(Value / Step - 1e-9) * Step;
}
std::string format(char const *Format, ...)
{
char buffer[512];
va_list arguments;
va_start(arguments, Format);
std::vsnprintf(buffer, sizeof(buffer), Format, arguments);
va_end(arguments);
return buffer;
}
struct local_point
{
double x{0.0};
@@ -157,8 +135,8 @@ void element_point(element const &Element, transition_shape const Shape, double
case element_kind::arc:
{
auto const angle{Element.turn * Distance / Element.radius};
Position = Element.origin + rotate(Element.normal, angle);
Direction = rotate(Element.direction, angle);
Position = Element.origin + turned(Element.normal, angle);
Direction = turned(Element.direction, angle);
break;
}
case element_kind::transition_in:
@@ -290,8 +268,8 @@ result fit_between(design const &Design)
auto const startreserve{std::max(0.0, Design.start_reserve)};
auto const endreserve{std::max(0.0, Design.end_reserve)};
auto const start{plan(Design.start)};
auto const end{plan(Design.end)};
auto const start{plan_of(Design.start)};
auto const end{plan_of(Design.end)};
auto const startdirection{glm::normalize(Design.start_direction)};
auto const enddirection{glm::normalize(Design.end_direction)};
auto const count{Design.vertices.size()};
@@ -578,7 +556,7 @@ result fit_between(design const &Design)
push(piece);
auto const local{spiral_point(segment[0], segment[1], segment[2], segment[2])};
origin += heading * local.x + piece.normal * local.y;
heading = rotate(heading, side * local.angle);
heading = turned(heading, side * local.angle);
}
vertexchainage[k] = (startchainage + chainage) * 0.5;
cursor = origin;
@@ -655,7 +633,7 @@ result fit_between(design const &Design)
curve.length = radius * std::max(0.0, arcangle);
curve.origin = centre;
curve.normal = arcstart - centre;
curve.direction = rotate(directionin, side * endin.angle);
curve.direction = turned(directionin, side * endin.angle);
vertexchainage[k] = chainage + curve.length * 0.5;
if (curve.length > 1e-6)
push(curve);
@@ -796,8 +774,8 @@ result compute(design const &Design)
bool tangent_intersection(design const &Design, glm::dvec2 &Point)
{
auto const start{plan(Design.start)};
auto const offset{plan(Design.end) - start};
auto const start{plan_of(Design.start)};
auto const offset{plan_of(Design.end) - start};
auto const startdirection{glm::normalize(Design.start_direction)};
auto const enddirection{glm::normalize(Design.end_direction)};
auto const determinant{cross(startdirection, enddirection)};
@@ -813,7 +791,7 @@ bool tangent_intersection(design const &Design, glm::dvec2 &Point)
bool collinear(design const &Design)
{
auto const offset{plan(Design.end) - plan(Design.start)};
auto const offset{plan_of(Design.end) - plan_of(Design.start)};
auto const startdirection{glm::normalize(Design.start_direction)};
auto const enddirection{glm::normalize(Design.end_direction)};
return std::abs(cross(startdirection, offset)) <= 0.01 && glm::dot(startdirection, enddirection) >= std::cos(1e-4) && glm::dot(startdirection, offset) > 0.0;

26
editor/editorFormat.hpp Normal file
View File

@@ -0,0 +1,26 @@
/*
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 <cstdio>
#include <string>
// printf-style formatting into a string, for the messages and the data lines of the editor
template <typename... Args>
std::string format(char const *Format, Args... Arguments)
{
auto const length{std::snprintf(nullptr, 0, Format, Arguments...)};
if (length <= 0)
return {};
std::string text(static_cast<std::size_t>(length) + 1, '\0');
std::snprintf(text.data(), text.size(), Format, Arguments...);
text.resize(static_cast<std::size_t>(length));
return text;
}

131
editor/editorGeometry.hpp Normal file
View File

@@ -0,0 +1,131 @@
/*
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 <algorithm>
#include <cmath>
#include <glm/glm.hpp>
#include "world/Segment.h"
// plan (x, z) geometry and cubic Bezier curves of the paths, shared by the track tools
namespace geometry
{
// points of the paths closer than this are taken for the same point
double constexpr same_point{0.05};
inline glm::dvec2 plan_of(glm::dvec3 const &Point)
{
return {Point.x, Point.z};
}
inline double plan_distance(glm::dvec3 const &A, glm::dvec3 const &B)
{
return std::hypot(A.x - B.x, A.z - B.z);
}
// z component of the 3d cross product; for a unit Direction it's the offset of the Point to its left
inline double cross(glm::dvec2 const &Direction, glm::dvec2 const &Point)
{
return Direction.x * Point.y - Direction.y * Point.x;
}
inline 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};
}
inline double signed_angle(glm::dvec2 const &From, glm::dvec2 const &To)
{
return std::atan2(cross(From, To), glm::dot(From, To));
}
// slope of the direction, rise over the run in the plan
inline double grade_of(glm::dvec3 const &Direction)
{
return Direction.y / std::max(1e-9, std::hypot(Direction.x, Direction.z));
}
// path as a cubic Bezier curve; a path without control points is a straight with evenly spread parameter
struct bezier
{
glm::dvec3 p0, p1, p2, p3;
explicit bezier(segment_data const &Path)
{
p0 = Path.points[segment_data::point::start];
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{})
{
p1 = glm::mix(p0, p3, 1.0 / 3.0);
p2 = glm::mix(p0, p3, 2.0 / 3.0);
}
else
{
p1 = p0 + control1;
p2 = p3 + control2;
}
}
glm::dvec3 point(double const T) const
{
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;
}
glm::dvec3 first(double const T) const
{
auto const u{1.0 - T};
return 3.0 * u * u * (p1 - p0) + 6.0 * u * T * (p2 - p1) + 3.0 * T * T * (p3 - p2);
}
glm::dvec3 second(double const T) const
{
return 6.0 * (1.0 - T) * (p2 - 2.0 * p1 + p0) + 6.0 * T * (p3 - 2.0 * p2 + p1);
}
double plan_length(int const Steps = 32) const
{
double length{0.0};
auto previous{plan_of(p0)};
for (int i = 1; i <= Steps; ++i)
{
auto const next{plan_of(point(static_cast<double>(i) / Steps))};
length += glm::distance(previous, next);
previous = next;
}
return length;
}
// parameter at specified plan distance from the start
double parameter(double const Distance, int const Steps = 256) const
{
double length{0.0};
auto previous{plan_of(p0)};
for (int i = 1; i <= Steps; ++i)
{
auto const next{plan_of(point(static_cast<double>(i) / Steps))};
auto const step{glm::distance(previous, next)};
if (length + step >= Distance)
return (i - 1 + (step > 1e-12 ? (Distance - length) / step : 0.0)) / Steps;
length += step;
previous = next;
}
return 1.0;
}
};
// distance between the ends of the path, in the plan
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]));
}
} // namespace geometry

View File

@@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "editor/editorInfra.hpp"
#include "editor/editorGeometry.hpp"
#include "world/Track.h"
#include <algorithm>
@@ -23,50 +24,21 @@ namespace infra
namespace
{
using geometry::bezier;
using geometry::plan_distance;
using geometry::plan_of;
int const kSteps{128};
struct curve
{
glm::dvec3 p0, p1, p2, p3;
};
curve curve_of(segment_data const &Path)
{
auto const &start{Path.points[segment_data::point::start]};
auto const &end{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{})
return {start, start + (end - start) / 3.0, end + (start - end) / 3.0, end};
return {start, start + control1, end + control2, end};
}
glm::dvec3 point(curve const &Curve, double const T)
{
auto const u{1.0 - T};
return Curve.p0 * (u * u * u) + Curve.p1 * (3.0 * u * u * T) + Curve.p2 * (3.0 * u * T * T) + Curve.p3 * (T * T * T);
}
glm::dvec3 derivative(curve const &Curve, double const T)
{
auto const u{1.0 - T};
return (Curve.p1 - Curve.p0) * (3.0 * u * u) + (Curve.p2 - Curve.p1) * (6.0 * u * T) + (Curve.p3 - Curve.p2) * (3.0 * T * T);
}
double plan(glm::dvec3 const &A, glm::dvec3 const &B)
{
return std::hypot(A.x - B.x, A.z - B.z);
}
// arc length in the plan at the evenly spread parameters
std::array<double, kSteps + 1> lengths(curve const &Curve)
std::array<double, kSteps + 1> lengths(bezier const &Curve)
{
std::array<double, kSteps + 1> result{};
auto previous{Curve.p0};
for (int i = 1; i <= kSteps; ++i)
{
auto const next{point(Curve, static_cast<double>(i) / kSteps)};
result[i] = result[i - 1] + plan(previous, next);
auto const next{Curve.point(static_cast<double>(i) / kSteps)};
result[i] = result[i - 1] + plan_distance(previous, next);
previous = next;
}
return result;
@@ -92,9 +64,9 @@ double length_at(std::array<double, kSteps + 1> const &Lengths, double const T)
return Lengths[index] + (Lengths[index + 1] - Lengths[index]) * (position - index);
}
glm::dvec2 plan_direction(curve const &Curve, double const T)
glm::dvec2 plan_direction(bezier const &Curve, double const T)
{
auto tangent{derivative(Curve, T)};
auto tangent{Curve.first(T)};
glm::dvec2 result{tangent.x, tangent.z};
if (glm::length(result) < 1e-9)
result = {Curve.p3.x - Curve.p0.x, Curve.p3.z - Curve.p0.z};
@@ -144,27 +116,27 @@ std::vector<category> const &categories()
double plan_length(segment_data const &Path)
{
return lengths(curve_of(Path)).back();
return lengths(bezier{Path}).back();
}
std::vector<glm::dvec3> outline(segment_data const &Path, double const Step)
{
auto const shape{curve_of(Path)};
bezier const shape{Path};
// the speed along the curve doesn't exceed three times the longest leg of the control polygon
auto const leg{std::max({plan(shape.p0, shape.p1), plan(shape.p1, shape.p2), plan(shape.p2, shape.p3)})};
auto const leg{std::max({plan_distance(shape.p0, shape.p1), plan_distance(shape.p1, shape.p2), plan_distance(shape.p2, shape.p3)})};
auto const count{std::clamp(static_cast<int>(std::ceil(3.0 * leg / std::max(Step, 0.1))), 1, 4096)};
std::vector<glm::dvec3> result;
result.reserve(count + 1);
for (int i = 0; i <= count; ++i)
result.push_back(point(shape, static_cast<double>(i) / count));
result.push_back(shape.point(static_cast<double>(i) / count));
return result;
}
frame frame_at(segment_data const &Path, double const S)
{
auto const shape{curve_of(Path)};
bezier const shape{Path};
auto const t{parameter(lengths(shape), S)};
return {point(shape, t), plan_direction(shape, t)};
return {shape.point(t), plan_direction(shape, t)};
}
frame frame_at(station const &Station)
@@ -176,9 +148,9 @@ frame frame_at(station const &Station)
double project(segment_data const &Path, glm::dvec3 const &Point, double &S, bool &Interior)
{
auto const shape{curve_of(Path)};
bezier const shape{Path};
auto const table{lengths(shape)};
auto const distance = [&](double const T) { return plan(point(shape, T), Point); };
auto const distance = [&](double const T) { return plan_distance(shape.point(T), Point); };
auto best{0};
auto bestdistance{distance(0.0)};
for (int i = 1; i <= kSteps; ++i)
@@ -256,7 +228,7 @@ anchor make_anchor(station const &At, glm::dvec3 const &Point)
result.at = At;
auto const axis{frame_at(At)};
glm::dvec2 const side{axis.forward.y, -axis.forward.x};
result.offset = glm::dot(glm::dvec2{Point.x - axis.point.x, Point.z - axis.point.z}, side);
result.offset = glm::dot(plan_of(Point - axis.point), side);
result.height = Point.y - axis.point.y;
result.foot = axis.point;
if (At.track != nullptr && At.path >= 0 && At.path < static_cast<int>(At.track->m_paths.size()))

View File

@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "editor/editorProfile.hpp"
#include "editor/editorFormat.hpp"
#include <algorithm>
#include <cmath>
@@ -21,14 +22,6 @@ namespace profile
namespace
{
template <typename... Args>
std::string format(char const *Format, Args... Arguments)
{
char text[256];
std::snprintf(text, sizeof(text), Format, Arguments...);
return text;
}
double polyline(line const &Line, double const Chainage)
{
auto const &points{Line.points};

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"
@@ -33,7 +35,14 @@ http:
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)
{
@@ -46,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);
@@ -75,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;
@@ -391,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();
}
@@ -454,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);
@@ -784,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])};
@@ -815,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))};
@@ -823,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;
}
@@ -848,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))
@@ -935,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;
@@ -949,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;
@@ -1198,11 +1174,6 @@ double split_margin(double const Length)
return std::max(0.5, 0.002 * Length);
}
double plan_length(segment_data const &Path)
{
return glm::length(plan_of(Path.points[segment_data::point::end] - Path.points[segment_data::point::start]));
}
} // namespace
editor_track::straight_run editor_track::straight_beyond(chain const &Chain, bool const Atend, straight_tolerance const &Tolerance)
@@ -1237,13 +1208,13 @@ editor_track::straight_run editor_track::straight_beyond(chain const &Chain, boo
if (run.tracks.empty())
{
auto const own{glm::normalize(plan_of(far - joint))};
if (std::abs(lateral_of(direction, own)) > 0.01 || glm::dot(direction, own) <= 0.0)
if (std::abs(cross(direction, own)) > 0.01 || glm::dot(direction, own) <= 0.0)
break;
direction = own;
}
auto const offset{plan_of(far) - origin};
auto const along{glm::dot(offset, direction)};
if (along <= run.length + 1e-3 || std::abs(lateral_of(direction, offset)) > Tolerance.offset)
if (along <= run.length + 1e-3 || std::abs(cross(direction, offset)) > Tolerance.offset)
break;
run.tracks.push_back(current);
run.outward.push_back(outward);
@@ -1258,7 +1229,7 @@ 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(plan_length(Run.tracks.back()->m_paths.front())) - 0.01);
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)
@@ -1271,7 +1242,7 @@ TTrack *editor_track::take_straight(straight_run const &Run, TTrack *Edge, glm::
auto *track{Run.tracks[i]};
auto const outward{Run.outward[i]};
auto const &path{track->m_paths.front()};
auto const pathlength{plan_length(path)};
auto const pathlength{chord_length(path)};
auto const margin{split_margin(pathlength)};
auto const remaining{length - covered};
if (remaining <= margin)
@@ -1307,71 +1278,6 @@ TTrack *editor_track::take_straight(straight_run const &Run, TTrack *Edge, glm::
namespace
{
struct bezier
{
glm::dvec3 p0, p1, p2, p3;
explicit bezier(segment_data const &Path)
{
p0 = Path.points[segment_data::point::start];
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{})
{
p1 = glm::mix(p0, p3, 1.0 / 3.0);
p2 = glm::mix(p0, p3, 2.0 / 3.0);
}
else
{
p1 = p0 + control1;
p2 = p3 + control2;
}
}
glm::dvec3 point(double const T) const
{
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;
}
glm::dvec3 first(double const T) const
{
auto const u{1.0 - T};
return 3.0 * u * u * (p1 - p0) + 6.0 * u * T * (p2 - p1) + 3.0 * T * T * (p3 - p2);
}
glm::dvec3 second(double const T) const
{
return 6.0 * (1.0 - T) * (p2 - 2.0 * p1 + p0) + 6.0 * T * (p3 - 2.0 * p2 + p1);
}
double plan_length(int const Steps = 32) const
{
double length{0.0};
auto previous{plan_of(point(0.0))};
for (int i = 1; i <= Steps; ++i)
{
auto const next{plan_of(point(static_cast<double>(i) / Steps))};
length += glm::distance(previous, next);
previous = next;
}
return length;
}
// parameter at specified plan distance from the start
double parameter(double const Distance) const
{
int constexpr steps{256};
double length{0.0};
auto previous{plan_of(point(0.0))};
for (int i = 1; i <= steps; ++i)
{
auto const next{plan_of(point(static_cast<double>(i) / steps))};
auto const step{glm::distance(previous, next)};
if (length + step >= Distance)
return (i - 1 + (step > 1e-12 ? (Distance - length) / step : 0.0)) / steps;
length += step;
previous = next;
}
return 1.0;
}
};
using route_node = std::tuple<TTrack *, int, bool>;
std::vector<route_node> route_next(route_node const &Node)
@@ -1524,7 +1430,7 @@ std::vector<editor_track::route_sample> editor_track::sample_route(route &Route,
sample.span = i;
if (planar > 1e-9)
{
sample.direction = glm::dvec2{derivative.x, derivative.z} / planar;
sample.direction = plan_of(derivative) / planar;
sample.grade = derivative.y / planar;
sample.curvature = (derivative.x * second.z - derivative.z * second.x) / (planar * planar * planar);
}
@@ -1540,10 +1446,10 @@ namespace
{
template <typename Getter>
double interpolate(std::vector<editor_track::route_sample> const &Samples, double const Chainage, Getter const &Get)
auto interpolate(std::vector<editor_track::route_sample> const &Samples, double const Chainage, Getter const &Get) -> decltype(Get(Samples.front()))
{
if (Samples.empty())
return 0.0;
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());
@@ -1583,7 +1489,7 @@ void plane_heights(segment_data &Path, glm::dvec3 const &Origin, glm::dvec2 cons
control1 = (end - start) / 3.0;
control2 = (start - end) / 3.0;
}
auto const height = [&](glm::dvec3 const &Point) { return Origin.y + Grade * glm::dot(glm::dvec2{Point.x - Origin.x, Point.z - Origin.z}, Direction); };
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);
@@ -1614,6 +1520,11 @@ double editor_track::sampled_grade(std::vector<route_sample> const &Samples, dou
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())
@@ -1815,7 +1726,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)
{
@@ -1831,8 +1742,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())
{
@@ -1840,9 +1750,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);
}
@@ -2045,7 +1953,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
@@ -2085,7 +1993,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};
@@ -2242,20 +2150,22 @@ TTrack *editor_track::create_path(TTrack const &Style, segment_data const &Path)
return track;
}
double editor_track::nearest_parameter(TTrack const &Track, glm::dvec3 const &Point)
glm::dvec3 editor_track::point_at(TTrack const &Track, double const T)
{
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 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 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))

View File

@@ -25,6 +25,7 @@ class editor_track
{
public:
enum class point_kind { start, control1, control2, end };
static bool is_end(point_kind const Kind) { return Kind == point_kind::start || Kind == point_kind::end; }
struct point_ref
{
int path{-1};
@@ -108,6 +109,8 @@ class editor_track
double length{0.0};
double grade{0.0};
double azimuth{0.0};
// distance of the point from the start, measured along the line in the plan
double along(glm::dvec3 const &Point) const { return (Point.x - start.x) * direction.x + (Point.z - start.z) * direction.y; }
};
struct straight_tolerance
{
@@ -177,6 +180,7 @@ class editor_track
static std::vector<route_sample> sample_route(route &Route, double const Step);
static double sampled_elevation(std::vector<route_sample> const &Samples, double const Chainage);
static double sampled_grade(std::vector<route_sample> const &Samples, double const Chainage);
static glm::dvec3 sampled_position(std::vector<route_sample> const &Samples, double const Chainage);
// grade of the path which adjoins the end of the route, positive rising along the route
static bool adjoining_grade(route const &Route, bool const Atend, double &Grade);
// path off the route whose end no longer meets the adjoining path in height
@@ -211,6 +215,8 @@ class editor_track
static TTrack *create_path(TTrack const &Style, segment_data const &Path);
static TTrack *split_path(TTrack &Track, double const T);
static double nearest_parameter(TTrack const &Track, glm::dvec3 const &Point);
// point of the first path at the parameter of nearest_parameter()
static glm::dvec3 point_at(TTrack const &Track, double const T);
static std::vector<segment_data> place_switch(switch_template const &Template, glm::dvec3 const &Origin, glm::dvec2 const &Direction, int const Side, double const Grade);
static TTrack *create_switch(switch_template const &Template, std::vector<segment_data> const &Paths, TTrack const &Style, std::string const &Name = {});
static void move_straights(std::vector<straight> const &Lines, std::vector<std::pair<glm::dvec3, glm::dvec3>> const &Ends);