/* 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 #include #include #include #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)); } inline std::vector arc_pieces(glm::dvec3 const &Point, glm::dvec2 const &Direction, double const Grade, double const Radius, double const Angle, int const Side) { std::vector result; double const side{static_cast(Side)}; glm::dvec2 const centre{plan_of(Point) + glm::dvec2{-Direction.y, Direction.x} * (side * Radius)}; auto const count{std::max(1, static_cast(std::ceil(Angle / glm::radians(90.0) - 1e-9)))}; auto const step{Angle / count}; auto const handle{4.0 / 3.0 * std::tan(step / 4.0) * Radius}; glm::dvec2 const radial{plan_of(Point) - centre}; for (int i = 0; i < count; ++i) { auto const a0{side * step * i}; auto const a1{side * step * (i + 1)}; auto const p0{centre + turned(radial, a0)}; auto const p3{centre + turned(radial, a1)}; auto const d0{turned(Direction, a0)}; auto const d3{turned(Direction, a1)}; auto const y0{Point.y + Grade * Radius * step * i}; auto const y3{Point.y + Grade * Radius * step * (i + 1)}; segment_data path; path.points[segment_data::point::start] = {p0.x, y0, p0.y}; path.points[segment_data::point::end] = {p3.x, y3, p3.y}; path.points[segment_data::point::control1] = {d0.x * handle, Grade * handle, d0.y * handle}; path.points[segment_data::point::control2] = {-d3.x * handle, -Grade * handle, -d3.y * handle}; path.radius = static_cast(Radius); result.push_back(path); } return result; } // path as a cubic Bezier curve; a path without control points is a straight with evenly spread parameter struct bezier { glm::dvec3 p0; glm::dvec3 p1; glm::dvec3 p2; glm::dvec3 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(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(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])); } 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 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(From)), glm::mix(Piece.rolls[0], Piece.rolls[1], static_cast(To))}; return result; } inline std::vector 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