17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 00:29:19 +02:00
Files
maszyna/editor/editorGeometry.hpp

163 lines
5.0 KiB
C++

/*
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 <vector>
#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));
}
inline std::vector<segment_data> arc_pieces(glm::dvec3 const &Point, glm::dvec2 const &Direction, double const Grade, double const Radius, double const Angle, int const Side)
{
std::vector<segment_data> result;
double const side{static_cast<double>(Side)};
glm::dvec2 const centre{plan_of(Point) + glm::dvec2{-Direction.y, Direction.x} * (side * Radius)};
auto const count{std::max(1, static_cast<int>(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<float>(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, 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