mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-25 02:07:37 +02:00
feat(track): rails and ballast blended into those of the tracks joined, where their textures differ
This commit is contained in:
@@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "world/TrackRails.h"
|
||||
#include "world/TrackBlend.h"
|
||||
#include "rendering/renderer.h"
|
||||
|
||||
namespace track_rails {
|
||||
@@ -213,7 +214,9 @@ reused_chunks::~reused_chunks() {
|
||||
reused = m_previous;
|
||||
}
|
||||
|
||||
gfx::geometry_handle insert_chunk( gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Bank, int const Type ) {
|
||||
namespace {
|
||||
|
||||
gfx::geometry_handle insert_weighed( gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Bank, int const Type ) {
|
||||
|
||||
if( reused == nullptr ) {
|
||||
return GfxRenderer->Insert( Vertices, Userdata, Bank, Type );
|
||||
@@ -237,6 +240,19 @@ gfx::geometry_handle insert_chunk( gfx::vertex_array &Vertices, gfx::userdata_ar
|
||||
return handle;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
gfx::geometry_handle insert_chunk( gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Bank, int const Type ) {
|
||||
|
||||
if( auto const *blending { track_blend::weighed() }; blending != nullptr ) {
|
||||
// the weights of the textures the track blends into near its ends; the user data given are the caller's own, often shared
|
||||
auto weighed { Userdata };
|
||||
blending->weigh( Vertices, weighed );
|
||||
return insert_weighed( Vertices, weighed, Bank, Type );
|
||||
}
|
||||
return insert_weighed( Vertices, Userdata, Bank, Type );
|
||||
}
|
||||
|
||||
void cut_faces( gfx::vertex_array const &Strip, std::size_t const Ring, bool const Start, bool const End, gfx::geometrybank_handle const &Bank, TTrack::geometryhandle_sequence &Geometry ) {
|
||||
|
||||
gfx::vertex_array triangles;
|
||||
|
||||
@@ -701,6 +701,18 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin)
|
||||
m_timber_model_name = modelpath;
|
||||
m_timber_skin_name = ( skinpath == "none" ? std::string{} : skinpath );
|
||||
}
|
||||
else if( str == "blend" ) {
|
||||
// blend <end> <length>: how far from the end 1 (p1), 2 (p2) or 3 (p4 of a switch) the rails and the ballast blend into those
|
||||
// of the track joined there, where their textures differ; 0: not at all
|
||||
auto const end { parser->getToken<int>() };
|
||||
auto const length { parser->getToken<double>() };
|
||||
if( end >= 1 && end <= static_cast<int>( m_blend.size() ) ) {
|
||||
m_blend[ end - 1 ] = static_cast<float>( std::max( 0.0, length ) );
|
||||
}
|
||||
else {
|
||||
ErrorLog( "Bad track: blend at end " + std::to_string( end ) + " of track \"" + m_name + "\" which has no such end" );
|
||||
}
|
||||
}
|
||||
else if( str == "friction" ) {
|
||||
// memory cell holding friction value modifiers
|
||||
m_friction.first = parser->getToken<std::string>();
|
||||
@@ -1546,6 +1558,24 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
|
||||
if( m_sleeper_enabled && false == m_sleepers3d.active && m_sleeper_local_transforms.empty() ) {
|
||||
build_sleeper_transforms();
|
||||
}
|
||||
// the rails and the ballast blended into those of the tracks joined at the ends, where their textures differ. the weights go with
|
||||
// the geometry as it's made, so the materials change only with it; not where the chunks are laid again as a movable nose moves
|
||||
track_blend::blending railsblend;
|
||||
track_blend::blending ballastblend;
|
||||
if( Bank != null_handle && false == movable ) {
|
||||
if( Geometry1.empty() ) {
|
||||
railsblend = track_blend::setup( *this, track_blend::kind::rails );
|
||||
m_blendmaterial1 = railsblend.material;
|
||||
}
|
||||
if( eType == tt_Switch ) {
|
||||
ballastblend = track_blend::setup( *this, track_blend::kind::ballast );
|
||||
m_blendmaterial3 = ballastblend.material;
|
||||
}
|
||||
else if( Geometry2.empty() ) {
|
||||
ballastblend = track_blend::setup( *this, track_blend::kind::ballast );
|
||||
m_blendmaterial2 = ballastblend.material;
|
||||
}
|
||||
}
|
||||
switch (iCategoryFlag & 15)
|
||||
{
|
||||
case 1: // tor
|
||||
@@ -1583,6 +1613,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
|
||||
gfx::vertex_array vertices;
|
||||
Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid > 0, texturelength);
|
||||
if( Bank != 0 && true == Geometry2.empty() ) {
|
||||
track_blend::weighing const weighing { &ballastblend };
|
||||
Geometry2.emplace_back(track_rails::insert_chunk(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
|
||||
}
|
||||
if( Bank == 0 && false == Geometry2.empty() ) {
|
||||
@@ -1597,6 +1628,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
|
||||
gfx::vertex_array vertices;
|
||||
auto const open { eType == tt_Normal && ( trPrev == nullptr || trNext == nullptr ) };
|
||||
if( Bank != 0 && true == Geometry1.empty() ) {
|
||||
track_blend::weighing const weighing { &railsblend };
|
||||
for( auto const side : { 1, -1 } ) {
|
||||
auto const &profile { side > 0 ? rpts1 : rpts2 };
|
||||
if( eType == tt_Normal && iTrapezoid == 0 && lay_rail( 0, profile, side, texturelength, track_rails::blade_runon( *this, side ), trPrev == nullptr, trNext == nullptr, Geometry1, GeometryCut1 ) ) {
|
||||
@@ -1695,6 +1727,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
|
||||
if( true == Global.CreateSwitchTrackbeds ) {
|
||||
gfx::vertex_array vertices;
|
||||
create_switch_trackbed( vertices );
|
||||
track_blend::weighing const weighing { &ballastblend };
|
||||
SwitchExtension->Geometry3 = track_rails::insert_chunk(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP);
|
||||
vertices.clear();
|
||||
}
|
||||
@@ -2715,6 +2748,11 @@ TTrack::export_as_text_( std::ostream &Output ) const {
|
||||
<< m_timber_model_name << ' '
|
||||
<< ( m_timber_skin_name.empty() ? std::string{ "none" } : m_timber_skin_name ) << ' ';
|
||||
}
|
||||
for( std::size_t end = 0; end < m_blend.size(); ++end ) {
|
||||
if( m_blend[ end ] != track_blend::default_length ) {
|
||||
Output << "blend " << end + 1 << ' ' << m_blend[ end ] << ' ';
|
||||
}
|
||||
}
|
||||
// footer
|
||||
Output
|
||||
<< "endtrack"
|
||||
|
||||
@@ -21,6 +21,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "world/Segment.h"
|
||||
#include "world/RailLayout.h"
|
||||
#include "world/TrackSleepers.h"
|
||||
#include "world/TrackBlend.h"
|
||||
#include "model/material.h"
|
||||
#include "scene/scenenode.h"
|
||||
#include "utilities/Names.h"
|
||||
@@ -237,6 +238,12 @@ public:
|
||||
// timbermodel <model> <skin>
|
||||
std::string m_timber_model_name;
|
||||
std::string m_timber_skin_name; // replacable skin, empty for the textures of the model
|
||||
// how far from its ends p1, p2 (and p4 of a switch) the rails and the ballast blend into those of the tracks joined there (blend)
|
||||
std::array<float, 3> m_blend { track_blend::default_length, track_blend::default_length, track_blend::default_length };
|
||||
// the materials the blending is drawn with, null where nothing's blended: the rails of a plain track, its ballast, the trackbed of a switch
|
||||
material_handle m_blendmaterial1 { null_handle };
|
||||
material_handle m_blendmaterial2 { null_handle };
|
||||
material_handle m_blendmaterial3 { null_handle };
|
||||
TModel3d *m_sleeper_model { nullptr }; // resolved on init; nullptr means no model / failed to load
|
||||
material_handle m_sleeper_skin { 0 }; // resolved replacable skin handle, 0 = use model defaults
|
||||
// precomputed local-space transforms (relative to m_origin) for every sleeper instance along the path.
|
||||
|
||||
258
world/TrackBlend.cpp
Normal file
258
world/TrackBlend.cpp
Normal file
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
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 {
|
||||
|
||||
// how far aside of the line of an end its weight reaches, past the widest trackbeds
|
||||
double constexpr lateral_reach { 6.0 };
|
||||
|
||||
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. the answer is
|
||||
// the same for that track back, but for the lengths and the kinds of tracks which blend: switches don't blend their rails
|
||||
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[ 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;
|
||||
}
|
||||
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 weight { 0.f };
|
||||
if( blend.active ) {
|
||||
// measured along the line of the end, so both tracks give the vertices of their joint the same weight
|
||||
auto const offset { position - blend.point };
|
||||
auto const along { glm::dot( offset, blend.inward ) };
|
||||
auto const aside { glm::length( offset - blend.inward * along ) };
|
||||
if( along < blend.length && aside < lateral_reach ) {
|
||||
weight = blend.peak * static_cast<float>( 1.0 - std::max( 0.0, along ) / blend.length );
|
||||
}
|
||||
}
|
||||
data[ 1 + end ] = weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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[ end ];
|
||||
// where both tracks blend they meet half way at the joint, otherwise this one goes all the way to the other one there
|
||||
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, Kind == kind::ballast ) };
|
||||
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
|
||||
70
world/TrackBlend.h
Normal file
70
world/TrackBlend.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "utilities/Classes.h"
|
||||
#include "interfaces/ITexture.h"
|
||||
#include "rendering/geometrybank.h"
|
||||
|
||||
class TTrack;
|
||||
|
||||
// the rails and the ballast of a track blended into those of the tracks joined at its ends, where their textures differ: near
|
||||
// an end the vertices carry the weight of the track joined there (y, z, w of their user data for the ends p1, p2 and p4 of a
|
||||
// switch) and the track is drawn with a material blending its texture with theirs. the ballast comes in as grains, the rails
|
||||
// fade into each other. a plain track blends its rails and its ballast, a switch the trackbed made for it
|
||||
namespace track_blend {
|
||||
|
||||
// how far from an end the textures blend, by default
|
||||
float constexpr default_length { 3.f };
|
||||
|
||||
enum class kind {
|
||||
rails,
|
||||
ballast
|
||||
};
|
||||
|
||||
struct blending {
|
||||
struct end {
|
||||
bool active { false };
|
||||
glm::dvec3 point { 0.0 }; // relative to the origin of the track
|
||||
glm::dvec3 inward { 0.0 }; // unit vector from the end into the track
|
||||
double length { 0.0 };
|
||||
float peak { 0.f }; // the weight at the end: a half where the track joined there blends back, whole where it doesn't
|
||||
};
|
||||
std::array<end, 3> ends;
|
||||
material_handle material { null_handle }; // the material the blending is drawn with, null where nothing's blended
|
||||
|
||||
bool any() const {
|
||||
return material != null_handle; }
|
||||
// the weights of the ends put in the user data of the vertices (relative to the origin of the track), made as long as the
|
||||
// vertices where it's shorter; what the user data hold at x is kept
|
||||
void weigh( gfx::vertex_array const &Vertices, gfx::userdata_array &Userdata ) const;
|
||||
};
|
||||
|
||||
// how the rails or the ballast of the track blend into those of the tracks joined to it, nothing for the tracks which don't
|
||||
blending setup( TTrack const &Track, kind const Kind );
|
||||
|
||||
// while one is alive, the chunks of geometry inserted with track_rails::insert_chunk() get the weights of the blending
|
||||
class weighing {
|
||||
public:
|
||||
explicit weighing( blending const *Blending );
|
||||
~weighing();
|
||||
weighing( weighing const & ) = delete;
|
||||
weighing &operator=( weighing const & ) = delete;
|
||||
private:
|
||||
blending const *m_previous { nullptr };
|
||||
};
|
||||
// the blending the chunks inserted now are weighed by, null when they aren't
|
||||
blending const *weighed();
|
||||
|
||||
} // namespace track_blend
|
||||
Reference in New Issue
Block a user