17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-07 16:54:50 +01:00

feat(editor): a switch path can be a chain of pieces: the attributes pieces1 and pieces2 keep the parts of each path after the classic bezier, which stays as the approximation older versions read and log the attributes as unknown; the simulation drives, draws and maps a path along its pieces by distance, pieces that don't join the ends of the path are reported and the bezier used; the catalogue Rz switches get the diverging path as the arc of their radius and the straight after it, moving, rotating and mirroring a switch carry the pieces, reshaping a path by hand drops them; self-test commands switchpieces and switchbreak

This commit is contained in:
maj00r
2026-10-12 18:32:23 +02:00
parent 46d41549c9
commit ea341af27a
8 changed files with 329 additions and 60 deletions

View File

@@ -1657,6 +1657,68 @@ editor_mode::selftest_result editor_mode::selftest_switch(std::string const &com
WriteLog(format("SELFTEST switchreplace %s %s: done %d, %s", name.c_str(), found->label.c_str(), done ? 1 : 0, status.c_str())); WriteLog(format("SELFTEST switchreplace %s %s: done %d, %s", name.c_str(), found->label.c_str(), done ? 1 : 0, status.c_str()));
return selftest_result::handled; return selftest_result::handled;
} }
if (command == "switchpieces" || command == "switchbreak")
{
double radius{0.0};
double ratio{0.0};
std::string name;
if (command == "switchpieces")
words >> radius >> ratio;
words >> name;
for (auto *track : simulation::Paths.sequence())
{
if (track == nullptr || track->m_editorremoved || track->eType != tt_Switch || track->m_paths.size() < 2 || track->SwitchExtension == nullptr)
continue;
if (false == name.empty() ? track->name() != name : track->m_paths[1].pieces.empty())
continue;
if (command == "switchbreak")
{
auto &pieces{track->m_paths[1].pieces};
if (pieces.size() < 2)
continue;
pieces[0].points[segment_data::point::end].x += 0.3;
editor_track::commit({track});
WriteLog(format("SELFTEST switchbreak %s: path 2 %.3f m, %zu pieces in use, %zu kept", track->name().c_str(), track->SwitchExtension->Segments[1]->GetLength(), track->SwitchExtension->Segments[1]->PieceCount(), pieces.size()));
return selftest_result::handled;
}
auto const &segment{*track->SwitchExtension->Segments[1]};
auto const &main{track->m_paths[0]};
auto const &branch{track->m_paths[1]};
auto const origin{segment.FastGetPoint(0.0)};
auto const plan{glm::normalize(geometry::plan_of(main.points[segment_data::point::end] - main.points[segment_data::point::start]))};
glm::dvec3 const along{plan.x, 0.0, plan.y};
auto const reach{branch.points[segment_data::point::end] - branch.points[segment_data::point::start]};
auto const hand{along.x * reach.z - along.z * reach.x > 0.0 ? 1.0 : -1.0};
auto const normal{glm::dvec3{-along.z, 0.0, along.x} * hand};
auto const centre{origin + normal * radius};
auto const angle{std::atan(1.0 / ratio)};
auto const arc{radius * angle};
auto const heading0{-std::atan2(along.x, along.z)};
glm::dvec3 position;
glm::vec3 rotation;
double worst{0.0};
for (int i = 0; i <= 10; ++i)
{
segment.RaPositionGet(arc * i / 10.0, position, rotation);
worst = std::max(worst, std::abs(geometry::plan_distance(position, centre) - radius));
}
segment.RaPositionGet(arc, position, rotation);
auto const expected{centre - normal * radius * std::cos(angle) + along * radius * std::sin(angle)};
auto const turn{std::abs(std::remainder(rotation.z - heading0, glm::two_pi<double>()))};
double low{1e9};
double high{-1e9};
for (int i = 0; i <= 10; ++i)
{
segment.RaPositionGet(arc + 0.05 + (segment.GetLength() - arc - 0.1) * i / 10.0, position, rotation);
low = std::min(low, static_cast<double>(rotation.z));
high = std::max(high, static_cast<double>(rotation.z));
}
segment.RaPositionGet(arc, position, rotation);
WriteLog(format("SELFTEST switchpieces %s: path 2 %.3f m in %zu pieces (%zu kept), arc %.3f m, arc end off %.4f m, turn there %.5f (angle %.5f), axis off the arc %.4f m, straight heading spread %.6f, end %.3f %.3f", track->name().c_str(),
segment.GetLength(), segment.PieceCount(), branch.pieces.size(), arc, geometry::plan_distance(position, expected), turn, angle, worst, high - low, segment.FastGetPoint(1.0).x, segment.FastGetPoint(1.0).z));
}
return selftest_result::handled;
}
if (command == "switches") if (command == "switches")
{ {
for (auto const &entry : switch_store::records()) for (auto const &entry : switch_store::records())
@@ -2483,6 +2545,8 @@ editor_mode::selftest_result editor_mode::selftest_terrain(std::string const &co
// splitkind <kind>, nudgekind <kind> <dx> <dz> the first path over an element of the kind split in two, or moved // splitkind <kind>, nudgekind <kind> <dx> <dz> the first path over an element of the kind split in two, or moved
// switchplace <template> <x> <z> <direction> <side> the catalogue template whose label starts so put into the straight // switchplace <template> <x> <z> <direction> <side> the catalogue template whose label starts so put into the straight
// nearest the point, or into the curve (switchcurve); slipplace <template> <x> <z> // nearest the point, or into the curve (switchcurve); slipplace <template> <x> <z>
// switchpieces <R> <n> [name] logs the diverging path made of pieces of each switch: length, the arc R of 1:n against the axis
// switchbreak [name] the first piece of the diverging path moved off its neighbour, the switch committed
// switches logs the switches kept in the scenery and the speed of each diverging track // switches logs the switches kept in the scenery and the speed of each diverging track
// deletetrack <name>, deletekind <kind> the path deleted as Del does it, or the first path over an element of the kind // deletetrack <name>, deletekind <kind> the path deleted as Del does it, or the first path over an element of the kind
// btfcheck <terrain file> the .txtf converted to .btf, the editor lines the binary file keeps logged // btfcheck <terrain file> the .txtf converted to .btf, the editor lines the binary file keeps logged

View File

@@ -1035,12 +1035,13 @@ void editor_mode::render_turnout_ui()
auto &local{own.local[i]}; auto &local{own.local[i]};
local = track->m_paths[i]; local = track->m_paths[i];
auto const tolocal = [&](glm::dvec3 const &Vector) { return glm::dvec3{glm::dot(plan_of(Vector), normal) * side, Vector.y, glm::dot(plan_of(Vector), axis)}; }; auto const tolocal = [&](glm::dvec3 const &Vector) { return glm::dvec3{glm::dot(plan_of(Vector), normal) * side, Vector.y, glm::dot(plan_of(Vector), axis)}; };
local.points[segment_data::point::start] = tolocal(track->m_paths[i].points[segment_data::point::start] - origin); local.transform([&](glm::dvec3 const &Point) { return tolocal(Point - origin); }, tolocal);
local.points[segment_data::point::end] = tolocal(track->m_paths[i].points[segment_data::point::end] - origin);
local.points[segment_data::point::control1] = tolocal(track->m_paths[i].points[segment_data::point::control1]);
local.points[segment_data::point::control2] = tolocal(track->m_paths[i].points[segment_data::point::control2]);
if (side < 0) if (side < 0)
{
local.rolls = {-local.rolls[0], -local.rolls[1]}; local.rolls = {-local.rolls[0], -local.rolls[1]};
for (auto &piece : local.pieces)
piece.rolls = {-piece.rolls[0], -piece.rolls[1]};
}
} }
replace(own, -side); replace(own, -side);
} }
@@ -6069,12 +6070,13 @@ bool editor_mode::plan_crossover(crossover_plan &Plan) const
auto &local{shape.local[i]}; auto &local{shape.local[i]};
local = path; local = path;
auto const tolocal = [&](glm::dvec3 const &Vector) { return glm::dvec3{glm::dot(plan_of(Vector), perpendicular) * side, Vector.y, glm::dot(plan_of(Vector), axis)}; }; auto const tolocal = [&](glm::dvec3 const &Vector) { return glm::dvec3{glm::dot(plan_of(Vector), perpendicular) * side, Vector.y, glm::dot(plan_of(Vector), axis)}; };
local.points[segment_data::point::start] = tolocal(path.points[segment_data::point::start] - origin); local.transform([&](glm::dvec3 const &Point) { return tolocal(Point - origin); }, tolocal);
local.points[segment_data::point::end] = tolocal(path.points[segment_data::point::end] - origin);
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 (side < 0.0) if (side < 0.0)
{
local.rolls = {-local.rolls[0], -local.rolls[1]}; local.rolls = {-local.rolls[0], -local.rolls[1]};
for (auto &piece : local.pieces)
piece.rolls = {-piece.rolls[0], -piece.rolls[1]};
}
} }
Plan.shape = shape; Plan.shape = shape;
auto const station{line.start + glm::dvec3{line.direction.x, line.grade, line.direction.y} * Plan.along}; auto const station{line.start + glm::dvec3{line.direction.x, line.grade, line.direction.y} * Plan.along};

View File

@@ -434,6 +434,11 @@ void editor_track::commit(std::vector<TTrack *> const &Tracks)
} }
for (auto *track : Tracks) for (auto *track : Tracks)
{ {
for (auto &path : track->m_paths)
if (false == path.pieces.empty() &&
(glm::distance(path.pieces.front().points[segment_data::point::start], path.points[segment_data::point::start]) > 0.02 ||
glm::distance(path.pieces.back().points[segment_data::point::end], path.points[segment_data::point::end]) > 0.02))
path.pieces.clear();
track->init_segments(false); track->init_segments(false);
track->update_location(); track->update_location();
track->m_area.radius = -1.f; track->m_area.radius = -1.f;
@@ -504,6 +509,7 @@ void editor_track::move_point(TTrack &Track, point_ref const &Point, glm::dvec3
return; return;
auto &path{Track.m_paths[Point.path]}; auto &path{Track.m_paths[Point.path]};
path.pieces.clear();
switch (Point.kind) switch (Point.kind)
{ {
case point_kind::start: case point_kind::start:
@@ -540,10 +546,7 @@ void editor_track::move_point(TTrack &Track, point_ref const &Point, glm::dvec3
void editor_track::translate(TTrack &Track, glm::dvec3 const &Offset) void editor_track::translate(TTrack &Track, glm::dvec3 const &Offset)
{ {
for (auto &path : Track.m_paths) for (auto &path : Track.m_paths)
{ path.translate(Offset);
path.points[segment_data::point::start] += Offset;
path.points[segment_data::point::end] += Offset;
}
} }
void editor_track::rotate_y(TTrack &Track, glm::dvec3 const &Pivot, double const Angle) void editor_track::rotate_y(TTrack &Track, glm::dvec3 const &Pivot, double const Angle)
@@ -553,12 +556,7 @@ void editor_track::rotate_y(TTrack &Track, glm::dvec3 const &Pivot, double const
auto const rotate = [&](glm::dvec3 const &Vector) { return glm::dvec3{Vector.x * c + Vector.z * s, Vector.y, -Vector.x * s + Vector.z * c}; }; auto const rotate = [&](glm::dvec3 const &Vector) { return glm::dvec3{Vector.x * c + Vector.z * s, Vector.y, -Vector.x * s + Vector.z * c}; };
for (auto &path : Track.m_paths) for (auto &path : Track.m_paths)
{ path.transform([&](glm::dvec3 const &Point) { return Pivot + rotate(Point - Pivot); }, rotate);
for (auto const index : {segment_data::point::start, segment_data::point::end})
path.points[index] = Pivot + rotate(path.points[index] - Pivot);
for (auto const index : {segment_data::point::control1, segment_data::point::control2})
path.points[index] = rotate(path.points[index]);
}
} }
glm::dvec3 editor_track::pivot(TTrack const &Track) glm::dvec3 editor_track::pivot(TTrack const &Track)
@@ -753,16 +751,6 @@ void editor_track::conform_switch(TTrack &Track)
if (Track.eType != tt_Switch || Track.m_paths.size() != 2) if (Track.eType != tt_Switch || Track.m_paths.size() != 2)
return; return;
auto const &main{Track.m_paths[0]}; auto const &main{Track.m_paths[0]};
auto &path{Track.m_paths[1]};
auto &start{path.points[segment_data::point::start]};
auto &end{path.points[segment_data::point::end]};
auto &control1{path.points[segment_data::point::control1]};
auto &control2{path.points[segment_data::point::control2]};
if (control1 == glm::dvec3{} && control2 == glm::dvec3{})
{
control1 = (end - start) / 3.0;
control2 = (start - end) / 3.0;
}
auto const height = [&](glm::dvec3 const &Point) { return surface_height(main, foot_on(main, Point)); }; auto const height = [&](glm::dvec3 const &Point) { return surface_height(main, foot_on(main, Point)); };
auto const slope = [&](glm::dvec3 const &Point, glm::dvec3 const &Control) { auto const slope = [&](glm::dvec3 const &Point, glm::dvec3 const &Control) {
auto const plan{plan_of(Control)}; auto const plan{plan_of(Control)};
@@ -773,17 +761,32 @@ void editor_track::conform_switch(TTrack &Track)
glm::dvec3 const offset{plan.x / run * step, 0.0, plan.y / run * step}; glm::dvec3 const offset{plan.x / run * step, 0.0, plan.y / run * step};
return (4.0 * height(Point + offset) - 3.0 * height(Point) - height(Point + 2.0 * offset)) / (2.0 * step) * run; return (4.0 * height(Point + offset) - 3.0 * height(Point) - height(Point + 2.0 * offset)) / (2.0 * step) * run;
}; };
start.y = height(start); auto const conform = [&](segment_data &Path) {
end.y = height(end); auto &start{Path.points[segment_data::point::start]};
control1.y = slope(start, control1); auto &end{Path.points[segment_data::point::end]};
control2.y = slope(end, control2); auto &control1{Path.points[segment_data::point::control1]};
bezier const curve{path}; auto &control2{Path.points[segment_data::point::control2]};
for (int i = 0; i < 2; ++i) if (control1 == glm::dvec3{} && control2 == glm::dvec3{})
{ {
auto const foot{foot_on(main, i == 0 ? start : end)}; control1 = (end - start) / 3.0;
auto const along{glm::dot(plan_of(curve.first(i)), foot.direction) >= 0.0 ? 1.0 : -1.0}; control2 = (start - end) / 3.0;
path.rolls[i] = static_cast<float>(foot_roll(main, foot) * along); }
} start.y = height(start);
end.y = height(end);
control1.y = slope(start, control1);
control2.y = slope(end, control2);
bezier const curve{Path};
for (int i = 0; i < 2; ++i)
{
auto const foot{foot_on(main, i == 0 ? start : end)};
auto const along{glm::dot(plan_of(curve.first(i)), foot.direction) >= 0.0 ? 1.0 : -1.0};
Path.rolls[i] = static_cast<float>(foot_roll(main, foot) * along);
}
};
auto &path{Track.m_paths[1]};
conform(path);
for (auto &piece : path.pieces)
conform(piece);
} }
double editor_track::switch_station(TTrack const &Track, glm::dvec3 const &Position) double editor_track::switch_station(TTrack const &Track, glm::dvec3 const &Position)
@@ -2545,10 +2548,7 @@ std::vector<editor_track::switch_template> editor_track::find_switch_templates()
auto const &path{track->m_paths[i]}; auto const &path{track->m_paths[i]};
auto &local{entry.local[i]}; auto &local{entry.local[i]};
local = path; local = path;
local.points[segment_data::point::start] = tolocal(path.points[segment_data::point::start] - origin); local.transform([&](glm::dvec3 const &Point) { return tolocal(Point - origin); }, tolocal);
local.points[segment_data::point::end] = tolocal(path.points[segment_data::point::end] - origin);
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(plan_of(entry.local[1].points[segment_data::point::start])) > kSamePoint) if (glm::length(plan_of(entry.local[1].points[segment_data::point::start])) > kSamePoint)
continue; continue;
@@ -2556,9 +2556,11 @@ std::vector<editor_track::switch_template> editor_track::find_switch_templates()
{ {
for (auto &path : entry.local) for (auto &path : entry.local)
{ {
for (auto &point : path.points) auto const mirror = [](glm::dvec3 const &Point) { return glm::dvec3{-Point.x, Point.y, Point.z}; };
point.x = -point.x; path.transform(mirror, mirror);
path.rolls = {-path.rolls[0], -path.rolls[1]}; path.rolls = {-path.rolls[0], -path.rolls[1]};
for (auto &piece : path.pieces)
piece.rolls = {-piece.rolls[0], -piece.rolls[1]};
} }
} }
entry.length = entry.local[0].points[segment_data::point::end].z; entry.length = entry.local[0].points[segment_data::point::end].z;
@@ -2594,12 +2596,13 @@ std::vector<segment_data> editor_track::place_switch(switch_template const &Temp
for (auto const &local : Template.local) for (auto const &local : Template.local)
{ {
segment_data path{local}; segment_data path{local};
path.points[segment_data::point::start] = Origin + vector(local.points[segment_data::point::start]); path.transform([&](glm::dvec3 const &Point) { return Origin + vector(Point); }, vector);
path.points[segment_data::point::end] = Origin + vector(local.points[segment_data::point::end]);
path.points[segment_data::point::control1] = vector(local.points[segment_data::point::control1]);
path.points[segment_data::point::control2] = vector(local.points[segment_data::point::control2]);
if (Side < 0) if (Side < 0)
{
path.rolls = {-local.rolls[0], -local.rolls[1]}; path.rolls = {-local.rolls[0], -local.rolls[1]};
for (auto &piece : path.pieces)
piece.rolls = {-piece.rolls[0], -piece.rolls[1]};
}
result.push_back(path); result.push_back(path);
} }
return result; return result;
@@ -2644,6 +2647,21 @@ std::vector<editor_track::switch_template> editor_track::standard_switch_templat
diverging.points[segment_data::point::control1] = {0.0, 0.0, a * 2.0 / 3.0}; diverging.points[segment_data::point::control1] = {0.0, 0.0, a * 2.0 / 3.0};
diverging.points[segment_data::point::control2] = {-offset * 2.0 / 3.0, 0.0, -b * 2.0 / 3.0}; diverging.points[segment_data::point::control2] = {-offset * 2.0 / 3.0, 0.0, -b * 2.0 / 3.0};
diverging.radius = static_cast<float>(definition.radius); diverging.radius = static_cast<float>(definition.radius);
auto const tangent{definition.radius * std::tan(angle * 0.5)};
glm::dvec3 const arcend{tangent * std::sin(angle), 0.0, tangent + tangent * std::cos(angle)};
if (glm::distance(arcend, diverging.points[segment_data::point::end]) > 0.5)
{
auto const handle{4.0 / 3.0 * definition.radius * std::tan(angle * 0.25)};
segment_data arc;
arc.points[segment_data::point::end] = arcend;
arc.points[segment_data::point::control1] = {0.0, 0.0, handle};
arc.points[segment_data::point::control2] = {-handle * std::sin(angle), 0.0, -handle * std::cos(angle)};
arc.radius = static_cast<float>(definition.radius);
segment_data straight;
straight.points[segment_data::point::start] = arcend;
straight.points[segment_data::point::end] = diverging.points[segment_data::point::end];
diverging.pieces = {arc, straight};
}
entry.label = std::string{definition.name} + " a " + std::to_string(a).substr(0, std::to_string(a).find('.') + 4) + " b " + std::to_string(b).substr(0, std::to_string(b).find('.') + 4) + (estimated ? " (b est.)" : ""); entry.label = std::string{definition.name} + " a " + std::to_string(a).substr(0, std::to_string(a).find('.') + 4) + " b " + std::to_string(b).substr(0, std::to_string(b).find('.') + 4) + (estimated ? " (b est.)" : "");
result.push_back(entry); result.push_back(entry);
} }

View File

@@ -359,10 +359,7 @@ std::vector<segment_data> editor_track::slid(TTrack const &Switch, double const
auto const shift{run * (Distance / planlength)}; auto const shift{run * (Distance / planlength)};
auto paths{Switch.m_paths}; auto paths{Switch.m_paths};
for (auto &path : paths) for (auto &path : paths)
{ path.translate(shift);
path.points[segment_data::point::start] += shift;
path.points[segment_data::point::end] += shift;
}
return paths; return paths;
} }

View File

@@ -965,10 +965,7 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements, st
auto const path_text = [](TTrack &Path, glm::dvec3 const &Offset) { auto const path_text = [](TTrack &Path, glm::dvec3 const &Offset) {
auto const paths{Path.m_paths}; auto const paths{Path.m_paths};
for (auto &path : Path.m_paths) for (auto &path : Path.m_paths)
{ path.translate(-Offset);
path.points[segment_data::point::start] -= Offset;
path.points[segment_data::point::end] -= Offset;
}
std::string text; std::string text;
Path.export_as_text(text); Path.export_as_text(text);
Path.m_paths = paths; Path.m_paths = paths;

View File

@@ -33,6 +33,16 @@ segment_data::deserialize( cParser &Input, glm::dvec3 const &Offset ) {
} }
void
segment_data::translate( glm::dvec3 const &Offset ) {
points[ segment_data::point::start ] += Offset;
points[ segment_data::point::end ] += Offset;
for( auto &piece : pieces ) {
piece.translate( Offset );
}
}
TSegment::TSegment(TTrack *owner) : TSegment::TSegment(TTrack *owner) :
pOwner( owner ) pOwner( owner )
{} {}
@@ -62,6 +72,8 @@ bool TSegment::Init(glm::dvec3 NewPoint1, glm::dvec3 NewPoint2, double fNewStep,
bool TSegment::Init(glm::dvec3 &NewPoint1, glm::dvec3 NewCPointOut, glm::dvec3 NewCPointIn, glm::dvec3 &NewPoint2, double fNewStep, double fNewRoll1, double fNewRoll2, bool bIsCurve) bool TSegment::Init(glm::dvec3 &NewPoint1, glm::dvec3 NewCPointOut, glm::dvec3 NewCPointIn, glm::dvec3 &NewPoint2, double fNewStep, double fNewRoll1, double fNewRoll2, bool bIsCurve)
{ // wersja uniwersalna (dla krzywej i prostego) { // wersja uniwersalna (dla krzywej i prostego)
m_pieces.clear();
m_starts.clear();
Point1 = NewPoint1; Point1 = NewPoint1;
CPointOut = NewCPointOut; CPointOut = NewCPointOut;
CPointIn = NewCPointIn; CPointIn = NewCPointIn;
@@ -133,6 +145,45 @@ bool TSegment::Init(glm::dvec3 &NewPoint1, glm::dvec3 NewCPointOut, glm::dvec3 N
return true; return true;
} }
bool TSegment::InitChain(std::vector<std::shared_ptr<TSegment>> Pieces)
{
if( Pieces.empty() )
return false;
m_pieces = std::move( Pieces );
m_starts.clear();
fLength = 0.0;
iSegCount = 0;
for( auto const &piece : m_pieces ) {
m_starts.push_back( fLength );
fLength += piece->fLength;
iSegCount += piece->iSegCount;
}
auto const &first { *m_pieces.front() };
auto const &last { *m_pieces.back() };
Point1 = first.Point1;
CPointOut = first.CPointOut;
CPointIn = last.CPointIn;
Point2 = last.Point2;
fRoll1 = first.fRoll1;
fRoll2 = last.fRoll2;
bCurve = true;
fDirection = -std::atan2(Point2.x - Point1.x, Point2.z - Point1.z);
fStoop = std::atan2(Point2.y - Point1.y, fLength);
fStep = fLength / iSegCount;
fTsBuffer.resize( iSegCount + 1 );
for( int i = 0; i <= iSegCount; ++i ) {
fTsBuffer[ i ] = static_cast<double>( i ) / iSegCount;
}
fTsSlope.assign( iSegCount + 1, fStep / fLength );
return true;
}
std::size_t TSegment::piece_at(double const s) const
{
auto const next { std::upper_bound( std::begin( m_starts ), std::end( m_starts ), s ) };
return next == std::begin( m_starts ) ? 0 : static_cast<std::size_t>( std::distance( std::begin( m_starts ), next ) - 1 );
}
void TSegment::InitTFromSInterpolateData() void TSegment::InitTFromSInterpolateData()
{ {
// pre-compute the curve parameter t at equal distance intervals // pre-compute the curve parameter t at equal distance intervals
@@ -235,6 +286,8 @@ double TSegment::FastGetTFromS(double const s) const
// NOTE: for s outside <0, fLength> the result is extrapolated // NOTE: for s outside <0, fLength> the result is extrapolated
// outside <0, 1>, which is the expected behavior (see e.g. usage // outside <0, 1>, which is the expected behavior (see e.g. usage
// in GetDirection()) // in GetDirection())
if( false == m_pieces.empty() )
return s / fLength;
int const i = std::clamp(static_cast<int>(s / fStep), 0, iSegCount - 1); // interval index int const i = std::clamp(static_cast<int>(s / fStep), 0, iSegCount - 1); // interval index
double const u = (s - i * fStep) / fStep; // position within interval (possibly outside) double const u = (s - i * fStep) / fStep; // position within interval (possibly outside)
double const u2 = u * u; double const u2 = u * u;
@@ -263,6 +316,8 @@ double TSegment::ComputeLength() const // McZapkie-150503: dlugosc miedzy punkta
// Ra: koniec rekurencji jeśli po podziale suma długości nie różni się więcej niż 0.5mm od // Ra: koniec rekurencji jeśli po podziale suma długości nie różni się więcej niż 0.5mm od
// poprzedniej // poprzedniej
// Ra: ewentualnie rozpoznać łuk okręgu płaskiego i liczyć ze wzoru na długość łuku // Ra: ewentualnie rozpoznać łuk okręgu płaskiego i liczyć ze wzoru na długość łuku
if( false == m_pieces.empty() )
return fLength;
double t; double t;
double l = 0; double l = 0;
glm::dvec3 last{0, 0, 0}; // długość liczona po przesunięciu odcinka do początku układu glm::dvec3 last{0, 0, 0}; // długość liczona po przesunięciu odcinka do początku układu
@@ -366,7 +421,11 @@ Math3D::vector3 TSegment::GetPoint(double const fDistance) const
*/ */
void TSegment::RaPositionGet(double const fDistance, glm::dvec3 &position, glm::vec3 &rotation) const { void TSegment::RaPositionGet(double const fDistance, glm::dvec3 &position, glm::vec3 &rotation) const {
if (bCurve) { if( false == m_pieces.empty() ) {
auto const piece { piece_at( fDistance ) };
m_pieces[ piece ]->RaPositionGet( fDistance - m_starts[ piece ], position, rotation );
}
else if (bCurve) {
// można by wprowadzić uproszczony wzór dla okręgów płaskich // można by wprowadzić uproszczony wzór dla okręgów płaskich
auto const t = FastGetTFromS(fDistance); // aproksymacja dystansu na krzywej Beziera na parametr (t) auto const t = FastGetTFromS(fDistance); // aproksymacja dystansu na krzywej Beziera na parametr (t)
position = FastGetPoint( t ); position = FastGetPoint( t );
@@ -393,6 +452,12 @@ void TSegment::RaPositionGet(double const fDistance, glm::dvec3 &position, glm::
glm::dvec3 TSegment::FastGetPoint(double const t) const glm::dvec3 TSegment::FastGetPoint(double const t) const
{ {
// return (bCurve?std::lerp(t,Point1,CPointOut,CPointIn,Point2):((1.0-t)*Point1+(t)*Point2)); // return (bCurve?std::lerp(t,Point1,CPointOut,CPointIn,Point2):((1.0-t)*Point1+(t)*Point2));
if( false == m_pieces.empty() ) {
auto const distance { t * fLength };
auto const index { piece_at( distance ) };
auto const &piece { *m_pieces[ index ] };
return piece.FastGetPoint( piece.FastGetTFromS( distance - m_starts[ index ] ) );
}
return true == bCurve || iSegCount != 1 ? RaInterpolate(t) : glm::mix(Point1, Point2, t); return true == bCurve || iSegCount != 1 ? RaInterpolate(t) : glm::mix(Point1, Point2, t);
} }

View File

@@ -12,6 +12,8 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/Classes.h" #include "utilities/Classes.h"
#include "rendering/geometrybank.h" #include "rendering/geometrybank.h"
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <memory>
#include <vector>
struct map_colored_paths { struct map_colored_paths {
std::vector<gfx::geometrybank_handle> switches; std::vector<gfx::geometrybank_handle> switches;
@@ -32,10 +34,21 @@ struct segment_data {
std::array<glm::dvec3, 4> points {}; std::array<glm::dvec3, 4> points {};
std::array<float, 2> rolls {}; std::array<float, 2> rolls {};
float radius {}; float radius {};
std::vector<segment_data> pieces; // consecutive parts of the path, which then is their approximation for older versions
// constructors // constructors
segment_data() = default; segment_data() = default;
// methods // methods
void deserialize( cParser &Input, glm::dvec3 const &Offset ); void deserialize( cParser &Input, glm::dvec3 const &Offset );
void translate( glm::dvec3 const &Offset );
// maps the ends of the path and of its pieces with Point and their control vectors with Vector
template <typename Pointmap, typename Vectormap>
void transform( Pointmap const &Point, Vectormap const &Vector ) {
for( auto const index : { point::start, point::end } ) {
points[ index ] = Point( points[ index ] ); }
for( auto const index : { point::control1, point::control2 } ) {
points[ index ] = Vector( points[ index ] ); }
for( auto &piece : pieces ) {
piece.transform( Point, Vector ); } }
}; };
class TSegment class TSegment
@@ -58,6 +71,8 @@ class TSegment
glm::dvec3 vB; glm::dvec3 vB;
glm::dvec3 vC; // współczynniki wielomianów trzeciego stopnia vD==Point1 glm::dvec3 vC; // współczynniki wielomianów trzeciego stopnia vD==Point1
TTrack *pOwner = nullptr; // wskaźnik na właściciela TTrack *pOwner = nullptr; // wskaźnik na właściciela
std::vector<std::shared_ptr<TSegment>> m_pieces; // parts of a path made of several curves, parametrized by distance
std::vector<double> m_starts; // distance from Point1 to the start of each part
glm::dvec3 GetFirstDerivative(double const fTime) const; glm::dvec3 GetFirstDerivative(double const fTime) const;
double RombergIntegral(double const fA, double const fB) const; double RombergIntegral(double const fA, double const fB) const;
@@ -66,6 +81,7 @@ class TSegment
double FastGetTFromS(double const s) const; double FastGetTFromS(double const s) const;
glm::dvec3 RaInterpolate(double const t) const; glm::dvec3 RaInterpolate(double const t) const;
glm::dvec3 RaInterpolate0(double const t) const; glm::dvec3 RaInterpolate0(double const t) const;
std::size_t piece_at(double const s) const;
public: public:
bool bCurve = false; bool bCurve = false;
@@ -73,6 +89,11 @@ public:
explicit TSegment(TTrack *owner); explicit TSegment(TTrack *owner);
bool Init(glm::dvec3 NewPoint1, glm::dvec3 NewPoint2, double fNewStep, double fNewRoll1 = 0, double fNewRoll2 = 0); bool Init(glm::dvec3 NewPoint1, glm::dvec3 NewPoint2, double fNewStep, double fNewRoll1 = 0, double fNewRoll2 = 0);
bool Init(glm::dvec3 &NewPoint1, glm::dvec3 NewCPointOut, glm::dvec3 NewCPointIn, glm::dvec3 &NewPoint2, double fNewStep, double fNewRoll1 = 0, double fNewRoll2 = 0, bool bIsCurve = true); bool Init(glm::dvec3 &NewPoint1, glm::dvec3 NewCPointOut, glm::dvec3 NewCPointIn, glm::dvec3 &NewPoint2, double fNewStep, double fNewRoll1 = 0, double fNewRoll2 = 0, bool bIsCurve = true);
bool InitChain(std::vector<std::shared_ptr<TSegment>> Pieces);
inline
std::size_t
PieceCount() const {
return m_pieces.size(); };
double double
ComputeLength() const; // McZapkie-150503 ComputeLength() const; // McZapkie-150503
// finds point on segment closest to specified point in 3d space. returns: point on segment as value in range 0-1 // finds point on segment closest to specified point in 3d space. returns: point on segment as value in range 0-1
@@ -118,6 +139,9 @@ public:
inline inline
float float
GetRoll(double const Distance) const { GetRoll(double const Distance) const {
if( false == m_pieces.empty() ) {
auto const piece { piece_at( Distance ) };
return m_pieces[ piece ]->GetRoll( Distance - m_starts[ piece ] ); }
return std::lerp( fRoll1, fRoll2, static_cast<float>(Distance / fLength) ); } return std::lerp( fRoll1, fRoll2, static_cast<float>(Distance / fLength) ); }
inline inline
void void

View File

@@ -45,6 +45,40 @@ const int iProsto3[4] = {1, -1, 2, 1}; // segmenty do jazdy prosto
const int iEnds3[13] = {3, 0, 2, 1, 2, 0, -1, 1, 0, 2, 0, 3, 1}; // numer sąsiedniego toru na końcu segmentu "-1" const int iEnds3[13] = {3, 0, 2, 1, 2, 0, -1, 1, 0, 2, 0, 3, 1}; // numer sąsiedniego toru na końcu segmentu "-1"
TIsolated *TIsolated::pRoot = nullptr; TIsolated *TIsolated::pRoot = nullptr;
namespace {
// a part of a switch path, shaped like the path itself: heights on the rail heads, straights made of their own points
void init_switch_piece( TSegment &Segment, segment_data const &Piece, bool const Rail ) {
auto p1 { Piece.points[ segment_data::point::start ] };
auto cp1 { Piece.points[ segment_data::point::control1 ] };
auto cp2 { Piece.points[ segment_data::point::control2 ] };
auto p2 { Piece.points[ segment_data::point::end ] };
if( Rail ) {
p1.y += 0.18;
p2.y += 0.18;
}
if( glm::length2( ( p1 + p1 + p2 ) / 3.0 - p1 - cp1 ) < sq( 0.02 )
|| glm::length2( ( p1 + p2 + p2 ) / 3.0 - p2 - cp2 ) < sq( 0.02 ) ) {
cp1 = cp2 = glm::dvec3( 0, 0, 0 );
}
auto const straight { cp1 == glm::dvec3() && cp2 == glm::dvec3() };
auto const segsize { (
Piece.radius != 0.f ?
std::clamp( std::abs( Piece.radius ) * ( 0.02 / Global.SplineFidelity ), 2.0 / Global.SplineFidelity, 10.0 / Global.SplineFidelity ) :
straight ?
10.0 :
std::clamp( glm::length( p1 - p2 ) * 0.1, 2.0 / Global.SplineFidelity, 10.0 / Global.SplineFidelity ) ) };
if( straight ) {
Segment.Init( p1, p2, segsize, Piece.rolls[ 0 ], Piece.rolls[ 1 ] );
}
else {
Segment.Init( p1, p1 + cp1, p2 + cp2, p2, segsize, Piece.rolls[ 0 ], Piece.rolls[ 1 ] );
}
}
} // namespace
TTrack::profiles_array TTrack::m_profiles; TTrack::profiles_array TTrack::m_profiles;
TTrack::profiles_map TTrack::m_profilesmap; TTrack::profiles_map TTrack::m_profilesmap;
@@ -518,8 +552,6 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin)
} }
} }
init_segments( true );
// optional attributes // optional attributes
parser->getTokens(); parser->getTokens();
*parser >> token; *parser >> token;
@@ -668,12 +700,30 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin)
// called after segment initialisation so the path geometry is final. // called after segment initialisation so the path geometry is final.
} }
} }
else if( str == "pieces1" || str == "pieces2" ) {
// consecutive parts of a path, kept after the path itself for versions which know only the path
auto const index { static_cast<std::size_t>( str.back() - '1' ) };
auto const count { parser->getToken<int>() };
std::vector<segment_data> pieces;
for( int piece = 0; piece < count; ++piece ) {
pieces.emplace_back();
pieces.back().deserialize( *parser, pOrigin );
}
if( index < m_paths.size() ) {
m_paths[ index ].pieces = std::move( pieces );
}
else {
ErrorLog( "Bad track: \"" + str + "\" defined for track \"" + m_name + "\" which has no such path" );
}
}
else else
ErrorLog("Bad track: unknown property: \"" + str + "\" defined for track \"" + m_name + "\""); ErrorLog("Bad track: unknown property: \"" + str + "\" defined for track \"" + m_name + "\"");
parser->getTokens(); parser->getTokens();
*parser >> token; *parser >> token;
str = token; str = token;
} }
init_segments( true );
// alternatywny zapis nazwy odcinka izolowanego - po znaku "@" w nazwie toru // alternatywny zapis nazwy odcinka izolowanego - po znaku "@" w nazwie toru
if ((i = m_name.find("@")) != std::string::npos && i < m_name.length()) if ((i = m_name.find("@")) != std::string::npos && i < m_name.length())
{ {
@@ -958,6 +1008,35 @@ void TTrack::init_segments( bool const Initial ) {
} }
} }
if( eType == tt_Switch ) {
for( std::size_t index = 0; index < 2; ++index ) {
auto const &pieces { m_paths[ index ].pieces };
if( pieces.empty() ) {
continue;
}
auto joined {
glm::distance( pieces.front().points[ segment_data::point::start ], m_paths[ index ].points[ segment_data::point::start ] ) < 0.02
&& glm::distance( pieces.back().points[ segment_data::point::end ], m_paths[ index ].points[ segment_data::point::end ] ) < 0.02 };
for( std::size_t piece = 1; piece < pieces.size(); ++piece ) {
joined = joined && glm::distance( pieces[ piece - 1 ].points[ segment_data::point::end ], pieces[ piece ].points[ segment_data::point::start ] ) < 0.02;
}
if( false == joined ) {
ErrorLog( "Bad track: pieces of path " + std::to_string( index + 1 ) + " of switch \"" + m_name + "\" don't join its ends, the path is used instead" );
continue;
}
auto const texheight { fTexHeight1 };
auto const texheightoffset { fTexHeightOffset };
std::vector<std::shared_ptr<TSegment>> chain;
for( auto const &piece : pieces ) {
chain.emplace_back( std::make_shared<TSegment>( this ) );
init_switch_piece( *chain.back(), piece, ( iCategoryFlag & 1 ) != 0 );
}
fTexHeight1 = texheight;
fTexHeightOffset = texheightoffset;
SwitchExtension->Segments[ index ]->InitChain( std::move( chain ) );
}
}
if( Initial ) { if( Initial ) {
Switch(0); Switch(0);
} // na stałe w położeniu 0 - nie ma początkowego stanu zwrotnicy we wpisie } // na stałe w położeniu 0 - nie ma początkowego stanu zwrotnicy we wpisie
@@ -2432,6 +2511,29 @@ TTrack::export_as_text_( std::ostream &Output ) const {
if( fVerticalRadius != 0.f ) { if( fVerticalRadius != 0.f ) {
Output << "vradius " << fVerticalRadius << ' '; Output << "vradius " << fVerticalRadius << ' ';
} }
for( std::size_t index = 0; index < m_paths.size(); ++index ) {
auto const &pieces { m_paths[ index ].pieces };
if( pieces.empty() ) {
continue;
}
auto const piecesprecision { Output.precision( std::numeric_limits<double>::digits10 ) };
Output << "pieces" << index + 1 << ' ' << pieces.size() << ' ';
for( auto const &piece : pieces ) {
for( auto const point : { segment_data::point::start, segment_data::point::control1, segment_data::point::control2, segment_data::point::end } ) {
Output
<< piece.points[ point ].x << ' '
<< piece.points[ point ].y << ' '
<< piece.points[ point ].z << ' ';
if( point == segment_data::point::start ) {
Output << piece.rolls[ 0 ] << ' ';
}
}
Output
<< piece.rolls[ 1 ] << ' '
<< piece.radius << ' ';
}
Output.precision( piecesprecision );
}
if( eType == tt_Switch if( eType == tt_Switch
&& SwitchExtension->m_material3 != null_handle) { && SwitchExtension->m_material3 != null_handle) {
auto texturefile { GfxRenderer->Material( SwitchExtension->m_material3 )->GetName() }; auto texturefile { GfxRenderer->Material( SwitchExtension->m_material3 )->GetName() };