From 7f8fd208f77b06ed538b3ac3538b2dc2b97dd22b Mon Sep 17 00:00:00 2001 From: rwf-rr Date: Fri, 22 Aug 2025 12:51:20 -0700 Subject: [PATCH 1/3] First part of prototype to show grade ahead in track monitor. Has the basic code to present the grade posts on the track monitor. Only creates grade posts for simple changes in grade; long steady grade before and after. Loosely modeled after mileposts. --- Source/Orts.Formats.Msts/TrackDatabaseFile.cs | 320 +++++++++++++++++- Source/Orts.Simulation/Simulation/Activity.cs | 5 +- .../Simulation/Physics/Train.cs | 64 ++++ .../Simulation/Signalling/Gradepost.cs | 59 ++++ .../Simulation/Signalling/Signals.cs | 107 +++++- .../Signalling/TrackCircuitGradepost.cs | 41 +++ .../Signalling/TrackCircuitItems.cs | 1 + .../Orts.Simulation/Simulation/Simulator.cs | 54 ++- .../Viewer3D/Popups/TrackMonitorWindow.cs | 92 +++-- 9 files changed, 712 insertions(+), 31 deletions(-) create mode 100644 Source/Orts.Simulation/Simulation/Signalling/Gradepost.cs create mode 100644 Source/Orts.Simulation/Simulation/Signalling/TrackCircuitGradepost.cs diff --git a/Source/Orts.Formats.Msts/TrackDatabaseFile.cs b/Source/Orts.Formats.Msts/TrackDatabaseFile.cs index 5ab0d0216f..9545836d91 100644 --- a/Source/Orts.Formats.Msts/TrackDatabaseFile.cs +++ b/Source/Orts.Formats.Msts/TrackDatabaseFile.cs @@ -164,15 +164,101 @@ public int TrackNodesIndexOf(TrackNode targetTN) throw new InvalidOperationException("Program Bug: Can't Find Track Node"); } + /// + /// Get the index of the vector node that precedes the specified vector node. + /// Expects to traverse a junction node to find the preceding vector node. + /// + /// The index of the incoming vector node, and the connected end if it. + // A Vector Node has a direction determined by the order of the vector sections. + // - Pin[0] is at the start (section[0]), Pin[1] is at the end (section[n]). + // - Pin Direction = 0 identifies that the vector node is connected to the trailing (out) side of a junction. + // - Pin Direction = 1 identifies that the vector node is connected to the leading (in) side of a junction. + // A Junction Node's direction is from the leading (in) side to the trailing (out) side. A typical Junction + // Node has one in Pin (at index 0) and two out Pins. The last out Pin seems to be the straight (primary) + // path. + // - Pin Direction = 0 indicates that the connected Vector Node is oriented towards the junction (ie. the + // last vector section connects to the junction). + // - Pin Direction = 1 indicates that the connected Vector Node is oriented away from the junction (ie. the + // first vector section connects to the junction). + public (int, int) GetIncomingVectorNodeIndex(TrackNode vectorNode) + { + int incomingVectorNodeIdx = -1; // error + int incomingVectorNodeEnd = 0; + + if (vectorNode == null || vectorNode.TrVectorNode == null || vectorNode.Inpins != 1) + { + Debug.Print("GetIncomingVectorNodeIndex() ERROR: source node is not a valid vector node"); + return (-1, 0); // error + } + + int junctionNodeIdx = vectorNode.TrPins[0].Link; + if (junctionNodeIdx <= 0 || junctionNodeIdx >= TrackNodes.Length) + { + Debug.Print(String.Format("GetIncomingVectorNodeIndex() ERROR: first incoming node index {0} is out of range (1..{1})", junctionNodeIdx, TrackNodes.Length - 1)); + return (-1, 0); // error + } + + TrackNode junctionNode = TrackNodes[junctionNodeIdx]; + + if (junctionNode.TrEndNode) { incomingVectorNodeIdx = 0; } // there is no incoming vector node + + else if (junctionNode.TrVectorNode != null) + { + Debug.Print(String.Format("GetIncomingVectorNodeIndex() WARNING: stitched vector nodes not expected; {0} - {1}", vectorNode.Index, junctionNode.Index)); + incomingVectorNodeIdx = (int)junctionNode.Index; + } + else if (junctionNode.TrJunctionNode == null) + { + Debug.Print(String.Format("GetIncomingVectorNodeIndex() WARNING: expected a junction node, got a {1} for the first incoming node {0}", junctionNode.Index, junctionNode.GetType())); + incomingVectorNodeIdx = 0; // there is no incoming vector node + } + else + { + int pinIdx = 0; // when connected to trailing side of junction, use in pin + if (vectorNode.TrPins[0].Direction != 0) + { + // connected to leading (facing) side, use longer next vector node + var out1 = junctionNode.TrPins.Length < 2 ? null : TrackNodes[junctionNode.TrPins[1].Link]?.TrVectorNode?.TrVectorSections; + int l1 = out1 == null ? 0 : out1.Length; + var out2 = junctionNode.TrPins.Length < 3 ? null : TrackNodes[junctionNode.TrPins[2].Link]?.TrVectorNode?.TrVectorSections; + int l2 = out2 == null ? 0 : out2.Length; + pinIdx = l1 >= l2 ? 1 : 2; + } + incomingVectorNodeIdx = junctionNode.TrPins[pinIdx].Link; + incomingVectorNodeEnd = junctionNode.TrPins[pinIdx].Direction == 0 ? 1 : 0; + + if (incomingVectorNodeIdx <= 0 || incomingVectorNodeIdx >= TrackNodes.Length) + { + Debug.Print(String.Format("GetIncomingVectorNodeIndex() ERROR: incoming node index {0} is out of range (1..{1})", incomingVectorNodeIdx, TrackNodes.Length - 1)); + return (-1, 0); // error + } + else if (incomingVectorNodeIdx == vectorNode.Index) + { + Debug.Print(String.Format("GetIncomingVectorNodeIndex() ERROR: incoming node index {0} same as query node {1} (circular)", incomingVectorNodeIdx, vectorNode.Index)); + return (-1, 0); // error + + } + else if (TrackNodes[incomingVectorNodeIdx].TrVectorNode == null) + { + Debug.Print(String.Format("GetIncomingVectorNodeIndex() ERROR: incoming node index {0} is not a vector node (type {1})", incomingVectorNodeIdx, TrackNodes[incomingVectorNodeIdx].GetType())); + return (-1, 0); // error + } + } + + return (incomingVectorNodeIdx, incomingVectorNodeEnd); + } + /// /// Add a number of TrItems (Track Items), created outside of the file, to the table of TrItems. /// This will also set the ID of the TrItems (since that gives the index in that array) /// /// The array of new items. + /// The index of the first item added (ie. the size of the array before). [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", Justification = "Keeping identifier consistent to use in MSTS")] - public void AddTrItems(TrItem[] newTrItems) + public int AddTrItems(TrItem[] newTrItems) { TrItem[] newTrItemTable; + int firstInsertIdx = 0; if (TrItemTable == null) { @@ -180,6 +266,7 @@ public void AddTrItems(TrItem[] newTrItems) } else { + firstInsertIdx = TrItemTable.Length; newTrItemTable = new TrItem[TrItemTable.Length + newTrItems.Length]; TrItemTable.CopyTo(newTrItemTable, 0); } @@ -192,6 +279,7 @@ public void AddTrItems(TrItem[] newTrItems) } TrItemTable = newTrItemTable; + return firstInsertIdx; } public void AddTrNodesToPointsOnApiMap(InfoApiMap infoApiMap) @@ -660,6 +748,23 @@ public class TrVectorNode /// The amount of TrItems in TrItemRefs public int NoItemRefs { get; set; } // it would have been better to use TrItemRefs.Length instead of keeping count ourselve + // to following members are calculated, not read from the file + public float LengthM; // track length + public float[] GradePctAtEnd = new float[] { 0f, 0f }; // grade at each end [start, end] + public float[] GradeLengthMAtEnd = new float[] { 0f, 0f }; // length of steady grade at each end [start, end] + public float[] GradepostDistanceMAtEnd = new float[] { -1f, -1f }; // distance of the first grade marker at each end; -1 if none + public struct GradeData // represents a segment of track with approx. the same grade, may span multiple vector sections + { + public float DistanceFromStartM; // for debug + public float LengthM; // length in meters + public float GradePct; // grade in percent + public int TileX, TileZ; // location of the start of this grade segment + public float X, Y, Z; + public GradeData(float distance, float length, float grade, int tileX, int tileZ, float x, float y, float z) { DistanceFromStartM = distance; LengthM = length; GradePct = grade; TileX = tileX; TileZ = tileZ; X = x; Y = y; Z = z; } + } + public List GradeList = new List(); + + /// /// Default constructor used during file parsing. /// @@ -736,11 +841,174 @@ public int TrVectorSectionsIndexOf(TrVectorSection targetTvs) public void AddTrItemRef(int newTrItemRef) { int[] newTrItemRefs = new int[NoItemRefs + 1]; - TrItemRefs.CopyTo(newTrItemRefs, 0); + TrItemRefs?.CopyTo(newTrItemRefs, 0); newTrItemRefs[NoItemRefs] = newTrItemRef; TrItemRefs = newTrItemRefs; //use the new item lists for the track node NoItemRefs++; } + + /// + /// Add a list of new TrItem references to the TrItemRefs. + /// + /// The reference to the new TrItem + [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", Justification = "Keeping identifier consistent to use in MSTS")] + public void AddTrItemRef(int startIndex, TrItem[] trItems) + { + int numNew = trItems.Length - startIndex; + + // create a new array and copy old refs + var newTrItemRefs = new int[NoItemRefs + numNew]; + TrItemRefs?.CopyTo(newTrItemRefs, 0); + + // set refs for added items + for (int refIdx = NoItemRefs, itemIdx = startIndex; refIdx < newTrItemRefs.Length && itemIdx < trItems.Length; refIdx++, itemIdx++) + newTrItemRefs[refIdx] = (int)trItems[itemIdx].TrItemId; + + // set vector node to use new array + TrItemRefs = newTrItemRefs; + NoItemRefs = TrItemRefs.Length; + } + + /// + /// Add grade info to the track vector node. Traverse the track sections and build a grade profile. + /// Sections with approximately the same grade are combined. + /// + // length and grade calc taken from TrackViewer, PathChartData.cs, AddPointAndTrackItems(), GetCurvature(), SectionLengthAlongTrack() + public void AddGradeInfo(uint vectorNodeIdx, TrackSections trackSections) + { + if (TrVectorSections != null && TrVectorSections.Length > 0) + { + // handle first section; start a new grade segment + TrVectorSection firstVS = TrVectorSections[0]; + TrackSection firstTS = trackSections[firstVS.SectionIndex]; + float firstLength = firstTS.SectionCurve != null ? MathHelper.ToRadians(Math.Abs(firstTS.SectionCurve.Angle)) * firstTS.SectionCurve.Radius : firstTS.SectionSize.Length; + float firstGrade = firstVS.AX * -100f; + GradeData gradeItem = new GradeData(0f, firstLength, firstGrade, firstVS.TileX, firstVS.TileZ, firstVS.X, firstVS.Y, firstVS.Z); + float distanceFromStartM = firstLength; + + for (int vsIdx = 1; vsIdx < TrVectorSections.Length; vsIdx++) + { + TrVectorSection vs = TrVectorSections[vsIdx]; + TrackSection ts = trackSections[vs.SectionIndex]; + float length = ts.SectionCurve != null ? MathHelper.ToRadians(Math.Abs(ts.SectionCurve.Angle)) * ts.SectionCurve.Radius : ts.SectionSize.Length; + float grade = vs.AX * -100f; + + if (length < 0.01f) { continue; } // ignore very short sections (and division by zero) + + // if less than one promille change, combine with previous section + if (Math.Abs(grade - gradeItem.GradePct) < 0.1) + { + gradeItem.GradePct = (gradeItem.LengthM > 0f) ? (gradeItem.GradePct * gradeItem.LengthM + grade * length) / (gradeItem.LengthM + length) : grade; + gradeItem.LengthM += length; + } + else + { + // save the current segment and start a new one with this section + if (gradeItem.LengthM > 0) + { + GradeList.Add(gradeItem); + } + gradeItem = new GradeData(distanceFromStartM, length, grade, vs.TileX, vs.TileZ, vs.X, vs.Y, vs.Z); + } + + distanceFromStartM += length; + this.LengthM += length; // sum the length of the vector section + } + + // save the final segment + if (gradeItem.LengthM > 0) + { + GradeList.Add(gradeItem); + } + } + + if (GradeList.Count > 0) + { + GradePctAtEnd[0] = GradeList[0].GradePct; + GradeLengthMAtEnd[0] = GradeList[0].LengthM; + GradePctAtEnd[1] = GradeList[GradeList.Count - 1].GradePct; + GradeLengthMAtEnd[1] = GradeList[GradeList.Count - 1].LengthM; + } + } + + /// + /// Process the grade info in the vector node, to determine where gradeposts should be placed. + /// Then create the gradeposts and add them to the vector node's existing list of track items. + /// + // Ensure that not too many gradeposts are created, as that would crowd the track monitor. + public void ProcessGradeInfoAndAddGradeposts(uint vectorNodeIdx, TrackDB trackDB) + { + const float minDistanceBetweenMakersM = 200f; + + if (GradeList.Count < 2) { return; /* for now we need at least two grades */ } // TODO: handle first and last grade data + + TrackNode trackNode = trackDB.TrackNodes[vectorNodeIdx]; + +#if false + float currentDistanceFromStartM = 0f; + float precedingGradePct = 0f; + float precedingGradeLengthM = 0f; + float firstGradepostAt = -1f; + float lastGradepostAt = -1f; + + int precedingVectorNodeIdx, precedingVectorNodeEnd; + (precedingVectorNodeIdx, precedingVectorNodeEnd) = trackDB.GetIncomingVectorNodeIndex(trackNode); + if (precedingVectorNodeIdx > 0) + { + TrVectorNode precedingVectorNode = trackDB.TrackNodes[precedingVectorNodeIdx].TrVectorNode; + precedingGradePct = precedingVectorNode.GradePctAtEnd[precedingVectorNodeEnd]; + precedingGradeLengthM = precedingVectorNode.GradeLengthMAtEnd[precedingVectorNodeEnd]; + } +#endif + float precedingGradePct = GradeList[0].GradePct; + float precedingGradeLengthM = GradeList[0].LengthM; + float currentDistanceFromStartM = GradeList[0].LengthM; + List newTrItems = new List(); + + for (int gradeIdx = 1; gradeIdx < GradeList.Count; gradeIdx++) + { + GradeData gradeInfo = GradeList[gradeIdx]; + + float absGradeDiff = Math.Abs(precedingGradePct - gradeInfo.GradePct); + + if (absGradeDiff < 0.1f) + { + // for now just accumulate; later update the length of the preeding grade post + precedingGradeLengthM += gradeInfo.LengthM; + } + + // else if there are long stretches before and after the change, create a marker + else if (precedingGradeLengthM > minDistanceBetweenMakersM && gradeInfo.LengthM > minDistanceBetweenMakersM) + { + var newItem = new GradePostItem(currentDistanceFromStartM, gradeInfo.GradePct, precedingGradePct * -1, gradeInfo.LengthM, precedingGradeLengthM, gradeInfo.TileX, gradeInfo.TileZ, gradeInfo.X, gradeInfo.Y, gradeInfo.Z); + newItem.TrackNodeIndex = vectorNodeIdx; + newItem.ItemName = String.Format("Calculated Grade in TrackNode {0} at distance {1}: grade {2:F2}/{3:F2} for {4:F1}/{5:F1}", + vectorNodeIdx, currentDistanceFromStartM, newItem.GradePct[0], newItem.GradePct[1], newItem.ForDistanceM[0], newItem.ForDistanceM[1]); // for debug + newTrItems.Add(newItem); + + precedingGradePct = gradeInfo.GradePct; + precedingGradeLengthM = gradeInfo.LengthM; + } + + // else undulating + else + { + // TODO + + precedingGradePct = gradeInfo.GradePct; + precedingGradeLengthM = gradeInfo.LengthM; + } + + currentDistanceFromStartM += gradeInfo.LengthM; + } + + // append new items to the Track DB's TrItemTable, and update references + if (newTrItems.Count > 0) + { + int firstInsertIdx = trackDB.AddTrItems(newTrItems.ToArray()); + AddTrItemRef(firstInsertIdx, trackDB.TrItemTable); + } + } } /// @@ -890,7 +1158,9 @@ public enum trItemType /// A pickup of fuel, water, ... trPICKUP, /// The place where cars are appear of disappear - trCARSPAWNER + trCARSPAWNER, + /// A post indicating the grade of the track ahead // TODO: Should mileposts be here instead of speed posts? + trGRADEPOST } /// Type of track item @@ -1518,6 +1788,50 @@ public PickupItem(STFReader stf, int idx) } } + /// + /// Represents a grade post, indicating the grade of the section ahead. + /// + // Initially these are calculated from the track profile. In the future, placing + // grade markers (or plates) with the track may be supported. + public class GradePostItem : TrItem + { + /// Grade in percent. Index 0 is in track direction, index 1 is reverse. + public float[] GradePct = new float[2]; + /// Distance (in meters) for which the grade applies. Index 0 is in track direction, index 1 is reverse. + public float[] ForDistanceM = new float[2]; + /// Distance of the grade post from the start of the track node. + public float DistanceFromStartM; + + // fields not read from file, set in post-processing + /// Set post construction. Reference (index) to the Track Node the grade post belongs to. + public uint TrackNodeIndex; + /// Set post construction. Reference to TrackCircuitGradepost (in signals). + public int SigObj; // TODO: rename, as it is not really a signalling object referenc; was copied from speed post + + /// + /// Default constructor used during file parsing. + /// + /// The STFreader containing the file stream + /// The index of this TrItem in the list of TrItems + public GradePostItem(STFReader stf, int idx) + { + Trace.TraceWarning("GradePostItem(STFReader stf, int idx) is not implemented. Placeholder for future."); + } + + /// + /// Create a Grade Marker (Post) based on grade info from the track profile. + /// + public GradePostItem(float distFromStart, float forwardGrade, float reverseGrade, float forwardDist, float reverseDist, int tileX, int tileZ, float x, float y, float z) + { + ItemType = trItemType.trGRADEPOST; + DistanceFromStartM = distFromStart; + GradePct[0] = forwardGrade; GradePct[1] = reverseGrade; + ForDistanceM[0] = forwardDist; ForDistanceM[1] = reverseDist; + TileX = tileX; TileZ = tileZ; + X = x; Y = y; Z = z; + } + } + #region CrossReference to TrackCircuitSection /// /// To make it possible for a MSTS (vector) TrackNode to have information about the TrackCircuitSections that diff --git a/Source/Orts.Simulation/Simulation/Activity.cs b/Source/Orts.Simulation/Simulation/Activity.cs index 4337c8cc64..bd8df6aadb 100644 --- a/Source/Orts.Simulation/Simulation/Activity.cs +++ b/Source/Orts.Simulation/Simulation/Activity.cs @@ -528,9 +528,10 @@ public void AddRestrictZones(Tr_RouteFile routeFile, TrackSectionsFile tsectionD zones.ActivityRestrictedSpeedZoneList[idxZone].EndPosition, false, worldPosition2, false); // Add the speedposts to the track database. This will set the TrItemId's of all speedposts - trackDB.AddTrItems(newSpeedPostItems); + trackDB.AddTrItems(newSpeedPostItems); + // TODO: shoulde the item be added to the TrVectorNode's TrItemRefs? - // And now update the various (vector) tracknodes (this needs the TrItemIds. + // And now update the various (vector) tracknodes (this needs the TrItemIds. var endOffset = AddItemIdToTrackNode(ref zones.ActivityRestrictedSpeedZoneList[idxZone].EndPosition, tsectionDat, trackDB, newSpeedPostItems[1], out traveller); var startOffset = AddItemIdToTrackNode(ref zones.ActivityRestrictedSpeedZoneList[idxZone].StartPosition, diff --git a/Source/Orts.Simulation/Simulation/Physics/Train.cs b/Source/Orts.Simulation/Simulation/Physics/Train.cs index 1ff683c027..ec5d0418cd 100644 --- a/Source/Orts.Simulation/Simulation/Physics/Train.cs +++ b/Source/Orts.Simulation/Simulation/Physics/Train.cs @@ -14678,6 +14678,7 @@ public String[] AddRestartTime(String[] stateString) public List[] PlayerTrainSpeedposts; // 0 forward, 1 backward public List[,] PlayerTrainDivergingSwitches; // 0 forward, 1 backward; second index 0 facing, 1 trailing public List[] PlayerTrainMileposts; // 0 forward, 1 backward + public List[] PlayerTrainGradeposts; // 0 forward, 1 backward public List[] PlayerTrainTunnels; // 0 forward, 1 backward /// @@ -14691,6 +14692,7 @@ public void InitializePlayerTrainData() PlayerTrainSpeedposts = new List[2]; PlayerTrainDivergingSwitches = new List[2, 2]; PlayerTrainMileposts = new List[2]; + PlayerTrainGradeposts = new List[2]; PlayerTrainTunnels = new List[2]; for (int dir = 0; dir < 2; dir++) { @@ -14702,6 +14704,7 @@ public void InitializePlayerTrainData() for (int i = 0; i < 2; i++) PlayerTrainDivergingSwitches[dir, i] = new List(); PlayerTrainMileposts[dir] = new List(); + PlayerTrainGradeposts[dir] = new List(); PlayerTrainTunnels[dir] = new List(); } } @@ -14717,6 +14720,8 @@ public void InitializePlayerTrainData() playerTrainDivergingSwitchList?.Clear(); foreach (var playerTrainMilepostList in PlayerTrainMileposts) playerTrainMilepostList?.Clear(); + foreach (var playerTrainGradepostList in PlayerTrainGradeposts) + playerTrainGradepostList?.Clear(); foreach (var playerTrainTunnelList in PlayerTrainTunnels) playerTrainTunnelList?.Clear(); } @@ -14816,6 +14821,9 @@ public void UpdatePlayerTrainData(float maxDistanceM) var routePath = ValidRoute[dir]; var prevMilepostValue = -1f; var prevMilepostDistance = -1f; + var prevGradepostValue = -1f; + var prevGradepostDistance = -1f; + while (index < routePath.Count && totalLength - lengthOffset < maxDistanceNORMALM) { var sectionDistanceToTrainM = totalLength - lengthOffset; @@ -14983,6 +14991,28 @@ public void UpdatePlayerTrainData(float maxDistanceM) } } + // search for grade posts + if (thisSection.CircuitItems.TrackCircuitGradeposts != null) + { + foreach (TrackCircuitGradepost thisGradepostItem in thisSection.CircuitItems.TrackCircuitGradeposts) + { + var gradepostDirection = sectionDirection == 1 ? 0 : 1; + Gradepost thisGradepost = thisGradepostItem.GradepostRef; + var distanceToTrainM = thisGradepostItem.GradepostLocation[gradepostDirection] + sectionDistanceToTrainM; + if (distanceToTrainM < maxDistanceM) + { + if (!(distanceToTrainM - prevGradepostDistance < 50 && thisGradepost.GradePct[gradepostDirection] == prevGradepostValue) && distanceToTrainM > 0) + { + thisItem = new TrainObjectItem(thisGradepost.GradePct[gradepostDirection], distanceToTrainM); + prevGradepostDistance = distanceToTrainM; + prevGradepostValue = thisGradepost.GradePct[gradepostDirection]; + PlayerTrainGradeposts[dir].Add(thisItem); + } + } + else break; + } + } + // search for tunnels if (thisSection.TunnelInfo != null) { @@ -15136,6 +15166,13 @@ public void GetTrainInfoAuto(ref TrainInfo thisInfo) else break; } + // Add all grade posts within maximum distance + foreach (TrainObjectItem thisTrainItem in PlayerTrainGradeposts[0]) + { + if (thisTrainItem.DistanceToTrainM <= maxDistanceM) thisInfo.ObjectInfoForward.Add(thisTrainItem); + else break; + } + // Add all diverging switches within maximum distance foreach (TrainObjectItem thisTrainItem in PlayerTrainDivergingSwitches[0, 0]) { @@ -15311,6 +15348,13 @@ public void GetTrainInfoManual(ref TrainInfo thisInfo) else break; } + // Add all grade posts within maximum distance + foreach (TrainObjectItem thisTrainItem in PlayerTrainGradeposts[0]) + { + if (thisTrainItem.DistanceToTrainM <= maxDistanceM) thisInfo.ObjectInfoForward.Add(thisTrainItem); + else break; + } + // Add all diverging switches within maximum distance foreach (TrainObjectItem thisTrainItem in PlayerTrainDivergingSwitches[0, 0]) { @@ -15358,6 +15402,13 @@ public void GetTrainInfoManual(ref TrainInfo thisInfo) else break; } + // Add all grade posts within maximum distance + foreach (TrainObjectItem thisTrainItem in PlayerTrainGradeposts[1]) + { + if (thisTrainItem.DistanceToTrainM <= maxDistanceM) thisInfo.ObjectInfoBackward.Add(thisTrainItem); + else break; + } + // Add all diverging switches within maximum distance foreach (TrainObjectItem thisTrainItem in PlayerTrainDivergingSwitches[1, 0]) { @@ -21431,6 +21482,7 @@ public enum TRAINOBJECTTYPE TRAILING_SWITCH, GENERIC_SIGNAL, TUNNEL, + GRADEPOST, } public enum SpeedItemType @@ -21452,6 +21504,7 @@ public enum SpeedItemType public SpeedItemType SpeedObjectType; public bool Valid; public string ThisMile; + public float GradePct; public bool IsRightSwitch; public SignalObject SignalObject; @@ -21595,6 +21648,17 @@ public TrainObjectItem(string thisMile, float thisDistanceM) ThisMile = thisMile; } + // Constructor for Gradepost + public TrainObjectItem(float gradePct, float thisDistanceM) + { + ItemType = TRAINOBJECTTYPE.GRADEPOST; + AuthorityType = END_AUTHORITY.NO_PATH_RESERVED; + SignalState = TrackMonitorSignalAspect.Clear_2; + AllowedSpeedMpS = -1; + DistanceToTrainM = thisDistanceM; + GradePct = gradePct; + } + // Constructor for facing or trailing Switch public TrainObjectItem(bool isRightSwitch, float thisDistanceM, TRAINOBJECTTYPE type) { diff --git a/Source/Orts.Simulation/Simulation/Signalling/Gradepost.cs b/Source/Orts.Simulation/Simulation/Signalling/Gradepost.cs new file mode 100644 index 0000000000..c223a65726 --- /dev/null +++ b/Source/Orts.Simulation/Simulation/Signalling/Gradepost.cs @@ -0,0 +1,59 @@ +// COPYRIGHT 2025 by the Open Rails project. +// +// This file is part of Open Rails. +// +// Open Rails is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Open Rails is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Open Rails. If not, see . + +// This is part of the effort to respresent grade information in the track monitor. +// The initial version determinates significant grade changes from the track database. +// +// Milempost are taken as a model for presenting the grade information in the simulation. +// This will allow, in the future, to add grade-posts to the track, as is the case at some +// railways (eg. in Switzerland). + +namespace Orts.Simulation.Signalling +{ + /// + /// Represents either a world-object grade post, or a calculated grade post. + /// Only the latter is currently supported. + /// + public class Gradepost + { + /// Reference to TrItem; index into TrackDB.TrItemTable. + public uint TrItemId; + /// Reference to TrackCircuitSection; index into Signals.TrackCircuitList. + public int TCReference = -1; // undefined + /// Position within TrackCircuit. Distance im meters? + public float TCOffset; + /// Grade in percent. Index 0 is in track circuit direction, index 1 is in reverse direction + public float[] GradePct = new float[2]; + /// Distance in meters for which the grade applies. Index 0 is in track circuit direction, index 1 is in reverse direction. + public float[] ForDistanceM = new float[2]; + /// Reference to TrackNode; index into TrackDB.TrackNodes. + public int TrackNodeIdx; + + /// Constructor with base attributes. + public Gradepost(uint trItemId, float forwardGradePct, float reverseGradePct, float forwardDistanceM, float reverseDistanceM) + { + TrItemId = trItemId; + GradePct[0] = forwardGradePct; GradePct[1] = reverseGradePct; + ForDistanceM[0] = forwardDistanceM; ForDistanceM[1] = reverseDistanceM; + } + + /// Dummy constructor + public Gradepost() + { + } + } +} diff --git a/Source/Orts.Simulation/Simulation/Signalling/Signals.cs b/Source/Orts.Simulation/Simulation/Signalling/Signals.cs index 16f1863b0c..2fd4183525 100644 --- a/Source/Orts.Simulation/Simulation/Signalling/Signals.cs +++ b/Source/Orts.Simulation/Simulation/Signalling/Signals.cs @@ -81,6 +81,9 @@ public class Signals public List MilepostList = new List(); // list of mileposts private int foundMileposts; + public List GradepostList = new List(); // list of gradeposts + private int FoundGradeposts; + public Signals(Simulator simulator, SignalConfigurationFile sigcfg, CancellationToken cancellation) { Simulator = simulator; @@ -264,6 +267,42 @@ public Signals(Simulator simulator, SignalConfigurationFile sigcfg, Cancellation DeadlockInfoList = new Dictionary(); deadlockIndex = 1; DeadlockReference = new Dictionary(); + +#if DEBUG + // dump grade posts + int cnt1 = 0; + if (GradepostList != null) + { + foreach (var gradepost in GradepostList) + { + Debug.WriteLine(String.Format("Signals-Gradepost: TrackNode = {0}, idx {1}, value {2:F2}/{3:F2}, for {4:F1}/{5:F1}, TrItemId = {6}, TCReference = {7}, tcOffset = {8}", + gradepost.TrackNodeIdx, cnt1, gradepost.GradePct[0], gradepost.GradePct[1], gradepost.ForDistanceM[0], gradepost.ForDistanceM[1], gradepost.TrItemId, gradepost.TCReference, gradepost.TCOffset)); + cnt1++; + } + } + Debug.WriteLine(String.Format("Signals-Gradepost-Count: {0}", cnt1)); + + // dump track circuit items of type grade post + int cnt2 = 0; + if (TrackCircuitList != null) + { + foreach (var tcSection in TrackCircuitList) + { + if (tcSection?.CircuitItems?.TrackCircuitGradeposts != null) + { + foreach (var tcGradeItem in tcSection.CircuitItems.TrackCircuitGradeposts) + { + var gradepost = tcGradeItem.GradepostRef; + Debug.WriteLine(String.Format("Signals-TrackCircuitGradepost: TrackCircuitSection {0}/{1}, TrackNodeIdx {2}, TrItemIdx {3}, location {4:F1}/{5:F1}, grade = {6:F2}/{7:F2}, for {8:F1}/{9:F1}, gp-TrItemId {10}, gp-Reference {11}, gp-Offset {12}", + tcSection.Index, tcSection.OriginalIndex, tcGradeItem.TrackNodeIdx, tcGradeItem.TrItemIdx, tcGradeItem.GradepostLocation[0], tcGradeItem.GradepostLocation[1], gradepost.GradePct[0], gradepost.GradePct[1], gradepost.ForDistanceM[0], gradepost.ForDistanceM[1], gradepost.TrItemId, gradepost.TCReference, gradepost.TCOffset)); + cnt2++; + } + } + else { Debug.WriteLine(String.Format("Signals-TrackCircuitGradepost: TCIdx {0}/{1}, none", tcSection.Index, tcSection.OriginalIndex)); } + } + } + Debug.WriteLine(String.Format("Signals-TrackCircuitGradepost-Count: {0}", cnt2)); +#endif } /// @@ -899,7 +938,8 @@ private void ScanSection(TrItem[] TrItems, TrackNode[] trackNodes, int index, // Is it a vector node then it may contain objects. if (trackNodes[index].TrVectorNode != null && trackNodes[index].TrVectorNode.NoItemRefs > 0) { - // Any objects ? + // for each TrItem belonging to the Track Vector Node + // TODO: rename index to trackNodeIdx, rename i to trItemRefIdx, rename TDBRef trItemIdx for (int i = 0; i < trackNodes[index].TrVectorNode.NoItemRefs; i++) { if (TrItems[trackNodes[index].TrVectorNode.TrItemRefs[i]] != null) @@ -964,10 +1004,16 @@ private void ScanSection(TrItem[] TrItems, TrackNode[] trackNodes, int index, platformList.Add(TDBRef, index); } } + else if (TrItems[TDBRef].ItemType == TrItem.trItemType.trGRADEPOST) + { + GradePostItem gradepostItem = (GradePostItem)TrItems[TDBRef]; + int gradePostIdx = AddGradepost(index, gradepostItem.GradePct, gradepostItem.ForDistanceM, TDBRef); + gradepostItem.SigObj = gradePostIdx; + } } } } - } + } /// /// Merge Heads @@ -1127,6 +1173,18 @@ private int AddMilepost(int trackNode, int nodeIndx, SpeedPostItem speedItem, in return foundMileposts - 1; } + /// This method adds a new Gradepost to the GradepostList in Signals. + /// The index of the gradepost added. + private int AddGradepost(int trackNodeIdx, float[] gradePct, float[] distance, int TDBRef) + { + Gradepost gradepost = new Gradepost((uint)TDBRef, gradePct[0], gradePct[1], distance[0], distance[1]); + gradepost.TrackNodeIdx = trackNodeIdx; + GradepostList.Add(gradepost); + + FoundGradeposts = GradepostList.Count; + return FoundGradeposts - 1; + } + /// /// Add the sigcfg reference to each signal object. /// @@ -2130,7 +2188,7 @@ public float[] InsertNode(TrackCircuitSection thisCircuit, TrItem thisItem, if (speedItem.SigObj >= 0) { if (!speedItem.IsMilePost) - { + { SignalObject thisSpeedpost = SignalObjects[speedItem.SigObj]; float speedpostDistance = thisSpeedpost.DistanceTo(TDBTrav); if (thisSpeedpost.direction == 1) @@ -2178,6 +2236,21 @@ public float[] InsertNode(TrackCircuitSection thisCircuit, TrItem thisItem, } } } + // Insert gradepost + else if (thisItem.ItemType == TrItem.trItemType.trGRADEPOST) + { + GradePostItem gradePostItem = (GradePostItem)thisItem; + Gradepost gradepost = GradepostList[gradePostItem.SigObj]; + + float gradepostDistance = TDBTrav.DistanceTo(thisItem.TileX, thisItem.TileZ, thisItem.X, thisItem.Y, thisItem.Z); + TrackCircuitGradepost newTCGradepost = new TrackCircuitGradepost(gradepost, gradepostDistance, thisCircuit.Length - gradepostDistance); + newTCGradepost.TrackNodeIdx = thisCircuit.OriginalIndex; + newTCGradepost.TrItemIdx = thisItem.TrItemId; + thisCircuit.CircuitItems.TrackCircuitGradeposts.Add(newTCGradepost); + + Debug.WriteLine(String.Format("Adding TrackCircuitGradepost {0} to TrackCircuitSection {1}, grade {2:F2}/{3:F2}, for {4:F1}/{5:F1}, from TrackNode {6}, TrItem {7}, named {8}", + thisCircuit.CircuitItems.TrackCircuitGradeposts.Count - 1, thisCircuit.Index, gradePostItem.GradePct[0], gradePostItem.GradePct[1], gradePostItem.ForDistanceM[0], gradePostItem.ForDistanceM[1], thisCircuit.OriginalIndex, thisItem.TrItemId, thisItem.ItemName)); + } // Insert crossover in special crossover list else if (thisItem.ItemType == TrItem.trItemType.trCROSSOVER) { @@ -2607,7 +2680,6 @@ private void splitSection(int orgSectionIndex, int newSectionIndex, float positi } // copy milepost information - foreach (TrackCircuitMilepost thisMilepost in orgSection.CircuitItems.TrackCircuitMileposts) { if (thisMilepost.MilepostLocation[0] > replSection.Length) @@ -2622,6 +2694,21 @@ private void splitSection(int orgSectionIndex, int newSectionIndex, float positi } } + // copy gradepost information + foreach (TrackCircuitGradepost thisGradepost in orgSection.CircuitItems.TrackCircuitGradeposts) + { + if (thisGradepost.GradepostLocation[0] > replSection.Length) + { + thisGradepost.GradepostLocation[0] -= replSection.Length; + newSection.CircuitItems.TrackCircuitGradeposts.Add(thisGradepost); + } + else + { + thisGradepost.GradepostLocation[1] -= newSection.Length; + replSection.CircuitItems.TrackCircuitGradeposts.Add(thisGradepost); + } + } + #if ACTIVITY_EDITOR // copy TrackCircuitElements @@ -3016,6 +3103,18 @@ private void setSignalCrossReference(int thisNode) thisMilepost.TCOffset = thisItem.MilepostLocation[0]; } } + + // process gradeposts + foreach (TrackCircuitGradepost thisItem in thisSection.CircuitItems.TrackCircuitGradeposts) + { + Gradepost thisGradepost = thisItem.GradepostRef; + + if (thisGradepost.TCReference <= 0) + { + thisGradepost.TCReference = thisNode; + thisGradepost.TCOffset = thisItem.GradepostLocation[0]; + } + } } /// diff --git a/Source/Orts.Simulation/Simulation/Signalling/TrackCircuitGradepost.cs b/Source/Orts.Simulation/Simulation/Signalling/TrackCircuitGradepost.cs new file mode 100644 index 0000000000..eeb1c39a96 --- /dev/null +++ b/Source/Orts.Simulation/Simulation/Signalling/TrackCircuitGradepost.cs @@ -0,0 +1,41 @@ +// COPYRIGHT 2025 by the Open Rails project. +// +// This file is part of Open Rails. +// +// Open Rails is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Open Rails is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Open Rails. If not, see . + +namespace Orts.Simulation.Signalling +{ + /// + /// Represents the track circuit (signalling) view of a grade marker. + /// + public class TrackCircuitGradepost + { + /// Gradepost; is reference to objecty in Signals.GradepostList. + public Gradepost GradepostRef; + /// Gradepost location (distance) from each end of the section. Index 0 is from start, index 1 is from end. + public float[] GradepostLocation = new float[2]; + /// Reference to grade post in TrItemTable; inxed into TrackDB.TrItemTable. + public uint TrItemIdx; + /// Reference to Track Node this gradepost is in; index into TrackDB.TrackNodes. + public int TrackNodeIdx; + + public TrackCircuitGradepost(Gradepost thisRef, float distanceFromStart, float distanceFromEnd) + { + GradepostRef = thisRef; + GradepostLocation[0] = distanceFromStart; + GradepostLocation[1] = distanceFromEnd; + } + } +} diff --git a/Source/Orts.Simulation/Simulation/Signalling/TrackCircuitItems.cs b/Source/Orts.Simulation/Simulation/Signalling/TrackCircuitItems.cs index a0b2790ef8..4a0981d6ab 100644 --- a/Source/Orts.Simulation/Simulation/Signalling/TrackCircuitItems.cs +++ b/Source/Orts.Simulation/Simulation/Signalling/TrackCircuitItems.cs @@ -26,6 +26,7 @@ public class TrackCircuitItems public Dictionary[] TrackCircuitSignals = new Dictionary[2]; // List of signals (per direction and per type) // public TrackCircuitSignalList[] TrackCircuitSpeedPosts = new TrackCircuitSignalList[2]; // List of speedposts (per direction) // public List TrackCircuitMileposts = new List(); // List of mileposts // + public List TrackCircuitGradeposts = new List(); // List of gradeposts; a gradpost has values for forward and reverse direction #if ACTIVITY_EDITOR // List of all Element coming from OR configuration in a generic form. diff --git a/Source/Orts.Simulation/Simulation/Simulator.cs b/Source/Orts.Simulation/Simulation/Simulator.cs index 1ab4a2e6d8..6697ab3599 100644 --- a/Source/Orts.Simulation/Simulation/Simulator.cs +++ b/Source/Orts.Simulation/Simulation/Simulator.cs @@ -326,9 +326,42 @@ public Simulator(UserSettings settings, string activityPath, bool useOpenRailsDi if (File.Exists(RoutePath + @"\TSECTION.DAT")) TSectionDat.AddRouteTSectionDatFile(RoutePath + @"\TSECTION.DAT"); + // add grade info to the vector nodes + foreach (var trackNode in TDB.TrackDB.TrackNodes) + { + if (trackNode?.TrVectorNode != null) { trackNode.TrVectorNode.AddGradeInfo(trackNode.Index, TSectionDat.TrackSections); } + } + + // create grade markers from the grade info in the vector nodes + foreach (var trackNode in TDB.TrackDB.TrackNodes) + { + if (trackNode?.TrVectorNode != null) { trackNode.TrVectorNode.ProcessGradeInfoAndAddGradeposts(trackNode.Index, TDB.TrackDB); } + } + +#if DEBUG + // dump grade data of each track nodes + int cnt = 0; + foreach (var trackNode in TDB.TrackDB.TrackNodes) + { + if (trackNode is null) continue; // first track node in list is empty + if (trackNode.TrVectorNode == null) continue; // only vector nodes have grades + if (trackNode.TrVectorNode.GradeList is null) { Debug.WriteLine(String.Format("Track-GradeData: TrackNode = {0}, none", trackNode.Index)); } + else { + + foreach (var gradeSegment in trackNode.TrVectorNode?.GradeList) + { + Debug.WriteLine(String.Format("Track-GradeData: TrackNode = {0}, idx = {1}, grade = {2:F2}, length = {3:F1}, distance = {4:F1}, TX = {5}, TZ = {6}", + trackNode.Index, cnt, gradeSegment.GradePct, gradeSegment.LengthM, gradeSegment.DistanceFromStartM, gradeSegment.TileX, gradeSegment.TileZ)); + cnt++; + } + } + } + Debug.WriteLine(String.Format("Track-GradeData-Count: {0}", cnt)); +#endif + #if ACTIVITY_EDITOR - // Where we try to load OR's specific data description (Station, connectors, etc...) - orRouteConfig = ORRouteConfig.LoadConfig(TRK.Tr_RouteFile.FileName, RoutePath, TypeEditor.NONE); + // Where we try to load OR's specific data description (Station, connectors, etc...) + orRouteConfig = ORRouteConfig.LoadConfig(TRK.Tr_RouteFile.FileName, RoutePath, TypeEditor.NONE); orRouteConfig.SetTraveller(TSectionDat, TDB); #endif @@ -372,6 +405,23 @@ public Simulator(UserSettings settings, string activityPath, bool useOpenRailsDi ContainerManager = new ContainerManager(this); ScriptManager = new ScriptManager(); Log = new CommandLog(this); + +#if DEBUG + // dump track items of type grade post + if (TDB.TrackDB.TrItemTable != null) + { + int cnt2 = 0; + foreach (var trItem in TDB.TrackDB.TrItemTable) + { + if (!(trItem is GradePostItem)) continue; + GradePostItem gradePost = (GradePostItem)trItem; + Debug.WriteLine(String.Format("Track-GradePostItem: TrackNode = {0}, TrItemId = {1}, grade = {2:F2}/{3:F2}, for = {4:F1}/{5:F1}, distance = {6:F1}, TX = {7}, TZ = {8}, name = {9}", + gradePost.TrackNodeIndex, gradePost.TrItemId, gradePost.GradePct[0], gradePost.GradePct[1], gradePost.ForDistanceM[0], gradePost.ForDistanceM[1], gradePost.DistanceFromStartM, gradePost.TileX, gradePost.TileZ, gradePost.ItemName)); + cnt2++; + } + Debug.WriteLine(String.Format("Track-GradePostItem-Count: {0}", cnt2)); + } +#endif } public void SetActivity(string activityPath) diff --git a/Source/RunActivity/Viewer3D/Popups/TrackMonitorWindow.cs b/Source/RunActivity/Viewer3D/Popups/TrackMonitorWindow.cs index 816b5b5316..718a4a8c47 100644 --- a/Source/RunActivity/Viewer3D/Popups/TrackMonitorWindow.cs +++ b/Source/RunActivity/Viewer3D/Popups/TrackMonitorWindow.cs @@ -71,7 +71,7 @@ public class TrackMonitorWindow : Window }; public TrackMonitorWindow(WindowManager owner) - : base(owner, Window.DecorationSize.X + owner.TextFontDefault.Height * 10, Window.DecorationSize.Y + owner.TextFontDefault.Height * (5 + TrackMonitorHeightInLinesOfText) + ControlLayout.SeparatorSize * 3, Viewer.Catalog.GetString("Track Monitor")) + : base(owner, Window.DecorationSize.X + owner.TextFontDefault.Height * 12, Window.DecorationSize.Y + owner.TextFontDefault.Height * (5 + TrackMonitorHeightInLinesOfText) + ControlLayout.SeparatorSize * 3, Viewer.Catalog.GetString("Track Monitor")) { ControlModeLabels = new Dictionary { @@ -116,7 +116,7 @@ protected override ControlLayout Layout(ControlLayout layout) vbox.AddHorizontalSeparator(); { var hbox = vbox.AddLayoutHorizontalLineOfText(); - hbox.Add(new Label(hbox.RemainingWidth, hbox.RemainingHeight, Viewer.Catalog.GetString(" Milepost Limit Dist"))); + hbox.Add(new Label(hbox.RemainingWidth, hbox.RemainingHeight, Viewer.Catalog.GetString(" Milepost Grade Limit Dist"))); } vbox.AddHorizontalSeparator(); vbox.Add(Monitor = new TrackMonitor(vbox.RemainingWidth, vbox.RemainingHeight, Owner)); @@ -222,7 +222,15 @@ public enum DisplayMode Train.TrainInfo validInfo; - const int DesignWidth = 150; // All Width/X values are relative to this width. + // default dimensions and positions + const int DesignWidth = 192; // All Width/X values are relative to this width. + const int DfltMilepostTextOffset = 0; + const int DfltGradPostTextOffset = 42; + const int DfltArrowOffset = 64; + const int DfltTrackOffset = 84; + const int DfltSpeedTextOffset = 112; + const int DfltSignalOffset = 137; + const int DfltDistanceTextOffset = 159; // position constants readonly int additionalInfoHeight = 16; // vertical offset on window for additional out-of-range info at top and bottom @@ -237,11 +245,12 @@ public enum DisplayMode // Vertical offset for text for forwards ([0]) and backwards ([1]). readonly int[] textOffset = new int[2] { -11, -3 }; - // Horizontal offsets for various elements. - readonly int distanceTextOffset = 117; - readonly int trackOffset = 42; - readonly int speedTextOffset = 70; - readonly int milepostTextOffset = 0; + // Horizontal offsets for various elements. Will be scaled. + readonly int distanceTextOffset = DfltDistanceTextOffset; + readonly int trackOffset = DfltTrackOffset; + readonly int speedTextOffset = DfltSpeedTextOffset; + readonly int milepostTextOffset = DfltMilepostTextOffset; + readonly int gradepostTextOffset = DfltGradPostTextOffset; // position definition arrays // contents : @@ -251,18 +260,18 @@ public enum DisplayMode // cell 3 : X size // cell 4 : Y size - int[] eyePosition = new int[5] { 42, -4, -20, 24, 24 }; - int[] trainPosition = new int[5] { 42, -12, -12, 24, 24 }; // Relative positioning - int[] otherTrainPosition = new int[5] { 42, -24, 0, 24, 24 }; // Relative positioning - int[] stationPosition = new int[5] { 42, 0, -24, 24, 12 }; // Relative positioning - int[] reversalPosition = new int[5] { 42, -21, -3, 24, 24 }; // Relative positioning - int[] waitingPointPosition = new int[5] { 42, -21, -3, 24, 24 }; // Relative positioning - int[] endAuthorityPosition = new int[5] { 42, -14, -10, 24, 24 }; // Relative positioning - int[] signalPosition = new int[5] { 95, -16, 0, 16, 16 }; // Relative positioning - int[] arrowPosition = new int[5] { 22, -12, -12, 24, 24 }; - int[] invalidReversalPosition = new int[5] { 42, -14, -10, 24, 24 }; // Relative positioning - int[] leftSwitchPosition = new int[5] { 37, -14, -10, 24, 24 }; // Relative positioning - int[] rightSwitchPosition = new int[5] { 47, -14, -10, 24, 24 }; // Relative positioning + int[] eyePosition = new int[5] { DfltTrackOffset, -4, -20, 24, 24 }; + int[] trainPosition = new int[5] { DfltTrackOffset, -12, -12, 24, 24 }; // Relative positioning + int[] otherTrainPosition = new int[5] { DfltTrackOffset, -24, 0, 24, 24 }; // Relative positioning + int[] stationPosition = new int[5] { DfltTrackOffset, 0, -24, 24, 12 }; // Relative positioning + int[] reversalPosition = new int[5] { DfltTrackOffset, -21, -3, 24, 24 }; // Relative positioning + int[] waitingPointPosition = new int[5] { DfltTrackOffset, -21, -3, 24, 24 }; // Relative positioning + int[] endAuthorityPosition = new int[5] { DfltTrackOffset, -14, -10, 24, 24 }; // Relative positioning + int[] signalPosition = new int[5] { DfltSignalOffset, -16, 0, 16, 16 }; // Relative positioning + int[] arrowPosition = new int[5] { DfltArrowOffset, -12, -12, 24, 24 }; + int[] invalidReversalPosition = new int[5] { DfltTrackOffset, -14, -10, 24, 24 }; // Relative positioning + int[] leftSwitchPosition = new int[5] { DfltTrackOffset - 5, -14, -10, 24, 24 }; // Relative positioning + int[] rightSwitchPosition = new int[5] { DfltTrackOffset + 5, -14, -10, 24, 24 }; // Relative positioning // texture rectangles : X-offset, Y-offset, width, height Rectangle eyeSprite = new Rectangle(0, 144, 24, 24); @@ -331,6 +340,8 @@ public TrackMonitor(int width, int height, WindowManager owner) ScaleDesign(ref distanceTextOffset); ScaleDesign(ref trackOffset); ScaleDesign(ref speedTextOffset); + ScaleDesign(ref milepostTextOffset); + ScaleDesign(ref gradepostTextOffset); ScaleDesign(ref eyePosition); ScaleDesign(ref trainPosition); @@ -672,6 +683,7 @@ void drawItems(SpriteBatch spriteBatch, Point offset, int startObjectArea, int e var signalShown = false; var firstLabelShown = false; var borderSignalShown = false; + float precedingGradePct = forward ? -validInfo.currentElevationPercent : validInfo.currentElevationPercent; foreach (var thisItem in itemList) { @@ -705,6 +717,10 @@ void drawItems(SpriteBatch spriteBatch, Point offset, int startObjectArea, int e lastLabelPosition = drawMilePost(spriteBatch, offset, startObjectArea, endObjectArea, zeroPoint, maxDistance, distanceFactor, firstLabelPosition, forward, lastLabelPosition, thisItem, ref firstLabelShown); break; + case Train.TrainObjectItem.TRAINOBJECTTYPE.GRADEPOST: + drawGradePost(spriteBatch, offset, startObjectArea, endObjectArea, zeroPoint, maxDistance, distanceFactor, firstLabelPosition, forward, lastLabelPosition, thisItem, ref firstLabelShown, ref precedingGradePct); + break; + case Train.TrainObjectItem.TRAINOBJECTTYPE.FACING_SWITCH: drawSwitch(spriteBatch, offset, startObjectArea, endObjectArea, zeroPoint, maxDistance, distanceFactor, firstLabelPosition, forward, lastLabelPosition, thisItem, ref firstLabelShown); break; @@ -1031,6 +1047,42 @@ int drawMilePost(SpriteBatch spriteBatch, Point offset, int startObjectArea, int return newLabelPosition; } + /// + /// Draw Grade. + /// + int drawGradePost(SpriteBatch spriteBatch, Point offset, int startObjectArea, int endObjectArea, int zeroPoint, float maxDistance, float distanceFactor, int firstLabelPosition, bool forward, int lastLabelPosition, Train.TrainObjectItem thisItem, ref bool firstLabelShown, ref float precedingGradePct) + { + var newLabelPosition = lastLabelPosition; + + if (thisItem.DistanceToTrainM < (maxDistance - textSpacing / distanceFactor)) + { + var itemOffset = Convert.ToInt32(thisItem.DistanceToTrainM * distanceFactor); + var itemLocation = forward ? zeroPoint - itemOffset : zeroPoint + itemOffset; + newLabelPosition = forward ? Math.Min(itemLocation, lastLabelPosition - textSpacing) : Math.Max(itemLocation, lastLabelPosition + textSpacing); + var labelPoint = new Point(offset.X + gradepostTextOffset, offset.Y + newLabelPosition + textOffset[forward ? 0 : 1]); + // TODO: support promille or 1-in-x + String gradeStr; var gradeColor = Color.White; char trendChar; + if (Math.Abs(thisItem.GradePct - precedingGradePct) < 0.1f) { trendChar = ' '; } + else if (precedingGradePct < thisItem.GradePct) { trendChar = '\u2197'; } + else { trendChar = '\u2198'; } + if (thisItem.GradePct < -0.00015) + { + gradeStr = String.Format("{0:F1}% {1} ", thisItem.GradePct, trendChar); + gradeColor = Color.LightSkyBlue; + } + else if (thisItem.GradePct > 0.00015) + { + gradeStr = String.Format("+{0:F1}% {1} ", thisItem.GradePct, trendChar); + gradeColor = Color.Yellow; + } + else { gradeStr = String.Format(" 0% {1} ", thisItem.GradePct, trendChar); } + Font.Draw(spriteBatch, labelPoint, gradeStr, gradeColor); + precedingGradePct = thisItem.GradePct; + } + + return newLabelPosition; + } + // draw switch information int drawSwitch(SpriteBatch spriteBatch, Point offset, int startObjectArea, int endObjectArea, int zeroPoint, float maxDistance, float distanceFactor, float firstLabelDistance, bool forward, int lastLabelPosition, Train.TrainObjectItem thisItem, ref bool firstLabelShown) { From 3e75bef4865489b8148041578fb182c1b55260d3 Mon Sep 17 00:00:00 2001 From: rwf-rr Date: Sat, 6 Sep 2025 12:16:24 -0700 Subject: [PATCH 2/3] Interim: combining grades (grade info objects), which I rejected. --- Source/Orts.Formats.Msts/TrackDatabaseFile.cs | 292 ++++++++++++++++-- .../Orts.Simulation/Simulation/AIs/AIPath.cs | 6 + .../Orts.Simulation/Simulation/Simulator.cs | 100 +++++- 3 files changed, 357 insertions(+), 41 deletions(-) diff --git a/Source/Orts.Formats.Msts/TrackDatabaseFile.cs b/Source/Orts.Formats.Msts/TrackDatabaseFile.cs index 9545836d91..e83a052fbe 100644 --- a/Source/Orts.Formats.Msts/TrackDatabaseFile.cs +++ b/Source/Orts.Formats.Msts/TrackDatabaseFile.cs @@ -753,6 +753,7 @@ public class TrVectorNode public float[] GradePctAtEnd = new float[] { 0f, 0f }; // grade at each end [start, end] public float[] GradeLengthMAtEnd = new float[] { 0f, 0f }; // length of steady grade at each end [start, end] public float[] GradepostDistanceMAtEnd = new float[] { -1f, -1f }; // distance of the first grade marker at each end; -1 if none + public GradePostItem[] GradepostNearestEnd = new GradePostItem[2]; // gradepost nearest to the start and end of the vector node public struct GradeData // represents a segment of track with approx. the same grade, may span multiple vector sections { public float DistanceFromStartM; // for debug @@ -869,15 +870,40 @@ public void AddTrItemRef(int startIndex, TrItem[] trItems) NoItemRefs = TrItemRefs.Length; } + /// + /// Check if there is a long segment of the same grade ahead. + /// + private bool SameGradeAhead( float maxDistanceM, float currentGrade, float currentLength, int currentIndex, TrackSections trackSections) + { + float inBetweenLength = currentLength; + bool foundSameGrade = false; + for (int idx = currentIndex + 1; idx < TrVectorSections.Length && inBetweenLength < maxDistanceM && !foundSameGrade; idx++) + { + TrVectorSection vs = TrVectorSections[idx]; + TrackSection ts = trackSections[vs.SectionIndex]; + float length = ts.SectionCurve != null ? MathHelper.ToRadians(Math.Abs(ts.SectionCurve.Angle)) * ts.SectionCurve.Radius : ts.SectionSize.Length; + float grade = vs.AX * -100f; + + if (Math.Abs(grade - currentGrade) < 0.1 && length > inBetweenLength) + { + foundSameGrade = true; + } + } + return foundSameGrade; + } + /// /// Add grade info to the track vector node. Traverse the track sections and build a grade profile. /// Sections with approximately the same grade are combined. /// // length and grade calc taken from TrackViewer, PathChartData.cs, AddPointAndTrackItems(), GetCurvature(), SectionLengthAlongTrack() - public void AddGradeInfo(uint vectorNodeIdx, TrackSections trackSections) + public void AddGradeInfo(uint vectorNodeIdx, TrackSections trackSections, /* for debug*/ int tvsZeroLenCnt) { if (TrVectorSections != null && TrVectorSections.Length > 0) { + const float minGradeItemLengthM = 30f; + const float medGradeItemLengthM = 70f; + // handle first section; start a new grade segment TrVectorSection firstVS = TrVectorSections[0]; TrackSection firstTS = trackSections[firstVS.SectionIndex]; @@ -886,33 +912,101 @@ public void AddGradeInfo(uint vectorNodeIdx, TrackSections trackSections) GradeData gradeItem = new GradeData(0f, firstLength, firstGrade, firstVS.TileX, firstVS.TileZ, firstVS.X, firstVS.Y, firstVS.Z); float distanceFromStartM = firstLength; - for (int vsIdx = 1; vsIdx < TrVectorSections.Length; vsIdx++) + int startIdx = 1; int endIndex = TrVectorSections.Length - 1; + +#if tru + // if the first section is short and the next section is long, start with the long section + if (gradeItem.LengthM < minGradeItemLengthM && endIndex >= 1) + { + TrVectorSection nextVS = TrVectorSections[1]; + TrackSection nextTS = trackSections[nextVS.SectionIndex]; + float nextLength = nextTS.SectionCurve != null ? MathHelper.ToRadians(Math.Abs(nextTS.SectionCurve.Angle)) * nextTS.SectionCurve.Radius : nextTS.SectionSize.Length; + float nextGrade = nextVS.AX * -100f; + if (nextGrade >= medGradeItemLengthM) + { + // add the first section to the second section + gradeItem.LengthM += nextLength; + gradeItem.GradePct = nextGrade; + startIdx = 2; + } + } +#endif + + for (int vsIdx = startIdx; vsIdx <= endIndex; vsIdx++) { TrVectorSection vs = TrVectorSections[vsIdx]; TrackSection ts = trackSections[vs.SectionIndex]; float length = ts.SectionCurve != null ? MathHelper.ToRadians(Math.Abs(ts.SectionCurve.Angle)) * ts.SectionCurve.Radius : ts.SectionSize.Length; float grade = vs.AX * -100f; - if (length < 0.01f) { continue; } // ignore very short sections (and division by zero) + if (length < 0.01f) { tvsZeroLenCnt++; goto nextSegment; } // ignore very short sections (and division by zero) - // if less than one promille change, combine with previous section + // if less than one promille change if (Math.Abs(grade - gradeItem.GradePct) < 0.1) { + // combine with previous section gradeItem.GradePct = (gradeItem.LengthM > 0f) ? (gradeItem.GradePct * gradeItem.LengthM + grade * length) / (gradeItem.LengthM + length) : grade; gradeItem.LengthM += length; + goto nextSegment; + } + +#if true + // if last section and it is short + if (vsIdx == endIndex && length < minGradeItemLengthM && gradeItem.LengthM > medGradeItemLengthM) + { + // add the last section to the preceding grade + gradeItem.LengthM += length; + goto nextSegment; + } +#endif + + // peek at next section + float nextLength = 0f; float nextGrade = 0f; + if (TrVectorSections.Length > vsIdx + 1) + { + TrVectorSection nextVS = TrVectorSections[1]; + TrackSection nextTS = trackSections[nextVS.SectionIndex]; + nextLength = nextTS.SectionCurve != null ? MathHelper.ToRadians(Math.Abs(nextTS.SectionCurve.Angle)) * nextTS.SectionCurve.Radius : nextTS.SectionSize.Length; + nextGrade = nextVS.AX * -100f; } + +#if true + // if the current grade is a short change between two equal grades + if (length < minGradeItemLengthM && gradeItem.LengthM > medGradeItemLengthM && nextLength > medGradeItemLengthM && Math.Abs(gradeItem.GradePct - nextGrade) < 0.1) + { + // ignore the grade of the current segment + gradeItem.LengthM += length; + } + + // if the current grade is a short change between two steady grades + else if (length < minGradeItemLengthM && gradeItem.LengthM > medGradeItemLengthM && nextLength > medGradeItemLengthM) + { + // add the current grade to the preceding grade + gradeItem.LengthM += length; + } + + // if short grade changes and next grade is not the same as the current grade + else if (length < minGradeItemLengthM && gradeItem.LengthM < medGradeItemLengthM && (nextLength > 0.01 || Math.Abs(grade - nextGrade) >= 0.1)) + { + // average the grade + gradeItem.GradePct = (gradeItem.LengthM > 0f) ? (gradeItem.GradePct * gradeItem.LengthM + grade * length) / (gradeItem.LengthM + length) : grade; + gradeItem.LengthM += length; + } +#endif + +// else grade change +#if true else +#endif { - // save the current segment and start a new one with this section - if (gradeItem.LengthM > 0) - { - GradeList.Add(gradeItem); - } + // add the grade item and start a new one + if (gradeItem.LengthM > 0) { GradeList.Add(gradeItem); } gradeItem = new GradeData(distanceFromStartM, length, grade, vs.TileX, vs.TileZ, vs.X, vs.Y, vs.Z); } + nextSegment: distanceFromStartM += length; - this.LengthM += length; // sum the length of the vector section + this.LengthM += length; // sum up the length of the vector section } // save the final segment @@ -932,73 +1026,202 @@ public void AddGradeInfo(uint vectorNodeIdx, TrackSections trackSections) } /// - /// Process the grade info in the vector node, to determine where gradeposts should be placed. + /// Process the forward grade info in the vector node, to determine where gradeposts should be placed. /// Then create the gradeposts and add them to the vector node's existing list of track items. /// // Ensure that not too many gradeposts are created, as that would crowd the track monitor. - public void ProcessGradeInfoAndAddGradeposts(uint vectorNodeIdx, TrackDB trackDB) + public void ProcessForwardGradeInfoAndAddGradeposts(uint vectorNodeIdx, TrackDB trackDB) { - const float minDistanceBetweenMakersM = 200f; - if (GradeList.Count < 2) { return; /* for now we need at least two grades */ } // TODO: handle first and last grade data + ////// RWF TEMP + return; + ////// RWF TEMP + + const float minDistanceBetweenGradepostM = 200f; TrackNode trackNode = trackDB.TrackNodes[vectorNodeIdx]; + TrVectorNode vectorNode = trackNode.TrVectorNode; + + (int precedingVectorNodeIdx, int precedingVectorNodeEnd) = trackDB.GetIncomingVectorNodeIndex(trackNode); -#if false - float currentDistanceFromStartM = 0f; float precedingGradePct = 0f; float precedingGradeLengthM = 0f; - float firstGradepostAt = -1f; - float lastGradepostAt = -1f; + GradePostItem precedingGradepost = null; + int precedingGradepostEnd = precedingVectorNodeEnd; + float precedingGradepostDistanceM = 9e9f; - int precedingVectorNodeIdx, precedingVectorNodeEnd; - (precedingVectorNodeIdx, precedingVectorNodeEnd) = trackDB.GetIncomingVectorNodeIndex(trackNode); if (precedingVectorNodeIdx > 0) { TrVectorNode precedingVectorNode = trackDB.TrackNodes[precedingVectorNodeIdx].TrVectorNode; precedingGradePct = precedingVectorNode.GradePctAtEnd[precedingVectorNodeEnd]; precedingGradeLengthM = precedingVectorNode.GradeLengthMAtEnd[precedingVectorNodeEnd]; + precedingGradepost = precedingVectorNode.GradepostNearestEnd[precedingVectorNodeEnd]; + if (precedingGradepost != null) + { + precedingGradepostEnd = precedingVectorNodeEnd; + precedingGradepostDistanceM = precedingVectorNodeEnd == 1 ? precedingGradepost.DistanceFromStartM : precedingVectorNode.LengthM - precedingGradepost.DistanceFromStartM; + } } -#endif - float precedingGradePct = GradeList[0].GradePct; - float precedingGradeLengthM = GradeList[0].LengthM; - float currentDistanceFromStartM = GradeList[0].LengthM; - List newTrItems = new List(); - for (int gradeIdx = 1; gradeIdx < GradeList.Count; gradeIdx++) + List newTrItems = new List(); + GradePostItem firstGradepost = null; + GradePostItem lastGradepost = null; + float currentDistanceFromStartM = 0f; + for (int gradeIdx = 0; gradeIdx < GradeList.Count; gradeIdx++) { GradeData gradeInfo = GradeList[gradeIdx]; - float absGradeDiff = Math.Abs(precedingGradePct - gradeInfo.GradePct); + // determine preceding gradepost orientation + int precedingForward = precedingGradepostEnd; + int precedingReverse = precedingGradepostEnd == 0 ? 1 : 0; - if (absGradeDiff < 0.1f) + // Debug: save preceding gradepost info for logging + float prevGpLocation = 0f, prevGpReverseGrade = 0f, prevGpReverseLength = 0f, prevGpForwardGrade = 0f, prevGpForwardLength = 0f; + if (precedingGradepost != null) { - // for now just accumulate; later update the length of the preeding grade post - precedingGradeLengthM += gradeInfo.LengthM; + prevGpLocation = precedingGradepost.DistanceFromStartM; + prevGpReverseGrade = precedingGradepost.GradePct[precedingReverse]; prevGpReverseLength = precedingGradepost.ForDistanceM[precedingReverse]; + prevGpForwardGrade = precedingGradepost.GradePct[precedingForward]; prevGpForwardLength = precedingGradepost.ForDistanceM[precedingForward]; } - // else if there are long stretches before and after the change, create a marker - else if (precedingGradeLengthM > minDistanceBetweenMakersM && gradeInfo.LengthM > minDistanceBetweenMakersM) + // if there is a preceding grade, and the difference is minimal + if (precedingGradeLengthM > 0f && Math.Abs(precedingGradePct - gradeInfo.GradePct) < 0.1f) { + // if there is an immediately preceding gradepost + if (precedingGradepost != null && precedingGradepostDistanceM == precedingGradeLengthM) + { + // extend the gradepost + precedingGradeLengthM = precedingGradepost.ForDistanceM[precedingForward] += gradeInfo.LengthM; + + Debug.WriteLine("Gradepost-Extending-short: tnIdx {0}, gpItemIdx {1}, at {2}, from {3:F1}, to {4:F1}, grade {5:F2}", + precedingGradepost.TrackNodeIndex, precedingGradepost.TrItemId, precedingGradepost.DistanceFromStartM, prevGpForwardLength, + precedingGradepost.ForDistanceM[precedingForward], precedingGradepost.GradePct[precedingForward]); + } + else + { + // accumulate the length + precedingGradeLengthM += gradeInfo.LengthM; + } + + // leave the preceding grade the same, to avoid creep + } + + // else if there are long stretches before and after + else if (precedingGradeLengthM > minDistanceBetweenGradepostM && gradeInfo.LengthM > minDistanceBetweenGradepostM) + { + Debug.Assert(precedingGradepostDistanceM >= precedingGradeLengthM, "two long stretches; preceding gradepost must be at least the reverse length away"); + + // create a new bidirectional gradepost var newItem = new GradePostItem(currentDistanceFromStartM, gradeInfo.GradePct, precedingGradePct * -1, gradeInfo.LengthM, precedingGradeLengthM, gradeInfo.TileX, gradeInfo.TileZ, gradeInfo.X, gradeInfo.Y, gradeInfo.Z); newItem.TrackNodeIndex = vectorNodeIdx; newItem.ItemName = String.Format("Calculated Grade in TrackNode {0} at distance {1}: grade {2:F2}/{3:F2} for {4:F1}/{5:F1}", vectorNodeIdx, currentDistanceFromStartM, newItem.GradePct[0], newItem.GradePct[1], newItem.ForDistanceM[0], newItem.ForDistanceM[1]); // for debug newTrItems.Add(newItem); + Debug.WriteLine("Gradepost-Creating-bidirectional: tnIdx {0}, gpItemIdx {1}, at {2}, fwdGrade {3:F2}, fwdDist {4:F1}, revGrade {5:F2}, revDist {6:F1}", + newItem.TrackNodeIndex, newItem.TrItemId, newItem.DistanceFromStartM, newItem.GradePct[0], newItem.ForDistanceM[0], newItem.GradePct[1], newItem.ForDistanceM[1]); + + // track first and last gradepost if vector + if (firstGradepost == null) { firstGradepost = newItem; } + lastGradepost = newItem; + precedingGradePct = gradeInfo.GradePct; precedingGradeLengthM = gradeInfo.LengthM; + precedingGradepostDistanceM = gradeInfo.LengthM; + precedingGradepost = newItem; + precedingGradepostEnd = 0; } + // else if there is a long forward stretch close to a preceding gradepost + else if (gradeInfo.LengthM > minDistanceBetweenGradepostM && precedingGradepost != null && precedingGradepostDistanceM < minDistanceBetweenGradepostM) + { + Debug.Assert(precedingGradepost.ForDistanceM[precedingForward] <= 0, "long forward grade just after a GP; GP should not have a forward grade"); + + float delta = currentDistanceFromStartM - (precedingGradepost.DistanceFromStartM + precedingGradepost.ForDistanceM[precedingForward]); + + // if the forward grade is the same as the preceding gradepost's forward grade + if (Math.Abs(gradeInfo.GradePct - precedingGradepost.GradePct[precedingForward]) < 0.1f) + { + // fully extend the preceding gradepost + precedingGradeLengthM = precedingGradepost.ForDistanceM[precedingForward] += delta + gradeInfo.LengthM; + + Debug.WriteLine("Gradepost-Extending-medium: tnIdx {0}, gpItemIdx {1}, at {2}, from {3:F1}, to {4:F1}, grade {5:F2}", + precedingGradepost.TrackNodeIndex, precedingGradepost.TrItemId, precedingGradepost.DistanceFromStartM, prevGpForwardLength, precedingGradepost.ForDistanceM[precedingForward], precedingGradepost.GradePct[precedingForward]); + } + else + { + // move the preceding gradepost and update the forward grade + precedingGradepost.DistanceFromStartM += delta / 2f; + precedingGradeLengthM = precedingGradepost.ForDistanceM[precedingReverse] += delta / 2f; + precedingGradepostDistanceM -= delta / 2f; + + // and set the forward grade to the new grade and extended length + precedingGradepost.GradePct[precedingForward] = gradeInfo.GradePct; + precedingGradepost.ForDistanceM[precedingForward] = gradeInfo.LengthM + delta / 2f; + + Debug.WriteLine("Gradepost-Moving-short: tnIdx {0}, gpItemIdx {1}, from {2:F1}, to {3:F1}, revDist {4:F1} -> {5:F1}, fwdGrade {6:F2} -> {7:F2}, fwdDist {8:F1} -> {9:F2}", + precedingGradepost.TrackNodeIndex, precedingGradepost.TrItemId, prevGpLocation, precedingGradepost.DistanceFromStartM, prevGpReverseLength, precedingGradepost.ForDistanceM[precedingReverse], + prevGpForwardGrade, precedingGradepost.GradePct[precedingForward], prevGpForwardLength, precedingGradepost.ForDistanceM[precedingForward]); + } + + precedingGradePct = gradeInfo.GradePct; + } + + // else if there is a long forward streach a bit beyond the preceding gradepost + else if (gradeInfo.LengthM > minDistanceBetweenGradepostM && precedingGradepost != null && precedingGradepostDistanceM < 2f * minDistanceBetweenGradepostM) + { + float delta = currentDistanceFromStartM - (precedingGradepost.DistanceFromStartM + precedingGradepost.ForDistanceM[precedingForward]); + + // if the forward grade is the same as the preceding gradepost's forward grade + if (Math.Abs(gradeInfo.GradePct - precedingGradepost.GradePct[precedingForward]) < 0.1f) + { + // fully extend the preceding gradepost + precedingGradeLengthM = precedingGradepost.ForDistanceM[precedingForward] += delta + gradeInfo.LengthM; + + Debug.WriteLine("Gradepost-Extending-long: tnIdx {0}, gpItemIdx {1}, at {2}, from {3:F1}, to {4:F1}, grade {5:F2}", + precedingGradepost.TrackNodeIndex, precedingGradepost.TrItemId, precedingGradepost.DistanceFromStartM, prevGpForwardLength, precedingGradepost.ForDistanceM[precedingForward],precedingGradepost.GradePct[precedingForward]); + } + else + { + // extend the preceding gradepost by half the difference + precedingGradeLengthM = precedingGradepost.ForDistanceM[precedingForward] += delta / 2f; + + Debug.WriteLine("Gradepost-Extending-medium: tnIdx {0}, gpItemIdx {1}, at {2}, from {3:F1}, to {4:F1}, grade {5:F2}", + precedingGradepost.TrackNodeIndex, precedingGradepost.TrItemId, precedingGradepost.DistanceFromStartM, prevGpForwardLength, precedingGradepost.ForDistanceM[precedingForward], precedingGradepost.GradePct[precedingForward]); + + // and create a new bidirectional gradepost + var newItem = new GradePostItem(currentDistanceFromStartM - delta / 2f, gradeInfo.GradePct, precedingGradepost.GradePct[precedingForward], gradeInfo.LengthM + delta / 2f, precedingGradepost.ForDistanceM[precedingForward], gradeInfo.TileX, gradeInfo.TileZ, gradeInfo.X, gradeInfo.Y, gradeInfo.Z); + newItem.TrackNodeIndex = vectorNodeIdx; + newItem.ItemName = String.Format("Calculated Grade in TrackNode {0} at distance {1}: grade {2:F2}/{3:F2} for {4:F1}/{5:F1}", + vectorNodeIdx, currentDistanceFromStartM, newItem.GradePct[0], newItem.GradePct[1], newItem.ForDistanceM[0], newItem.ForDistanceM[1]); // for debug + newTrItems.Add(newItem); + + Debug.WriteLine("Gradepost-Creating-bidirectional: tnIdx {0}, gpItemIdx {1}, at {2}, fwdGrade {3:F2}, fwdDist {4:F1}, revGrade {5:F2}, revDist {6:F1}", + newItem.TrackNodeIndex, newItem.TrItemId, newItem.DistanceFromStartM, newItem.GradePct[0], newItem.ForDistanceM[0], newItem.GradePct[1], newItem.ForDistanceM[1]); + + // track first and last gradepost if vector + if (firstGradepost == null) { firstGradepost = newItem; } + lastGradepost = newItem; + + + + } + + + } + + // else undulating else - { + { // TODO precedingGradePct = gradeInfo.GradePct; precedingGradeLengthM = gradeInfo.LengthM; + precedingGradepostDistanceM += gradeInfo.LengthM; } + precedingGradepostDistanceM += gradeInfo.LengthM; currentDistanceFromStartM += gradeInfo.LengthM; } @@ -1007,6 +1230,9 @@ public void ProcessGradeInfoAndAddGradeposts(uint vectorNodeIdx, TrackDB trackDB { int firstInsertIdx = trackDB.AddTrItems(newTrItems.ToArray()); AddTrItemRef(firstInsertIdx, trackDB.TrItemTable); + + vectorNode.GradepostNearestEnd[0] = firstGradepost; + vectorNode.GradepostNearestEnd[1] = lastGradepost; } } } diff --git a/Source/Orts.Simulation/Simulation/AIs/AIPath.cs b/Source/Orts.Simulation/Simulation/AIs/AIPath.cs index 5d498612b8..09cef369bd 100644 --- a/Source/Orts.Simulation/Simulation/AIs/AIPath.cs +++ b/Source/Orts.Simulation/Simulation/AIs/AIPath.cs @@ -262,6 +262,7 @@ public class AIPathNode public WorldLocation Location; // coordinates for this path node public int JunctionIndex = -1; // index of junction node, -1 if none public bool IsFacingPoint; // true if this node entered from the facing point end + public bool IsIntermediateNode; // true if the path node is an intermediate node within a vector node //public bool IsLastSwitchUse; //true if this node is last to touch a switch public bool IsVisited; // true if the train has visited this node @@ -300,6 +301,7 @@ public AIPathNode(AIPathNode otherNode) Location = otherNode.Location; JunctionIndex = otherNode.JunctionIndex; IsFacingPoint = otherNode.IsFacingPoint; + IsIntermediateNode = otherNode.IsIntermediateNode; IsVisited = otherNode.IsVisited; } @@ -318,6 +320,8 @@ public AIPathNode(AIPathNode otherNode) // in principle it would be more logical to have it in PATfile.cs. But this leads to too much code duplication private void InterpretPathNodeFlags(TrPathNode tpn, TrackPDP pdp, bool isTimetableMode) { + if ((tpn.pathFlags & 04) != 0) IsIntermediateNode = true; + if ((tpn.pathFlags & 03) == 0) return; // Bit 0 and/or bit 1 is set. @@ -386,6 +390,7 @@ public AIPathNode(BinaryReader inf) NextSidingTVNIndex = inf.ReadInt32(); JunctionIndex = inf.ReadInt32(); IsFacingPoint = inf.ReadBoolean(); + IsIntermediateNode = inf.ReadBoolean(); Location = new WorldLocation(); Location.TileX = inf.ReadInt32(); Location.TileZ = inf.ReadInt32(); @@ -407,6 +412,7 @@ public void Save(BinaryWriter outf) outf.Write(NextSidingTVNIndex); outf.Write(JunctionIndex); outf.Write(IsFacingPoint); + outf.Write(IsIntermediateNode); outf.Write(Location.TileX); outf.Write(Location.TileZ); outf.Write(Location.Location.X); diff --git a/Source/Orts.Simulation/Simulation/Simulator.cs b/Source/Orts.Simulation/Simulation/Simulator.cs index 6697ab3599..681935e34a 100644 --- a/Source/Orts.Simulation/Simulation/Simulator.cs +++ b/Source/Orts.Simulation/Simulation/Simulator.cs @@ -37,6 +37,7 @@ using System.Diagnostics; using System.IO; using Event = Orts.Common.Event; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; namespace Orts.Simulation { @@ -177,6 +178,10 @@ public class Simulator public bool OpenDoorsInAITrains; public int ActiveMovingTableIndex = -1; + + // rwf-rr: for debug + int TvsWithZeroLenCnt = 0; + public MovingTable ActiveMovingTable { get @@ -329,34 +334,36 @@ public Simulator(UserSettings settings, string activityPath, bool useOpenRailsDi // add grade info to the vector nodes foreach (var trackNode in TDB.TrackDB.TrackNodes) { - if (trackNode?.TrVectorNode != null) { trackNode.TrVectorNode.AddGradeInfo(trackNode.Index, TSectionDat.TrackSections); } + if (trackNode?.TrVectorNode != null) { trackNode.TrVectorNode.AddGradeInfo(trackNode.Index, TSectionDat.TrackSections, TvsWithZeroLenCnt); } } // create grade markers from the grade info in the vector nodes foreach (var trackNode in TDB.TrackDB.TrackNodes) { - if (trackNode?.TrVectorNode != null) { trackNode.TrVectorNode.ProcessGradeInfoAndAddGradeposts(trackNode.Index, TDB.TrackDB); } + if (trackNode?.TrVectorNode != null) { trackNode.TrVectorNode.ProcessForwardGradeInfoAndAddGradeposts(trackNode.Index, TDB.TrackDB); } } #if DEBUG // dump grade data of each track nodes - int cnt = 0; + int gradeCnt = 0, tnCnt = 0, tvnCnt = 0, tvsCnt = 0; foreach (var trackNode in TDB.TrackDB.TrackNodes) { if (trackNode is null) continue; // first track node in list is empty + tnCnt++; if (trackNode.TrVectorNode == null) continue; // only vector nodes have grades + tvnCnt++; if (trackNode.TrVectorNode.GradeList is null) { Debug.WriteLine(String.Format("Track-GradeData: TrackNode = {0}, none", trackNode.Index)); } else { - - foreach (var gradeSegment in trackNode.TrVectorNode?.GradeList) + tvsCnt += trackNode.TrVectorNode.TrVectorSections.Length; + foreach (var gradeSegment in trackNode.TrVectorNode.GradeList) { Debug.WriteLine(String.Format("Track-GradeData: TrackNode = {0}, idx = {1}, grade = {2:F2}, length = {3:F1}, distance = {4:F1}, TX = {5}, TZ = {6}", - trackNode.Index, cnt, gradeSegment.GradePct, gradeSegment.LengthM, gradeSegment.DistanceFromStartM, gradeSegment.TileX, gradeSegment.TileZ)); - cnt++; + trackNode.Index, gradeCnt, gradeSegment.GradePct, gradeSegment.LengthM, gradeSegment.DistanceFromStartM, gradeSegment.TileX, gradeSegment.TileZ)); + gradeCnt++; } } } - Debug.WriteLine(String.Format("Track-GradeData-Count: {0}", cnt)); + Debug.WriteLine(String.Format("Track-GradeData-Count: grades {0}, vectorSections {1}, vectorNodes {2}, trackNodes {3}; zero-length-sections {4}", gradeCnt, tvsCnt, tvnCnt, tnCnt, TvsWithZeroLenCnt)); #endif #if ACTIVITY_EDITOR @@ -1465,6 +1472,83 @@ private Train InitializePlayerTrain() // if ((PlayerLocomotive as MSTSLocomotive).EOTEnabled != MSTSLocomotive.EOTenabled.no) // train.EOT = new EOT((PlayerLocomotive as MSTSLocomotive).EOTEnabled, false, train); +#if DEBUG + // dump path, for now just grade posts + float distanceFromPathStart = 0; + int maxNodes = aiPath.Nodes.Count; // limit, in case there is a loop + AIPathNode currentPathNode = aiPath.FirstNode; + while (currentPathNode != null && maxNodes >= 0) + { + if (!currentPathNode.IsIntermediateNode) + { + // only follow the main path + int tvnIdx = currentPathNode.NextMainTVNIndex; + if (tvnIdx > 0) + { + TrVectorNode trackVectorNode = TDB.TrackDB.TrackNodes[tvnIdx].TrVectorNode; + + int forward = 0; int backward = 1; + if (currentPathNode.JunctionIndex > 0) + { + if (TDB.TrackDB.TrackNodes[tvnIdx].TrPins[1].Link == currentPathNode.JunctionIndex) + { + forward = 1; backward = 0; + } + } + else if (currentPathNode.NextMainNode.JunctionIndex > 0) + { + if (TDB.TrackDB.TrackNodes[tvnIdx].TrPins[0].Link == currentPathNode.NextMainNode.JunctionIndex) + { + forward = 1; backward = 0; + } + } + + // for now assuming that gradeposts (their refs) are in distance order + bool foundGradepost = false; + if (forward == 0) + { + for (int refIdx = 0; refIdx < trackVectorNode.NoItemRefs; refIdx++) + { + int trItemIdx = trackVectorNode.TrItemRefs[refIdx]; + TrItem item = TDB.TrackDB.TrItemTable[trItemIdx]; + if (item is GradePostItem) + { + GradePostItem gpItem = (GradePostItem)item; + float distanceInNode = gpItem.DistanceFromStartM; + Debug.WriteLine("Gradepost: TrackNodeIdx {0}, RefIdx {1}, ItemIdx {2}, fromPathStart {3:F0}, fromNodeStart {4:F0}, fwdGrade {5:F1}, fwdLength {6:F0}, revGrade {7:F1}, revLength {8:F0}", + gpItem.TrackNodeIndex, refIdx, gpItem.TrItemId, distanceFromPathStart + distanceInNode, distanceInNode, gpItem.GradePct[forward], gpItem.ForDistanceM[forward], gpItem.GradePct[backward], gpItem.ForDistanceM[backward]); + foundGradepost = true; + } + } + } + else + { + for (int refIdx = trackVectorNode.NoItemRefs - 1; refIdx >= 0; refIdx--) + { + int trItemIdx = trackVectorNode.TrItemRefs[refIdx]; + TrItem item = TDB.TrackDB.TrItemTable[trItemIdx]; + if (item is GradePostItem) + { + GradePostItem gpItem = (GradePostItem)item; + float distanceInNode = trackVectorNode.LengthM - gpItem.DistanceFromStartM; + Debug.WriteLine("Gradepost: TrackNodeIdx {0}, RefIdx {1}, ItemIdx {2}, fromPathStart {3:F0}, fromNodeStart {4:F0}, fwdGrade {5:F1}, fwdLength {6:F0}, revGrade {7:F1}, revLength {8:F0}", + gpItem.TrackNodeIndex, refIdx, gpItem.TrItemId, distanceFromPathStart + distanceInNode, distanceInNode, gpItem.GradePct[forward], gpItem.ForDistanceM[forward], gpItem.GradePct[backward], gpItem.ForDistanceM[backward]); + foundGradepost = true; + } + } + } + + if (!foundGradepost) { Debug.WriteLine("Gradepost: TrackNode {0}, no gradeposts in {1} items", tvnIdx, trackVectorNode.NoItemRefs); } + + distanceFromPathStart += trackVectorNode.LengthM; + } + } + + currentPathNode = currentPathNode.NextMainNode; + maxNodes--; + } +#endif + return (train); } From 41d3b4359d921615c6d3bc6b0b4a30ecdf24cbbe Mon Sep 17 00:00:00 2001 From: rwf-rr Date: Tue, 16 Sep 2025 12:57:34 -0700 Subject: [PATCH 3/3] Partial changes to use unidirectional gradeposts. Changes are incomplete, with lots of debug code. Gradeposts are correct now, including reverse location. TRack Circuit gradeposts seem ok, but have not been fully verified. TYrain view is wrong. Working on "search for grade posts" in Train.cs at line 15,000+. --- Source/Orts.Formats.Msts/TrackDatabaseFile.cs | 634 +++++++++++------- .../Simulation/Physics/Train.cs | 50 +- .../Simulation/Signalling/Gradepost.cs | 17 +- .../Simulation/Signalling/Signals.cs | 133 ++-- .../Signalling/TrackCircuitGradepost.cs | 18 +- .../Signalling/TrackCircuitItems.cs | 3 +- .../Orts.Simulation/Simulation/Simulator.cs | 69 +- 7 files changed, 557 insertions(+), 367 deletions(-) diff --git a/Source/Orts.Formats.Msts/TrackDatabaseFile.cs b/Source/Orts.Formats.Msts/TrackDatabaseFile.cs index e83a052fbe..526fee9a9e 100644 --- a/Source/Orts.Formats.Msts/TrackDatabaseFile.cs +++ b/Source/Orts.Formats.Msts/TrackDatabaseFile.cs @@ -164,24 +164,29 @@ public int TrackNodesIndexOf(TrackNode targetTN) throw new InvalidOperationException("Program Bug: Can't Find Track Node"); } + // Note on how vector nodes connect: + // A Vector Node has a direction determined by the order of the vector sections. + // - Pin[0] is at the start (section[0]), Pin[1] is at the end (section[n-1]). + // - Pin Direction = 0 identifies that the vector node is connected to the trailing (out) side of a junction. + // - Pin Direction = 1 identifies that the vector node is connected to the leading (in) side of a junction. + // A Junction Node's direction is from the leading (in) side to the trailing (out) side. + // - A typical Junction Node has one in Pin (at index 0) and two out Pins. Either out pin may be the straight + // (primary) path. + // - Pin Direction = 0 indicates that the connected Vector Node is oriented towards the junction (ie. the + // last vector section connects to the junction). + // - Pin Direction = 1 indicates that the connected Vector Node is oriented away from the junction (ie. the + // first vector section connects to the junction). + /// /// Get the index of the vector node that precedes the specified vector node. /// Expects to traverse a junction node to find the preceding vector node. /// /// The index of the incoming vector node, and the connected end if it. - // A Vector Node has a direction determined by the order of the vector sections. - // - Pin[0] is at the start (section[0]), Pin[1] is at the end (section[n]). - // - Pin Direction = 0 identifies that the vector node is connected to the trailing (out) side of a junction. - // - Pin Direction = 1 identifies that the vector node is connected to the leading (in) side of a junction. - // A Junction Node's direction is from the leading (in) side to the trailing (out) side. A typical Junction - // Node has one in Pin (at index 0) and two out Pins. The last out Pin seems to be the straight (primary) - // path. - // - Pin Direction = 0 indicates that the connected Vector Node is oriented towards the junction (ie. the - // last vector section connects to the junction). - // - Pin Direction = 1 indicates that the connected Vector Node is oriented away from the junction (ie. the - // first vector section connects to the junction). - public (int, int) GetIncomingVectorNodeIndex(TrackNode vectorNode) + public (int, int) GetIncomingVectorNodeIndex(TrackNode vectorNode, int direction) { + const int connectedToTrailingSide = 0; // re: direction of vector node link to junction + + int inPinIdx = direction == 0 ? 0 : 1; int incomingVectorNodeIdx = -1; // error int incomingVectorNodeEnd = 0; @@ -191,7 +196,7 @@ public int TrackNodesIndexOf(TrackNode targetTN) return (-1, 0); // error } - int junctionNodeIdx = vectorNode.TrPins[0].Link; + int junctionNodeIdx = vectorNode.TrPins[inPinIdx].Link; if (junctionNodeIdx <= 0 || junctionNodeIdx >= TrackNodes.Length) { Debug.Print(String.Format("GetIncomingVectorNodeIndex() ERROR: first incoming node index {0} is out of range (1..{1})", junctionNodeIdx, TrackNodes.Length - 1)); @@ -215,7 +220,7 @@ public int TrackNodesIndexOf(TrackNode targetTN) else { int pinIdx = 0; // when connected to trailing side of junction, use in pin - if (vectorNode.TrPins[0].Direction != 0) + if (vectorNode.TrPins[inPinIdx].Direction != connectedToTrailingSide) { // connected to leading (facing) side, use longer next vector node var out1 = junctionNode.TrPins.Length < 2 ? null : TrackNodes[junctionNode.TrPins[1].Link]?.TrVectorNode?.TrVectorSections; @@ -248,6 +253,82 @@ public int TrackNodesIndexOf(TrackNode targetTN) return (incomingVectorNodeIdx, incomingVectorNodeEnd); } + /// + /// Get the index of the vector node that follows the specified vector node. + /// Expects to traverse a junction node to find the next vector node. + /// + /// The index of the outgoing vector node, and the connected end if it. + public (int, int) GetOutgoingVectorNodeIndex(TrackNode vectorNode, int direction) + { + const int connectedToTrailingSide = 0; // re: direction of vector node link to junction + + int outPinIdx = direction == 0 ? 0 : 1; + int outgoingVectorNodeIdx = -1; // error + int outgoingVectorNodeEnd = 0; + + if (vectorNode == null || vectorNode.TrVectorNode == null || vectorNode.Inpins != 1) + { + Debug.Print("GetOutgoingVectorNodeIndex() ERROR: source node is not a valid vector node"); + return (-1, 0); // error + } + + int junctionNodeIdx = vectorNode.TrPins[outPinIdx].Link; + if (junctionNodeIdx <= 0 || junctionNodeIdx >= TrackNodes.Length) + { + Debug.Print(String.Format("GetOutgoingVectorNodeIndex() ERROR: first outgoing node index {0} is out of range (1..{1})", junctionNodeIdx, TrackNodes.Length - 1)); + return (-1, 0); // error + } + + TrackNode junctionNode = TrackNodes[junctionNodeIdx]; + + if (junctionNode.TrEndNode) { outgoingVectorNodeIdx = 0; } // there is no outgoing vector node + + else if (junctionNode.TrVectorNode != null) + { + Debug.Print(String.Format("GetOutgoingVectorNodeIndex() WARNING: stitched vector nodes not expected; {0} - {1}", vectorNode.Index, junctionNode.Index)); + outgoingVectorNodeIdx = (int)junctionNode.Index; + } + else if (junctionNode.TrJunctionNode == null) + { + Debug.Print(String.Format("GetOutgoingVectorNodeIndex() WARNING: expected a junction node, got a {1} for the first outgoing node {0}", junctionNode.Index, junctionNode.GetType())); + outgoingVectorNodeIdx = 0; // there is no incoming vector node + } + else + { + int pinIdx = 0; // when connected to trailing side of junction, use in pin + if (vectorNode.TrPins[outPinIdx].Direction != connectedToTrailingSide) + { + // connected to leading (facing) side, use longer next vector node + var out1 = junctionNode.TrPins.Length < 2 ? null : TrackNodes[junctionNode.TrPins[1].Link]?.TrVectorNode?.TrVectorSections; + int l1 = out1 == null ? 0 : out1.Length; + var out2 = junctionNode.TrPins.Length < 3 ? null : TrackNodes[junctionNode.TrPins[2].Link]?.TrVectorNode?.TrVectorSections; + int l2 = out2 == null ? 0 : out2.Length; + pinIdx = l1 >= l2 ? 1 : 2; + } + outgoingVectorNodeIdx = junctionNode.TrPins[pinIdx].Link; + outgoingVectorNodeEnd = junctionNode.TrPins[pinIdx].Direction == 0 ? 1 : 0; + + if (outgoingVectorNodeIdx <= 0 || outgoingVectorNodeIdx >= TrackNodes.Length) + { + Debug.Print(String.Format("GetOutgoingVectorNodeIndex() ERROR: outgoing node index {0} is out of range (1..{1})", outgoingVectorNodeIdx, TrackNodes.Length - 1)); + return (-1, 0); // error + } + else if (outgoingVectorNodeIdx == vectorNode.Index) + { + Debug.Print(String.Format("GetOutgoingVectorNodeIndex() ERROR: outgoing node index {0} same as query node {1} (circular)", outgoingVectorNodeIdx, vectorNode.Index)); + return (-1, 0); // error + + } + else if (TrackNodes[outgoingVectorNodeIdx].TrVectorNode == null) + { + Debug.Print(String.Format("GetOutgoingVectorNodeIndex() ERROR: outgoing node index {0} is not a vector node (type {1})", outgoingVectorNodeIdx, TrackNodes[outgoingVectorNodeIdx].GetType())); + return (-1, 0); // error + } + } + + return (outgoingVectorNodeIdx, outgoingVectorNodeEnd); + } + /// /// Add a number of TrItems (Track Items), created outside of the file, to the table of TrItems. /// This will also set the ID of the TrItems (since that gives the index in that array) @@ -753,7 +834,9 @@ public class TrVectorNode public float[] GradePctAtEnd = new float[] { 0f, 0f }; // grade at each end [start, end] public float[] GradeLengthMAtEnd = new float[] { 0f, 0f }; // length of steady grade at each end [start, end] public float[] GradepostDistanceMAtEnd = new float[] { -1f, -1f }; // distance of the first grade marker at each end; -1 if none - public GradePostItem[] GradepostNearestEnd = new GradePostItem[2]; // gradepost nearest to the start and end of the vector node + public GradePostItem[] FirstGradePost = new GradePostItem[2]; // first gradepost in each direction; 0 = in track node direction, 1 = in reverse direction + public GradePostItem[] LastGradePost = new GradePostItem[2]; // last gradepost in each direction; 0 = in track node direction, 1 = in reverse direction + public struct GradeData // represents a segment of track with approx. the same grade, may span multiple vector sections { public float DistanceFromStartM; // for debug @@ -870,40 +953,15 @@ public void AddTrItemRef(int startIndex, TrItem[] trItems) NoItemRefs = TrItemRefs.Length; } - /// - /// Check if there is a long segment of the same grade ahead. - /// - private bool SameGradeAhead( float maxDistanceM, float currentGrade, float currentLength, int currentIndex, TrackSections trackSections) - { - float inBetweenLength = currentLength; - bool foundSameGrade = false; - for (int idx = currentIndex + 1; idx < TrVectorSections.Length && inBetweenLength < maxDistanceM && !foundSameGrade; idx++) - { - TrVectorSection vs = TrVectorSections[idx]; - TrackSection ts = trackSections[vs.SectionIndex]; - float length = ts.SectionCurve != null ? MathHelper.ToRadians(Math.Abs(ts.SectionCurve.Angle)) * ts.SectionCurve.Radius : ts.SectionSize.Length; - float grade = vs.AX * -100f; - - if (Math.Abs(grade - currentGrade) < 0.1 && length > inBetweenLength) - { - foundSameGrade = true; - } - } - return foundSameGrade; - } - /// /// Add grade info to the track vector node. Traverse the track sections and build a grade profile. /// Sections with approximately the same grade are combined. /// // length and grade calc taken from TrackViewer, PathChartData.cs, AddPointAndTrackItems(), GetCurvature(), SectionLengthAlongTrack() - public void AddGradeInfo(uint vectorNodeIdx, TrackSections trackSections, /* for debug*/ int tvsZeroLenCnt) + public void AddGradeInfo(uint vectorNodeIdx, TrackSections trackSections, /* for debug */ int tvsZeroLenCnt) { if (TrVectorSections != null && TrVectorSections.Length > 0) { - const float minGradeItemLengthM = 30f; - const float medGradeItemLengthM = 70f; - // handle first section; start a new grade segment TrVectorSection firstVS = TrVectorSections[0]; TrackSection firstTS = trackSections[firstVS.SectionIndex]; @@ -912,27 +970,7 @@ private bool SameGradeAhead( float maxDistanceM, float currentGrade, float curre GradeData gradeItem = new GradeData(0f, firstLength, firstGrade, firstVS.TileX, firstVS.TileZ, firstVS.X, firstVS.Y, firstVS.Z); float distanceFromStartM = firstLength; - int startIdx = 1; int endIndex = TrVectorSections.Length - 1; - -#if tru - // if the first section is short and the next section is long, start with the long section - if (gradeItem.LengthM < minGradeItemLengthM && endIndex >= 1) - { - TrVectorSection nextVS = TrVectorSections[1]; - TrackSection nextTS = trackSections[nextVS.SectionIndex]; - float nextLength = nextTS.SectionCurve != null ? MathHelper.ToRadians(Math.Abs(nextTS.SectionCurve.Angle)) * nextTS.SectionCurve.Radius : nextTS.SectionSize.Length; - float nextGrade = nextVS.AX * -100f; - if (nextGrade >= medGradeItemLengthM) - { - // add the first section to the second section - gradeItem.LengthM += nextLength; - gradeItem.GradePct = nextGrade; - startIdx = 2; - } - } -#endif - - for (int vsIdx = startIdx; vsIdx <= endIndex; vsIdx++) + for (int vsIdx = 1; vsIdx < TrVectorSections.Length; vsIdx++) { TrVectorSection vs = TrVectorSections[vsIdx]; TrackSection ts = trackSections[vs.SectionIndex]; @@ -947,57 +985,8 @@ private bool SameGradeAhead( float maxDistanceM, float currentGrade, float curre // combine with previous section gradeItem.GradePct = (gradeItem.LengthM > 0f) ? (gradeItem.GradePct * gradeItem.LengthM + grade * length) / (gradeItem.LengthM + length) : grade; gradeItem.LengthM += length; - goto nextSegment; } - -#if true - // if last section and it is short - if (vsIdx == endIndex && length < minGradeItemLengthM && gradeItem.LengthM > medGradeItemLengthM) - { - // add the last section to the preceding grade - gradeItem.LengthM += length; - goto nextSegment; - } -#endif - - // peek at next section - float nextLength = 0f; float nextGrade = 0f; - if (TrVectorSections.Length > vsIdx + 1) - { - TrVectorSection nextVS = TrVectorSections[1]; - TrackSection nextTS = trackSections[nextVS.SectionIndex]; - nextLength = nextTS.SectionCurve != null ? MathHelper.ToRadians(Math.Abs(nextTS.SectionCurve.Angle)) * nextTS.SectionCurve.Radius : nextTS.SectionSize.Length; - nextGrade = nextVS.AX * -100f; - } - -#if true - // if the current grade is a short change between two equal grades - if (length < minGradeItemLengthM && gradeItem.LengthM > medGradeItemLengthM && nextLength > medGradeItemLengthM && Math.Abs(gradeItem.GradePct - nextGrade) < 0.1) - { - // ignore the grade of the current segment - gradeItem.LengthM += length; - } - - // if the current grade is a short change between two steady grades - else if (length < minGradeItemLengthM && gradeItem.LengthM > medGradeItemLengthM && nextLength > medGradeItemLengthM) - { - // add the current grade to the preceding grade - gradeItem.LengthM += length; - } - - // if short grade changes and next grade is not the same as the current grade - else if (length < minGradeItemLengthM && gradeItem.LengthM < medGradeItemLengthM && (nextLength > 0.01 || Math.Abs(grade - nextGrade) >= 0.1)) - { - // average the grade - gradeItem.GradePct = (gradeItem.LengthM > 0f) ? (gradeItem.GradePct * gradeItem.LengthM + grade * length) / (gradeItem.LengthM + length) : grade; - gradeItem.LengthM += length; - } -#endif - -// else grade change -#if true else -#endif { // add the grade item and start a new one if (gradeItem.LengthM > 0) { GradeList.Add(gradeItem); } @@ -1010,10 +999,7 @@ private bool SameGradeAhead( float maxDistanceM, float currentGrade, float curre } // save the final segment - if (gradeItem.LengthM > 0) - { - GradeList.Add(gradeItem); - } + if (gradeItem.LengthM > 0) { GradeList.Add(gradeItem); } } if (GradeList.Count > 0) @@ -1032,197 +1018,322 @@ private bool SameGradeAhead( float maxDistanceM, float currentGrade, float curre // Ensure that not too many gradeposts are created, as that would crowd the track monitor. public void ProcessForwardGradeInfoAndAddGradeposts(uint vectorNodeIdx, TrackDB trackDB) { + const int direction = 0; // forward - ////// RWF TEMP - return; - ////// RWF TEMP - + // for tuning the aggregation + const float shortGradeLengthM = 30f; + const float mediumGradeLengthM = 70f; + const float longGradeLengthM = 150f; const float minDistanceBetweenGradepostM = 200f; + GradePostItem gradepost = null; + TrackNode trackNode = trackDB.TrackNodes[vectorNodeIdx]; TrVectorNode vectorNode = trackNode.TrVectorNode; - (int precedingVectorNodeIdx, int precedingVectorNodeEnd) = trackDB.GetIncomingVectorNodeIndex(trackNode); - - float precedingGradePct = 0f; - float precedingGradeLengthM = 0f; - GradePostItem precedingGradepost = null; - int precedingGradepostEnd = precedingVectorNodeEnd; - float precedingGradepostDistanceM = 9e9f; - + // start with the last gradepost in the preceding vector node + bool isExistingGradepost = false; + (int precedingVectorNodeIdx, int precedingVectorNodeEnd) = trackDB.GetIncomingVectorNodeIndex(trackNode, direction); if (precedingVectorNodeIdx > 0) { TrVectorNode precedingVectorNode = trackDB.TrackNodes[precedingVectorNodeIdx].TrVectorNode; - precedingGradePct = precedingVectorNode.GradePctAtEnd[precedingVectorNodeEnd]; - precedingGradeLengthM = precedingVectorNode.GradeLengthMAtEnd[precedingVectorNodeEnd]; - precedingGradepost = precedingVectorNode.GradepostNearestEnd[precedingVectorNodeEnd]; - if (precedingGradepost != null) + GradePostItem precedingGradepost = null; + if (precedingVectorNodeEnd == 0) + precedingGradepost = precedingVectorNode.FirstGradePost[direction]; + else + precedingGradepost = precedingVectorNode.LastGradePost[direction]; + if (precedingGradepost != null && precedingGradepost.DistanceFromStartM + precedingGradepost.ForDistanceM >= precedingVectorNode.LengthM) { - precedingGradepostEnd = precedingVectorNodeEnd; - precedingGradepostDistanceM = precedingVectorNodeEnd == 1 ? precedingGradepost.DistanceFromStartM : precedingVectorNode.LengthM - precedingGradepost.DistanceFromStartM; + gradepost = precedingGradepost; + isExistingGradepost = true; } } - List newTrItems = new List(); + List newTrItems = new List(); // to collect all new gradeposts, and add them all at once at the end + GradePostItem firstGradepost = null; GradePostItem lastGradepost = null; float currentDistanceFromStartM = 0f; + for (int gradeIdx = 0; gradeIdx < GradeList.Count; gradeIdx++) { GradeData gradeInfo = GradeList[gradeIdx]; - // determine preceding gradepost orientation - int precedingForward = precedingGradepostEnd; - int precedingReverse = precedingGradepostEnd == 0 ? 1 : 0; - - // Debug: save preceding gradepost info for logging - float prevGpLocation = 0f, prevGpReverseGrade = 0f, prevGpReverseLength = 0f, prevGpForwardGrade = 0f, prevGpForwardLength = 0f; - if (precedingGradepost != null) + // if there is no current gradepost: + if (gradepost == null) { - prevGpLocation = precedingGradepost.DistanceFromStartM; - prevGpReverseGrade = precedingGradepost.GradePct[precedingReverse]; prevGpReverseLength = precedingGradepost.ForDistanceM[precedingReverse]; - prevGpForwardGrade = precedingGradepost.GradePct[precedingForward]; prevGpForwardLength = precedingGradepost.ForDistanceM[precedingForward]; + // start a new gradepost with the current grade + isExistingGradepost = false; + gradepost = new GradePostItem(currentDistanceFromStartM, gradeInfo.GradePct, gradeInfo.LengthM, direction, gradeInfo.TileX, gradeInfo.TileZ, gradeInfo.X, gradeInfo.Y, gradeInfo.Z); + gradepost.TrackNodeIndex = vectorNodeIdx; // for debug + gradepost.ItemName = String.Format("Calculated first: TrackNode {0} at distance {1}: direction {2}, grade {3:F2} for {4:F1}", + vectorNodeIdx, currentDistanceFromStartM, direction, gradepost.GradePct, gradepost.ForDistanceM); // for debug + + goto nextLoop; } - // if there is a preceding grade, and the difference is minimal - if (precedingGradeLengthM > 0f && Math.Abs(precedingGradePct - gradeInfo.GradePct) < 0.1f) + // if the current grade is the same as the gradepost + if (Math.Abs(gradeInfo.GradePct - gradepost.GradePct) < 0.1f) { - // if there is an immediately preceding gradepost - if (precedingGradepost != null && precedingGradepostDistanceM == precedingGradeLengthM) - { - // extend the gradepost - precedingGradeLengthM = precedingGradepost.ForDistanceM[precedingForward] += gradeInfo.LengthM; + // add to the gradepost + gradepost.ForDistanceM += gradeInfo.LengthM; - Debug.WriteLine("Gradepost-Extending-short: tnIdx {0}, gpItemIdx {1}, at {2}, from {3:F1}, to {4:F1}, grade {5:F2}", - precedingGradepost.TrackNodeIndex, precedingGradepost.TrItemId, precedingGradepost.DistanceFromStartM, prevGpForwardLength, - precedingGradepost.ForDistanceM[precedingForward], precedingGradepost.GradePct[precedingForward]); - } - else + goto nextLoop; + } + + // if a long gradepost followed by a long grade (min+, long+, --) + if (gradepost.ForDistanceM > minDistanceBetweenGradepostM && gradeInfo.LengthM > longGradeLengthM) + { + // finalize (add) the gradepost and start a new one with the current grade + if (!isExistingGradepost) { - // accumulate the length - precedingGradeLengthM += gradeInfo.LengthM; + newTrItems.Add(gradepost); + if (firstGradepost == null) { firstGradepost = gradepost; } + lastGradepost = gradepost; } - // leave the preceding grade the same, to avoid creep + isExistingGradepost = false; + gradepost = new GradePostItem(currentDistanceFromStartM, gradeInfo.GradePct, gradeInfo.LengthM, direction, gradeInfo.TileX, gradeInfo.TileZ, gradeInfo.X, gradeInfo.Y, gradeInfo.Z); + gradepost.TrackNodeIndex = vectorNodeIdx; // for debug + gradepost.ItemName = String.Format("Calculated long-long: TrackNode {0} at distance {1}: direction {2}, grade {3:F2} for {4:F1}", + vectorNodeIdx, currentDistanceFromStartM, direction, gradepost.GradePct, gradepost.ForDistanceM); // for debug + + goto nextLoop; } - // else if there are long stretches before and after - else if (precedingGradeLengthM > minDistanceBetweenGradepostM && gradeInfo.LengthM > minDistanceBetweenGradepostM) + // if a short gradepost followed by a long grade (short-, long+, --) + if (gradepost.ForDistanceM <= shortGradeLengthM && gradeInfo.LengthM > longGradeLengthM) { - Debug.Assert(precedingGradepostDistanceM >= precedingGradeLengthM, "two long stretches; preceding gradepost must be at least the reverse length away"); - - // create a new bidirectional gradepost - var newItem = new GradePostItem(currentDistanceFromStartM, gradeInfo.GradePct, precedingGradePct * -1, gradeInfo.LengthM, precedingGradeLengthM, gradeInfo.TileX, gradeInfo.TileZ, gradeInfo.X, gradeInfo.Y, gradeInfo.Z); - newItem.TrackNodeIndex = vectorNodeIdx; - newItem.ItemName = String.Format("Calculated Grade in TrackNode {0} at distance {1}: grade {2:F2}/{3:F2} for {4:F1}/{5:F1}", - vectorNodeIdx, currentDistanceFromStartM, newItem.GradePct[0], newItem.GradePct[1], newItem.ForDistanceM[0], newItem.ForDistanceM[1]); // for debug - newTrItems.Add(newItem); - - Debug.WriteLine("Gradepost-Creating-bidirectional: tnIdx {0}, gpItemIdx {1}, at {2}, fwdGrade {3:F2}, fwdDist {4:F1}, revGrade {5:F2}, revDist {6:F1}", - newItem.TrackNodeIndex, newItem.TrItemId, newItem.DistanceFromStartM, newItem.GradePct[0], newItem.ForDistanceM[0], newItem.GradePct[1], newItem.ForDistanceM[1]); - - // track first and last gradepost if vector - if (firstGradepost == null) { firstGradepost = newItem; } - lastGradepost = newItem; - - precedingGradePct = gradeInfo.GradePct; - precedingGradeLengthM = gradeInfo.LengthM; - precedingGradepostDistanceM = gradeInfo.LengthM; - precedingGradepost = newItem; - precedingGradepostEnd = 0; + // adopt the current grade + gradepost.GradePct = gradeInfo.GradePct; + gradepost.ForDistanceM += gradeInfo.LengthM; + + goto nextLoop; } - // else if there is a long forward stretch close to a preceding gradepost - else if (gradeInfo.LengthM > minDistanceBetweenGradepostM && precedingGradepost != null && precedingGradepostDistanceM < minDistanceBetweenGradepostM) - { - Debug.Assert(precedingGradepost.ForDistanceM[precedingForward] <= 0, "long forward grade just after a GP; GP should not have a forward grade"); + // get the next grade segment + float nextGradePct = gradeIdx + 1 < GradeList.Count ? GradeList[gradeIdx + 1].GradePct : 918273f; // never matches a real grade + float nextGradeLengthM = gradeIdx + 1 < GradeList.Count ? GradeList[gradeIdx + 1].LengthM : 0f; - float delta = currentDistanceFromStartM - (precedingGradepost.DistanceFromStartM + precedingGradepost.ForDistanceM[precedingForward]); + // if there is a brief deviation from a steady grade (same, short/medium, same) + if (nextGradeLengthM > 0f && Math.Abs(gradepost.GradePct - nextGradePct) < 0.1f && + ((gradepost.ForDistanceM > longGradeLengthM && nextGradeLengthM > longGradeLengthM && gradeInfo.LengthM <= mediumGradeLengthM) || + (gradepost.ForDistanceM > mediumGradeLengthM && nextGradeLengthM > mediumGradeLengthM && gradeInfo.LengthM <= shortGradeLengthM))) + { + // absorbe the current (short) grade into the gradepost (add length, but keep grade); next loop will also add the next grade + gradepost.ForDistanceM += gradeInfo.LengthM; - // if the forward grade is the same as the preceding gradepost's forward grade - if (Math.Abs(gradeInfo.GradePct - precedingGradepost.GradePct[precedingForward]) < 0.1f) - { - // fully extend the preceding gradepost - precedingGradeLengthM = precedingGradepost.ForDistanceM[precedingForward] += delta + gradeInfo.LengthM; + goto nextLoop; + } - Debug.WriteLine("Gradepost-Extending-medium: tnIdx {0}, gpItemIdx {1}, at {2}, from {3:F1}, to {4:F1}, grade {5:F2}", - precedingGradepost.TrackNodeIndex, precedingGradepost.TrItemId, precedingGradepost.DistanceFromStartM, prevGpForwardLength, precedingGradepost.ForDistanceM[precedingForward], precedingGradepost.GradePct[precedingForward]); - } - else + // if gradepost is long enough + if (gradepost.ForDistanceM > minDistanceBetweenGradepostM) + { + // finalize (add) the gradepost and start a new one with the current grade + if (!isExistingGradepost) { - // move the preceding gradepost and update the forward grade - precedingGradepost.DistanceFromStartM += delta / 2f; - precedingGradeLengthM = precedingGradepost.ForDistanceM[precedingReverse] += delta / 2f; - precedingGradepostDistanceM -= delta / 2f; - - // and set the forward grade to the new grade and extended length - precedingGradepost.GradePct[precedingForward] = gradeInfo.GradePct; - precedingGradepost.ForDistanceM[precedingForward] = gradeInfo.LengthM + delta / 2f; - - Debug.WriteLine("Gradepost-Moving-short: tnIdx {0}, gpItemIdx {1}, from {2:F1}, to {3:F1}, revDist {4:F1} -> {5:F1}, fwdGrade {6:F2} -> {7:F2}, fwdDist {8:F1} -> {9:F2}", - precedingGradepost.TrackNodeIndex, precedingGradepost.TrItemId, prevGpLocation, precedingGradepost.DistanceFromStartM, prevGpReverseLength, precedingGradepost.ForDistanceM[precedingReverse], - prevGpForwardGrade, precedingGradepost.GradePct[precedingForward], prevGpForwardLength, precedingGradepost.ForDistanceM[precedingForward]); + newTrItems.Add(gradepost); + if (firstGradepost == null) { firstGradepost = gradepost; } + lastGradepost = gradepost; } - precedingGradePct = gradeInfo.GradePct; + isExistingGradepost = false; + gradepost = new GradePostItem(currentDistanceFromStartM, gradeInfo.GradePct,gradeInfo.LengthM, direction, gradeInfo.TileX, gradeInfo.TileZ, gradeInfo.X, gradeInfo.Y, gradeInfo.Z); + gradepost.TrackNodeIndex = vectorNodeIdx; // for debug + gradepost.ItemName = String.Format("Calculated long-gradepost: TrackNode {0} at distance {1}: direction {2}, grade {3:F2} for {4:F1}", + vectorNodeIdx, currentDistanceFromStartM, direction, gradepost.GradePct, gradepost.ForDistanceM); // for debug + + goto nextLoop; } - // else if there is a long forward streach a bit beyond the preceding gradepost - else if (gradeInfo.LengthM > minDistanceBetweenGradepostM && precedingGradepost != null && precedingGradepostDistanceM < 2f * minDistanceBetweenGradepostM) - { - float delta = currentDistanceFromStartM - (precedingGradepost.DistanceFromStartM + precedingGradepost.ForDistanceM[precedingForward]); + // else, average the grade + gradepost.GradePct = (gradepost.GradePct * gradepost.ForDistanceM + gradeInfo.GradePct * gradeInfo.LengthM) / (gradepost.ForDistanceM + gradeInfo.LengthM); + gradepost.ForDistanceM += gradeInfo.LengthM; - // if the forward grade is the same as the preceding gradepost's forward grade - if (Math.Abs(gradeInfo.GradePct - precedingGradepost.GradePct[precedingForward]) < 0.1f) - { - // fully extend the preceding gradepost - precedingGradeLengthM = precedingGradepost.ForDistanceM[precedingForward] += delta + gradeInfo.LengthM; +nextLoop: + currentDistanceFromStartM += gradeInfo.LengthM; + } - Debug.WriteLine("Gradepost-Extending-long: tnIdx {0}, gpItemIdx {1}, at {2}, from {3:F1}, to {4:F1}, grade {5:F2}", - precedingGradepost.TrackNodeIndex, precedingGradepost.TrItemId, precedingGradepost.DistanceFromStartM, prevGpForwardLength, precedingGradepost.ForDistanceM[precedingForward],precedingGradepost.GradePct[precedingForward]); - } - else - { - // extend the preceding gradepost by half the difference - precedingGradeLengthM = precedingGradepost.ForDistanceM[precedingForward] += delta / 2f; + // add the last gradepost + if (gradepost?.ForDistanceM > minDistanceBetweenGradepostM && !isExistingGradepost) + { + newTrItems.Add(gradepost); + if (firstGradepost == null) { firstGradepost = gradepost; } + lastGradepost = gradepost; + } - Debug.WriteLine("Gradepost-Extending-medium: tnIdx {0}, gpItemIdx {1}, at {2}, from {3:F1}, to {4:F1}, grade {5:F2}", - precedingGradepost.TrackNodeIndex, precedingGradepost.TrItemId, precedingGradepost.DistanceFromStartM, prevGpForwardLength, precedingGradepost.ForDistanceM[precedingForward], precedingGradepost.GradePct[precedingForward]); + // append new items to the Track DB's TrItemTable, and update references + if (newTrItems.Count > 0) + { + int firstInsertIdx = trackDB.AddTrItems(newTrItems.ToArray()); + AddTrItemRef(firstInsertIdx, trackDB.TrItemTable); - // and create a new bidirectional gradepost - var newItem = new GradePostItem(currentDistanceFromStartM - delta / 2f, gradeInfo.GradePct, precedingGradepost.GradePct[precedingForward], gradeInfo.LengthM + delta / 2f, precedingGradepost.ForDistanceM[precedingForward], gradeInfo.TileX, gradeInfo.TileZ, gradeInfo.X, gradeInfo.Y, gradeInfo.Z); - newItem.TrackNodeIndex = vectorNodeIdx; - newItem.ItemName = String.Format("Calculated Grade in TrackNode {0} at distance {1}: grade {2:F2}/{3:F2} for {4:F1}/{5:F1}", - vectorNodeIdx, currentDistanceFromStartM, newItem.GradePct[0], newItem.GradePct[1], newItem.ForDistanceM[0], newItem.ForDistanceM[1]); // for debug - newTrItems.Add(newItem); + vectorNode.FirstGradePost[direction] = firstGradepost; + vectorNode.LastGradePost[direction] = lastGradepost; + } + } - Debug.WriteLine("Gradepost-Creating-bidirectional: tnIdx {0}, gpItemIdx {1}, at {2}, fwdGrade {3:F2}, fwdDist {4:F1}, revGrade {5:F2}, revDist {6:F1}", - newItem.TrackNodeIndex, newItem.TrItemId, newItem.DistanceFromStartM, newItem.GradePct[0], newItem.ForDistanceM[0], newItem.GradePct[1], newItem.ForDistanceM[1]); + /// Process the reverse grade info in the vector node, to determine where gradeposts should be placed. + /// Then create the gradeposts and add them to the vector node's existing list of track items. + /// + // Ensure that not too many gradeposts are created, as that would crowd the track monitor. + public void ProcessReverseGradeInfoAndAddGradeposts(uint vectorNodeIdx, TrackDB trackDB) + { + const int direction = 1; // reverse - // track first and last gradepost if vector - if (firstGradepost == null) { firstGradepost = newItem; } - lastGradepost = newItem; + // for tuning the aggregation + const float shortGradeLengthM = 30f; + const float mediumGradeLengthM = 70f; + const float longGradeLengthM = 150f; + const float minDistanceBetweenGradepostM = 200f; + GradePostItem gradepost = null; + GradeData precedingGrade = new GradeData(); // in reverse direction, we need the location of the preceding grade + TrackNode trackNode = trackDB.TrackNodes[vectorNodeIdx]; + TrVectorNode vectorNode = trackNode.TrVectorNode; + // start with the last gradepost in the preceding vector node + bool isExistingGradepost = false; + (int precedingVectorNodeIdx, int precedingVectorNodeEnd) = trackDB.GetIncomingVectorNodeIndex(trackNode, direction); + if (precedingVectorNodeIdx > 0) + { + TrVectorNode precedingVectorNode = trackDB.TrackNodes[precedingVectorNodeIdx].TrVectorNode; + GradePostItem precedingGradepost = null; + if (precedingVectorNodeEnd == 0) + { + precedingGradepost = precedingVectorNode.FirstGradePost[direction]; + if (precedingVectorNode.GradeList?.Count > 0) { precedingGrade = precedingVectorNode.GradeList[0]; } + } + else + { + precedingGradepost = precedingVectorNode.LastGradePost[direction]; + if (precedingVectorNode.GradeList?.Count > 0) { precedingGrade = precedingVectorNode.GradeList[precedingVectorNode.GradeList.Count - 1]; } + } + if (precedingGradepost != null && precedingGradepost.DistanceFromStartM + precedingGradepost.ForDistanceM >= precedingVectorNode.LengthM) + { + gradepost = precedingGradepost; + isExistingGradepost = true; + } + } + + List newTrItems = new List(); // to collect all new gradeposts, and add them all at once at the end + + GradePostItem firstGradepost = null; + GradePostItem lastGradepost = null; + float currentDistanceFromStartM = vectorNode.LengthM; + + for (int gradeIdx = GradeList.Count - 1; gradeIdx >= 0; gradeIdx--) + { + GradeData tempGradeInfo = GradeList[gradeIdx]; + GradeData gradeInfo = tempGradeInfo; + gradeInfo.GradePct = -1 * gradeInfo.GradePct; + + // if there is no current gradepost: + if (gradepost == null) + { + // start a new gradepost with the current grade, and preceding grade location + if (precedingGrade.LengthM <= 0f) { precedingGrade = gradeInfo; } // use current grade (location) if there is no preceding grade + gradepost = new GradePostItem(currentDistanceFromStartM, gradeInfo.GradePct, gradeInfo.LengthM, direction, precedingGrade.TileX, precedingGrade.TileZ, precedingGrade.X, precedingGrade.Y, precedingGrade.Z); + gradepost.TrackNodeIndex = vectorNodeIdx; // for debug + gradepost.ItemName = String.Format("Calculated first: TrackNode {0} at distance {1}: direction {2}, grade {3:F2} for {4:F1}", + vectorNodeIdx, currentDistanceFromStartM, direction, gradepost.GradePct, gradepost.ForDistanceM); // for debug + isExistingGradepost = false; + + goto nextLoop; + } + + // if the current grade is the same as the gradepost + if (Math.Abs(gradeInfo.GradePct - gradepost.GradePct) < 0.1f) + { + // add to the gradepost + gradepost.ForDistanceM += gradeInfo.LengthM; + + goto nextLoop; + } + + // if a long gradepost followed by a long grade (min+, long+, --) + if (gradepost.ForDistanceM > minDistanceBetweenGradepostM && gradeInfo.LengthM > longGradeLengthM) + { + // finalize (add) the gradepost + if (!isExistingGradepost) + { + newTrItems.Add(gradepost); + if (firstGradepost == null) { firstGradepost = gradepost; } + lastGradepost = gradepost; } + // start a new gradepost with the current grade, and preceding grade location + gradepost = new GradePostItem(currentDistanceFromStartM, gradeInfo.GradePct, gradeInfo.LengthM, direction, precedingGrade.TileX, precedingGrade.TileZ, precedingGrade.X, precedingGrade.Y, precedingGrade.Z); + gradepost.TrackNodeIndex = vectorNodeIdx; // for debug + gradepost.ItemName = String.Format("Calculated long-long: TrackNode {0} at distance {1}: direction {2}, grade {3:F2} for {4:F1}", + vectorNodeIdx, currentDistanceFromStartM, direction, gradepost.GradePct, gradepost.ForDistanceM); // for debug + isExistingGradepost = false; + goto nextLoop; } + // if a short gradepost followed by a long grade (short-, long+, --) + if (gradepost.ForDistanceM <= shortGradeLengthM && gradeInfo.LengthM > longGradeLengthM) + { + // adopt the current grade + gradepost.GradePct = gradeInfo.GradePct; + gradepost.ForDistanceM += gradeInfo.LengthM; - // else undulating - else - { - // TODO + goto nextLoop; + } + + // get the next grade segment + float nextGradePct = gradeIdx - 1 >= 0 ? GradeList[gradeIdx - 1].GradePct : 918273f; // never matches a real grade + float nextGradeLengthM = gradeIdx - 1 >= 0 ? GradeList[gradeIdx - 1].LengthM : 0f; - precedingGradePct = gradeInfo.GradePct; - precedingGradeLengthM = gradeInfo.LengthM; - precedingGradepostDistanceM += gradeInfo.LengthM; + // if there is a brief deviation from a steady grade (same, short/medium, same) + if (nextGradeLengthM > 0f && Math.Abs(gradepost.GradePct - nextGradePct) < 0.1f && + ((gradepost.ForDistanceM > longGradeLengthM && nextGradeLengthM > longGradeLengthM && gradeInfo.LengthM <= mediumGradeLengthM) || + (gradepost.ForDistanceM > mediumGradeLengthM && nextGradeLengthM > mediumGradeLengthM && gradeInfo.LengthM <= shortGradeLengthM))) + { + // absorbe the current (short) grade into the gradepost (add length, but keep grade); next loop will also add the next grade + gradepost.ForDistanceM += gradeInfo.LengthM; + + goto nextLoop; } - precedingGradepostDistanceM += gradeInfo.LengthM; - currentDistanceFromStartM += gradeInfo.LengthM; + // if gradepost is long enough + if (gradepost.ForDistanceM > minDistanceBetweenGradepostM) + { + // finalize (add) the gradepost + if (!isExistingGradepost) + { + newTrItems.Add(gradepost); + if (firstGradepost == null) { firstGradepost = gradepost; } + lastGradepost = gradepost; + } + + // start a new gradepost with the current grade, and preceding grade location + gradepost = new GradePostItem(currentDistanceFromStartM, gradeInfo.GradePct, gradeInfo.LengthM, direction, precedingGrade.TileX, precedingGrade.TileZ, precedingGrade.X, precedingGrade.Y, precedingGrade.Z); + gradepost.TrackNodeIndex = vectorNodeIdx; // for debug + gradepost.ItemName = String.Format("Calculated long-gradepost: TrackNode {0} at distance {1}: direction {2}, grade {3:F2} for {4:F1}", + vectorNodeIdx, currentDistanceFromStartM, direction, gradepost.GradePct, gradepost.ForDistanceM); // for debug + isExistingGradepost = false; + + goto nextLoop; + } + + // else, average the grade + gradepost.GradePct = (gradepost.GradePct * gradepost.ForDistanceM + gradeInfo.GradePct * gradeInfo.LengthM) / (gradepost.ForDistanceM + gradeInfo.LengthM); + gradepost.ForDistanceM += gradeInfo.LengthM; + +nextLoop: + currentDistanceFromStartM -= gradeInfo.LengthM; + precedingGrade = gradeInfo; + } + + // add the last gradepost + if (gradepost?.ForDistanceM > minDistanceBetweenGradepostM && !isExistingGradepost) + { + newTrItems.Add(gradepost); + if (firstGradepost == null) { firstGradepost = gradepost; } + lastGradepost = gradepost; } // append new items to the Track DB's TrItemTable, and update references @@ -1231,11 +1342,11 @@ public void ProcessForwardGradeInfoAndAddGradeposts(uint vectorNodeIdx, TrackDB int firstInsertIdx = trackDB.AddTrItems(newTrItems.ToArray()); AddTrItemRef(firstInsertIdx, trackDB.TrItemTable); - vectorNode.GradepostNearestEnd[0] = firstGradepost; - vectorNode.GradepostNearestEnd[1] = lastGradepost; + vectorNode.FirstGradePost[direction] = firstGradepost; + vectorNode.LastGradePost[direction] = lastGradepost; } } - } + } // end class TrVectorNode /// /// Describes a single section in a vector node. @@ -2021,18 +2132,20 @@ public PickupItem(STFReader stf, int idx) // grade markers (or plates) with the track may be supported. public class GradePostItem : TrItem { - /// Grade in percent. Index 0 is in track direction, index 1 is reverse. - public float[] GradePct = new float[2]; - /// Distance (in meters) for which the grade applies. Index 0 is in track direction, index 1 is reverse. - public float[] ForDistanceM = new float[2]; - /// Distance of the grade post from the start of the track node. - public float DistanceFromStartM; + /// Grade in percent. + public float GradePct; + /// Distance (in meters) for which the grade applies. + public float ForDistanceM; + /// Direction in which the grade applies. 0 is in track node direction, 1 is in reverse direction. + public int Direction; // fields not read from file, set in post-processing + /// Distance of the grade post from the start of the track node. TBD if alwasy from start, or if depends on direction. TBD if needed or for DEBUG. + public float DistanceFromStartM; /// Set post construction. Reference (index) to the Track Node the grade post belongs to. public uint TrackNodeIndex; /// Set post construction. Reference to TrackCircuitGradepost (in signals). - public int SigObj; // TODO: rename, as it is not really a signalling object referenc; was copied from speed post + public int TcGradepostIdx; // based on SigObj from speed post /// /// Default constructor used during file parsing. @@ -2047,12 +2160,13 @@ public GradePostItem(STFReader stf, int idx) /// /// Create a Grade Marker (Post) based on grade info from the track profile. /// - public GradePostItem(float distFromStart, float forwardGrade, float reverseGrade, float forwardDist, float reverseDist, int tileX, int tileZ, float x, float y, float z) + public GradePostItem(float distFromStart, float grade, float length, int direction, int tileX, int tileZ, float x, float y, float z) { ItemType = trItemType.trGRADEPOST; DistanceFromStartM = distFromStart; - GradePct[0] = forwardGrade; GradePct[1] = reverseGrade; - ForDistanceM[0] = forwardDist; ForDistanceM[1] = reverseDist; + GradePct = grade; + ForDistanceM = length; + Direction = direction; TileX = tileX; TileZ = tileZ; X = x; Y = y; Z = z; } diff --git a/Source/Orts.Simulation/Simulation/Physics/Train.cs b/Source/Orts.Simulation/Simulation/Physics/Train.cs index ec5d0418cd..2f7f6173ad 100644 --- a/Source/Orts.Simulation/Simulation/Physics/Train.cs +++ b/Source/Orts.Simulation/Simulation/Physics/Train.cs @@ -69,6 +69,7 @@ using ORTS.Common; using ORTS.Scripting.Api; using ORTS.Settings; +using static System.Collections.Specialized.BitVector32; using Event = Orts.Common.Event; namespace Orts.Simulation.Physics @@ -14736,6 +14737,11 @@ public void UpdatePlayerTrainData() //TODO add generation of other train data } +#if DEBUG + public int NextDumpTime = 0; // DEBUG + public int CallCount = 2; +#endif + /// /// Updates the Player train data; /// For every section it adds the TrainObjectItems to the various lists; @@ -14992,21 +14998,27 @@ public void UpdatePlayerTrainData(float maxDistanceM) } // search for grade posts - if (thisSection.CircuitItems.TrackCircuitGradeposts != null) + // no change if using sectionDirection, match as 0 or match as 1 + int relativeDirection = sectionDirection == dir ? 0 : 1; + if (thisSection.CircuitItems.TrackCircuitGradeposts[relativeDirection] != null) { - foreach (TrackCircuitGradepost thisGradepostItem in thisSection.CircuitItems.TrackCircuitGradeposts) + foreach (TrackCircuitGradepost thisGradepostItem in thisSection.CircuitItems.TrackCircuitGradeposts[sectionDirection]) { - var gradepostDirection = sectionDirection == 1 ? 0 : 1; Gradepost thisGradepost = thisGradepostItem.GradepostRef; - var distanceToTrainM = thisGradepostItem.GradepostLocation[gradepostDirection] + sectionDistanceToTrainM; + var distanceToTrainM = thisGradepostItem.GradepostLocation + sectionDistanceToTrainM; if (distanceToTrainM < maxDistanceM) { - if (!(distanceToTrainM - prevGradepostDistance < 50 && thisGradepost.GradePct[gradepostDirection] == prevGradepostValue) && distanceToTrainM > 0) + if (!(distanceToTrainM - prevGradepostDistance < 50 && thisGradepost.GradePct == prevGradepostValue) && distanceToTrainM > 0) { - thisItem = new TrainObjectItem(thisGradepost.GradePct[gradepostDirection], distanceToTrainM); + thisItem = new TrainObjectItem(thisGradepost.GradePct, distanceToTrainM); prevGradepostDistance = distanceToTrainM; - prevGradepostValue = thisGradepost.GradePct[gradepostDirection]; + prevGradepostValue = thisGradepost.GradePct; PlayerTrainGradeposts[dir].Add(thisItem); +#if DEBUG + if (System.DateTime.Now.Second > NextDumpTime || CallCount > 0) + Debug.WriteLine(String.Format("Train-Adding: dir {0}, TNIdx {1}, distance {2:F1}, grade {3:F2}, circuitIDX {4}, TrItemId {5}", + dir, thisGradepost.TrackNodeIdx, thisItem.DistanceToTrainM, thisItem.GradePct, thisSection.Index, thisGradepost.TrItemId)); +#endif } } else break; @@ -15049,6 +15061,30 @@ public void UpdatePlayerTrainData(float maxDistanceM) continue; } } +#if DEBUG + if (System.DateTime.Now.Second > NextDumpTime || CallCount > 0) + { + int cnt = 0; + foreach (var gradepost in PlayerTrainGradeposts[0]) + { + Debug.WriteLine(String.Format("TrainGradepost-Fwd: {0}, distance {1:F1}, grade = {2:F2}", + cnt, gradepost.DistanceToTrainM, gradepost.GradePct)); + cnt++; + + } + int cnt1 = 0; + foreach (var gradepost in PlayerTrainGradeposts[1]) + { + Debug.WriteLine(String.Format("TrainGradepost-Rev: {0}, distance {1:F1}, grade = {2:F2}", + cnt1, gradepost.DistanceToTrainM, gradepost.GradePct)); + cnt1++; + + } + Debug.WriteLine(String.Format("TrainGradepost-count: fwd {0}, rev {1}", cnt, cnt1)); + NextDumpTime = System.DateTime.Now.Second + 60; + CallCount--; + } +#endif } /// diff --git a/Source/Orts.Simulation/Simulation/Signalling/Gradepost.cs b/Source/Orts.Simulation/Simulation/Signalling/Gradepost.cs index c223a65726..738b19f28e 100644 --- a/Source/Orts.Simulation/Simulation/Signalling/Gradepost.cs +++ b/Source/Orts.Simulation/Simulation/Signalling/Gradepost.cs @@ -36,19 +36,22 @@ public class Gradepost public int TCReference = -1; // undefined /// Position within TrackCircuit. Distance im meters? public float TCOffset; - /// Grade in percent. Index 0 is in track circuit direction, index 1 is in reverse direction - public float[] GradePct = new float[2]; - /// Distance in meters for which the grade applies. Index 0 is in track circuit direction, index 1 is in reverse direction. - public float[] ForDistanceM = new float[2]; + /// Grade in percent. + public float GradePct; + /// Distance in meters for which the grade applies. + public float ForDistanceM; + /// The direction in which the grade applies. 0 is in track circuit direction, 1 is in reverse direction. + public int Direction; /// Reference to TrackNode; index into TrackDB.TrackNodes. public int TrackNodeIdx; /// Constructor with base attributes. - public Gradepost(uint trItemId, float forwardGradePct, float reverseGradePct, float forwardDistanceM, float reverseDistanceM) + public Gradepost(uint trItemId, float gradePct, float distanceM, int dir) { TrItemId = trItemId; - GradePct[0] = forwardGradePct; GradePct[1] = reverseGradePct; - ForDistanceM[0] = forwardDistanceM; ForDistanceM[1] = reverseDistanceM; + GradePct = gradePct; + ForDistanceM = distanceM; + Direction = dir; } /// Dummy constructor diff --git a/Source/Orts.Simulation/Simulation/Signalling/Signals.cs b/Source/Orts.Simulation/Simulation/Signalling/Signals.cs index 2fd4183525..dd3a7f7f0e 100644 --- a/Source/Orts.Simulation/Simulation/Signalling/Signals.cs +++ b/Source/Orts.Simulation/Simulation/Signalling/Signals.cs @@ -81,8 +81,7 @@ public class Signals public List MilepostList = new List(); // list of mileposts private int foundMileposts; - public List GradepostList = new List(); // list of gradeposts - private int FoundGradeposts; + public List[] GradepostList = new List[2]; // list of gradeposts in each direction (0 = forward) public Signals(Simulator simulator, SignalConfigurationFile sigcfg, CancellationToken cancellation) { @@ -92,6 +91,9 @@ public Signals(Simulator simulator, SignalConfigurationFile sigcfg, Cancellation File.Delete(@"C:\temp\printproc.txt"); #endif + // needed in all constructors + GradepostList[0] = new List(); GradepostList[1] = new List(); + SignalRefList = new Dictionary(); SignalHeadList = new Dictionary(); Dictionary platformList = new Dictionary(); @@ -270,38 +272,58 @@ public Signals(Simulator simulator, SignalConfigurationFile sigcfg, Cancellation #if DEBUG // dump grade posts - int cnt1 = 0; - if (GradepostList != null) + int cnt1 = 0; int cnt2 = 0; + if (GradepostList[0] != null) { - foreach (var gradepost in GradepostList) + foreach (var gradepost in GradepostList[0]) { - Debug.WriteLine(String.Format("Signals-Gradepost: TrackNode = {0}, idx {1}, value {2:F2}/{3:F2}, for {4:F1}/{5:F1}, TrItemId = {6}, TCReference = {7}, tcOffset = {8}", - gradepost.TrackNodeIdx, cnt1, gradepost.GradePct[0], gradepost.GradePct[1], gradepost.ForDistanceM[0], gradepost.ForDistanceM[1], gradepost.TrItemId, gradepost.TCReference, gradepost.TCOffset)); + Debug.WriteLine(String.Format("Signals-Gradepost-Fwd: TrackNode = {0}, idx {1}, grade {2:F2}, for {3:F1}, dir {4}, TrItemId = {5}, TCReference = {6}, tcOffset = {7}", + gradepost.TrackNodeIdx, cnt1, gradepost.GradePct, gradepost.ForDistanceM, gradepost.Direction, gradepost.TrItemId, gradepost.TCReference, gradepost.TCOffset)); cnt1++; } } - Debug.WriteLine(String.Format("Signals-Gradepost-Count: {0}", cnt1)); + if (GradepostList[1] != null) + { + foreach (var gradepost in GradepostList[1]) + { + Debug.WriteLine(String.Format("Signals-Gradepost-Rev: TrackNode = {0}, idx {1}, grade {2:F2}, for {3:F1}, dir {4}, TrItemId = {5}, TCReference = {6}, tcOffset = {7}", + gradepost.TrackNodeIdx, cnt2, gradepost.GradePct, gradepost.ForDistanceM, gradepost.Direction, gradepost.TrItemId, gradepost.TCReference, gradepost.TCOffset)); + cnt2++; + } + } + Debug.WriteLine(String.Format("Signals-Gradepost-Count: {0}/{1}", cnt1, cnt2)); // dump track circuit items of type grade post - int cnt2 = 0; + int cnt3 = 0; int cnt4 = 0; if (TrackCircuitList != null) { foreach (var tcSection in TrackCircuitList) { - if (tcSection?.CircuitItems?.TrackCircuitGradeposts != null) + if (tcSection?.CircuitItems?.TrackCircuitGradeposts[0] != null) { - foreach (var tcGradeItem in tcSection.CircuitItems.TrackCircuitGradeposts) + foreach (var tcGradeItem in tcSection.CircuitItems.TrackCircuitGradeposts[0]) { var gradepost = tcGradeItem.GradepostRef; - Debug.WriteLine(String.Format("Signals-TrackCircuitGradepost: TrackCircuitSection {0}/{1}, TrackNodeIdx {2}, TrItemIdx {3}, location {4:F1}/{5:F1}, grade = {6:F2}/{7:F2}, for {8:F1}/{9:F1}, gp-TrItemId {10}, gp-Reference {11}, gp-Offset {12}", - tcSection.Index, tcSection.OriginalIndex, tcGradeItem.TrackNodeIdx, tcGradeItem.TrItemIdx, tcGradeItem.GradepostLocation[0], tcGradeItem.GradepostLocation[1], gradepost.GradePct[0], gradepost.GradePct[1], gradepost.ForDistanceM[0], gradepost.ForDistanceM[1], gradepost.TrItemId, gradepost.TCReference, gradepost.TCOffset)); - cnt2++; + Debug.WriteLine(String.Format("Signals-TrackCircuitGradepost-Fwd: TrackCircuitSection {0}/{1}, TrackNodeIdx {2}, TrItemIdx {3}, location {4:F1}, grade = {5:F2}, for {6:F1}, direction {7}, gp-TrItemId {8}, gp-Reference {9}, gp-Offset {10}", + tcSection.Index, tcSection.OriginalIndex, tcGradeItem.TrackNodeIdx, tcGradeItem.TrItemIdx, tcGradeItem.GradepostLocation, gradepost.GradePct, gradepost.ForDistanceM, gradepost.Direction, gradepost.TrItemId, gradepost.TCReference, gradepost.TCOffset)); + cnt3++; } } - else { Debug.WriteLine(String.Format("Signals-TrackCircuitGradepost: TCIdx {0}/{1}, none", tcSection.Index, tcSection.OriginalIndex)); } + else { Debug.WriteLine(String.Format("Signals-TrackCircuitGradepost-Fwd: TCIdx {0}/{1}, none", tcSection.Index, tcSection.OriginalIndex)); } + if (tcSection?.CircuitItems?.TrackCircuitGradeposts[1] != null) + { + foreach (var tcGradeItem in tcSection.CircuitItems.TrackCircuitGradeposts[1]) + { + var gradepost = tcGradeItem.GradepostRef; + Debug.WriteLine(String.Format("Signals-TrackCircuitGradepost-Rev: TrackCircuitSection {0}/{1}, TrackNodeIdx {2}, TrItemIdx {3}, location {4:F1}, grade = {5:F2}, for {6:F1}, direction {7}, gp-TrItemId {8}, gp-Reference {9}, gp-Offset {10}", + tcSection.Index, tcSection.OriginalIndex, tcGradeItem.TrackNodeIdx, tcGradeItem.TrItemIdx, tcGradeItem.GradepostLocation, gradepost.GradePct, gradepost.ForDistanceM, gradepost.Direction, gradepost.TrItemId, gradepost.TCReference, gradepost.TCOffset)); + cnt4++; + } + } + else { Debug.WriteLine(String.Format("Signals-TrackCircuitGradepost-Rev: TCIdx {0}/{1}, none", tcSection.Index, tcSection.OriginalIndex)); } } } - Debug.WriteLine(String.Format("Signals-TrackCircuitGradepost-Count: {0}", cnt2)); + Debug.WriteLine(String.Format("Signals-TrackCircuitGradepost-Count: {0}/{1}", cnt3, cnt4)); #endif } @@ -311,6 +333,9 @@ public Signals(Simulator simulator, SignalConfigurationFile sigcfg, Cancellation public Signals(Simulator simulator, SignalConfigurationFile sigcfg, BinaryReader inf, CancellationToken cancellation) : this(simulator, sigcfg, cancellation) { + // needed in all constructors + GradepostList[0] = new List(); GradepostList[1] = new List(); + int signalIndex = inf.ReadInt32(); while (signalIndex >= 0) { @@ -1007,8 +1032,8 @@ private void ScanSection(TrItem[] TrItems, TrackNode[] trackNodes, int index, else if (TrItems[TDBRef].ItemType == TrItem.trItemType.trGRADEPOST) { GradePostItem gradepostItem = (GradePostItem)TrItems[TDBRef]; - int gradePostIdx = AddGradepost(index, gradepostItem.GradePct, gradepostItem.ForDistanceM, TDBRef); - gradepostItem.SigObj = gradePostIdx; + int gradePostIdx = AddGradepost(index, gradepostItem.GradePct, gradepostItem.ForDistanceM, gradepostItem.Direction, TDBRef); + gradepostItem.TcGradepostIdx = gradePostIdx; } } } @@ -1175,14 +1200,13 @@ private int AddMilepost(int trackNode, int nodeIndx, SpeedPostItem speedItem, in /// This method adds a new Gradepost to the GradepostList in Signals. /// The index of the gradepost added. - private int AddGradepost(int trackNodeIdx, float[] gradePct, float[] distance, int TDBRef) + private int AddGradepost(int trackNodeIdx, float gradePct, float distance, int dir, int TDBRef) { - Gradepost gradepost = new Gradepost((uint)TDBRef, gradePct[0], gradePct[1], distance[0], distance[1]); + Gradepost gradepost = new Gradepost((uint)TDBRef, gradePct,distance, dir); gradepost.TrackNodeIdx = trackNodeIdx; - GradepostList.Add(gradepost); + GradepostList[dir].Add(gradepost); - FoundGradeposts = GradepostList.Count; - return FoundGradeposts - 1; + return GradepostList[dir].Count - 1; } /// @@ -2240,16 +2264,20 @@ public float[] InsertNode(TrackCircuitSection thisCircuit, TrItem thisItem, else if (thisItem.ItemType == TrItem.trItemType.trGRADEPOST) { GradePostItem gradePostItem = (GradePostItem)thisItem; - Gradepost gradepost = GradepostList[gradePostItem.SigObj]; + int direction = gradePostItem.Direction; + + Gradepost gradepost = GradepostList[direction][gradePostItem.TcGradepostIdx]; float gradepostDistance = TDBTrav.DistanceTo(thisItem.TileX, thisItem.TileZ, thisItem.X, thisItem.Y, thisItem.Z); - TrackCircuitGradepost newTCGradepost = new TrackCircuitGradepost(gradepost, gradepostDistance, thisCircuit.Length - gradepostDistance); + // if (direction != 0) { gradepostDistance = thisCircuit.Length - gradepostDistance; } + + TrackCircuitGradepost newTCGradepost = new TrackCircuitGradepost(gradepost, gradepostDistance, direction); newTCGradepost.TrackNodeIdx = thisCircuit.OriginalIndex; newTCGradepost.TrItemIdx = thisItem.TrItemId; - thisCircuit.CircuitItems.TrackCircuitGradeposts.Add(newTCGradepost); + thisCircuit.CircuitItems.TrackCircuitGradeposts[direction].Add(newTCGradepost); - Debug.WriteLine(String.Format("Adding TrackCircuitGradepost {0} to TrackCircuitSection {1}, grade {2:F2}/{3:F2}, for {4:F1}/{5:F1}, from TrackNode {6}, TrItem {7}, named {8}", - thisCircuit.CircuitItems.TrackCircuitGradeposts.Count - 1, thisCircuit.Index, gradePostItem.GradePct[0], gradePostItem.GradePct[1], gradePostItem.ForDistanceM[0], gradePostItem.ForDistanceM[1], thisCircuit.OriginalIndex, thisItem.TrItemId, thisItem.ItemName)); + Debug.WriteLine(String.Format("Adding TrackCircuitGradepost {0} to TrackCircuitSection {1}, grade {2:F2}, for {3:F1}, direction {4}, from TrackNode {5}, TrItem {6}, named {7}", + thisCircuit.CircuitItems.TrackCircuitGradeposts[direction].Count - 1, thisCircuit.Index, gradePostItem.GradePct, gradePostItem.ForDistanceM, gradePostItem.Direction, thisCircuit.OriginalIndex, thisItem.TrItemId, thisItem.ItemName)); } // Insert crossover in special crossover list else if (thisItem.ItemType == TrItem.trItemType.trCROSSOVER) @@ -2694,18 +2722,37 @@ private void splitSection(int orgSectionIndex, int newSectionIndex, float positi } } - // copy gradepost information - foreach (TrackCircuitGradepost thisGradepost in orgSection.CircuitItems.TrackCircuitGradeposts) + // copy forward gradepost information + var orgGradepostList = orgSection.CircuitItems.TrackCircuitGradeposts[0]; + var replGradepostList = replSection.CircuitItems.TrackCircuitGradeposts[0]; + var newGreadpostList = newSection.CircuitItems.TrackCircuitGradeposts[0]; + foreach (TrackCircuitGradepost thisGradepost in orgGradepostList) { - if (thisGradepost.GradepostLocation[0] > replSection.Length) + if (thisGradepost.GradepostLocation <= newSection.Length) { - thisGradepost.GradepostLocation[0] -= replSection.Length; - newSection.CircuitItems.TrackCircuitGradeposts.Add(thisGradepost); + newGreadpostList.Add(thisGradepost); } else { - thisGradepost.GradepostLocation[1] -= newSection.Length; - replSection.CircuitItems.TrackCircuitGradeposts.Add(thisGradepost); + thisGradepost.GradepostLocation -= newSection.Length; + replGradepostList.Add(thisGradepost); + } + } + + // copy reverse gradepost information + orgGradepostList = orgSection.CircuitItems.TrackCircuitGradeposts[1]; + replGradepostList = replSection.CircuitItems.TrackCircuitGradeposts[1]; + newGreadpostList = newSection.CircuitItems.TrackCircuitGradeposts[1]; + foreach (TrackCircuitGradepost thisGradepost in orgGradepostList) + { + if (thisGradepost.GradepostLocation > replSection.Length) + { + thisGradepost.GradepostLocation -= replSection.Length; + newGreadpostList.Add(thisGradepost); + } + else + { + replGradepostList.Add(thisGradepost); } } @@ -3104,15 +3151,25 @@ private void setSignalCrossReference(int thisNode) } } - // process gradeposts - foreach (TrackCircuitGradepost thisItem in thisSection.CircuitItems.TrackCircuitGradeposts) + // process gradeposts, each direction + foreach (TrackCircuitGradepost thisItem in thisSection.CircuitItems.TrackCircuitGradeposts[0]) + { + Gradepost thisGradepost = thisItem.GradepostRef; + + if (thisGradepost.TCReference <= 0) + { + thisGradepost.TCReference = thisNode; + thisGradepost.TCOffset = thisItem.GradepostLocation; + } + } + foreach (TrackCircuitGradepost thisItem in thisSection.CircuitItems.TrackCircuitGradeposts[1]) { Gradepost thisGradepost = thisItem.GradepostRef; if (thisGradepost.TCReference <= 0) { thisGradepost.TCReference = thisNode; - thisGradepost.TCOffset = thisItem.GradepostLocation[0]; + thisGradepost.TCOffset = thisItem.GradepostLocation; } } } diff --git a/Source/Orts.Simulation/Simulation/Signalling/TrackCircuitGradepost.cs b/Source/Orts.Simulation/Simulation/Signalling/TrackCircuitGradepost.cs index eeb1c39a96..0043f4e157 100644 --- a/Source/Orts.Simulation/Simulation/Signalling/TrackCircuitGradepost.cs +++ b/Source/Orts.Simulation/Simulation/Signalling/TrackCircuitGradepost.cs @@ -22,20 +22,22 @@ namespace Orts.Simulation.Signalling /// public class TrackCircuitGradepost { - /// Gradepost; is reference to objecty in Signals.GradepostList. + /// Reference to objecty in Signals' GradepostList (by direction). public Gradepost GradepostRef; - /// Gradepost location (distance) from each end of the section. Index 0 is from start, index 1 is from end. - public float[] GradepostLocation = new float[2]; - /// Reference to grade post in TrItemTable; inxed into TrackDB.TrItemTable. + /// Gradepost location (distance) from the start of the section. End of section for the reverse direction. + public float GradepostLocation; + /// 0 is in track circuit direction, 1 is in reverse direction. + public int GradepostDirection; + /// Reference to grade post in TrItemTable; index into TrackDB's TrItemTable. public uint TrItemIdx; - /// Reference to Track Node this gradepost is in; index into TrackDB.TrackNodes. + /// Reference to Track Node this gradepost is in; index into TrackDB's TrackNodes. public int TrackNodeIdx; - public TrackCircuitGradepost(Gradepost thisRef, float distanceFromStart, float distanceFromEnd) + public TrackCircuitGradepost(Gradepost thisRef, float distanceFromStart, int dir) { GradepostRef = thisRef; - GradepostLocation[0] = distanceFromStart; - GradepostLocation[1] = distanceFromEnd; + GradepostLocation = distanceFromStart; + GradepostDirection = dir; } } } diff --git a/Source/Orts.Simulation/Simulation/Signalling/TrackCircuitItems.cs b/Source/Orts.Simulation/Simulation/Signalling/TrackCircuitItems.cs index 4a0981d6ab..b2f725b800 100644 --- a/Source/Orts.Simulation/Simulation/Signalling/TrackCircuitItems.cs +++ b/Source/Orts.Simulation/Simulation/Signalling/TrackCircuitItems.cs @@ -26,7 +26,7 @@ public class TrackCircuitItems public Dictionary[] TrackCircuitSignals = new Dictionary[2]; // List of signals (per direction and per type) // public TrackCircuitSignalList[] TrackCircuitSpeedPosts = new TrackCircuitSignalList[2]; // List of speedposts (per direction) // public List TrackCircuitMileposts = new List(); // List of mileposts // - public List TrackCircuitGradeposts = new List(); // List of gradeposts; a gradpost has values for forward and reverse direction + public List[] TrackCircuitGradeposts = new List[2]; // List of gradeposts (per direction, 0 = in track circuit direction) #if ACTIVITY_EDITOR // List of all Element coming from OR configuration in a generic form. @@ -44,6 +44,7 @@ public TrackCircuitItems(IDictionary signalFunctions) } TrackCircuitSpeedPosts[iDirection] = new TrackCircuitSignalList(); + TrackCircuitGradeposts[iDirection] = new List(); } } } diff --git a/Source/Orts.Simulation/Simulation/Simulator.cs b/Source/Orts.Simulation/Simulation/Simulator.cs index 681935e34a..c96004a5b8 100644 --- a/Source/Orts.Simulation/Simulation/Simulator.cs +++ b/Source/Orts.Simulation/Simulation/Simulator.cs @@ -340,7 +340,11 @@ public Simulator(UserSettings settings, string activityPath, bool useOpenRailsDi // create grade markers from the grade info in the vector nodes foreach (var trackNode in TDB.TrackDB.TrackNodes) { - if (trackNode?.TrVectorNode != null) { trackNode.TrVectorNode.ProcessForwardGradeInfoAndAddGradeposts(trackNode.Index, TDB.TrackDB); } + if (trackNode?.TrVectorNode != null) + { + trackNode.TrVectorNode.ProcessForwardGradeInfoAndAddGradeposts(trackNode.Index, TDB.TrackDB); + trackNode.TrVectorNode.ProcessReverseGradeInfoAndAddGradeposts(trackNode.Index, TDB.TrackDB); + } } #if DEBUG @@ -422,8 +426,8 @@ public Simulator(UserSettings settings, string activityPath, bool useOpenRailsDi { if (!(trItem is GradePostItem)) continue; GradePostItem gradePost = (GradePostItem)trItem; - Debug.WriteLine(String.Format("Track-GradePostItem: TrackNode = {0}, TrItemId = {1}, grade = {2:F2}/{3:F2}, for = {4:F1}/{5:F1}, distance = {6:F1}, TX = {7}, TZ = {8}, name = {9}", - gradePost.TrackNodeIndex, gradePost.TrItemId, gradePost.GradePct[0], gradePost.GradePct[1], gradePost.ForDistanceM[0], gradePost.ForDistanceM[1], gradePost.DistanceFromStartM, gradePost.TileX, gradePost.TileZ, gradePost.ItemName)); + Debug.WriteLine(String.Format("Track-GradePostItem: TrackNode = {0}, TrItemId = {1}, grade = {2:F2}, for = {3:F1}, distance = {4:F1}, direction {5}, TX = {6}, TZ = {7}, name = {8}", + gradePost.TrackNodeIndex, gradePost.TrItemId, gradePost.GradePct, gradePost.ForDistanceM, gradePost.DistanceFromStartM, gradePost.Direction, gradePost.TileX, gradePost.TileZ, gradePost.ItemName)); cnt2++; } Debug.WriteLine(String.Format("Track-GradePostItem-Count: {0}", cnt2)); @@ -1474,7 +1478,7 @@ private Train InitializePlayerTrain() #if DEBUG // dump path, for now just grade posts - float distanceFromPathStart = 0; + float distanceFromPathStart = 0f; // does not account for offset of path start int maxNodes = aiPath.Nodes.Count; // limit, in case there is a loop AIPathNode currentPathNode = aiPath.FirstNode; while (currentPathNode != null && maxNodes >= 0) @@ -1485,62 +1489,35 @@ private Train InitializePlayerTrain() int tvnIdx = currentPathNode.NextMainTVNIndex; if (tvnIdx > 0) { - TrVectorNode trackVectorNode = TDB.TrackDB.TrackNodes[tvnIdx].TrVectorNode; + TrackNode trackNode = TDB.TrackDB.TrackNodes[tvnIdx]; + TrVectorNode vectorNode = trackNode.TrVectorNode; - int forward = 0; int backward = 1; - if (currentPathNode.JunctionIndex > 0) - { - if (TDB.TrackDB.TrackNodes[tvnIdx].TrPins[1].Link == currentPathNode.JunctionIndex) - { - forward = 1; backward = 0; - } - } - else if (currentPathNode.NextMainNode.JunctionIndex > 0) - { - if (TDB.TrackDB.TrackNodes[tvnIdx].TrPins[0].Link == currentPathNode.NextMainNode.JunctionIndex) - { - forward = 1; backward = 0; - } - } + int trackDirection = 0; + if (currentPathNode.JunctionIndex > 0 && trackNode.TrPins[1].Link == currentPathNode.JunctionIndex) { trackDirection = 1; } + else if (currentPathNode.NextMainNode.JunctionIndex > 0 && trackNode.TrPins[0].Link == currentPathNode.NextMainNode.JunctionIndex) { trackDirection = 1; } // for now assuming that gradeposts (their refs) are in distance order bool foundGradepost = false; - if (forward == 0) + for (int refIdx = 0; refIdx < vectorNode.NoItemRefs; refIdx++) { - for (int refIdx = 0; refIdx < trackVectorNode.NoItemRefs; refIdx++) + int trItemIdx = vectorNode.TrItemRefs[refIdx]; + TrItem item = TDB.TrackDB.TrItemTable[trItemIdx]; + if (item is GradePostItem) { - int trItemIdx = trackVectorNode.TrItemRefs[refIdx]; - TrItem item = TDB.TrackDB.TrItemTable[trItemIdx]; - if (item is GradePostItem) + GradePostItem gpItem = (GradePostItem)item; + if (gpItem.Direction == trackDirection) { - GradePostItem gpItem = (GradePostItem)item; float distanceInNode = gpItem.DistanceFromStartM; - Debug.WriteLine("Gradepost: TrackNodeIdx {0}, RefIdx {1}, ItemIdx {2}, fromPathStart {3:F0}, fromNodeStart {4:F0}, fwdGrade {5:F1}, fwdLength {6:F0}, revGrade {7:F1}, revLength {8:F0}", - gpItem.TrackNodeIndex, refIdx, gpItem.TrItemId, distanceFromPathStart + distanceInNode, distanceInNode, gpItem.GradePct[forward], gpItem.ForDistanceM[forward], gpItem.GradePct[backward], gpItem.ForDistanceM[backward]); - foundGradepost = true; - } - } - } - else - { - for (int refIdx = trackVectorNode.NoItemRefs - 1; refIdx >= 0; refIdx--) - { - int trItemIdx = trackVectorNode.TrItemRefs[refIdx]; - TrItem item = TDB.TrackDB.TrItemTable[trItemIdx]; - if (item is GradePostItem) - { - GradePostItem gpItem = (GradePostItem)item; - float distanceInNode = trackVectorNode.LengthM - gpItem.DistanceFromStartM; - Debug.WriteLine("Gradepost: TrackNodeIdx {0}, RefIdx {1}, ItemIdx {2}, fromPathStart {3:F0}, fromNodeStart {4:F0}, fwdGrade {5:F1}, fwdLength {6:F0}, revGrade {7:F1}, revLength {8:F0}", - gpItem.TrackNodeIndex, refIdx, gpItem.TrItemId, distanceFromPathStart + distanceInNode, distanceInNode, gpItem.GradePct[forward], gpItem.ForDistanceM[forward], gpItem.GradePct[backward], gpItem.ForDistanceM[backward]); + Debug.WriteLine("TrainInit-Gradepost: TrackNodeIdx {0}, RefIdx {1}, ItemIdx {2}, estFromPathStart {3:F0}, fromNodeStart {4:F0}, grade {5:F1}, length {6:F0}", + gpItem.TrackNodeIndex, refIdx, gpItem.TrItemId, distanceFromPathStart + distanceInNode, distanceInNode, gpItem.GradePct, gpItem.ForDistanceM); foundGradepost = true; } } } - if (!foundGradepost) { Debug.WriteLine("Gradepost: TrackNode {0}, no gradeposts in {1} items", tvnIdx, trackVectorNode.NoItemRefs); } + if (!foundGradepost) { Debug.WriteLine("Gradepost: TrackNode {0}, no gradeposts in {1} items", tvnIdx, vectorNode.NoItemRefs); } - distanceFromPathStart += trackVectorNode.LengthM; + distanceFromPathStart += vectorNode.LengthM; } }