/// <summary> /// Determine the trackItems and their location in the given tracknode. /// </summary> /// <param name="tn">The tracknode in which to search for track items</param> /// <returns>The list/set of track itemss together with their position information</returns> public IEnumerable <ChartableTrackItem> GetItemsInTracknode(TrackNode tn) { if (cachedItems.ContainsKey(tn)) { return(cachedItems[tn]); } List <ChartableTrackItem> tracknodeItems = new List <ChartableTrackItem>(); TrackVectorNode vectorNode = tn as TrackVectorNode; if (vectorNode?.TrackItemIndices == null) { return(tracknodeItems); } foreach (int trackItemIndex in vectorNode.TrackItemIndices) { TrackItem trItem = trackDB.TrackItems[trackItemIndex]; if (trItem is PlatformItem || trItem is SpeedPostItem) { var travellerAtItem = new Traveller(tsectionDat, trackDB.TrackNodes, vectorNode, trItem.Location, Traveller.TravellerDirection.Forward); if (travellerAtItem != null) { tracknodeItems.Add(new ChartableTrackItem(trItem, travellerAtItem)); } } } tracknodeItems.Sort(new AlongTrackComparer()); cachedItems[tn] = tracknodeItems; return(tracknodeItems); }
/// <summary> /// Determine where exactly the current trainpath node is on the track node /// </summary> /// <param name="startNode">The start node</param> /// <param name="nextNode">The next node (so also the direction can be understood)</param> /// <param name="tn">The tracknode connecting the startNode and nextNode</param> /// <param name="isForward">Output: whether going from startNode to nextNode is in the forward direction of the track</param> /// <param name="tvsiStart">Output: the track vector section index of where the startNode is</param> /// <param name="sectionOffsetStart">Output: the offset in the section (in the direction of the tracknode, not necessarily in the direction from startNode to nextNode)</param> private void DetermineSectionDetails(TrainpathNode startNode, TrainpathNode nextNode, TrackNode tn, out bool isForward, out int tvsiStart, out float sectionOffsetStart) { TrainpathVectorNode currentNodeAsVector = startNode as TrainpathVectorNode; TrainpathJunctionNode currentNodeAsJunction = startNode as TrainpathJunctionNode; if (currentNodeAsJunction != null) { // we start at a junction node isForward = (currentNodeAsJunction.JunctionIndex == tn.JunctionIndexAtStart()); if (isForward) { tvsiStart = 0; sectionOffsetStart = 0; } else { TrackVectorNode tvn = tn as TrackVectorNode; tvsiStart = tvn.TrackVectorSections.Length - 1; sectionOffsetStart = SectionLengthAlongTrack(tvn, tvsiStart); } } else { // we start at a vector node isForward = currentNodeAsVector.IsEarlierOnTrackThan(nextNode); tvsiStart = currentNodeAsVector.TrackVectorSectionIndex; sectionOffsetStart = currentNodeAsVector.TrackSectionOffset; } }
/// <summary> /// Draw (possibly part of) the track of a MSTS vectorNode (from track database) /// </summary> /// <param name="drawArea">Area to draw upon</param> /// <param name="trackVectorNode">The tracknode from track database (assumed to be a vector node)</param> /// <param name="colors">Colorscheme to use</param> /// <param name="tvsiStart">Index of first track vector section to draw (at least partially)</param> /// <param name="tvsiStop">Index of last track vector section to draw (at least partially)</param> /// <param name="sectionOffsetStart">start-offset in the first track section to draw</param> /// <param name="sectionOffsetStop">stop-offset in the last track section to draw</param> /// <remarks>Very similar to DrawVectorNode in class DrawTrackDB, but this one allows to draw partial vector nodes.</remarks> private void DrawVectorNode(DrawArea drawArea, TrackVectorNode trackVectorNode, ColorScheme colors, int tvsiStart, int tvsiStop, float sectionOffsetStart, float sectionOffsetStop) { TrackVectorSection tvs; if (tvsiStart == tvsiStop) { tvs = trackVectorNode.TrackVectorSections[tvsiStart]; DrawTrackSection(drawArea, tvs, colors, sectionOffsetStart, sectionOffsetStop); } else { // first section tvs = trackVectorNode.TrackVectorSections[tvsiStart]; DrawTrackSection(drawArea, tvs, colors, sectionOffsetStart, -1); // all intermediate sections for (int tvsi = tvsiStart + 1; tvsi <= tvsiStop - 1; tvsi++) { tvs = trackVectorNode.TrackVectorSections[tvsi]; DrawTrackSection(drawArea, tvs, colors, 0, -1); } // last section tvs = trackVectorNode.TrackVectorSections[tvsiStop]; DrawTrackSection(drawArea, tvs, colors, 0, sectionOffsetStop); } }
/// <summary> /// From section information create a point for charting the path, and add it to newPoints. /// In case there are track items in this particular section, add those also (starting with the last item as seen from the direction of the path) /// </summary> /// <param name="newPoints">The list to which to add the point</param> /// <param name="vectorNode">The vectorNode to use for curvature and grade</param> /// <param name="trackItems">A list of track items in this vector tracknode</param> /// <param name="isForward">Is the path in the same direction as the tracknode</param> /// <param name="height">Height to store in the point</param> /// <param name="tvsi">The section index in the track vector node</param> /// <param name="sectionOffsetStart">Offset of the start of this section (in forward direction of track, not of path)</param> /// <param name="sectionOffsetEnd">Offset of the end of this section (in forward direction of track, not of path)</param> private void AddPointAndTrackItems(List <PathChartPoint> newPoints, TrackVectorNode vectorNode, IEnumerable <ChartableTrackItem> trackItems, bool isForward, float height, int tvsi, float sectionOffsetStart, float sectionOffsetEnd) { //Note, we are adding points in in reverse direction var additionalPoints = new List <PathChartPoint>(); // not a percentage. We can safely assume the pitch is small enough so we do not to take tan(pitch) float gradeFromPitch = -vectorNode.TrackVectorSections[tvsi].Direction.X * (isForward ? 1 : -1); float curvature = GetCurvature(vectorNode, tvsi, isForward); List <ChartableTrackItem> items_local = trackItems.ToList(); if (isForward) { items_local.Reverse(); } PathChartPoint newPoint; foreach (ChartableTrackItem chartableItem in items_local) { if (chartableItem.TrackVectorSectionIndex == tvsi && sectionOffsetStart <= chartableItem.TrackVectorSectionOffset && chartableItem.TrackVectorSectionOffset < sectionOffsetEnd) { if (isForward) { //For forward, we start at the last item in the track newPoint = new PathChartPoint(chartableItem.Height, curvature, gradeFromPitch, sectionOffsetEnd - chartableItem.TrackVectorSectionOffset, chartableItem.ItemText, chartableItem.ItemType); sectionOffsetEnd = chartableItem.TrackVectorSectionOffset; } else { //For reverse, we have to swap forward and reverse speed limits ChartableTrackItemType itemType = chartableItem.ItemType == ChartableTrackItemType.SpeedLimitForward ? ChartableTrackItemType.SpeedLimitReverse : chartableItem.ItemType == ChartableTrackItemType.SpeedLimitReverse ? ChartableTrackItemType.SpeedLimitForward : chartableItem.ItemType; //For reverse, we start at the first item in the track newPoint = new PathChartPoint(chartableItem.Height, curvature, gradeFromPitch, chartableItem.TrackVectorSectionOffset - sectionOffsetStart, chartableItem.ItemText, itemType); sectionOffsetStart = chartableItem.TrackVectorSectionOffset; } additionalPoints.Add(newPoint); } } newPoint = new PathChartPoint(height, curvature, gradeFromPitch, sectionOffsetEnd - sectionOffsetStart); additionalPoints.Add(newPoint); newPoints.AddRange(additionalPoints); }
/// <summary> /// Find the angle that the signal needs to be drawn at /// </summary> /// <param name="tsectionDat">Database with track sections</param> /// <param name="trackDB">Database with tracks</param> /// <param name="tn">TrackNode on which the signal actually is</param> public void FindAngle(TrackSectionsFile tsectionDat, TrackDB trackDB, TrackVectorNode tn) { this.angle = 0; try { Traveller signalTraveller = new Traveller(tsectionDat, trackDB.TrackNodes, tn, WorldLocation, this.direction); this.angle = signalTraveller.RotY; // Shift signal a little bit to be able to distinguish backfacing from normal facing Microsoft.Xna.Framework.Vector3 shiftedLocation = this.WorldLocation.Location + 0.0001f * new Microsoft.Xna.Framework.Vector3((float)Math.Cos(this.angle), 0f, -(float)Math.Sin(this.angle)); this.WorldLocation = new WorldLocation(this.WorldLocation.TileX, this.WorldLocation.TileZ, shiftedLocation); } catch { } }
/// <summary> /// Find the exact distance of the start of the current tracksection (from the beginning of the vector node) /// </summary> /// <returns></returns> private float GetSectionStartDistance() { float distanceFromStart = 0; TrackVectorNode tn = TrackDB.TrackNodes[TvnIndex] as TrackVectorNode; for (int tvsi = 0; tvsi < TrackVectorSectionIndex; tvsi++) { TrackVectorSection tvs = tn.TrackVectorSections[tvsi]; TrackSection trackSection = TsectionDat.TrackSections.Get(tvs.SectionIndex); if (trackSection != null) // if trackSection is missing somehow, well, do without. { distanceFromStart += DrawTrackDB.GetLength(trackSection); } } return(distanceFromStart); }
/// <summary> /// constructor based on a nodeCandidate: a TrainpathVectorNode based on mouse location, does not contain all information /// </summary> /// <param name="nodeCandidate"></param> public TrainpathVectorNode(TrainpathVectorNode nodeCandidate) : base(nodeCandidate) { TvnIndex = nodeCandidate.TvnIndex; TrackVectorSectionIndex = nodeCandidate.TrackVectorSectionIndex; TrackSectionOffset = nodeCandidate.TrackSectionOffset; NextMainTvnIndex = nodeCandidate.TvnIndex; Location = nodeCandidate.Location; ForwardOriented = true; // only initial setting TrackVectorNode tn = TrackDB.TrackNodes[TvnIndex] as TrackVectorNode; Traveller traveller = new Traveller(TsectionDat, TrackDB.TrackNodes, tn, Location, Traveller.TravellerDirection.Forward); CopyDataFromTraveller(traveller); trackAngleForward = traveller.RotY; // traveller also has TvnIndex, tvs, offset, etc, but we are not using that (should be consistent though) }
/// <summary> /// Get the curvature for the current section index in a vector track node. /// </summary> /// <param name="vectorNode">The vector track node</param> /// <param name="tvsi">The tracknode vector section index in the given verctor track node</param> /// <param name="isForward">Is the path in the same direction as the vector track node?</param> private float GetCurvature(TrackVectorNode vectorNode, int tvsi, bool isForward) { TrackVectorSection tvs = vectorNode.TrackVectorSections[tvsi]; TrackSection trackSection = tsectionDat.TrackSections.Get(tvs.SectionIndex); float curvature = 0; if (trackSection?.Curved ?? false) // if it is null, something is wrong but we do not want to crash { curvature = Math.Sign(trackSection.Angle) / trackSection.Radius; if (!isForward) { curvature *= -1; } } return(curvature); }
/// <summary> /// Place a traveller at the junction node location, but on a track leaving it. /// </summary> /// <param name="linkingTvnIndex">The index of the track leaving it</param> /// <returns>The traveller, with direction leaving this node.</returns> public Traveller PlaceTravellerAfterJunction(int linkingTvnIndex) { // it is a junction. Place a traveller onto the tracknode and find the orientation from it. try { //for broken paths the tracknode doesn't exit or the traveller cannot be placed. TrackVectorNode linkingTN = TrackDB.TrackNodes[linkingTvnIndex] as TrackVectorNode; Traveller traveller = new Traveller(TsectionDat, TrackDB.TrackNodes, linkingTN, Location, Traveller.TravellerDirection.Forward); if (linkingTN.JunctionIndexAtStart() != this.JunctionIndex) { // the tracknode is oriented in the other direction. traveller.ReverseDirection(); } return(traveller); } catch { return(null); } }
private List <string> StationNamesBetweenNodes(TrainpathNode firstNode, TrainpathNode secondNode) { var stationNames = new List <string>(); int tvnIndex = firstNode.NextMainTvnIndex; if (tvnIndex < 0) { return(stationNames); } TrackVectorNode tvn = trackDB.TrackNodes[tvnIndex] as TrackVectorNode; if (tvn == null) { return(stationNames); } if (tvn.TrackItemIndices == null) { return(stationNames); } foreach (int trackItemIndex in tvn.TrackItemIndices) { TrackItem trItem = trackDB.TrackItems[trackItemIndex]; if (trItem is PlatformItem) { var traveller = new Traveller(tsectionDat, trackDB.TrackNodes, tvn, trItem.Location, Traveller.TravellerDirection.Forward); if (traveller != null) { var platformNode = new TrainpathVectorNode(firstNode, traveller); if (platformNode.IsBetween(firstNode, secondNode)) { PlatformItem platform = trItem as PlatformItem; stationNames.Add(platform.Station); } } } } return(stationNames); }
/// <summary> /// Determine the length of the section along the track. /// </summary> /// <param name="tn">The current tracknode, which needs to be a vector node</param> /// <param name="tvsi">The track vector section index</param> private float SectionLengthAlongTrack(TrackVectorNode tn, int tvsi) { float fullSectionLength; TrackVectorSection tvs = tn.TrackVectorSections[tvsi]; TrackSection trackSection = tsectionDat.TrackSections.Get(tvs.SectionIndex); if (trackSection == null) { return(100); // need to return something. Not easy to recover } if (trackSection.Curved) { fullSectionLength = trackSection.Radius * Math.Abs(Microsoft.Xna.Framework.MathHelper.ToRadians(trackSection.Angle)); } else { fullSectionLength = trackSection.Length; } return(fullSectionLength); }
//check TDB for long curves and determine each section's position/elev in the curve public SuperElevation(Simulator simulator) { if (null == simulator) { throw new ArgumentNullException(nameof(simulator)); } Curves = new List <List <TrackVectorSection> >(); Sections = new Dictionary <int, List <TrackVectorSection> >(); MaximumAllowedM = 0.07f + simulator.Settings.UseSuperElevation / 100f;//max allowed elevation controlled by user setting List <TrackVectorSection> SectionList = new List <TrackVectorSection>(); foreach (TrackNode node in simulator.TrackDatabase.TrackDB.TrackNodes) { TrackVectorNode trackVectorNode = node as TrackVectorNode; if (trackVectorNode == null) { continue; } bool StartCurve = false; int CurveDir = 0; float Len = 0.0f; SectionList.Clear(); int i = 0; int count = trackVectorNode.TrackVectorSections.Length; foreach (TrackVectorSection section in trackVectorNode.TrackVectorSections)//loop all curves { i++; TrackSection sec = simulator.TSectionDat.TrackSections.Get(section.SectionIndex); if (sec == null) { continue; } if (Math.Abs(sec.Width - (simulator.Settings.SuperElevationGauge / 1000f)) > 0.2) { continue;//the main route has a gauge different than mine } float angle = sec.Angle; if (sec.Curved && !angle.AlmostEqual(0f, 0.01f)) //a good curve { if (i == 1 || i == count) { //if (theCurve.Radius * (float)Math.Abs(theCurve.Angle * 0.0174) < 15f) continue; } //do not want the first and last piece of short curved track to be in the curve (they connected to switches) if (!StartCurve) //we are beginning a curve { StartCurve = true; CurveDir = Math.Sign(sec.Angle); Len = 0f; } else if (CurveDir != Math.Sign(sec.Angle)) //we are in curve, but bending different dir { MarkSections(simulator, SectionList, Len); //treat the sections encountered so far, then restart with other dir CurveDir = Math.Sign(sec.Angle); SectionList.Clear(); Len = 0f; //StartCurve remains true as we are still in a curve } Len += sec.Radius * (float)Math.Abs(MathHelper.ToRadians(sec.Angle)); SectionList.Add(section); } else //meet a straight line { if (StartCurve) //we are in a curve, need to finish { MarkSections(simulator, SectionList, Len); Len = 0f; SectionList.Clear(); } StartCurve = false; } } if (StartCurve) // we are in a curve after looking at every section { MarkSections(simulator, SectionList, Len); } SectionList.Clear(); } }
public TrackEndSegment(TrackEndNode trackEndNode, TrackVectorNode connectedVectorNode, TrackSections sections) { ref readonly WorldLocation location = ref trackEndNode.UiD.Location;
/// <summary> /// Determine the ChartPoints from the startNode (included) until but not including the endNode=startNode.NextMainNode /// Each tracksection-begin should be a new point /// </summary> /// <param name="thisNode">The node to start with</param> /// <remarks>The assumption is that the two trainpath nodes only have a single tracknode connecting them</remarks> /// <returns>At least one new chart point</returns> private IEnumerable <PathChartPoint> DetermineChartPoints(TrainpathNode thisNode) { // The track consists of a number of sections. These sections might be along the direction we are going in (isForward) or not // The first point (belonging to currentNode) is the first we return, and possibly the only one. // Any new points we are going to add are all at the boundaries of sections // From the track database we get the (height) data only at start of a section. // If we are moving forward the height at the section boundary is coming from the section just after the boundary // If we are moving reverse the height at the section boundary is coming from the section just before the boundary; var newPoints = new List <PathChartPoint>(); TrainpathNode nextNode = thisNode.NextMainNode; if (nextNode == null) { PathChartPoint singlePoint = new PathChartPoint(thisNode); newPoints.Add(singlePoint); return(newPoints); } if (thisNode.IsBroken || nextNode.IsBroken || thisNode.NextMainTvnIndex == -1) { PathChartPoint singlePoint = CreateBrokenChartPoint(thisNode, nextNode); newPoints.Add(singlePoint); return(newPoints); } TrackNode tn = trackDB.TrackNodes[thisNode.NextMainTvnIndex]; TrackVectorNode vectorNode = tn as TrackVectorNode; var trackItemsInTracknode = trackItems.GetItemsInTracknode(tn); DetermineSectionDetails(thisNode, nextNode, tn, out bool isForward, out int tvsiStart, out float sectionOffsetStart); DetermineSectionDetails(nextNode, thisNode, tn, out bool isReverse, out int tvsiStop, out float sectionOffsetStop); float height; if (isForward) { // We add points in reverse order, so starting at the last section and its index float sectionOffsetNext = sectionOffsetStop; for (int tvsi = tvsiStop; tvsi > tvsiStart; tvsi--) { height = vectorNode.TrackVectorSections[tvsi].Location.Location.Y; AddPointAndTrackItems(newPoints, vectorNode, trackItemsInTracknode, isForward, height, tvsi, 0, sectionOffsetNext); sectionOffsetNext = SectionLengthAlongTrack(vectorNode, tvsi - 1); } //Also works in case this is the only point we are adding height = thisNode.Location.Location.Y; AddPointAndTrackItems(newPoints, vectorNode, trackItemsInTracknode, isForward, height, tvsiStart, sectionOffsetStart, sectionOffsetNext); } else { //reverse // We add points in reverse order, so starting at the first section and its index float sectionOffsetNext = sectionOffsetStop; for (int tvsi = tvsiStop; tvsi < tvsiStart; tvsi++) { // The height needs to come from the end of the section, so the where the next section starts. And we only know the height at the start. height = vectorNode.TrackVectorSections[tvsi + 1].Location.Location.Y; AddPointAndTrackItems(newPoints, vectorNode, trackItemsInTracknode, isForward, height, tvsi, sectionOffsetNext, SectionLengthAlongTrack(vectorNode, tvsi)); sectionOffsetNext = 0; } //Also works in case this is the only point we are adding height = thisNode.Location.Location.Y; AddPointAndTrackItems(newPoints, vectorNode, trackItemsInTracknode, isForward, height, tvsiStart, sectionOffsetNext, sectionOffsetStart); } newPoints.Reverse(); return(newPoints); }
/// <summary> /// Draw a path on a vector node, meaning that the vector node will be drawn (possibly partly), in path colors /// </summary> /// <param name="drawArea">Area to draw upon</param> /// <param name="colors">Colorscheme to use</param> /// <param name="currentNode">Current path node</param> /// <param name="nextNode">Next path Node</param> /// <param name="TvnIndex">The index of the track vector node that is between the two path nodes</param> /// <remarks>Note that it is not clear yet whether the direction of current to next is the same as the /// direction of the vector node</remarks> private void DrawPathOnVectorNode(DrawArea drawArea, ColorScheme colors, TrainpathNode currentNode, TrainpathNode nextNode, int TvnIndex) { if (currentNode.IsBrokenOffTrack || nextNode.IsBrokenOffTrack || (TvnIndex == -1)) { DrawPathBrokenNode(drawArea, colors, currentNode, nextNode); return; } TrackVectorNode tvn = trackDB.TrackNodes[TvnIndex] as TrackVectorNode; TrainpathJunctionNode nextJunctionNode = nextNode as TrainpathJunctionNode; TrainpathVectorNode nextVectorNode = nextNode as TrainpathVectorNode; //Default situation (and most occuring) is to draw the complete vector node int tvsiStart = 0; int tvsiStop = tvn.TrackVectorSections.Length - 1; float sectionOffsetStart = 0; float sectionOffsetStop = -1; if (currentNode is TrainpathJunctionNode) { // If both ends are junctions, just draw the full track. Otherwise: if (nextVectorNode != null) { // Draw from the current junction node to the next mid-point node if (nextVectorNode.IsEarlierOnTrackThan(currentNode)) { // trackvectornode is oriented the other way as path tvsiStart = nextVectorNode.TrackVectorSectionIndex; sectionOffsetStart = nextVectorNode.TrackSectionOffset; } else { // trackvectornode is oriented in the same way as path tvsiStop = nextVectorNode.TrackVectorSectionIndex; sectionOffsetStop = nextVectorNode.TrackSectionOffset; } } } else { TrainpathVectorNode currentVectorNode = currentNode as TrainpathVectorNode; if (nextJunctionNode != null) { // Draw from current mid-point node to next junction node if (currentVectorNode.IsEarlierOnTrackThan(nextNode)) { // trackvectornode is oriented in the same way as path tvsiStart = currentVectorNode.TrackVectorSectionIndex; sectionOffsetStart = currentVectorNode.TrackSectionOffset; } else { // trackvectornode is oriented the other way around. tvsiStop = currentVectorNode.TrackVectorSectionIndex; sectionOffsetStop = currentVectorNode.TrackSectionOffset; } } if (nextVectorNode != null) { // Draw from a current vector node to the next vector node, e.g. for multiple wait points if (currentVectorNode.IsEarlierOnTrackThan(nextVectorNode)) { // from current to next is in the direction of the vector node tvsiStart = currentVectorNode.TrackVectorSectionIndex; tvsiStop = nextVectorNode.TrackVectorSectionIndex; sectionOffsetStart = currentVectorNode.TrackSectionOffset; sectionOffsetStop = nextVectorNode.TrackSectionOffset; } else { // from next to current is in the direction of the vector node tvsiStart = nextVectorNode.TrackVectorSectionIndex; tvsiStop = currentVectorNode.TrackVectorSectionIndex; sectionOffsetStart = nextVectorNode.TrackSectionOffset; sectionOffsetStop = currentVectorNode.TrackSectionOffset; } } } DrawVectorNode(drawArea, tvn, colors, tvsiStart, tvsiStop, sectionOffsetStart, sectionOffsetStop); }
private void AddTrackSegments() { double minX = double.MaxValue, minY = double.MaxValue, maxX = double.MinValue, maxY = double.MinValue; List <TrackSegment> trackSegments = new List <TrackSegment>(); List <TrackEndSegment> endSegments = new List <TrackEndSegment>(); List <JunctionSegment> junctionSegments = new List <JunctionSegment>(); List <TrackSegment> roadSegments = new List <TrackSegment>(); List <TrackEndSegment> roadEndSegments = new List <TrackEndSegment>(); foreach (TrackNode trackNode in trackDB?.TrackNodes ?? Enumerable.Empty <TrackNode>()) { switch (trackNode) { case TrackEndNode trackEndNode: TrackVectorNode connectedVectorNode = trackDB.TrackNodes[trackEndNode.TrackPins[0].Link] as TrackVectorNode; endSegments.Add(new TrackEndSegment(trackEndNode, connectedVectorNode, trackSectionsFile.TrackSections)); break; case TrackVectorNode trackVectorNode: foreach (TrackVectorSection trackVectorSection in trackVectorNode.TrackVectorSections) { TrackSection trackSection = trackSectionsFile.TrackSections.Get(trackVectorSection.SectionIndex); if (trackSection != null) { trackSegments.Add(new TrackSegment(trackVectorSection, trackSection, trackVectorNode.Index)); } } break; case TrackJunctionNode trackJunctionNode: foreach (TrackPin pin in trackJunctionNode.TrackPins) { if (trackDB.TrackNodes[pin.Link] is TrackVectorNode vectorNode && vectorNode.TrackVectorSections.Length > 0) { TrackVectorSection item = pin.Direction == Common.TrackDirection.Reverse ? vectorNode.TrackVectorSections.First() : vectorNode.TrackVectorSections.Last(); } } junctionSegments.Add(new JunctionSegment(trackJunctionNode)); break; } } TrackSegments = new TileIndexedList <TrackSegment, Tile>(trackSegments); JunctionSegments = new TileIndexedList <JunctionSegment, Tile>(junctionSegments); TrackEndSegments = new TileIndexedList <TrackEndSegment, Tile>(endSegments); TrackNodeSegments = trackSegments.GroupBy(t => t.TrackNodeIndex).ToDictionary(i => i.Key, i => i.ToList()); foreach (TrackNode trackNode in roadTrackDB?.TrackNodes ?? Enumerable.Empty <TrackNode>()) { switch (trackNode) { case TrackEndNode trackEndNode: TrackVectorNode connectedVectorNode = roadTrackDB.TrackNodes[trackEndNode.TrackPins[0].Link] as TrackVectorNode; roadEndSegments.Add(new RoadEndSegment(trackEndNode, connectedVectorNode, trackSectionsFile.TrackSections)); break; case TrackVectorNode trackVectorNode: foreach (TrackVectorSection trackVectorSection in trackVectorNode.TrackVectorSections) { TrackSection trackSection = trackSectionsFile.TrackSections.Get(trackVectorSection.SectionIndex); if (trackSection != null) { roadSegments.Add(new RoadSegment(trackVectorSection, trackSection, trackVectorNode.Index)); } } break; } } RoadSegments = new TileIndexedList <RoadSegment, Tile>(roadSegments); RoadEndSegments = new TileIndexedList <RoadEndSegment, Tile>(roadEndSegments); RoadTrackNodeSegments = roadSegments.GroupBy(t => t.TrackNodeIndex).ToDictionary(i => i.Key, i => i.ToList()); Tiles = new TileIndexedList <GridTile, Tile>( TrackSegments.Select(d => d.Tile as ITile).Distinct() .Union(TrackEndSegments.Select(d => d.Tile as ITile).Distinct()) .Union(RoadSegments.Select(d => d.Tile as ITile).Distinct()) .Union(RoadEndSegments.Select(d => d.Tile as ITile).Distinct()) .Select(t => new GridTile(t))); if (Tiles.Count == 1) { foreach (TrackEndSegment trackEndSegment in TrackEndSegments) { minX = Math.Min(minX, trackEndSegment.Location.X); minY = Math.Min(minY, trackEndSegment.Location.Y); maxX = Math.Max(maxX, trackEndSegment.Location.X); maxY = Math.Max(maxY, trackEndSegment.Location.Y); } } else { minX = Math.Min(minX, Tiles[0][0].Tile.X); maxX = Math.Max(maxX, Tiles[Tiles.Count - 1][0].Tile.X); foreach (GridTile tile in Tiles) { minY = Math.Min(minY, tile.Tile.Z); maxY = Math.Max(maxY, tile.Tile.Z); } minX = minX * WorldLocation.TileSize - WorldLocation.TileSize / 2; maxX = maxX * WorldLocation.TileSize + WorldLocation.TileSize / 2; minY = minY * WorldLocation.TileSize - WorldLocation.TileSize / 2; maxY = maxY * WorldLocation.TileSize + WorldLocation.TileSize / 2; } Bounds = new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY)); }