17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-27 08:17:29 +01:00

feat(sleepers): the timbers of a switch get the fastenings of the rails from the submodels plate, chair and check of their model: a plate under each rail where it lies on a timber, slide chairs under the stock rails along the blades, a fastening of both where a check rail or another rail of the frog lies close beside, the legs of the nose in one plate where they're joined; the rails sit on the plate, the blades ride on the slide plates of the chairs as high as the model has them, their webs shortened; the fastenings drawn within 120 m; selftest fastenings

This commit is contained in:
maj00r
2026-10-23 16:46:58 +02:00
parent fc714d50ec
commit 5aedbcdc1b
12 changed files with 440 additions and 10 deletions

View File

@@ -100,6 +100,7 @@ set(SOURCES
"world/SleeperPaths.cpp"
"world/SleeperFeatures.cpp"
"world/SleeperLines.cpp"
"world/SleeperFastenings.cpp"
"world/SleeperLayout.cpp"
"world/SleeperModels.cpp"
"world/SleeperWorker.cpp"

View File

@@ -49,6 +49,7 @@ http://mozilla.org/MPL/2.0/.
#include "application/editormodeselftest.h"
#include "world/TrackSleepers.h"
#include "world/SleeperFastenings.h"
editor_mode::selftest_result editor_mode::selftest_sleepers(std::string const &command, std::istringstream &words)
{
@@ -149,6 +150,37 @@ editor_mode::selftest_result editor_mode::selftest_sleepers(std::string const &c
}
return selftest_result::handled;
}
if (command == "fastenings")
{
// the fastenings on the timbers of the track: along the track, the part and where across it, which way its +x goes
std::string name;
words >> name;
auto *track{simulation::Paths.find(name)};
auto const *model{track != nullptr ? track_sleepers::timber_model(*track) : nullptr};
if (model == nullptr || false == model->fastened())
{
WriteLog("SELFTEST fastenings: no track " + name + " with the fastenings in the model of its timbers");
return selftest_result::handled;
}
std::array<char const *, track_sleepers::part_count> const names{"begin", "main", "end", "plate", "chair", "check"};
track_sleepers::fastened_rails const rails{*track};
std::array<int, track_sleepers::part_count> counts{};
for (auto const &beam : track_sleepers::place(*track))
{
if (beam.plain)
continue;
auto const middle{0.5 * (beam.from + beam.to)};
std::string text{format("SELFTEST fastenings %s at %.2f %.2f:", track->name().c_str(), middle.x, middle.z)};
for (auto const &fastening : rails.on(beam, *model))
{
text += format(" %s %.3f %.3f %+.0f", names[fastening.part], fastening.point.x, fastening.point.z, glm::degrees(std::atan2(fastening.across.y, fastening.across.x)));
++counts[fastening.part];
}
WriteLog(text);
}
WriteLog(format("SELFTEST fastenings %s: %d plates, %d chairs, %d checks", track->name().c_str(), counts[track_sleepers::part_plate], counts[track_sleepers::part_chair], counts[track_sleepers::part_check]));
return selftest_result::handled;
}
if (command == "sleepercheck")
{
auto const start{std::chrono::steady_clock::now()};

View File

@@ -22,6 +22,7 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/opengl46/opengl46geometrybank.h"
#include "rendering/screenshot.h"
#include "rendering/editoroverlay.h"
#include "world/SleeperRules.h"
#include <imgui/imgui_impl_opengl3.h>
//#define EU07_DEBUG_OPENGL
@@ -6263,6 +6264,9 @@ void opengl46_renderer::Render_Timbers( scene::basic_cell::path_sequence::const_
if( false == m_renderpass.pass_camera.visible( scene::bounding_area{ centre, chunk.radius } ) ) { continue; }
if( laid.timbers->stretchable ) {
for( int part = 0; part < track_sleepers::part_count; ++part ) {
if( chunk.last[ part ] <= chunk.first[ part ] ) { continue; }
// the fastenings only near, they're small
if( part >= track_sleepers::timber_parts && distance > std::min( range, track_sleepers::fastening_range ) + chunk.radius ) { continue; }
auto &instances = bucket_for( laid.timbers, part, 0.f );
for( auto index = chunk.first[ part ]; index < chunk.last[ part ]; ++index ) {
instances.emplace_back( trackmodelview * laid.instances[ part ][ index ] );
@@ -6289,7 +6293,9 @@ void opengl46_renderer::Render_Timbers( scene::basic_cell::path_sequence::const_
if( bucket.part >= 0 ) {
// only the part of the model is visible, its siblings are skipped as the model is walked
for( int part = 0; part < track_sleepers::part_count; ++part ) {
beammodel.parts[ part ]->iVisible = ( part == bucket.part ? 1 : 0 );
if( beammodel.parts[ part ] != nullptr ) {
beammodel.parts[ part ]->iVisible = ( part == bucket.part ? 1 : 0 );
}
}
}
material_data skins {};
@@ -6314,7 +6320,9 @@ void opengl46_renderer::Render_Timbers( scene::basic_cell::path_sequence::const_
++m_renderpass.draw_stats.instanced_drawcalls;
if( bucket.part >= 0 ) {
for( int part = 0; part < track_sleepers::part_count; ++part ) {
beammodel.parts[ part ]->iVisible = 1;
if( beammodel.parts[ part ] != nullptr ) {
beammodel.parts[ part ]->iVisible = 1;
}
}
}
}

View File

@@ -755,6 +755,16 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
&& ( Distance < Span.from + frommargin || Distance > Span.to - tomargin ); } ), stations.end() );
}
auto bottom { ShapePoints.front().position.y };
for( auto const &point : ShapePoints ) {
bottom = std::min( bottom, point.position.y );
}
auto const raised = [&]( float const Height ) {
if( Span.rise <= 0.0 || Height >= Span.head.x || Span.head.x <= bottom ) {
return Height;
}
return static_cast<float>( Height + Span.rise * ( Span.head.x - Height ) / ( Span.head.x - bottom ) ); };
auto const ring = [&]( double const Distance, double const Middle, std::vector<gfx::basic_vertex> &Ring, std::vector<float> &Wear ) {
glm::dvec3 point;
glm::dvec3 across;
@@ -820,7 +830,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
// nothing of the profile between the limits there: the ring shrinks to one point, the step draws nothing
auto const outside { std::clamp( 0.5 * ( std::min( extent, farthest ) + std::max( innerextent, nearest ) ), nearest, farthest ) };
auto pt { parallel * static_cast<float>( Span.side * ( Span.halfwidth + outside + offset ) ) + position };
pt.y += ShapePoints.front().position.y + Span.lift;
pt.y += raised( ShapePoints.front().position.y ) + Span.lift;
for( std::size_t j = 0; j < count; ++j ) {
Ring.emplace_back( pt, glm::vec3{ 0.f, 1.f, 0.f }, glm::vec2{ ShapePoints[ j ].texture.x, 0.f } );
Wear.push_back( 1.f );
@@ -899,6 +909,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
if( drop > 0.0 && shape.position.y > Span.head.x && Span.head.y > Span.head.x ) {
shape.position.y -= static_cast<float>( drop * ( shape.position.y - Span.head.x ) / ( Span.head.y - Span.head.x ) );
}
shape.position.y = raised( shape.position.y );
auto pt { parallel * static_cast<float>( Span.side * ( Span.halfwidth + outside + offset ) ) + position + along * static_cast<float>( outside + offset ) };
pt.y += shape.position.y + Span.lift;
auto norm { shape.normal.x * parallel };

View File

@@ -47,6 +47,7 @@ struct loft_span {
loft_end toend;
knots::line drops;
glm::dvec2 head { 0.0 };
double rise { 0.0 }; // how far the foot of the profile is raised, the web below the head (head.x) shortened by as much
};
struct segment_data {

207
world/SleeperFastenings.cpp Normal file
View File

@@ -0,0 +1,207 @@
/*
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 "world/SleeperFastenings.h"
#include "world/SleeperGeometry.h"
#include "world/SleeperPaths.h"
#include "world/SleeperRules.h"
#include "world/TrackRails.h"
#include "world/Track.h"
namespace track_sleepers {
fastened_rails::role fastened_rails::rail::at( double const Distance ) const {
auto const within = [&]( glm::dvec2 const &Stretch ) {
return Stretch.y > Stretch.x && Distance >= Stretch.x && Distance <= Stretch.y; };
if( within( blade ) ) {
return role::blade;
}
if( within( stock ) ) {
return role::stock;
}
if( std::any_of( checks.begin(), checks.end(), within ) ) {
return role::checked;
}
return role::plain;
}
fastened_rails::fastened_rails( TTrack &Track ) {
std::vector<TTrack *> tracks;
tracks_near( Track, [&]( TTrack *Other ) {
if( bedded( *Other ) && std::find( tracks.begin(), tracks.end(), Other ) == tracks.end() ) {
tracks.push_back( Other );
} } );
for( auto *track : tracks ) {
auto const middle { 0.5 * std::abs( track->fTrackWidth ) + track->rail_dimensions().web };
auto const cut { track_rails::cut_rails( *track ) };
auto const switched { track->eType == tt_Switch && track->SwitchExtension != nullptr };
// the side of the blade of the path, and the stretch along it from the tip to the heel
auto const bladeside = [&]( int const Path ) {
return ( track->SwitchExtension->RightSwitch == ( Path == 0 ) ? 1 : -1 ); };
auto const bladestretch = [&]( int const Path ) {
auto const &shape { track->blade( Path ) };
return glm::dvec2 { shape.valid ? shape.tip : 0.0, track_rails::blade_length( *track, Path ) }; };
for( int path = 0; path < path_count( *track ); ++path ) {
auto const *segment { path_segment( *track, path ) };
if( segment == nullptr ) {
continue;
}
auto const length { segment->GetLength() };
auto const count { std::max( 1, static_cast<int>( std::ceil( length / fastening_step ) ) ) };
for( auto const side : { -1, 1 } ) {
rail line;
line.track = track;
line.path = path;
line.side = side;
line.low = glm::dvec2 { std::numeric_limits<double>::max() };
line.high = glm::dvec2 { std::numeric_limits<double>::lowest() };
for( int step = 0; step <= count; ++step ) {
auto const distance { length * step / count };
glm::dvec3 point, across;
segment->plan_frame( distance, point, across );
auto const flat { plan( across ) };
auto const inward { glm::length( flat ) > 1e-9 ? glm::normalize( flat ) * static_cast<double>( -side ) : glm::dvec2 { 1.0, 0.0 } };
auto const position { plan( point ) - inward * middle };
line.points.push_back( position );
line.heights.push_back( point.y );
line.inward.push_back( inward );
line.distances.push_back( distance );
line.low = glm::min( line.low, position );
line.high = glm::max( line.high, position );
}
if( switched ) {
if( side == bladeside( path ) ) {
line.blade = { 0.0, bladestretch( path ).y };
}
if( side == bladeside( 1 - path ) ) {
auto const stretch { bladestretch( 1 - path ) };
line.stock = { std::max( 0.0, stretch.x - chair_lead ), stretch.y };
}
}
if( cut ) {
for( auto const &check : track_rails::rail_checks( *track )[ path ][ side > 0 ? 1 : 0 ] ) {
line.checks.emplace_back( check.from, check.to );
}
}
m_rails.emplace_back( std::move( line ) );
}
}
}
}
std::vector<fastening> fastened_rails::on( beam const &Beam, beam_model const &Model ) const {
std::vector<fastening> result;
auto const span { Beam.to - Beam.from };
auto const length { glm::length( plan( span ) ) };
if( length < 0.1 ) {
return result;
}
auto const start { plan( Beam.from ) };
auto const axis { plan( span ) / length };
auto const low { glm::min( start, plan( Beam.to ) ) };
auto const high { glm::max( start, plan( Beam.to ) ) };
std::vector<hit> hits;
for( auto const &line : m_rails ) {
if( line.high.x < low.x || line.low.x > high.x || line.high.y < low.y || line.low.y > high.y ) {
continue;
}
auto const last { line.points.size() - 1 };
for( std::size_t index = 1; index <= last; ++index ) {
auto const from { line.points[ index - 1 ] };
auto const chord { line.points[ index ] - from };
double u, v;
if( false == track_rails::meet( start, plan( span ), from, chord, 1e-12, u, v ) ) {
continue;
}
if( u < 0.0 || u > 1.0 || v < 0.0 || v > 1.0 || ( v == 1.0 && index < last ) ) {
continue;
}
auto const direction { glm::normalize( chord ) };
if( std::abs( cross( axis, direction ) ) < crossing_sine_min ) {
continue;
}
auto const height { glm::mix( line.heights[ index - 1 ], line.heights[ index ], v ) };
if( std::abs( height - glm::mix( Beam.from.y, Beam.to.y, u ) ) > level_tolerance ) {
continue;
}
hits.push_back( {
u * length,
line.at( glm::mix( line.distances[ index - 1 ], line.distances[ index ], v ) ),
glm::normalize( glm::mix( line.inward[ index - 1 ], line.inward[ index ], v ) ) } );
}
}
std::sort( hits.begin(), hits.end(), []( hit const &Left, hit const &Right ) { return Left.at < Right.at; } );
// one rail where several lie on one another, the stock rail before the rail checked, before a plain one; a blade with no
// other rail there slides on the chair of its stock rail
auto const rank = []( role const Kind ) {
switch( Kind ) {
case role::stock: { return 3; }
case role::checked: { return 2; }
case role::plain: { return 1; }
default: { return 0; }
} };
std::vector<hit> rails;
for( std::size_t index = 0; index < hits.size(); ) {
auto best { hits[ index ] };
auto next { index + 1 };
for( ; next < hits.size() && hits[ next ].at - hits[ next - 1 ].at < fastening_merge; ++next ) {
if( rank( hits[ next ].kind ) > rank( best.kind ) ) {
best = hits[ next ];
}
}
if( best.kind != role::blade ) {
rails.push_back( best );
}
index = next;
}
auto const point = [&]( double const At ) {
return Beam.from + span * ( At / length ); };
auto const has = [&]( int const Part ) {
return Model.parts[ Part ] != nullptr; };
for( std::size_t index = 0; index < rails.size(); ++index ) {
auto const &rail { rails[ index ] };
if( index + 1 < rails.size() && rails[ index + 1 ].at - rail.at < fastening_pair && rail.kind != role::stock && rails[ index + 1 ].kind != role::stock ) {
auto const &other { rails[ index + 1 ] };
if( other.at - rail.at < fastening_nose ) {
result.push_back( { part_plate, point( 0.5 * ( rail.at + other.at ) ), rail.inward } );
}
else if( has( part_check ) ) {
result.push_back( { part_check, point( rail.at ), axis } );
}
else {
result.push_back( { part_plate, point( rail.at ), rail.inward } );
result.push_back( { part_plate, point( other.at ), other.inward } );
}
++index;
continue;
}
switch( rail.kind ) {
case role::stock: {
result.push_back( { has( part_chair ) ? part_chair : part_plate, point( rail.at ), -rail.inward } );
break;
}
case role::checked: {
result.push_back( { has( part_check ) ? part_check : part_plate, point( rail.at ), rail.inward } );
break;
}
default: {
result.push_back( { part_plate, point( rail.at ), rail.inward } );
break;
}
}
}
return result;
}
} // namespace track_sleepers

73
world/SleeperFastenings.h Normal file
View File

@@ -0,0 +1,73 @@
/*
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 <vector>
#include <glm/glm.hpp>
#include "world/TrackSleepers.h"
// the fastenings of the rails on the timbers: where a rail lies on a timber, the part of the model of the timbers which holds it
// there. a stock rail with the blade beside it lies in a slide chair, a rail with a check rail or another rail of the frog close
// beside it in a fastening of both, the blade on the chair of its stock rail, and the other rails each in a plate of its own
namespace track_sleepers {
// a fastening on a timber
struct fastening {
int part { part_plate };
glm::dvec3 point { 0.0 }; // under the middle of the rail, on the top of the timber
glm::dvec2 across { 1.0, 0.0 }; // unit vector in plan square to the rail, the way the +x of the part goes
};
// the rails of the tracks around a track, which lie on its timbers
class fastened_rails {
public:
explicit fastened_rails( TTrack &Track );
// the fastenings of the rails on the timber, of the parts the model has
std::vector<fastening> on( beam const &Beam, beam_model const &Model ) const;
private:
// what a rail is where it lies on a timber
enum class role {
plain,
blade, // a blade of a switch, which slides on the chair of its stock rail
stock, // the stock rail of a switch with the blade beside it
checked // a rail with a check rail beside it
};
// a rail by points along its middle
struct rail {
TTrack *track { nullptr };
int path { 0 };
int side { 1 };
std::vector<glm::dvec2> points;
std::vector<double> heights;
std::vector<glm::dvec2> inward; // unit vectors in plan from the rail toward the middle of its path
std::vector<double> distances;
glm::dvec2 low { 0.0 };
glm::dvec2 high { 0.0 };
// the stretches along the path the rail is a blade, a stock rail with a chair, checked along
glm::dvec2 blade { -1.0 };
glm::dvec2 stock { -1.0 };
std::vector<glm::dvec2> checks;
role at( double const Distance ) const;
};
// where a rail crosses a timber
struct hit {
double at { 0.0 }; // along the timber, from its from end
role kind { role::plain };
glm::dvec2 inward { 1.0, 0.0 };
};
std::vector<rail> m_rails;
};
} // namespace track_sleepers

View File

@@ -25,6 +25,7 @@ http://mozilla.org/MPL/2.0/.
#include <filesystem>
#include <fstream>
#include <iterator>
#include <optional>
namespace track_sleepers {
@@ -50,6 +51,40 @@ void vertices_of( TSubModel *Submodel, Visit Visitor ) {
}
}
// the highest the faces of the submodel reach over the point of the plan of the model, in the space of the model; none where
// no face lies over it
std::optional<float> surface_at( TSubModel *Submodel, float const X, float const Z ) {
glm::mat4 transform { 1.f };
if( ( Submodel->iFlags & 0xC000 ) && ( Submodel->GetMatrix() != nullptr ) ) {
transform = glm::make_mat4( Submodel->GetMatrix()->readArray() );
}
auto const indexed { Submodel->m_geometry.handle != null_handle };
auto const &vertices { indexed ? GfxRenderer->Vertices( Submodel->m_geometry.handle ) : Submodel->Vertices };
auto const &indices { indexed ? GfxRenderer->Indices( Submodel->m_geometry.handle ) : Submodel->Indices };
auto const count { indices.empty() ? vertices.size() : indices.size() };
auto const corner = [&]( std::size_t const Index ) {
return glm::vec3 { transform * glm::vec4 { vertices[ indices.empty() ? Index : indices[ Index ] ].position, 1.f } }; };
std::optional<float> result;
for( std::size_t index = 0; index + 2 < count; index += 3 ) {
auto const a { corner( index ) };
auto const b { corner( index + 1 ) };
auto const c { corner( index + 2 ) };
auto const area { ( b.x - a.x ) * ( c.z - a.z ) - ( c.x - a.x ) * ( b.z - a.z ) };
if( std::abs( area ) < 1e-9f ) {
continue;
}
auto const u { ( ( X - a.x ) * ( c.z - a.z ) - ( c.x - a.x ) * ( Z - a.z ) ) / area };
auto const v { ( ( b.x - a.x ) * ( Z - a.z ) - ( X - a.x ) * ( b.z - a.z ) ) / area };
if( u < -1e-5f || v < -1e-5f || u + v > 1.f + 1e-5f ) {
continue;
}
auto const height { a.y + u * ( b.y - a.y ) + v * ( c.y - a.y ) };
result = ( result ? std::max( *result, height ) : height );
}
return result;
}
// the model of the beams of the name and skin, loaded and measured on the first use; null when it can't be loaded. the feet of
// the rails go to the given seat in it, or to its top where the seat is below zero
beam_model const *find_model( std::string const &Name, std::string const &Skinname, float const Seat ) {
@@ -74,16 +109,16 @@ beam_model const *find_model( std::string const &Name, std::string const &Skinna
model.skin = GfxRenderer->Fetch_Material( Skin );
}
// a timber put together from its parts, with a part between the ends stretched and repeated to the length
std::array<char const *, part_count> const names { "begin", "main", "end" };
std::array<char const *, part_count> const names { "begin", "main", "end", "plate", "chair", "check" };
model.stretchable = true;
for( int part = 0; part < part_count; ++part ) {
model.parts[ part ] = model.model->GetFromName( names[ part ] );
model.stretchable = model.stretchable && model.parts[ part ] != nullptr;
model.stretchable = model.stretchable && ( part >= timber_parts || model.parts[ part ] != nullptr );
}
auto top { std::numeric_limits<float>::lowest() };
model.span = { std::numeric_limits<float>::max(), std::numeric_limits<float>::lowest() };
if( model.stretchable ) {
for( int part = 0; part < part_count; ++part ) {
for( int part = 0; part < timber_parts; ++part ) {
glm::vec2 span { std::numeric_limits<float>::max(), std::numeric_limits<float>::lowest() };
vertices_of( model.parts[ part ], [&]( glm::vec3 const &Position ) {
span = { std::min( span.x, Position.x ), std::max( span.y, Position.x ) };
@@ -112,8 +147,14 @@ beam_model const *find_model( std::string const &Name, std::string const &Skinna
}
}
std::sort( model.bands.begin(), model.bands.end() );
// the rails sit on the seat of the sleeper, below the fastenings sticking up over it
// the rails sit on the seat of the sleeper, below the fastenings sticking up over it, or on the plate of the fastening
model.seat = ( Seat >= 0.f ? Seat : top );
if( Seat < 0.f && model.fastened() ) {
model.seat = std::max( top, surface_at( model.parts[ part_plate ], 0.f, 0.f ).value_or( top ) );
}
if( model.fastened() && model.parts[ part_chair ] != nullptr ) {
model.slide = std::max( 0.f, surface_at( model.parts[ part_chair ], static_cast<float>( -slide_probe ), 0.f ).value_or( model.seat ) - model.seat );
}
if( model.length() < 0.5f ) {
ErrorLog( "Bad sleepers3d: model \"" + Name + "\" has no beam along x" );
model.model = nullptr;
@@ -125,7 +166,11 @@ beam_model const *find_model( std::string const &Name, std::string const &Skinna
+ ( model.stretchable ?
", main " + to_string( model.spans[ part_main ].x, 3 ) + ".." + to_string( model.spans[ part_main ].y, 3 ) :
std::string {} )
+ ", seat " + to_string( model.seat, 3 ) );
+ ( model.fastened() ?
std::string { ", fastenings plate" } + ( model.parts[ part_chair ] != nullptr ? " chair" : "" ) + ( model.parts[ part_check ] != nullptr ? " check" : "" ) :
std::string {} )
+ ", seat " + to_string( model.seat, 3 )
+ ( model.slide > 0.f ? ", slide " + to_string( model.slide, 3 ) : std::string {} ) );
return &model;
}
return ( found->second.model != nullptr ? &found->second : nullptr );

View File

@@ -49,6 +49,20 @@ double constexpr level_tolerance { 1.0 };
double constexpr around_margin { 20.0 };
// how many beams make a run drawn or left out together
std::size_t constexpr chunk_beams { 16 };
// how far apart the points of the rails are, as the places they lie on the timbers are found
double constexpr fastening_step { 0.25 };
// rails closer than this where they cross a timber are one rail there, held by one fastening
double constexpr fastening_merge { 0.03 };
// two rails closer than this lie in one fastening of both, as a rail of the frog with the one beside it; closer than the second
// one, as the legs of the nose where they're joined, in one plate between them
double constexpr fastening_pair { 0.24 };
double constexpr fastening_nose { 0.05 };
// how far before the tip of the blade the slide chairs of its stock rail begin
double constexpr chair_lead { 0.3 };
// how far from the stock rail toward its blade the slide plate of a chair is measured
double constexpr slide_probe { 0.12 };
// how far from the camera the fastenings are drawn at most
double constexpr fastening_range { 120.0 };
// how many tracks a cluster takes at most
std::size_t constexpr cluster_cap { 256 };

View File

@@ -2259,8 +2259,9 @@ void TTrack::loft_blade( int const Path, gfx::vertex_array const &Rail, gfx::ver
offsets.emplace_back( bend.x, -thrown * bend.y );
}
auto const size { rail_dimensions() };
auto const rise { track_sleepers::blade_rise( *this ) };
auto const length { SwitchExtension->Segments[ Path ]->GetLength() };
SwitchExtension->Segments[ Path ]->RenderLoftSpan( Vertices, m_origin, Rail, { .side = blade_side( Path ), .halfwidth = 0.5f * std::abs( fTrackWidth ), .texturelength = Texturelength, .from = shape.tip, .to = std::min( shape.heel, length ), .offsets = offsets, .extents = shape.extents, .walls = true, .drops = shape.drops, .head = { size.neck, size.crest } } );
SwitchExtension->Segments[ Path ]->RenderLoftSpan( Vertices, m_origin, Rail, { .side = blade_side( Path ), .halfwidth = 0.5f * std::abs( fTrackWidth ), .texturelength = Texturelength, .from = shape.tip, .to = std::min( shape.heel, length ), .offsets = offsets, .extents = shape.extents, .walls = true, .drops = shape.drops, .head = { size.neck, size.crest }, .rise = rise } );
// the blade running on past the end of the path goes on along the plain track joined there, whose rail starts behind the heel
if( auto const *next { track_rails::runon_track( *this, Path ) }; next != nullptr && shape.heel > length + 1e-3 ) {
auto const along = [&]( std::vector<glm::dvec2> const &Knots ) {
@@ -2269,7 +2270,7 @@ void TTrack::loft_blade( int const Path, gfx::vertex_array const &Rail, gfx::ver
result.emplace_back( knot.x - length, knot.y );
}
return result; };
next->Segment->RenderLoftSpan( Vertices, m_origin, Rail, { .side = blade_side( Path ), .halfwidth = 0.5f * std::abs( fTrackWidth ), .texturelength = Texturelength, .from = 0.0, .to = shape.heel - length, .offsets = along( offsets ), .extents = along( shape.extents ), .walls = true, .drops = along( shape.drops ), .head = { size.neck, size.crest } } );
next->Segment->RenderLoftSpan( Vertices, m_origin, Rail, { .side = blade_side( Path ), .halfwidth = 0.5f * std::abs( fTrackWidth ), .texturelength = Texturelength, .from = 0.0, .to = shape.heel - length, .offsets = along( offsets ), .extents = along( shape.extents ), .walls = true, .drops = along( shape.drops ), .head = { size.neck, size.crest }, .rise = rise } );
}
return;
}

View File

@@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "world/TrackSleepers.h"
#include "world/SleeperFastenings.h"
#include "world/SleeperGeometry.h"
#include "world/SleeperLayout.h"
#include "world/SleeperPaths.h"
@@ -24,6 +25,7 @@ http://mozilla.org/MPL/2.0/.
#include <algorithm>
#include <map>
#include <optional>
#include <set>
namespace track_sleepers {
@@ -63,6 +65,11 @@ bool lays( TTrack const &Track ) {
return bedded( Track ) && timber_model( Track ) != nullptr;
}
double blade_rise( TTrack const &Track ) {
return ( Track.eType == tt_Switch && lays( Track ) ? timber_model( Track )->slide : 0.0 );
}
std::vector<std::pair<double, double>> shared_zones( TTrack const &Track, int const Path ) {
std::vector<std::pair<double, double>> result;
@@ -134,6 +141,9 @@ void lay( TTrack &Track ) {
// the layout of the cluster isn't needed by this track any more, the instances below are what's drawn
forget_layout( Track );
// the rails around, where they lie on the timbers, for the fastenings of the model
std::optional<fastened_rails> const rails { parts.fastened() ? std::make_optional<fastened_rails>( Track ) : std::nullopt };
auto const partlength = [&]( int const Part ) {
return static_cast<double>( parts.spans[ Part ].y - parts.spans[ Part ].x ); };
auto const beginlength { partlength( part_begin ) };
@@ -232,6 +242,19 @@ void lay( TTrack &Track ) {
put( part_end, scale, parts.spans[ part_end ].x, -half );
put( part_begin, scale, parts.spans[ part_begin ].y, half );
}
if( rails && false == ( beam.plain && laid.plains != nullptr ) ) {
// each square to its rail on the top of the timber, the plate under the foot of the rail
for( auto const &fastening : rails->on( beam, parts ) ) {
auto across { glm::dvec3 { fastening.across.x, 0.0, fastening.across.y } };
across = glm::normalize( across - y * glm::dot( across, y ) );
glm::mat4 place { 1.f };
place[ 0 ] = glm::vec4 { glm::vec3 { across }, 0.f };
place[ 1 ] = glm::vec4 { glm::vec3 { y }, 0.f };
place[ 2 ] = glm::vec4 { glm::vec3 { glm::cross( across, y ) }, 0.f };
place[ 3 ] = glm::vec4 { glm::vec3 { fastening.point - Track.m_origin }, 1.f };
laid.instances[ fastening.part ].emplace_back( glm::translate( place, glm::vec3 { 0.f, -parts.seat, 0.f } ) );
}
}
runbeams.emplace_back( middle, half );
if( runbeams.size() >= chunk_beams ) {
close_run();

View File

@@ -26,6 +26,8 @@ class TSubModel;
// them, repeated and stretched to the length of the beam, so one model makes the timbers of any length; a model without such
// parts is drawn whole, stretched. The sleepers of plain tracks are a whole model of their own, set with sleepers3dplain, drawn
// at their own length. A track can have models of its own: of its sleepers with sleepermodel, of its timbers with timbermodel.
// A model of timbers put together from parts can have the fastenings of the rails among its submodels too: plate, chair and
// check, put on the timbers where the rails lie on them, and the rails sit on the plate.
namespace track_sleepers {
// the parts of the model a beam is put together from, by the names of the submodels
@@ -33,8 +35,14 @@ enum part : int {
part_begin, // the end of the beam on its +x side
part_main, // the piece between the ends
part_end, // the end on the -x side
part_plate, // the fastening of a rail, its origin under the middle of the foot of the rail
part_chair, // the slide chair of a stock rail with the blade beside it, its origin under the stock rail, the slide plate on its -x side
part_check, // the fastening of a rail with another one close beside it, as a check rail or a rail of the frog, its origin under the
// rail, the other one on its +x side
part_count
};
// the parts a timber is put together from, the fastenings after them
int constexpr timber_parts { part_end + 1 };
// a sleeper or a timber, by the middle of its top face at both ends
struct beam {
@@ -59,8 +67,11 @@ struct beam_model {
std::array<glm::vec2, part_count> spans {}; // from and to along x, of the parts
glm::vec2 span { -1.3f, 1.3f }; // from and to along x, of the most detailed submodels of the whole model
float seat { 0.f }; // the height in the model the feet of the rails go to
float slide { 0.f }; // how far over the seat the blades of a switch slide, on the slide plates of the chairs; 0 without chairs
std::vector<float> bands; // the squared distances the levels of detail of the model are seen from, the nearest first
float length() const { return span.y - span.x; }
// the model has the fastenings of the rails, put where the rails lie on its timbers
bool fastened() const { return stretchable && parts[ part_plate ] != nullptr; }
};
// a run of beams of a track drawn or left out together, by the sphere around them
@@ -105,6 +116,9 @@ double plain_length( TTrack const &Track );
bool bedded( TTrack const &Track );
// whether the track gets beams: a rail track with the ballast made by the simulator, and the model is there, its own or of eu07.ini
bool lays( TTrack const &Track );
// how far the feet of the blades of the switch ride over those of its other rails, on the slide plates of the chairs of its
// timbers; 0 where it lays none with chairs
double blade_rise( TTrack const &Track );
// where along the path of the track its rails lie on timbers shared with other tracks: where another track runs so near that
// the plain sleepers of the two would touch, of the lengths of the models they'd be made of. this goes by the shape of the
// tracks alone, so it holds whether the beams are drawn or not. the stretches in the order along the path, none where there