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:
@@ -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"
|
||||
|
||||
@@ -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()};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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
207
world/SleeperFastenings.cpp
Normal 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
73
world/SleeperFastenings.h
Normal 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
|
||||
@@ -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 );
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user