3232using System . Linq ;
3333using System . Xml ;
3434using System . Xml . Schema ;
35+ using System . Text . RegularExpressions ;
3536
3637namespace Orts . Viewer3D
3738{
@@ -222,20 +223,20 @@ public void Mark()
222223 /// The result is cached in the vector section object for future use
223224 /// </summary>
224225 /// <returns>Index of viewer.TRPs</returns>
225- public static int GetBestTrackProfile ( Viewer viewer , TrVectorSection trSection , string shapeName = "" )
226+ public static int GetBestTrackProfile ( Viewer viewer , TrVectorSection trSection , string shapePath = "" )
226227 {
227228 int trpIndex ;
228- if ( shapeName == "" && viewer . Simulator . TSectionDat . TrackShapes . ContainsKey ( trSection . ShapeIndex ) )
229- shapeName = viewer . Simulator . TSectionDat . TrackShapes . Get ( trSection . ShapeIndex ) . FileName ;
229+ if ( shapePath == "" && viewer . Simulator . TSectionDat . TrackShapes . ContainsKey ( trSection . ShapeIndex ) )
230+ shapePath = String . Concat ( viewer . Simulator . BasePath , @"\Global\Shapes\" , viewer . Simulator . TSectionDat . TrackShapes . Get ( trSection . ShapeIndex ) . FileName ) ;
230231
231- if ( viewer . TrackProfileIndicies . ContainsKey ( shapeName ) )
232- viewer . TrackProfileIndicies . TryGetValue ( shapeName , out trpIndex ) ;
233- else if ( shapeName != "" ) // Haven't checked this track shape yet
232+ if ( viewer . TrackProfileIndicies . ContainsKey ( shapePath ) )
233+ viewer . TrackProfileIndicies . TryGetValue ( shapePath , out trpIndex ) ;
234+ else if ( shapePath != "" ) // Haven't checked this track shape yet
234235 {
235236 // Need to load the shape file if not already loaded
236- var trackShape = viewer . ShapeManager . Get ( viewer . Simulator . BasePath + @"\Global\Shapes\" + shapeName ) ;
237+ var trackShape = viewer . ShapeManager . Get ( shapePath ) ;
237238 trpIndex = GetBestTrackProfile ( viewer , trackShape ) ;
238- viewer . TrackProfileIndicies . Add ( shapeName , trpIndex ) ;
239+ viewer . TrackProfileIndicies . Add ( shapePath , trpIndex ) ;
239240 }
240241 else // Not enough info-use default track profile
241242 trpIndex = 0 ;
@@ -255,25 +256,117 @@ public static int GetBestTrackProfile(Viewer viewer, SharedShape shape)
255256 {
256257 float score = float . NegativeInfinity ;
257258 int bestIndex = - 1 ;
259+
258260 for ( int i = 0 ; i < viewer . TRPs . Count ; i ++ )
259261 {
260262 float bestScore = score ;
261263 score = 0 ;
262- // Default behavior: Attempt to match track shape to track profile using texture names alone
263- foreach ( string image in viewer . TRPs [ i ] . TrackProfile . Images )
264+ if ( viewer . TRPs [ i ] . TrackProfile . IncludeImages == null && viewer . TRPs [ i ] . TrackProfile . ExcludeImages == null
265+ && viewer . TRPs [ i ] . TrackProfile . IncludeShapes == null && viewer . TRPs [ i ] . TrackProfile . ExcludeShapes == null )
264266 {
265- if ( shape . ImageNames != null && shape . ImageNames . Contains ( image , StringComparer . InvariantCultureIgnoreCase ) )
266- score ++ ;
267- else // Slight bias against track profiles with extra textures defined
268- score -= 0.05f ;
267+ // Default behavior: Attempt to match track shape to track profile using texture names alone
268+ // If shape file is missing any textures, we can't use this method
269+ if ( shape . ImageNames == null )
270+ {
271+ score = float . NegativeInfinity ;
272+ continue ;
273+ }
274+ foreach ( string image in viewer . TRPs [ i ] . TrackProfile . Images )
275+ {
276+ if ( shape . ImageNames . Contains ( image , StringComparer . InvariantCultureIgnoreCase ) )
277+ score ++ ;
278+ else // Slight bias against track profiles with extra textures defined
279+ score -= 0.05f ;
280+ }
281+ if ( score > bestScore && score > 0 ) // Only continue checking if current profile might be the best one
282+ {
283+ foreach ( string image in shape . ImageNames )
284+ {
285+ // Strong bias against track profiles that are missing textures
286+ if ( ! viewer . TRPs [ i ] . TrackProfile . Images . Contains ( image , StringComparer . InvariantCultureIgnoreCase ) )
287+ score -= 0.25f ;
288+ }
289+ }
269290 }
270- if ( score > bestScore && shape . ImageNames != null ) // Only continue checking if current profile might be the best one
291+ else // Manual override: Match track shape to track profile using user-defined filters
271292 {
272- foreach ( string image in shape . ImageNames )
293+ // Check if the track shape is excluded by the track profile
294+ // If it is excluded, skip processing
295+ if ( viewer . TRPs [ i ] . TrackProfile . ExcludeShapes != null )
273296 {
274- // Strong bias against track profiles that are missing textures
275- if ( ! viewer . TRPs [ i ] . TrackProfile . Images . Contains ( image , StringComparer . InvariantCultureIgnoreCase ) )
276- score -= 0.25f ;
297+ foreach ( Regex filter in viewer . TRPs [ i ] . TrackProfile . ExcludeShapes )
298+ {
299+ if ( filter . IsMatch ( Path . GetFileNameWithoutExtension ( shape . FilePath ) ) )
300+ {
301+ score = float . NegativeInfinity ;
302+ break ;
303+ }
304+ }
305+ }
306+ if ( score > float . NegativeInfinity && shape . ImageNames != null
307+ && viewer . TRPs [ i ] . TrackProfile . ExcludeImages != null )
308+ {
309+ foreach ( Regex filter in viewer . TRPs [ i ] . TrackProfile . ExcludeImages )
310+ {
311+ foreach ( string image in shape . ImageNames )
312+ {
313+ if ( filter . IsMatch ( image ) )
314+ {
315+ score = float . NegativeInfinity ;
316+ break ;
317+ }
318+ }
319+ }
320+ }
321+ // If no exclusions are found, check for inclusions instead
322+ if ( score > float . NegativeInfinity )
323+ {
324+ // Still need to consider that this shape may need to be excluded if the track profile doesn't include its shape or textures
325+ // If the track profile doesn't specify any shapes or textures to include, assume the profile can be used
326+ bool shapeIncluded = false ;
327+ bool imageIncluded = false ;
328+ if ( viewer . TRPs [ i ] . TrackProfile . IncludeShapes != null )
329+ {
330+ foreach ( Regex filter in viewer . TRPs [ i ] . TrackProfile . IncludeShapes )
331+ {
332+ if ( filter . IsMatch ( Path . GetFileNameWithoutExtension ( shape . FilePath ) ) )
333+ {
334+ shapeIncluded = true ;
335+ score += 10.0f / viewer . TRPs [ i ] . TrackProfile . IncludeShapes . Count ;
336+ }
337+ }
338+ }
339+ else // No include filter set for shapes, assume this shape is included
340+ {
341+ shapeIncluded = true ;
342+ score += 5.0f ;
343+ }
344+ if ( shapeIncluded )
345+ {
346+ if ( viewer . TRPs [ i ] . TrackProfile . IncludeImages != null && shape . ImageNames != null )
347+ {
348+ foreach ( Regex filter in viewer . TRPs [ i ] . TrackProfile . IncludeImages )
349+ {
350+ foreach ( string image in shape . ImageNames )
351+ {
352+ if ( filter . IsMatch ( image ) )
353+ {
354+ imageIncluded = true ;
355+ score += 10.0f / viewer . TRPs [ i ] . TrackProfile . IncludeImages . Count ;
356+ break ;
357+ }
358+ }
359+ }
360+ }
361+ else // No include filter set for textures, assume this shape is included
362+ {
363+ imageIncluded = true ;
364+ score += 5.0f ;
365+ }
366+ }
367+ // If the shape wasn't included or the textures weren't included, this track profile shouldn't be used
368+ if ( shapeIncluded == false || imageIncluded == false )
369+ score = float . NegativeInfinity ;
277370 }
278371 }
279372 if ( score > bestScore )
@@ -305,7 +398,7 @@ public class TRPFile
305398
306399 /// <summary>
307400 /// Creates a List<TRPFile></TRPFile> instance from a set of track profile file(s)
308- /// (XML or STF) or canned. (Precedence is XML [.XML], STF [.DAT ], default [canned]).
401+ /// (XML or STF) or canned. (Precedence is XML [.XML], STF [.STF ], default [canned]).
309402 /// </summary>
310403 /// <param name="viewer">Viewer.</param>
311404 /// <param name="routePath">Path to route.</param>
@@ -443,7 +536,16 @@ public TRPFile(Viewer viewer, string filespec)
443536 settings . ValidationEventHandler += new ValidationEventHandler ( ValidationCallback ) ;
444537 settings . ValidationFlags |= XmlSchemaValidationFlags . ReportValidationWarnings ;
445538 settings . ValidationType = ValidationType . Schema ; // Independent external file
446- settings . Schemas . Add ( "TrProfile.xsd" , XmlReader . Create ( xsdFilespec ) ) ; // Add schema from file
539+ try
540+ {
541+ settings . Schemas . Add ( "TrProfile.xsd" , XmlReader . Create ( xsdFilespec ) ) ; // Add schema from file
542+ }
543+ catch
544+ {
545+ Trace . TraceWarning ( "Track profile XML constructor failed, could not create XML schema " + xsdFilespec ) ;
546+ TrackProfile = new TrProfile ( viewer ) ;
547+ break ;
548+ }
447549
448550 // Create an XML reader for the .xml file
449551 using ( XmlReader reader = XmlReader . Create ( filespec , settings ) )
@@ -494,6 +596,11 @@ public class TrProfile
494596 public float PitchControlScalar ; // Scalar parameter for PitchControls
495597 public ArrayList LODs = new ArrayList ( ) ; // Array of Levels-Of-Detail
496598 public List < string > Images = new List < string > ( ) ;
599+ // Manual overrides for matching track shapes/textures to track profiles
600+ public List < Regex > IncludeShapes ;
601+ public List < Regex > ExcludeShapes ;
602+ public List < Regex > IncludeImages ;
603+ public List < Regex > ExcludeImages ;
497604
498605 /// <summary>
499606 /// Enumeration of LOD control methods
@@ -683,6 +790,10 @@ public TrProfile(Viewer viewer, STFReader stf)
683790 new STFReader . TokenProcessor ( "chordspan" , ( ) => { ChordSpan = stf . ReadFloatBlock ( STFReader . UNITS . Distance , null ) ; } ) ,
684791 new STFReader . TokenProcessor ( "pitchcontrol" , ( ) => { PitchControl = GetPitchControl ( stf . ReadStringBlock ( null ) ) ; } ) ,
685792 new STFReader . TokenProcessor ( "pitchcontrolscalar" , ( ) => { PitchControlScalar = stf . ReadFloatBlock ( STFReader . UNITS . Distance , null ) ; } ) ,
793+ new STFReader . TokenProcessor ( "includedshapes" , ( ) => { IncludeShapes = ConvertToRegex ( stf . ReadStringBlock ( null ) ) ; } ) ,
794+ new STFReader . TokenProcessor ( "excludedshapes" , ( ) => { ExcludeShapes = ConvertToRegex ( stf . ReadStringBlock ( null ) ) ; } ) ,
795+ new STFReader . TokenProcessor ( "includedtextures" , ( ) => { IncludeImages = ConvertToRegex ( stf . ReadStringBlock ( null ) ) ; } ) ,
796+ new STFReader . TokenProcessor ( "excludedtextures" , ( ) => { ExcludeImages = ConvertToRegex ( stf . ReadStringBlock ( null ) ) ; } ) ,
686797 new STFReader . TokenProcessor ( "lod" , ( ) => { LODs . Add ( new LOD ( viewer , stf ) ) ; } ) ,
687798 } ) ;
688799
@@ -717,6 +828,10 @@ public TrProfile(Viewer viewer, XmlReader reader)
717828 ChordSpan = float . Parse ( reader . GetAttribute ( "ChordSpan" ) ) ;
718829 PitchControl = GetPitchControl ( reader . GetAttribute ( "PitchControl" ) ) ;
719830 PitchControlScalar = float . Parse ( reader . GetAttribute ( "PitchControlScalar" ) ) ;
831+ IncludeShapes = ConvertToRegex ( reader . GetAttribute ( "IncludedShapes" ) ) ;
832+ ExcludeShapes = ConvertToRegex ( reader . GetAttribute ( "ExcludedShapes" ) ) ;
833+ IncludeImages = ConvertToRegex ( reader . GetAttribute ( "IncludedTextures" ) ) ;
834+ ExcludeImages = ConvertToRegex ( reader . GetAttribute ( "ExcludedTextures" ) ) ;
720835 }
721836 else
722837 {
@@ -858,6 +973,30 @@ public void Mark()
858973 foreach ( LOD lod in LODs )
859974 lod . Mark ( ) ;
860975 }
976+
977+ /// <summary>
978+ /// Converts given string into a list of regular expression objects by splitting
979+ /// the string at each comma to get individual filter substrings, then replacing
980+ /// any wildcards * or ? with their regex equivalent.
981+ /// </summary>
982+ public static List < Regex > ConvertToRegex ( string filters )
983+ {
984+ List < Regex > regexFilters = null ;
985+
986+ if ( filters != null )
987+ {
988+ // Split the string of filters into an array of individual filters
989+ string [ ] filterList = filters . Replace ( " " , "" ) . Replace ( "\" " , "" ) . Split ( ',' ) ;
990+
991+ regexFilters = new List < Regex > ( ) ;
992+
993+ // Convert filters to regular expressions that will use * and ? as wildcards. Case is to be ignored in this instance.
994+ for ( int i = 0 ; i < filterList . Length ; i ++ )
995+ regexFilters . Add ( new Regex ( string . Concat ( "^" , Regex . Escape ( filterList [ i ] ) . Replace ( "\\ ?" , "." ) . Replace ( "\\ *" , ".*" ) , "$" ) , RegexOptions . IgnoreCase ) ) ;
996+ }
997+
998+ return regexFilters ;
999+ }
8611000 }
8621001
8631002 public class LOD
0 commit comments