mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 01:39:20 +02:00
feat(editor): infrastructure bound to the paths follows their changes
This commit is contained in:
304
editor/editorInfra.cpp
Normal file
304
editor/editorInfra.cpp
Normal file
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
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/editorInfra.hpp"
|
||||
|
||||
#include "world/Track.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
|
||||
namespace infra
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
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> 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);
|
||||
previous = next;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
double parameter(std::array<double, kSteps + 1> const &Lengths, double const S)
|
||||
{
|
||||
if (S <= 0.0)
|
||||
return 0.0;
|
||||
if (S >= Lengths.back())
|
||||
return 1.0;
|
||||
auto const next{std::upper_bound(Lengths.begin(), Lengths.end(), S)};
|
||||
auto const index{static_cast<int>(std::distance(Lengths.begin(), next)) - 1};
|
||||
auto const run{Lengths[index + 1] - Lengths[index]};
|
||||
auto const fraction{run > 1e-12 ? (S - Lengths[index]) / run : 0.0};
|
||||
return (index + fraction) / kSteps;
|
||||
}
|
||||
|
||||
double length_at(std::array<double, kSteps + 1> const &Lengths, double const T)
|
||||
{
|
||||
auto const position{std::clamp(T, 0.0, 1.0) * kSteps};
|
||||
auto const index{std::min(static_cast<int>(position), kSteps - 1)};
|
||||
return Lengths[index] + (Lengths[index + 1] - Lengths[index]) * (position - index);
|
||||
}
|
||||
|
||||
glm::dvec2 plan_direction(curve const &Curve, double const T)
|
||||
{
|
||||
auto tangent{derivative(Curve, 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};
|
||||
return glm::length(result) > 1e-9 ? glm::normalize(result) : glm::dvec2{0.0, 1.0};
|
||||
}
|
||||
|
||||
std::string lower(std::string Text)
|
||||
{
|
||||
std::transform(Text.begin(), Text.end(), Text.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return Text;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
char const *name(kind const Kind)
|
||||
{
|
||||
switch (Kind)
|
||||
{
|
||||
case kind::model: return "model";
|
||||
case kind::include: return "include";
|
||||
case kind::traction: return "traction";
|
||||
case kind::memcell: return "memcell";
|
||||
case kind::launcher: return "eventlauncher";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
char const *name(category const Category)
|
||||
{
|
||||
switch (Category)
|
||||
{
|
||||
case category::signal: return "Signals";
|
||||
case category::sign: return "Signs";
|
||||
case category::pole: return "Poles and gantries";
|
||||
case category::catenary: return "Catenary";
|
||||
case category::logic: return "Logic";
|
||||
case category::other: return "Other";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::vector<category> const &categories()
|
||||
{
|
||||
static std::vector<category> const result{category::signal, category::sign, category::pole, category::catenary, category::logic, category::other};
|
||||
return result;
|
||||
}
|
||||
|
||||
double plan_length(segment_data const &Path)
|
||||
{
|
||||
return lengths(curve_of(Path)).back();
|
||||
}
|
||||
|
||||
std::vector<glm::dvec3> outline(segment_data const &Path, double const Step)
|
||||
{
|
||||
auto const shape{curve_of(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 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));
|
||||
return result;
|
||||
}
|
||||
|
||||
frame frame_at(segment_data const &Path, double const S)
|
||||
{
|
||||
auto const shape{curve_of(Path)};
|
||||
auto const t{parameter(lengths(shape), S)};
|
||||
return {point(shape, t), plan_direction(shape, t)};
|
||||
}
|
||||
|
||||
frame frame_at(station const &Station)
|
||||
{
|
||||
if (Station.track == nullptr || Station.path < 0 || Station.path >= static_cast<int>(Station.track->m_paths.size()))
|
||||
return {};
|
||||
return frame_at(Station.track->m_paths[Station.path], Station.s);
|
||||
}
|
||||
|
||||
double project(segment_data const &Path, glm::dvec3 const &Point, double &S, bool &Interior)
|
||||
{
|
||||
auto const shape{curve_of(Path)};
|
||||
auto const table{lengths(shape)};
|
||||
auto const distance = [&](double const T) { return plan(point(shape, T), Point); };
|
||||
auto best{0};
|
||||
auto bestdistance{distance(0.0)};
|
||||
for (int i = 1; i <= kSteps; ++i)
|
||||
{
|
||||
auto const d{distance(static_cast<double>(i) / kSteps)};
|
||||
if (d < bestdistance)
|
||||
{
|
||||
bestdistance = d;
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
// golden section search around the nearest sample
|
||||
auto low{std::max(0, best - 1) / static_cast<double>(kSteps)};
|
||||
auto high{std::min(kSteps, best + 1) / static_cast<double>(kSteps)};
|
||||
double const ratio{0.6180339887498949};
|
||||
auto a{high - ratio * (high - low)};
|
||||
auto b{low + ratio * (high - low)};
|
||||
auto da{distance(a)}, db{distance(b)};
|
||||
for (int i = 0; i < 40; ++i)
|
||||
{
|
||||
if (da < db)
|
||||
{
|
||||
high = b;
|
||||
b = a;
|
||||
db = da;
|
||||
a = high - ratio * (high - low);
|
||||
da = distance(a);
|
||||
}
|
||||
else
|
||||
{
|
||||
low = a;
|
||||
a = b;
|
||||
da = db;
|
||||
b = low + ratio * (high - low);
|
||||
db = distance(b);
|
||||
}
|
||||
}
|
||||
auto t{(low + high) * 0.5};
|
||||
if (distance(0.0) <= distance(t))
|
||||
t = 0.0;
|
||||
if (distance(1.0) <= distance(t))
|
||||
t = 1.0;
|
||||
S = length_at(table, t);
|
||||
Interior = (t > 1e-6 && t < 1.0 - 1e-6);
|
||||
return distance(t);
|
||||
}
|
||||
|
||||
double project(TTrack &Track, glm::dvec3 const &Point, station &Station, bool &Interior)
|
||||
{
|
||||
auto best{-1.0};
|
||||
for (int i = 0; i < static_cast<int>(Track.m_paths.size()); ++i)
|
||||
{
|
||||
double s;
|
||||
bool interior;
|
||||
auto const d{project(Track.m_paths[i], Point, s, interior)};
|
||||
// an interior point takes precedence over an end shared with the other path
|
||||
if (best < 0.0 || d < best - 1e-6 || (std::abs(d - best) <= 1e-6 && interior && false == Interior))
|
||||
{
|
||||
best = d;
|
||||
Station = {&Track, i, s};
|
||||
Interior = interior;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
double heading(glm::dvec2 const &Forward)
|
||||
{
|
||||
return glm::degrees(std::atan2(Forward.x, Forward.y));
|
||||
}
|
||||
|
||||
anchor make_anchor(station const &At, glm::dvec3 const &Point)
|
||||
{
|
||||
anchor result;
|
||||
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.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()))
|
||||
{
|
||||
auto const &path{At.track->m_paths[At.path]};
|
||||
result.start = path.points[segment_data::point::start];
|
||||
result.end = path.points[segment_data::point::end];
|
||||
result.length = plan_length(path);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::dvec3 place(anchor &Anchor)
|
||||
{
|
||||
auto const axis{frame_at(Anchor.at)};
|
||||
Anchor.foot = axis.point;
|
||||
glm::dvec2 const side{axis.forward.y, -axis.forward.x};
|
||||
return {axis.point.x + side.x * Anchor.offset, axis.point.y + Anchor.height, axis.point.z + side.y * Anchor.offset};
|
||||
}
|
||||
|
||||
std::vector<rule> default_rules()
|
||||
{
|
||||
return {
|
||||
{category::signal, "semafor sem_ sem- tarcza tarcz_ tz_ tm_ tzw powtarz sygnaliz sygnal signal"},
|
||||
{category::sign, "wskaz wsk_ wsk- w4 w8 w11 w24 w27 kilometr hektometr slupek_km znak tablic sign"},
|
||||
{category::catenary, "trakcj siec_ siec- catenary przewod lina_nosna"},
|
||||
{category::pole, "slup maszt wysieg bramk konstrukcj kotw odciag pole gantry"},
|
||||
};
|
||||
}
|
||||
|
||||
category classify(std::string const &Name, std::vector<rule> const &Rules)
|
||||
{
|
||||
auto const text{lower(Name)};
|
||||
for (auto const &rule : Rules)
|
||||
{
|
||||
std::istringstream words{lower(rule.keywords)};
|
||||
std::string word;
|
||||
while (words >> word)
|
||||
if (text.find(word) != std::string::npos)
|
||||
return rule.group;
|
||||
}
|
||||
return category::other;
|
||||
}
|
||||
|
||||
} // namespace infra
|
||||
128
editor/editorInfra.hpp
Normal file
128
editor/editorInfra.hpp
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
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 <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "world/Segment.h"
|
||||
#include "scene/scenenode.h"
|
||||
|
||||
class TTrack;
|
||||
|
||||
// objects which belong to the railway infrastructure (signals, signs, catenary, poles, the logic placed along the
|
||||
// line) bound to the paths they stand by, so they follow the changes of the track geometry
|
||||
namespace infra
|
||||
{
|
||||
|
||||
enum class kind
|
||||
{
|
||||
model,
|
||||
include,
|
||||
traction,
|
||||
memcell,
|
||||
launcher
|
||||
};
|
||||
enum class category
|
||||
{
|
||||
signal,
|
||||
sign,
|
||||
pole,
|
||||
catenary,
|
||||
logic,
|
||||
other
|
||||
};
|
||||
char const *name(kind Kind);
|
||||
char const *name(category Category);
|
||||
std::vector<category> const &categories();
|
||||
|
||||
// point of a path, measured in the plan from the start of the path
|
||||
struct station
|
||||
{
|
||||
TTrack *track{nullptr};
|
||||
int path{0};
|
||||
double s{0.0};
|
||||
};
|
||||
struct frame
|
||||
{
|
||||
glm::dvec3 point{0.0};
|
||||
glm::dvec2 forward{0.0, 1.0}; // in the plan, x and z
|
||||
};
|
||||
|
||||
double plan_length(segment_data const &Path);
|
||||
// points of the path, no farther apart in the plan than about specified step; the ends included
|
||||
std::vector<glm::dvec3> outline(segment_data const &Path, double Step);
|
||||
frame frame_at(segment_data const &Path, double S);
|
||||
frame frame_at(station const &Station);
|
||||
// nearest point of specified path in the plan. returns: distance in the plan; Interior tells the nearest point
|
||||
// isn't one of the ends of the path
|
||||
double project(segment_data const &Path, glm::dvec3 const &Point, double &S, bool &Interior);
|
||||
// nearest point among the paths of the track
|
||||
double project(TTrack &Track, glm::dvec3 const &Point, station &Station, bool &Interior);
|
||||
// angle around the vertical axis which turns a model to face specified direction, in degrees
|
||||
double heading(glm::dvec2 const &Forward);
|
||||
|
||||
// location of a point relative to the rail axis: the station, a sideways offset and a height above the rail top
|
||||
struct anchor
|
||||
{
|
||||
station at;
|
||||
double offset{0.0}; // positive to the right of the direction of the path
|
||||
double height{0.0};
|
||||
glm::dvec3 foot{0.0}; // point of the axis the location was measured from
|
||||
// ends and length of the path when the station was measured, to tell whether the path changed since
|
||||
glm::dvec3 start{0.0}, end{0.0};
|
||||
double length{0.0};
|
||||
};
|
||||
anchor make_anchor(station const &At, glm::dvec3 const &Point);
|
||||
// location for the current geometry of the path
|
||||
glm::dvec3 place(anchor &Anchor);
|
||||
|
||||
// rules telling infrastructure apart by the names of the files and nodes
|
||||
struct rule
|
||||
{
|
||||
category group{category::other};
|
||||
std::string keywords; // separated by spaces, matched as parts of the lower case name
|
||||
};
|
||||
std::vector<rule> default_rules();
|
||||
// first rule in the list which matches. returns: category::other if none does
|
||||
category classify(std::string const &Name, std::vector<rule> const &Rules);
|
||||
|
||||
struct binding
|
||||
{
|
||||
kind type{kind::model};
|
||||
category group{category::other};
|
||||
scene::basic_node *node{nullptr}; // the object, unless it's an include
|
||||
scene::instance_handle include{0};
|
||||
std::vector<anchor> anchors; // one per point which defines the object: four for a traction piece
|
||||
double yaw{0.0}; // rotation of the object relative to the direction of the path
|
||||
bool turns{false}; // the object has a rotation which follows the path
|
||||
std::string label;
|
||||
bool lost{false}; // none of the paths around could take the object over
|
||||
|
||||
bool same(binding const &Other) const
|
||||
{
|
||||
return type == Other.type && node == Other.node && include == Other.include;
|
||||
}
|
||||
};
|
||||
|
||||
// placement of a bound object, to take a change back
|
||||
struct object_state
|
||||
{
|
||||
binding saved;
|
||||
glm::dvec3 location{0.0};
|
||||
glm::vec3 angles{0.f};
|
||||
std::array<glm::dvec3, 4> points{};
|
||||
std::string directive;
|
||||
bool moved{false}; // the object followed a change since the state was taken
|
||||
};
|
||||
|
||||
} // namespace infra
|
||||
@@ -124,8 +124,15 @@ bool adjoining_direction(TTrack const *Neighbour, glm::dvec3 const &Joint, bool
|
||||
|
||||
}
|
||||
|
||||
std::function<void(TTrack const &)> editor_track::on_capture;
|
||||
std::function<void(std::vector<TTrack *> const &)> editor_track::on_commit;
|
||||
std::function<void(TTrack &)> editor_track::on_retire;
|
||||
std::function<void(TTrack &, TTrack &, segment_data const &)> editor_track::on_split;
|
||||
|
||||
editor_track::state editor_track::capture(TTrack const &Track)
|
||||
{
|
||||
if (on_capture)
|
||||
on_capture(Track);
|
||||
state result;
|
||||
result.paths = Track.m_paths;
|
||||
result.velocity = Track.fVelocity;
|
||||
@@ -235,6 +242,8 @@ void editor_track::commit(std::vector<TTrack *> const &Tracks)
|
||||
for (auto *track : affected)
|
||||
for (auto *vehicle : track->Dynamics)
|
||||
vehicle->Move(0.000001);
|
||||
if (on_commit)
|
||||
on_commit(Tracks);
|
||||
}
|
||||
|
||||
void editor_track::commit_parameters(TTrack &Track)
|
||||
@@ -2300,6 +2309,8 @@ TTrack *editor_track::split_path(TTrack &Track, double const T)
|
||||
second.rolls[0] = roll;
|
||||
auto *created{create_path(Track, second)};
|
||||
Track.m_paths.front() = first;
|
||||
if (on_split)
|
||||
on_split(Track, *created, first);
|
||||
commit({&Track, created});
|
||||
return created;
|
||||
}
|
||||
@@ -2336,6 +2347,8 @@ void editor_track::retire(TTrack &Track)
|
||||
update_transition(*neighbour);
|
||||
rebuild_geometry(*neighbour);
|
||||
}
|
||||
if (on_retire)
|
||||
on_retire(Track);
|
||||
}
|
||||
|
||||
void editor_track::revive(TTrack &Track)
|
||||
|
||||
@@ -227,6 +227,13 @@ class editor_track
|
||||
static void velocity(TTrack &Track, double const Velocity);
|
||||
static void damage(TTrack &Track, int const Damage);
|
||||
|
||||
// let the objects which belong to the paths follow their changes
|
||||
static std::function<void(TTrack const &)> on_capture;
|
||||
static std::function<void(std::vector<TTrack *> const &)> on_commit;
|
||||
static std::function<void(TTrack &)> on_retire;
|
||||
// called before the commit; the first argument keeps the beginning of the path, given in the last one
|
||||
static std::function<void(TTrack &, TTrack &, segment_data const &)> on_split;
|
||||
|
||||
private:
|
||||
static void disconnect(TTrack &Track);
|
||||
static void join(TTrack &Track);
|
||||
|
||||
Reference in New Issue
Block a user