17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-24 22:37:36 +02:00
Files
maszyna/world/TrackBlend.cpp

257 lines
9.0 KiB
C++

/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "world/TrackBlend.h"
#include "world/Track.h"
#include "world/TrackSleepers.h"
#include "rendering/renderer.h"
#include "utilities/Globals.h"
#include <algorithm>
#include <limits>
namespace track_blend {
namespace {
blending const *current { nullptr };
int end_count( TTrack const &Track ) {
switch( Track.eType ) {
case tt_Normal: { return 2; }
case tt_Switch: { return ( Track.SwitchExtension != nullptr ? 3 : 0 ); }
default: { return 0; }
}
}
// the segment an end of the track is the start (end 0) or the end of: p1, p2 of the first path, p4 of the second of a switch
TSegment const *end_segment( TTrack const &Track, int const End ) {
if( Track.eType == tt_Switch ) {
return ( End == 2 ? Track.SwitchExtension->Segments[ 1 ].get() : Track.SwitchExtension->Segments[ 0 ].get() );
}
return Track.Segment.get();
}
bool end_frame( TTrack const &Track, int const End, glm::dvec3 &Point, glm::dvec3 &Inward ) {
auto const *segment { end_segment( Track, End ) };
if( segment == nullptr ) {
return false;
}
auto const length { segment->GetLength() };
if( length < 0.01 ) {
return false;
}
auto const step { std::clamp( 0.25 / length, 0.0001, 0.5 ) };
auto const atstart { End == 0 };
Point = ( atstart ? segment->FastGetPoint_0() : segment->FastGetPoint_1() );
auto const way { segment->FastGetPoint( atstart ? step : 1.0 - step ) - Point };
if( glm::length( way ) < 1e-6 ) {
return false;
}
Inward = glm::normalize( way );
return true;
}
TTrack *neighbour( TTrack const &Track, int const End ) {
if( Track.eType == tt_Switch ) {
switch( End ) {
case 0: { return Track.SwitchExtension->pPrevs[ 0 ]; }
case 1: { return Track.SwitchExtension->pNexts[ 0 ]; }
case 2: { return Track.SwitchExtension->pNexts[ 1 ]; }
default: { return nullptr; }
}
}
return ( End == 0 ? Track.trPrev : ( End == 1 ? Track.trNext : nullptr ) );
}
// the end of the track the other one is joined at, the nearest to the point of those joined to it; -1 where it isn't joined
int end_toward( TTrack const &Track, TTrack const *Other, glm::dvec3 const &Point ) {
auto result { -1 };
auto nearest { std::numeric_limits<double>::max() };
for( int end = 0; end < end_count( Track ); ++end ) {
auto joined { neighbour( Track, end ) == Other };
if( Track.eType == tt_Switch && end == 0 ) {
// the second path of a switch starts at p3, its p1 as well
joined = joined || Track.SwitchExtension->pPrevs[ 1 ] == Other;
}
glm::dvec3 point, inward;
if( false == joined || false == end_frame( Track, end, point, inward ) ) {
continue;
}
auto const distance { glm::length( point - Point ) };
if( distance < nearest ) {
nearest = distance;
result = end;
}
}
return result;
}
// the trackbed of a switch, taken from the plain tracks joined to it where it has none of its own, as create_switch_trackbed() does
material_handle switch_trackbed( TTrack const &Switch ) {
auto material { Switch.SwitchExtension->m_material3 };
if( material == null_handle && Switch.trPrev != nullptr && Switch.trPrev->eType == tt_Normal ) {
material = Switch.trPrev->m_material2;
}
if( material == null_handle && Switch.trNext != nullptr && Switch.trNext->eType == tt_Normal ) {
material = Switch.trNext->m_material2;
}
return material;
}
// the material the track draws its rails or its ballast with at the end, null where it has none there
material_handle material_of( TTrack const &Track, kind const Kind, int const End ) {
if( false == Track.m_visible || Track.m_editorremoved || ( Track.iCategoryFlag & 15 ) != 1 ) {
return null_handle;
}
if( Kind == kind::rails ) {
switch( Track.eType ) {
case tt_Normal: { return Track.m_material1; }
case tt_Switch: { return ( End == 2 ? Track.m_material2 : Track.m_material1 ); }
default: { return null_handle; }
}
}
// no ballast is drawn under the 3D sleepers laid on the ground alone
if( false == Global.Sleepers3DBed && track_sleepers::lays( Track ) ) {
return null_handle;
}
switch( Track.eType ) {
case tt_Normal: { return Track.m_material2; }
case tt_Switch: { return ( Global.CreateSwitchTrackbeds && Track.SwitchExtension != nullptr ? switch_trackbed( Track ) : null_handle ); }
default: { return null_handle; }
}
}
// whether the track blends its rails or ballast at the end into those of the track joined there, and what with: not where
// either of them has a length of 0 there. the answer is the same for that track back, but for the kinds of tracks which blend:
// switches don't blend their rails, a plain track fades its rails all the way into theirs
bool blends( TTrack const &Track, kind const Kind, int const End, TTrack const **Other = nullptr, int *Otherend = nullptr, material_handle *Othermaterial = nullptr ) {
if( End < 0 || End >= end_count( Track ) ) {
return false;
}
if( Kind == kind::rails && Track.eType != tt_Normal ) {
return false;
}
if( Track.m_blend[ static_cast<int>( Kind ) ][ End ] <= 0.f ) {
return false;
}
auto const own { material_of( Track, Kind, End ) };
if( own == null_handle ) {
return false;
}
auto const *other { neighbour( Track, End ) };
glm::dvec3 point, inward;
if( other == nullptr || other == &Track || false == end_frame( Track, End, point, inward ) ) {
return false;
}
auto const otherend { end_toward( *other, &Track, point ) };
if( otherend < 0 ) {
return false;
}
// a length of 0 at either side leaves the joint sharp
if( other->m_blend[ static_cast<int>( Kind ) ][ otherend ] <= 0.f ) {
return false;
}
auto const theirs { material_of( *other, Kind, otherend ) };
if( theirs == null_handle || theirs == own ) {
return false;
}
if( false == GfxRenderer->Blendable_Material( own ) || false == GfxRenderer->Blendable_Material( theirs ) ) {
return false;
}
if( Other != nullptr ) { *Other = other; }
if( Otherend != nullptr ) { *Otherend = otherend; }
if( Othermaterial != nullptr ) { *Othermaterial = theirs; }
return true;
}
} // namespace
void blending::weigh( gfx::vertex_array const &Vertices, gfx::userdata_array &Userdata ) const {
Userdata.resize( Vertices.size(), gfx::vertex_userdata { glm::vec4 { 0.f } } );
for( std::size_t index = 0; index < Vertices.size(); ++index ) {
glm::dvec3 const position { Vertices[ index ].position };
auto &data { Userdata[ index ].data };
for( int end = 0; end < 3; ++end ) {
auto const &blend { ends[ end ] };
auto value { none_mark };
if( blend.active ) {
// measured along the line of the end, so both tracks give the vertices of their joint the same distance
auto const fraction { static_cast<float>( glm::dot( position - blend.point, blend.inward ) / blend.length ) };
value = ( blend.peak < 1.f ? fraction : single_mark - fraction );
}
data[ 1 + end ] = value;
}
}
}
blending setup( TTrack const &Track, kind const Kind ) {
blending result;
std::array<material_handle, 3> others { null_handle, null_handle, null_handle };
auto found { false };
for( int end = 0; end < end_count( Track ); ++end ) {
TTrack const *other { nullptr };
auto otherend { -1 };
auto theirs { null_handle };
if( false == blends( Track, Kind, end, &other, &otherend, &theirs ) ) {
continue;
}
auto &blend { result.ends[ end ] };
end_frame( Track, end, blend.point, blend.inward );
blend.point -= Track.m_origin;
blend.length = Track.m_blend[ static_cast<int>( Kind ) ][ end ];
// where both tracks blend they meet half way at the joint; a plain track goes all the way into the rails of a switch
blend.peak = ( blends( *other, Kind, otherend ) ? 0.5f : 1.f );
blend.active = true;
others[ end ] = theirs;
found = true;
}
if( false == found ) {
return {};
}
auto const own { material_of( Track, Kind, 0 ) };
auto const material { GfxRenderer->Blend_Material( own, others ) };
if( material == own ) {
// the renderer can't blend them
return {};
}
result.material = material;
return result;
}
weighing::weighing( blending const *Blending ) :
m_previous { current } {
current = ( Blending != nullptr && Blending->any() ? Blending : nullptr );
}
weighing::~weighing() {
current = m_previous;
}
blending const *weighed() {
return current;
}
} // namespace track_blend