From 173fe2ec3c4aae259adada4e5dcc3645e14e66df Mon Sep 17 00:00:00 2001 From: Hirek193 Date: Wed, 21 Oct 2026 23:51:43 +0200 Subject: [PATCH] feat(editor): ballast and rails blending lengths apart, as sliders --- application/editormodetrackprops.cpp | 92 ++++++++++++++++++++++++---- editor/editorTrack.cpp | 10 ++- editor/editorTrack.hpp | 3 +- lang/editor_pl.po | 36 +++++++---- world/Track.cpp | 28 ++++++--- world/Track.h | 7 ++- world/TrackBlend.cpp | 4 +- world/TrackBlend.h | 1 + 8 files changed, 138 insertions(+), 43 deletions(-) diff --git a/application/editormodetrackprops.cpp b/application/editormodetrackprops.cpp index d0961aa8..5fdfa40c 100644 --- a/application/editormodetrackprops.cpp +++ b/application/editormodetrackprops.cpp @@ -199,7 +199,8 @@ enum class field text, choice, check, - texture + texture, + slider // a number dragged between least and most, or typed with Ctrl+click }; // parts of the sheet, in their order @@ -244,6 +245,8 @@ struct track_property bool late{false}; // pasted after the others, it depends on them bool more{false}; int texture{-1}; // which list of the textures on the tracks: 0 rails, 1 trackbed + float least{0.f}; // the range of a slider + float most{1.f}; char const *label{""}; char const *tooltip{""}; char const *empty{nullptr}; // hint of an empty field @@ -506,25 +509,44 @@ std::vector make_properties() blades("bladethrow", "Blade throw (m)", "How far the tips of the blades move over between the two paths.\n0: the usual 0.160 m", &TSwitchExtension::m_bladethrow); } { - // the ends of a plain track are p1 and p2, a switch has p4 as the end of its second path - char const *const ends[][3] = {{"blend1", "Texture blending at p1 (m)", "How far from the end p1 the rails and the ballast blend into those of the track joined there, where their textures differ, fading into each other; 0: not blended"}, - {"blend2", "Texture blending at p2 (m)", "How far from the end p2 the rails and the ballast blend into those of the track joined there, where their textures differ, fading into each other; 0: not blended"}, - {"blend3", "Texture blending at p4 (m)", "How far from the end p4 of the switch its trackbed blends into the ballast of the track joined there, where their textures differ; 0: not blended"}}; - for (int end = 0; end < 3; ++end) + // how far from each end the ballast and the rails blend, apart: the ends of a plain track are p1 and p2, a switch has p4 as + // the end of its second path; a switch blends its trackbed alone, not its rails + struct blend_field { - auto &property{add(list, ends[end][0], field::number, section::look, group::blending, ends[end][1], ends[end][2])}; + char const *key; + char const *label; + char const *tooltip; + track_blend::kind kind; + int end; + }; + blend_field const fields[] = { + {"blendballast1", "Ballast blending at p1 (m)", "How far from the end p1 the ballast fades into that of the track joined there, where their textures differ; 0: not blended", track_blend::kind::ballast, 0}, + {"blendballast2", "Ballast blending at p2 (m)", "How far from the end p2 the ballast fades into that of the track joined there, where their textures differ; 0: not blended", track_blend::kind::ballast, 1}, + {"blendballast3", "Ballast blending at p4 (m)", "How far from the end p4 of the switch its trackbed fades into the ballast of the track joined there, where their textures differ; 0: not blended", track_blend::kind::ballast, 2}, + {"blendrails1", "Rails blending at p1 (m)", "How far from the end p1 the rails fade into those of the track joined there, where their textures differ; 0: not blended", track_blend::kind::rails, 0}, + {"blendrails2", "Rails blending at p2 (m)", "How far from the end p2 the rails fade into those of the track joined there, where their textures differ; 0: not blended", track_blend::kind::rails, 1}}; + for (auto const &blend : fields) + { + auto &property{add(list, blend.key, field::slider, section::look, group::blending, blend.label, blend.tooltip)}; property.rebuild = true; property.more = true; - property.applies = [end](TTrack const &Track) { return Track.m_visible && (Track.iCategoryFlag & 15) == kRailCategory && (Track.eType == tt_Switch || (Track.eType == tt_Normal && end < 2)); }; - property.get = [end](TTrack const &Track) { return decimal(Track.m_blend[end], 2); }; - property.set = [end](TTrack &Track, std::string const &Text) { + property.least = 0.f; + property.most = 20.f; + auto const kind{static_cast(blend.kind)}; + auto const end{blend.end}; + auto const rails{blend.kind == track_blend::kind::rails}; + property.applies = [end, rails](TTrack const &Track) { + return Track.m_visible && (Track.iCategoryFlag & 15) == kRailCategory && (Track.eType == tt_Normal ? end < 2 : (Track.eType == tt_Switch && false == rails)); + }; + property.get = [kind, end](TTrack const &Track) { return decimal(Track.m_blend[kind][end], 2); }; + property.set = [kind, end](TTrack &Track, std::string const &Text) { double length; if (false == parse_number(Text, length)) return false; auto const clamped{static_cast(std::max(0.0, length))}; - if (decimal(clamped, 2) == decimal(Track.m_blend[end], 2)) + if (decimal(clamped, 2) == decimal(Track.m_blend[kind][end], 2)) return false; - Track.m_blend[end] = clamped; + Track.m_blend[kind][end] = clamped; return true; }; } @@ -924,7 +946,7 @@ std::size_t editor_mode::track_property_apply(int const Property, std::vectorset_status(format(STR_C("%s: \"%s\" isn't a number"), STR_C(property.label), Text.c_str()), true); else if (std::string{property.key} == "sleepers" && Text == "1" && targets.front()->m_sleeper_model_name.empty()) ui()->set_status(STR_C("Give the sleeper model first"), true); @@ -1246,6 +1268,49 @@ void editor_mode::render_track_properties(std::vector const &Tracks) } }; + // dragged without changing the tracks, which are changed once as it's let go, in one step of the undo + auto const slider_row = [&](std::size_t const Index) { + auto const &property{list[Index]}; + auto const &values{cache.values[Index]}; + auto &edit{m_track_field}; + auto const editing{edit.property == static_cast(Index)}; + double shown{0.0}; + if (false == values.empty()) + parse_number(values.front().first, shown); + auto value{static_cast(shown)}; + if (editing) + { + double typed; + if (parse_number(edit.text.data(), typed)) + value = static_cast(typed); + } + // the most common value, marked where the tracks differ + auto const format{values.size() > 1 && false == editing ? std::string{"%.1f m *"} : std::string{"%.1f m"}}; + ImGui::SetNextItemWidth(-FLT_MIN); + auto const changed{ImGui::SliderFloat("##value", &value, property.least, property.most, format.c_str())}; + if (ImGui::IsItemActivated()) + { + edit.property = static_cast(Index); + edit.tracks = Tracks; + edit.initial = decimal(shown, 2); + std::snprintf(edit.text.data(), edit.text.size(), "%s", edit.initial.c_str()); + } + if (changed && edit.property == static_cast(Index)) + std::snprintf(edit.text.data(), edit.text.size(), "%s", decimal(std::max(0.f, value), 2).c_str()); + if (edit.property == static_cast(Index)) + edit.frame = ImGui::GetFrameCount(); + row_tooltip(property, values, cache.applies[Index], Tracks.size()); + if (ImGui::IsItemDeactivated() && edit.property == static_cast(Index)) + { + auto const tracks{edit.tracks}; + std::string const typed{edit.text.data()}; + auto const initial{edit.initial}; + edit = {}; + if (typed != initial) + track_property_apply(static_cast(Index), tracks, typed); + } + }; + auto const choice_row = [&](std::size_t const Index) { auto const &property{list[Index]}; auto const &values{cache.values[Index]}; @@ -1338,6 +1403,7 @@ void editor_mode::render_track_properties(std::vector const &Tracks) case field::choice: choice_row(i); break; case field::check: check_row(i); break; case field::texture: texture_row(i); break; + case field::slider: slider_row(i); break; default: text_row(i); break; } ImGui::PopID(); diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index d5890729..192609b1 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -3035,9 +3035,13 @@ void write_ending(std::ostream &Text, TTrack const &Style) Text << "sleepermodel " << Style.m_sleeper_frequency << ' ' << Style.m_sleeper_model_name << ' ' << (Style.m_sleeper_skin_name.empty() ? std::string{"none"} : Style.m_sleeper_skin_name) << ' ' << Style.m_sleeper_offset.x << ' ' << Style.m_sleeper_offset.y << ' ' << Style.m_sleeper_offset.z << ' ' << Style.m_sleeper_ballast_z << ' '; if (false == Style.m_timber_model_name.empty()) Text << "timbermodel " << Style.m_timber_model_name << ' ' << (Style.m_timber_skin_name.empty() ? std::string{"none"} : Style.m_timber_skin_name) << ' '; - for (std::size_t end = 0; end < Style.m_blend.size(); ++end) - if (Style.m_blend[end] != track_blend::default_length) - Text << "blend " << end + 1 << ' ' << Style.m_blend[end] << ' '; + for (auto const kind : {track_blend::kind::rails, track_blend::kind::ballast}) + { + auto const &lengths{Style.m_blend[static_cast(kind)]}; + for (std::size_t end = 0; end < lengths.size(); ++end) + if (lengths[end] != track_blend::default_length) + Text << (kind == track_blend::kind::rails ? "blendrails " : "blendballast ") << end + 1 << ' ' << lengths[end] << ' '; + } Text << "endtrack\n"; } diff --git a/editor/editorTrack.hpp b/editor/editorTrack.hpp index 389b3b7c..81cf938f 100644 --- a/editor/editorTrack.hpp +++ b/editor/editorTrack.hpp @@ -71,7 +71,8 @@ class editor_track float sleeper_ballast{0.f}; std::string timber_model; std::string timber_skin; - std::array blend{track_blend::default_length, track_blend::default_length, track_blend::default_length}; + std::array, 2> blend{{{track_blend::default_length, track_blend::default_length, track_blend::default_length}, + {track_blend::default_length, track_blend::default_length, track_blend::default_length}}}; std::pair rail_profile; std::array>, 6> events; std::string name; diff --git a/lang/editor_pl.po b/lang/editor_pl.po index 2b62bf27..568143dd 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -10261,20 +10261,32 @@ msgstr[2] "Stosuje zestaw na %d zaznaczonych torach" msgid "Texture blending" msgstr "Mieszanie tekstur" -msgid "Texture blending at p1 (m)" -msgstr "Mieszanie tekstur przy p1 (m)" +msgid "Ballast blending at p1 (m)" +msgstr "Mieszanie podsypki przy p1 (m)" -msgid "Texture blending at p2 (m)" -msgstr "Mieszanie tekstur przy p2 (m)" +msgid "Ballast blending at p2 (m)" +msgstr "Mieszanie podsypki przy p2 (m)" -msgid "Texture blending at p4 (m)" -msgstr "Mieszanie tekstur przy p4 (m)" +msgid "Ballast blending at p4 (m)" +msgstr "Mieszanie podsypki przy p4 (m)" -msgid "How far from the end p1 the rails and the ballast blend into those of the track joined there, where their textures differ, fading into each other; 0: not blended" -msgstr "Na jakiej długości od końca p1 szyny i podsypka płynnie przechodzą w te z toru tam dołączonego, gdy ich tekstury są różne; 0: bez mieszania" +msgid "Rails blending at p1 (m)" +msgstr "Mieszanie szyn przy p1 (m)" -msgid "How far from the end p2 the rails and the ballast blend into those of the track joined there, where their textures differ, fading into each other; 0: not blended" -msgstr "Na jakiej długości od końca p2 szyny i podsypka płynnie przechodzą w te z toru tam dołączonego, gdy ich tekstury są różne; 0: bez mieszania" +msgid "Rails blending at p2 (m)" +msgstr "Mieszanie szyn przy p2 (m)" -msgid "How far from the end p4 of the switch its trackbed blends into the ballast of the track joined there, where their textures differ; 0: not blended" -msgstr "Na jakiej długości od końca p4 rozjazdu jego podsypka przechodzi w podsypkę toru tam dołączonego, gdy ich tekstury są różne; 0: bez mieszania" +msgid "How far from the end p1 the ballast fades into that of the track joined there, where their textures differ; 0: not blended" +msgstr "Na jakiej długości od końca p1 podsypka płynnie przechodzi w podsypkę toru tam dołączonego, gdy ich tekstury są różne; 0: bez mieszania" + +msgid "How far from the end p2 the ballast fades into that of the track joined there, where their textures differ; 0: not blended" +msgstr "Na jakiej długości od końca p2 podsypka płynnie przechodzi w podsypkę toru tam dołączonego, gdy ich tekstury są różne; 0: bez mieszania" + +msgid "How far from the end p4 of the switch its trackbed fades into the ballast of the track joined there, where their textures differ; 0: not blended" +msgstr "Na jakiej długości od końca p4 rozjazdu jego podsypka płynnie przechodzi w podsypkę toru tam dołączonego, gdy ich tekstury są różne; 0: bez mieszania" + +msgid "How far from the end p1 the rails fade into those of the track joined there, where their textures differ; 0: not blended" +msgstr "Na jakiej długości od końca p1 szyny płynnie przechodzą w szyny toru tam dołączonego, gdy ich tekstury są różne; 0: bez mieszania" + +msgid "How far from the end p2 the rails fade into those of the track joined there, where their textures differ; 0: not blended" +msgstr "Na jakiej długości od końca p2 szyny płynnie przechodzą w szyny toru tam dołączonego, gdy ich tekstury są różne; 0: bez mieszania" diff --git a/world/Track.cpp b/world/Track.cpp index d1e2e7dd..b9b6b01d 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -701,16 +701,21 @@ 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 : 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 + else if( str == "blendrails" || str == "blendballast" || str == "blend" ) { + // blendrails/blendballast : how far from the end 1 (p1), 2 (p2) or 3 (p4 of a switch) the rails or the ballast + // blend into those of the track joined there, where their textures differ; 0: not at all. blend: both of them auto const end { parser->getToken() }; - auto const length { parser->getToken() }; - if( end >= 1 && end <= static_cast( m_blend.size() ) ) { - m_blend[ end - 1 ] = static_cast( std::max( 0.0, length ) ); + auto const length { static_cast( std::max( 0.0, parser->getToken() ) ) }; + if( end >= 1 && end <= static_cast( m_blend[ 0 ].size() ) ) { + if( str != "blendballast" ) { + m_blend[ static_cast( track_blend::kind::rails ) ][ end - 1 ] = length; + } + if( str != "blendrails" ) { + m_blend[ static_cast( track_blend::kind::ballast ) ][ end - 1 ] = length; + } } else { - ErrorLog( "Bad track: blend at end " + std::to_string( end ) + " of track \"" + m_name + "\" which has no such end" ); + ErrorLog( "Bad track: " + str + " at end " + std::to_string( end ) + " of track \"" + m_name + "\" which has no such end" ); } } else if( str == "friction" ) { @@ -2748,9 +2753,12 @@ 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 ] << ' '; + for( auto const kind : { track_blend::kind::rails, track_blend::kind::ballast } ) { + auto const &lengths { m_blend[ static_cast( kind ) ] }; + for( std::size_t end = 0; end < lengths.size(); ++end ) { + if( lengths[ end ] != track_blend::default_length ) { + Output << ( kind == track_blend::kind::rails ? "blendrails " : "blendballast " ) << end + 1 << ' ' << lengths[ end ] << ' '; + } } } // footer diff --git a/world/Track.h b/world/Track.h index fc76d8ff..5c491758 100644 --- a/world/Track.h +++ b/world/Track.h @@ -238,8 +238,11 @@ public: // timbermodel 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 m_blend { track_blend::default_length, track_blend::default_length, track_blend::default_length }; + // 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 + // (blendrails, blendballast), by the kind (track_blend::kind) + std::array, 2> m_blend { { + { track_blend::default_length, track_blend::default_length, track_blend::default_length }, + { 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 }; diff --git a/world/TrackBlend.cpp b/world/TrackBlend.cpp index 1d51ecec..ee56a7e2 100644 --- a/world/TrackBlend.cpp +++ b/world/TrackBlend.cpp @@ -150,7 +150,7 @@ bool blends( TTrack const &Track, kind const Kind, int const End, TTrack const * if( Kind == kind::rails && Track.eType != tt_Normal ) { return false; } - if( Track.m_blend[ End ] <= 0.f ) { + if( Track.m_blend[ static_cast( Kind ) ][ End ] <= 0.f ) { return false; } auto const own { material_of( Track, Kind, End ) }; @@ -219,7 +219,7 @@ blending setup( TTrack const &Track, kind const Kind ) { auto &blend { result.ends[ end ] }; end_frame( Track, end, blend.point, blend.inward ); blend.point -= Track.m_origin; - blend.length = Track.m_blend[ end ]; + blend.length = Track.m_blend[ static_cast( Kind ) ][ 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; diff --git a/world/TrackBlend.h b/world/TrackBlend.h index 6599d556..ea9b2b38 100644 --- a/world/TrackBlend.h +++ b/world/TrackBlend.h @@ -28,6 +28,7 @@ namespace track_blend { // how far from an end the textures blend, by default float constexpr default_length { 3.f }; +// the order of the lengths of TTrack::m_blend enum class kind { rails, ballast