// create route gradient profile internal static Bitmap CreateRouteGradientProfile(int Width, int Height) { // find first and last used element based on stations int n = TrackManager.CurrentTrack.Elements.Length; int n0 = n - 1; int n1 = 0; for (int i = 0; i < TrackManager.CurrentTrack.Elements.Length; i++) { for (int j = 0; j < TrackManager.CurrentTrack.Elements[i].Events.Length; j++) { if (TrackManager.CurrentTrack.Elements[i].Events[j] is TrackManager.StationStartEvent) { if (i < n0) { n0 = i; } if (i > n1) { n1 = i; } } } } n0 -= 4; n1 += 8; if (n0 < 0) { n0 = 0; } if (n1 >= TrackManager.CurrentTrack.Elements.Length) { n1 = TrackManager.CurrentTrack.Elements.Length - 1; } if (n1 <= n0) { n1 = n0 + 1; } // find dimensions double y0 = double.PositiveInfinity, y1 = double.NegativeInfinity; for (int i = n0; i <= n1; i++) { double y = TrackManager.CurrentTrack.Elements[i].WorldPosition.Y; if (y < y0) { y0 = y; } if (y > y1) { y1 = y; } } if (y0 >= y1 - 1.0) { y0 = y1 - 1.0; } double nd = 1.0 / (double)(n1 - n0); double yd = 1.0 / (double)(y1 - y0); double ox = 8.0, oy = 8.0; double w = (double)(Width - 16); double h = (double)(Height - 32); // create bitmap Bitmap b = new Bitmap(Width, Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb); Graphics g = Graphics.FromImage(b); g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; g.Clear(Color.White); // draw below sea level { double y = oy + h * (1.0 - 0.5 * (double)(-Game.RouteInitialElevation - y0) * yd); double x0 = ox - w * (double)(n0) * nd; double x1 = ox + w * (double)(n - n0) * nd; g.FillRectangle(Brushes.PaleGoldenrod, (float)x0, (float)y, (float)x1, (float)(oy + h) - (float)y); g.DrawLine(Pens.Gray, (float)x0, (float)y, (float)x1, (float)y); } // draw route path { PointF[] p = new PointF[n + 2]; p[0] = new PointF((float)(ox - w * (double)n0 * nd), (float)(oy + h)); for (int i = 0; i < n; i++) { double x = ox + w * (double)(i - n0) * nd; double y = oy + h * (1.0 - 0.5 * (double)(TrackManager.CurrentTrack.Elements[i].WorldPosition.Y - y0) * yd); p[i + 1] = new PointF((float)x, (float)y); } p[n + 1] = new PointF((float)(ox + w * (double)(n - n0 - 1) * nd), (float)(oy + h)); g.FillPolygon(Brushes.Tan, p); for (int i = 1; i < n; i++) { g.DrawLine(Pens.Black, p[i], p[i + 1]); } g.DrawLine(Pens.Black, 0.0f, (float)(oy + h), (float)Width, (float)(oy + h)); } // draw station names { Font f = new Font(FontFamily.GenericSansSerif, 12.0f, GraphicsUnit.Pixel); StringFormat m = new StringFormat(); for (int i = 0; i < n; i++) { for (int j = 0; j < TrackManager.CurrentTrack.Elements[i].Events.Length; j++) { if (TrackManager.CurrentTrack.Elements[i].Events[j] is TrackManager.StationStartEvent) { TrackManager.StationStartEvent e = (TrackManager.StationStartEvent)TrackManager.CurrentTrack.Elements[i].Events[j]; if (Game.Stations[e.StationIndex].Name != string.Empty) { bool stop = Game.PlayerStopsAtStation(e.StationIndex); if (Interface.IsJapanese(Game.Stations[e.StationIndex].Name)) { m.Alignment = StringAlignment.Near; m.LineAlignment = StringAlignment.Near; double x = ox + w * (double)(i - n0) * nd; double y = oy + h * (1.0 - 0.5 * (double)(TrackManager.CurrentTrack.Elements[i].WorldPosition.Y - y0) * yd); string t = Game.Stations[e.StationIndex].Name; float tx = 0.0f, ty = (float)oy; for (int k = 0; k < t.Length; k++) { SizeF s = g.MeasureString(t.Substring(k, 1), f, 65536, StringFormat.GenericTypographic); if (s.Width > tx) { tx = s.Width; } } for (int k = 0; k < t.Length; k++) { g.DrawString(t.Substring(k, 1), f, stop ? Brushes.Black : Brushes.LightGray, (float)x - 0.5f * tx, ty); SizeF s = g.MeasureString(t.Substring(k, 1), f, 65536, StringFormat.GenericTypographic); ty += s.Height; } g.DrawLine(stop ? Pens.Gray : Pens.LightGray, new PointF((float)x, ty + 4.0f), new PointF((float)x, (float)y)); } else { m.Alignment = StringAlignment.Far; m.LineAlignment = StringAlignment.Near; double x = ox + w * (double)(i - n0) * nd; double y = oy + h * (1.0 - 0.5 * (double)(TrackManager.CurrentTrack.Elements[i].WorldPosition.Y - y0) * yd); g.RotateTransform(-90.0f); g.TranslateTransform((float)x, (float)oy, System.Drawing.Drawing2D.MatrixOrder.Append); g.DrawString(Game.Stations[e.StationIndex].Name, f, stop ? Brushes.Black : Brushes.LightGray, new PointF(0.0f, -5.0f), m); g.ResetTransform(); SizeF s = g.MeasureString(Game.Stations[e.StationIndex].Name, f); g.DrawLine(stop ? Pens.Gray : Pens.LightGray, new PointF((float)x, (float)(oy + s.Width + 4)), new PointF((float)x, (float)y)); } } } } } } // draw route markers { Font f = new Font(FontFamily.GenericSansSerif, 10.0f, GraphicsUnit.Pixel); Font fs = new Font(FontFamily.GenericSansSerif, 9.0f, GraphicsUnit.Pixel); StringFormat m = new StringFormat(); m.Alignment = StringAlignment.Far; m.LineAlignment = StringAlignment.Center; System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; int k = 48 * n / Width; for (int i = 0; i < n; i += k) { double x = ox + w * (double)(i - n0) * nd; double y = (double)(TrackManager.CurrentTrack.Elements[i].WorldPosition.Y - y0) * yd; if (x < w) { string t = ((int)Math.Round(TrackManager.CurrentTrack.Elements[i].StartingTrackPosition)).ToString(Culture); g.DrawString(t + "m", f, Brushes.Black, (float)x, (float)(oy + h + 6.0)); } { y = oy + h * (1.0 - 0.5 * y) + 2.0f; string t = ((int)Math.Round(Game.RouteInitialElevation + TrackManager.CurrentTrack.Elements[i].WorldPosition.Y)).ToString(Culture); SizeF s = g.MeasureString(t, fs); if (y < oy + h - (double)s.Width - 10.0) { g.RotateTransform(-90.0f); g.TranslateTransform((float)x, (float)y + 4.0f, System.Drawing.Drawing2D.MatrixOrder.Append); g.DrawString(t + "m", fs, Brushes.Black, 0.0f, 0.0f, m); g.ResetTransform(); g.DrawLine(Pens.Gray, (float)x, (float)(y + s.Width + 12.0), (float)x, (float)(oy + h)); } } } } // finalize return(b); }
// // CREATE GRADIENT PROFILE // /// <summary>Creates the route gradient profile.</summary> /// <returns>The route gradient profile as Bitmap.</returns> /// <param name="Width">The width of the bitmap to create.</param> /// <param name="Height">The height of the bitmap to create.</param> /// <param name="inGame"><c>true</c> = bitmap for in-game overlay | <c>false</c> = for standard window.</param> internal static Bitmap CreateRouteGradientProfile(int Width, int Height, bool inGame) { if (TrackManager.CurrentTrack.Elements.Length > 36 && Game.Stations.Length == 0) { // If we have track elements, but no stations, show a specific error message, rather // than the more generic one thrown later // NOTE: Will throw the generic error message on routes shorter than 900m with no stations throw new Exception(Interface.GetInterfaceString("errors_route_corrupt_nostations")); } // Track elements are assumed to be all of the same length, and this length // is used as measure unit, rather than computing the incremental track length // in any 'real world' unit (like m). // HORIZONTAL RANGE: find first and last used element based on stations int n, n0, n1; RouteRange(out n, out n0, out n1); // VERTICAL RANGE double y0 = double.PositiveInfinity, y1 = double.NegativeInfinity; for (int i = n0; i <= n1; i++) { double y = TrackManager.CurrentTrack.Elements[i].WorldPosition.Y; if (y < y0) { y0 = y; } if (y > y1) { y1 = y; } } if (y0 >= y1 - 1.0) { y0 = y1 - 1.0; } // allow for some padding around actual data double ox = LeftPad, oy = TopPad; double w = (double)(Width - (LeftPad + RightPad)); double h = (double)(Height - (TopPad + BottomPad + TrackOffsPad)); // horizontal and vertical scale double nd = w / (double)(n1 - n0); double yd = h / (double)(y1 - y0); // set total bitmap track position range; used by in-game profile to place // the current position of the trains; as the train positions are known as track positions, // actual track positions are needed here, rather than indices into the track element array. double minX = TrackManager.CurrentTrack.Elements[n0].StartingTrackPosition; double maxX = TrackManager.CurrentTrack.Elements[n1].StartingTrackPosition; double offX = ox * (maxX - minX) / w; lastGradientMinTrack = (int)(minX - offX); lastGradientMaxTrack = (int)(maxX + offX); // BITMAP (in-game display needs transparency) Bitmap b = new Bitmap(Width, Height, inGame ? System.Drawing.Imaging.PixelFormat.Format32bppArgb : System.Drawing.Imaging.PixelFormat.Format24bppRgb); Graphics g = Graphics.FromImage(b); g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; int mode = inGame ? 1 : 0; g.Clear(mapColors[mode].background); // BELOW SEA LEVEL { double y = oy + (h - 0.5 * (double)(-Game.RouteInitialElevation - y0) * yd); double x0 = ox - (double)(0) * nd; double x1 = ox + (double)(n1 - n0) * nd; g.FillRectangle(mapColors[mode].belowSeaFill, (float)x0, (float)y, (float)x1, (float)(oy + h) - (float)y); g.DrawLine(mapColors[mode].belowSeaBrdr, (float)x0, (float)y, (float)x1, (float)y); } // GRADIENT PROFILE { n = n1 - n0 + 1; PointF[] p = new PointF[n + 2]; p[0] = new PointF((float)ox, (float)(oy + h)); for (int i = n0; i <= n1; i++) { double x = ox + (double)(i - n0) * nd; double y = oy + (h - 0.5 * (double)(TrackManager.CurrentTrack.Elements[i].WorldPosition.Y - y0) * yd); p[i - n0 + 1] = new PointF((float)x, (float)y); } p[n + 1] = new PointF((float)(ox + (double)(n - 1) * nd), (float)(oy + h)); g.FillPolygon(mapColors[mode].elevFill, p); for (int i = 1; i < n; i++) { g.DrawLine(mapColors[mode].elevBrdr, p[i], p[i + 1]); } g.DrawLine(mapColors[mode].elevBrdr, 0.0f, (float)(oy + h), (float)Width, (float)(oy + h)); } // STATION NAMES { Font f = new Font(FontFamily.GenericSansSerif, 12.0f, GraphicsUnit.Pixel); StringFormat m = new StringFormat(); for (int i = n0; i <= n1; i++) { for (int j = 0; j < TrackManager.CurrentTrack.Elements[i].Events.Length; j++) { if (TrackManager.CurrentTrack.Elements[i].Events[j] is TrackManager.StationStartEvent) { TrackManager.StationStartEvent e = (TrackManager.StationStartEvent)TrackManager.CurrentTrack.Elements[i].Events[j]; if (Game.Stations[e.StationIndex].Name != string.Empty) { bool stop = Game.PlayerStopsAtStation(e.StationIndex); if (Interface.IsJapanese(Game.Stations[e.StationIndex].Name)) { m.Alignment = StringAlignment.Near; m.LineAlignment = StringAlignment.Near; double x = ox + (double)(i - n0) * nd; double y = oy + (h - 0.5 * (double)(TrackManager.CurrentTrack.Elements[i].WorldPosition.Y - y0) * yd); string t = Game.Stations[e.StationIndex].Name; float tx = 0.0f, ty = (float)oy; for (int k = 0; k < t.Length; k++) { SizeF s = g.MeasureString(t.Substring(k, 1), f, 65536, StringFormat.GenericTypographic); if (s.Width > tx) { tx = s.Width; } } for (int k = 0; k < t.Length; k++) { g.DrawString(t.Substring(k, 1), f, stop ? mapColors[mode].actNameText : mapColors[mode].inactNameText, (float)x - 0.5f * tx, ty); SizeF s = g.MeasureString(t.Substring(k, 1), f, 65536, StringFormat.GenericTypographic); ty += s.Height; } g.DrawLine(stop ? mapColors[mode].actNameBrdr : mapColors[mode].inactNameBrdr, new PointF((float)x, ty + 4.0f), new PointF((float)x, (float)y)); } else { m.Alignment = StringAlignment.Far; m.LineAlignment = StringAlignment.Near; double x = ox + (double)(i - n0) * nd; double y = oy + (h - 0.5 * (double)(TrackManager.CurrentTrack.Elements[i].WorldPosition.Y - y0) * yd); g.RotateTransform(-90.0f); g.TranslateTransform((float)x, (float)oy, System.Drawing.Drawing2D.MatrixOrder.Append); g.DrawString(Game.Stations[e.StationIndex].Name, f, stop ? mapColors[mode].actNameText : mapColors[mode].inactNameText, new PointF(0.0f, -5.0f), m); g.ResetTransform(); SizeF s = g.MeasureString(Game.Stations[e.StationIndex].Name, f); g.DrawLine(stop ? mapColors[mode].actNameBrdr : mapColors[mode].inactNameBrdr, new PointF((float)x, (float)(oy + s.Width + 4)), new PointF((float)x, (float)y)); } } } } } } // ROUTE MARKERS { Font f = new Font(FontFamily.GenericSansSerif, 10.0f, GraphicsUnit.Pixel); Font fs = new Font(FontFamily.GenericSansSerif, 9.0f, GraphicsUnit.Pixel); StringFormat m = new StringFormat { Alignment = StringAlignment.Far, LineAlignment = StringAlignment.Center }; System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; int k = TrackOffDist * n / Width; if (k == 0) { //If k is equal to zero, this generally means that the WithTrack section is missing from our routefile //Adding zero to the loop control variable will also produce an infinite loop, so that's a bad idea too throw new Exception(Interface.GetInterfaceString("errors_route_corrupt_withtrack")); } for (int i = n0; i <= n1; i += k) { double x = ox + (double)(i - n0) * nd; double y = (double)(TrackManager.CurrentTrack.Elements[i].WorldPosition.Y - y0) * yd; // track offset label if (x < w) { string t = ((int)Math.Round(TrackManager.CurrentTrack.Elements[i].StartingTrackPosition)).ToString(Culture); g.DrawString(t + "m", f, mapColors[mode].actNameText, (float)x, (float)(oy + h + TrackOffY)); } // route height at track offset (with measure and vertical line) { y = oy + (h - 0.5 * y) + 2.0f; string t = ((int)Math.Round(Game.RouteInitialElevation + TrackManager.CurrentTrack.Elements[i].WorldPosition.Y)).ToString(Culture); SizeF s = g.MeasureString(t, fs); if (y < oy + h - (double)s.Width - 10.0) { g.RotateTransform(-90.0f); g.TranslateTransform((float)x, (float)y + 4.0f, System.Drawing.Drawing2D.MatrixOrder.Append); g.DrawString(t + "m", fs, mapColors[mode].actNameText, 0.0f, 0.0f, m); g.ResetTransform(); g.DrawLine(mapColors[mode].inactNameBrdr, (float)x, (float)(y + s.Width + 12.0), (float)x, (float)(oy + h)); } } } } // finalize return(b); }
// collect data internal static void CollectData(out Table Table) { System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; Table.Stations = new Station[16]; Table.Tracks = new Track[16]; int n = 0; double Limit = -1.0, LastLimit = 6.94444444444444; int LastArrivalHours = -1, LastDepartureHours = -1; double LastTime = -1.0; for (int i = 0; i < TrackManager.CurrentTrack.Elements.Length; i++) { for (int j = 0; j < TrackManager.CurrentTrack.Elements[i].Events.Length; j++) { TrackManager.StationStartEvent sse = TrackManager.CurrentTrack.Elements[i].Events[j] as TrackManager.StationStartEvent; if (sse != null && Game.Stations[sse.StationIndex].Name != string.Empty) { if (Limit == -1.0) { Limit = LastLimit; } // update station if (n == Table.Stations.Length) { Array.Resize <Station>(ref Table.Stations, Table.Stations.Length << 1); } Table.Stations[n].Name = Game.Stations[sse.StationIndex].Name; Table.Stations[n].NameJapanese = Interface.IsJapanese(Game.Stations[sse.StationIndex].Name); Table.Stations[n].Pass = !Game.PlayerStopsAtStation(sse.StationIndex); Table.Stations[n].Terminal = Game.Stations[sse.StationIndex].StationType != Game.StationType.Normal; double x; if (Game.Stations[sse.StationIndex].ArrivalTime >= 0.0) { x = Game.Stations[sse.StationIndex].ArrivalTime; x -= 86400.0 * Math.Floor(x / 86400.0); int hours = (int)Math.Floor(x / 3600.0); x -= 3600.0 * (double)hours; int minutes = (int)Math.Floor(x / 60.0); x -= 60.0 * (double)minutes; int seconds = (int)Math.Floor(x); Table.Stations[n].Arrival.Hour = hours != LastArrivalHours?hours.ToString("00", Culture) : ""; Table.Stations[n].Arrival.Minute = minutes.ToString("00", Culture); Table.Stations[n].Arrival.Second = seconds.ToString("00", Culture); LastArrivalHours = hours; } else { Table.Stations[n].Arrival.Hour = ""; Table.Stations[n].Arrival.Minute = ""; Table.Stations[n].Arrival.Second = ""; } if (Game.Stations[sse.StationIndex].DepartureTime >= 0.0) { x = Game.Stations[sse.StationIndex].DepartureTime; x -= 86400.0 * Math.Floor(x / 86400.0); int hours = (int)Math.Floor(x / 3600.0); x -= 3600.0 * (double)hours; int minutes = (int)Math.Floor(x / 60.0); x -= 60.0 * (double)minutes; int seconds = (int)Math.Floor(x); Table.Stations[n].Departure.Hour = hours != LastDepartureHours?hours.ToString("00", Culture) : ""; Table.Stations[n].Departure.Minute = minutes.ToString("00", Culture); Table.Stations[n].Departure.Second = seconds.ToString("00", Culture); LastDepartureHours = hours; } else { Table.Stations[n].Departure.Hour = ""; Table.Stations[n].Departure.Minute = ""; Table.Stations[n].Departure.Second = ""; } // update track if (n >= 1) { int m = n - 1; if (m == Table.Tracks.Length) { Array.Resize <Track>(ref Table.Tracks, Table.Tracks.Length << 1); } // speed x = Math.Round(3.6 * Limit); Table.Tracks[m].Speed = x.ToString(Culture); // time if (LastTime >= 0.0) { if (Game.Stations[sse.StationIndex].ArrivalTime >= 0.0) { x = Game.Stations[sse.StationIndex].ArrivalTime; } else if (Game.Stations[sse.StationIndex].DepartureTime >= 0.0) { x = Game.Stations[sse.StationIndex].DepartureTime; } else { x = -1.0; } if (x >= 0.0) { x -= LastTime; int hours = (int)Math.Floor(x / 3600.0); x -= 3600.0 * (double)hours; int minutes = (int)Math.Floor(x / 60.0); x -= 60.0 * (double)minutes; int seconds = (int)Math.Floor(x); Table.Tracks[m].Time.Hour = hours != 0 ? hours.ToString("0", Culture) : ""; Table.Tracks[m].Time.Minute = minutes != 0 ? minutes.ToString("00", Culture) : ""; Table.Tracks[m].Time.Second = seconds != 0 ? seconds.ToString("00", Culture) : ""; } else { Table.Tracks[m].Time.Hour = ""; Table.Tracks[m].Time.Minute = ""; Table.Tracks[m].Time.Second = ""; } } else { Table.Tracks[m].Time.Hour = ""; Table.Tracks[m].Time.Minute = ""; Table.Tracks[m].Time.Second = ""; } } // update last data if (Game.Stations[sse.StationIndex].DepartureTime >= 0.0) { LastTime = Game.Stations[sse.StationIndex].DepartureTime; } else if (Game.Stations[sse.StationIndex].ArrivalTime >= 0.0) { LastTime = Game.Stations[sse.StationIndex].ArrivalTime; } else { LastTime = -1.0; } LastLimit = Limit; Limit = -1.0; n++; } if (n >= 1) { TrackManager.LimitChangeEvent lce = TrackManager.CurrentTrack.Elements[i].Events[j] as TrackManager.LimitChangeEvent; if (lce != null) { if (lce.NextSpeedLimit != double.PositiveInfinity & lce.NextSpeedLimit > Limit) { Limit = lce.NextSpeedLimit; } } } } } Array.Resize <Station>(ref Table.Stations, n); if (n >= 2) { Array.Resize <Track>(ref Table.Tracks, n - 1); } else { Table.Tracks = new Track[] { }; } }