Skip to content

Commit f837ffd

Browse files
committed
Add support for more routes, add custom filters, update documentation
1 parent 84c2f4b commit f837ffd

File tree

3 files changed

+162
-23
lines changed

3 files changed

+162
-23
lines changed
Binary file not shown.

Source/RunActivity/Viewer3D/DynamicTrack.cs

Lines changed: 160 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
using System.Linq;
3333
using System.Xml;
3434
using System.Xml.Schema;
35+
using System.Text.RegularExpressions;
3536

3637
namespace 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

Source/RunActivity/Viewer3D/SuperElevation.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ public static bool DecomposeStaticSuperElevation(Viewer viewer, List<DynamicTrac
8787

8888
// Determine the track profile to use for this section
8989
// It's possible that the tsection ID used by the track section points to the wrong shape
90-
// Use the static shape name instead to get expected results
91-
DynamicTrackViewer.GetBestTrackProfile(viewer, tmp, Path.GetFileName(shapeFilePath));
90+
// Instead, send the shape file path to ensure the correct shape is used in calculations
91+
DynamicTrackViewer.GetBestTrackProfile(viewer, tmp, shapeFilePath);
9292

9393
drawn++;
9494
}

0 commit comments

Comments
 (0)