Represents a 32-bit color with red, green, blue and alpha channels at 8 bits each.
		/// <summary>Gets the color that is half-way between the two specified colors.</summary>
		/// <param name="a">The first color.</param>
		/// <param name="b">The second color.</param>
		/// <returns>The mixed color.</returns>
		private static Color32 GetInterpolatedColor11(Color32 a, Color32 b) {
			return new Color32(
				(byte)((uint)a.R + (uint)b.R >> 1),
				(byte)((uint)a.G + (uint)b.G >> 1),
				(byte)((uint)a.B + (uint)b.B >> 1),
				(byte)((uint)a.A + (uint)b.A >> 1)
			);
		}
		/// <summary>Gets the color that is two thirds the way between the two specified colors.</summary>
		/// <param name="a">The first color.</param>
		/// <param name="b">The second color.</param>
		/// <returns>The mixed color.</returns>
		private static Color32 GetInterpolatedColor21(Color32 a, Color32 b) {
			return new Color32(
				(byte)((2 * (uint)a.R + (uint)b.R) / 3),
				(byte)((2 * (uint)a.G + (uint)b.G) / 3),
				(byte)((2 * (uint)a.B + (uint)b.B) / 3),
				(byte)((2 * (uint)a.A + (uint)b.A) / 3)
			);
		}
			internal Material(Material Prototype) {
				this.Color = Prototype.Color;
				this.EmissiveColor = Prototype.EmissiveColor;
				this.EmissiveColorUsed = Prototype.EmissiveColorUsed;
				this.TransparentColor = Prototype.TransparentColor;
				this.TransparentColorUsed = Prototype.TransparentColorUsed;
				this.DaytimeTexture = Prototype.DaytimeTexture;
				this.NighttimeTexture = Prototype.NighttimeTexture;
				this.BlendMode = Prototype.BlendMode;
				this.GlowAttenuationData = Prototype.GlowAttenuationData;
			}
			internal Material() {
				this.Color = new Color32(255, 255, 255, 255);
				this.EmissiveColor = new Color24(0, 0, 0);
				this.EmissiveColorUsed = false;
				this.TransparentColor = new Color24(0, 0, 0);
				this.TransparentColorUsed = false;
				this.DaytimeTexture = null;
				this.NighttimeTexture = null;
				this.BlendMode = World.MeshMaterialBlendMode.Normal;
				this.GlowAttenuationData = 0;
			}
			internal Material() {
				this.Color = new Color32(255, 255, 255, 255);
				this.EmissiveColor = new Color24(0, 0, 0);
				this.EmissiveColorUsed = false;
				this.TransparentColor = new Color24(0, 0, 0);
				this.TransparentColorUsed = false;
				this.DaytimeTexture = null;
				this.NighttimeTexture = null;
				this.BlendMode = World.MeshMaterialBlendMode.Normal;
				this.GlowAttenuationData = 0;
				this.TextColor = System.Drawing.Color.Black;
				this.BackgroundColor = System.Drawing.Color.White;
				this.TextPadding = new Vector2(0, 0);
				this.Font = "Arial";
			}
Exemple #6
0
		/// <summary>Creates the background color for anti-aliasing text</summary>
		/// <param name="Original">The original background color</param>
		/// <param name="SystemColor">The color of the message text</param>
		private static void CreateBackColor(Color32 Original, MessageColor SystemColor, out float R, out float G, out float B, out float A)
		{
			if (Original.R == 0 & Original.G == 0 & Original.B == 0)
			{
				switch (SystemColor)
				{
					case MessageColor.Black:
						R = 0.0f; G = 0.0f; B = 0.0f;
						break;
					case MessageColor.Gray:
						R = 0.4f; G = 0.4f; B = 0.4f;
						break;
					case MessageColor.White:
						R = 1.0f; G = 1.0f; B = 1.0f;
						break;
					case MessageColor.Red:
						R = 1.0f; G = 0.0f; B = 0.0f;
						break;
					case MessageColor.Orange:
						R = 0.9f; G = 0.7f; B = 0.0f;
						break;
					case MessageColor.Green:
						R = 0.2f; G = 0.8f; B = 0.0f;
						break;
					case MessageColor.Blue:
						R = 0.0f; G = 0.7f; B = 1.0f;
						break;
					case MessageColor.Magenta:
						R = 1.0f; G = 0.0f; B = 0.7f;
						break;
					default:
						R = 1.0f; G = 1.0f; B = 1.0f;
						break;
				}
			}
			else
			{
				R = inv255 * (float)Original.R;
				G = inv255 * (float)Original.G;
				B = inv255 * (float)Original.B;
			}
			A = inv255 * (float)Original.A;
		}
Exemple #7
0
			internal HudElement() {
				this.Subject = null;
				this.Position.X = 0.0f;
				this.Position.Y = 0.0f;
				this.Alignment.X = -1;
				this.Alignment.Y = -1;
				this.BackgroundColor = new Color32(255, 255, 255, 255);
				this.OverlayColor = new Color32(255, 255, 255, 255);
				this.TextColor = new Color32(255, 255, 255, 255);
				this.TextPosition.X = 0.0f;
				this.TextPosition.Y = 0.0f;
				this.TextAlignment.X = -1;
				this.TextAlignment.Y = 0;
				this.Font = Fonts.VerySmallFont;
				this.TextShadow = true;
				this.Text = null;
				this.Value1 = 0.0f;
				this.Value2 = 0.0f;
				this.Transition = HudTransition.None;
				this.TransitionState = 1.0;
			}
		/// <summary>Loads an ACE texture from uncompressed data.</summary>
		/// <param name="data">The uncompressed data.</param>
		/// <returns>The texture.</returns>
		private static Texture LoadFromUncompressedData(byte[] data) {
			using (MemoryStream stream = new MemoryStream(data)) {
				using (BinaryReader reader = new BinaryReader(stream)) {
					// --- header ---
					ulong identifier;
					identifier = reader.ReadUInt64();
					if (identifier != 0x40404153494D4953) {
						throw new InvalidDataException();
					}
					identifier = reader.ReadUInt64();
					if (identifier != 0x4040404040404040) {
						throw new InvalidDataException();
					}
					int unknown1 = reader.ReadInt32();
					if (unknown1 != 1) {
						throw new InvalidDataException();
					}
					int unknown2 = reader.ReadInt32();
					int width = reader.ReadInt32();
					int height = reader.ReadInt32();
					int type = reader.ReadInt32();
					/* 14 = 24 bits
					 * 16 = 24 bit 1 bit alpha
					 * 17 = 24 bit 8 bit alpha
					 * 18 = dtx1 */
					if (type != 14 & type != 16 & type != 17 & type != 18) {
						throw new InvalidDataException();
					}
					int channels = reader.ReadInt32();
					/* 3 = 24 bit
					 * 4 = 24 bits 1 bit alpha
					 * 5 = 24 bit 8 bit alpha */
					if (channels != 3 & channels != 4 & channels != 5) {
						throw new InvalidDataException();
					}
					int unknown3 = reader.ReadInt32();
					byte[] name = reader.ReadBytes(16);
					byte[] copyright = reader.ReadBytes(72);
					int unknown4 = reader.ReadInt32();
					if (channels == 3) {
						byte[] unknown = reader.ReadBytes(80);
					} else if (channels == 4) {
						byte[] unknown = reader.ReadBytes(96);
					} else if (channels == 5) {
						byte[] unknown = reader.ReadBytes(112);
					}
					// --- actual pixel data ---
					byte[] bytes = new byte[4 * width * height];
					if (type == 14 & channels == 3) {
						// --- rgb ---
						int[] streamOffsets = new int[height];
						for (int y = 0; y < height; y++) {
							streamOffsets[y] = 16 + reader.ReadInt32();
						}
						int offset = 0;
						int offsetIncrement = -4 * width + 1;
						for (int y = 0; y < height; y++) {
							stream.Position = streamOffsets[y];
							for (int x = 0; x < width; x++) {
								bytes[offset] = reader.ReadByte();
								offset += 4;
							}
							offset += offsetIncrement;
							for (int x = 0; x < width; x++) {
								bytes[offset] = reader.ReadByte();
								offset += 4;
							}
							offset += offsetIncrement;
							for (int x = 0; x < width; x++) {
								bytes[offset] = reader.ReadByte();
								offset += 4;
							}
							offset += offsetIncrement;
							for (int x = 0; x < width; x++) {
								bytes[offset] = 255;
								offset += 4;
							}
							offset -= 3;
						}
					} else if (type == 16 & channels == 4) {
						// --- rgb (1-bit transparency) ---
						int[] streamOffsets = new int[height];
						for (int y = 0; y < height; y++) {
							streamOffsets[y] = 16 + reader.ReadInt32();
						}
						int offset = 0;
						int offsetIncrement = -4 * width + 1;
						for (int y = 0; y < height; y++) {
							stream.Position = streamOffsets[y];
							for (int x = 0; x < width; x++) {
								bytes[offset] = reader.ReadByte();
								offset += 4;
							}
							offset += offsetIncrement;
							for (int x = 0; x < width; x++) {
								bytes[offset] = reader.ReadByte();
								offset += 4;
							}
							offset += offsetIncrement;
							for (int x = 0; x < width; x++) {
								bytes[offset] = reader.ReadByte();
								offset += 4;
							}
							offset += offsetIncrement;
							int value = 0;
							int counter = 0;
							for (int x = 0; x < width; x++) {
								if (counter == 0) {
									value = reader.ReadByte();
									counter = 7;
								} else {
									counter--;
								}
								bytes[offset] = (value >> 7) == 0 ? (byte)0 : (byte)255;
								value <<= 1;
								offset += 4;
							}
							offset -= 3;
						}
					} else if (type == 17 & channels == 5) {
						// --- rgb (8-bit alpha) ---
						int[] streamOffsets = new int[height];
						for (int y = 0; y < height; y++) {
							streamOffsets[y] = 16 + reader.ReadInt32();
						}
						int offset = 0;
						int offsetIncrement = -4 * width + 1;
						for (int y = 0; y < height; y++) {
							stream.Position = streamOffsets[y];
							for (int x = 0; x < width; x++) {
								bytes[offset] = reader.ReadByte();
								offset += 4;
							}
							offset += offsetIncrement;
							for (int x = 0; x < width; x++) {
								bytes[offset] = reader.ReadByte();
								offset += 4;
							}
							offset += offsetIncrement;
							for (int x = 0; x < width; x++) {
								bytes[offset] = reader.ReadByte();
								offset += 4;
							}
							offset += offsetIncrement;
							stream.Position += (width + 7) / 8;
							for (int x = 0; x < width; x++) {
								bytes[offset] = reader.ReadByte();
								offset += 4;
							}
							offset -= 3;
						}
					} else if (type == 18 & (channels == 3 | channels == 4)) {
						// --- dxt1 ---
						int mipmapOffset0 = reader.ReadInt32() + 20;
						stream.Position = mipmapOffset0;
						int offset = 0;
						int offsetIncrementY = 12 * width;
						int offsetIncrementX = -16 * width + 16;
						int offsetIncrementDy = 4 * width - 16;
						Color32[] colors = new Color32[4];
						Color32 black = channels == 4 ? Color32.Transparent : Color32.Black;
						for (int y = 0; y < height; y += 4) {
							for (int x = 0; x < width; x += 4) {
								ushort entry0 = reader.ReadUInt16();
								ushort entry1 = reader.ReadUInt16();
								colors[0] = GetColor(entry0);
								colors[1] = GetColor(entry1);
								if (entry0 > entry1) {
									colors[2] = GetInterpolatedColor21(colors[0], colors[1]);
									colors[3] = GetInterpolatedColor12(colors[0], colors[1]);
								} else {
									colors[2] = GetInterpolatedColor11(colors[0], colors[1]);
									colors[3] = black;
								}
								uint lookup = reader.ReadUInt32();
								for (int dy = 0; dy < 4; dy++) {
									for (int dx = 0; dx < 4; dx++) {
										uint index = lookup & 3;
										lookup >>= 2;
										bytes[offset + 0] = colors[index].R;
										bytes[offset + 1] = colors[index].G;
										bytes[offset + 2] = colors[index].B;
										bytes[offset + 3] = colors[index].A;
										offset += 4;
									}
									offset += offsetIncrementDy;
								}
								offset += offsetIncrementX;
							}
							offset += offsetIncrementY;
						}
					} else {
						// --- not supported ---
						throw new NotSupportedException();
					}
					// --- return texture ---
					return new Texture(width, height, 32, bytes);
				}
			}
		}
Exemple #9
0
		// parse panel config
		internal static void ParsePanelConfig(string TrainPath, System.Text.Encoding Encoding, TrainManager.Train Train) {
			// read lines
			System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture;
			string FileName = OpenBveApi.Path.CombineFile(TrainPath, "panel.cfg");
			string[] Lines = System.IO.File.ReadAllLines(FileName, Encoding);
			for (int i = 0; i < Lines.Length; i++) {
				Lines[i] = Lines[i].Trim();
				int j = Lines[i].IndexOf(';');
				if (j >= 0) {
					Lines[i] = Lines[i].Substring(0, j).TrimEnd();
				}
			}
			// initialize
			double FullWidth = 480, FullHeight = 440, SemiHeight = 240;
			double AspectRatio = FullWidth / FullHeight;
			double WorldWidth, WorldHeight;
			if (Screen.Width >= Screen.Height) {
				WorldWidth = 2.0 * Math.Tan(0.5 * World.HorizontalViewingAngle) * EyeDistance;
				WorldHeight = WorldWidth / AspectRatio;
			} else {
				WorldHeight = 2.0 * Math.Tan(0.5 * World.VerticalViewingAngle) * EyeDistance;
				WorldWidth = WorldHeight * AspectRatio;
			}
			World.CameraRestrictionBottomLeft = new Vector3(-0.5 * WorldWidth, -0.5 * WorldHeight, EyeDistance);
			World.CameraRestrictionTopRight = new Vector3(0.5 * WorldWidth, 0.5 * WorldHeight, EyeDistance);
			double WorldLeft = Train.Cars[Train.DriverCar].DriverX - 0.5 * WorldWidth;
			double WorldTop = Train.Cars[Train.DriverCar].DriverY + 0.5 * WorldHeight;
			double WorldZ = Train.Cars[Train.DriverCar].DriverZ;
			const double UpDownAngleConstant = -0.191986217719376;
			double PanelYaw = 0.0;
			double PanelPitch = UpDownAngleConstant;
			string PanelBackground = OpenBveApi.Path.CombineFile(TrainPath, "panel.bmp");
			// parse lines for panel and view
			for (int i = 0; i < Lines.Length; i++) {
				if (Lines[i].Length > 0) {
					if (Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal)) {
						string Section = Lines[i].Substring(1, Lines[i].Length - 2).Trim();
						switch (Section.ToLowerInvariant()) {
								// panel
							case "panel":
								i++; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal))) {
									int j = Lines[i].IndexOf('=');
									if (j >= 0) {
										string Key = Lines[i].Substring(0, j).TrimEnd();
										string Value = Lines[i].Substring(j + 1).TrimStart();
										switch (Key.ToLowerInvariant()) {
											case "background":
												if (Interface.ContainsInvalidPathChars(Value)) {
													Interface.AddMessage(Interface.MessageType.Error, false, "FileName contains illegal characters in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
												} else {
													if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
													PanelBackground = OpenBveApi.Path.CombineFile(TrainPath, Value);
													if (!System.IO.File.Exists(PanelBackground)) {
														Interface.AddMessage(Interface.MessageType.Error, true, "FileName " + PanelBackground + "could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													}
												}
												break;
										}
									} i++;
								} i--; break;
								// view
							case "view":
								i++; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal))) {
									int j = Lines[i].IndexOf('=');
									if (j >= 0) {
										string Key = Lines[i].Substring(0, j).TrimEnd();
										string Value = Lines[i].Substring(j + 1).TrimStart();
										switch (Key.ToLowerInvariant()) {
											case "yaw":
												{
													double yaw = 0.0;
													if (Value.Length > 0 && !Interface.TryParseDoubleVb6(Value, out yaw)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "ValueInDegrees is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														yaw = 0.0;
													}
													PanelYaw = Math.Atan(yaw);
												} break;
											case "pitch":
												{
													double pitch = 0.0;
													if (Value.Length > 0 && !Interface.TryParseDoubleVb6(Value, out pitch)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "ValueInDegrees is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														pitch = 0.0;
													}
													PanelPitch = Math.Atan(pitch) + UpDownAngleConstant;
												} break;
										}
									} i++;
								} i--; break;
						}
					}
				}
			}
			Train.Cars[Train.DriverCar].DriverYaw = PanelYaw;
			Train.Cars[Train.DriverCar].DriverPitch = PanelPitch;
			// panel
			{
				if (!System.IO.File.Exists(PanelBackground)) {
					Interface.AddMessage(Interface.MessageType.Error, true, "The panel image could not be found in " + FileName);
				} else {
					Textures.Texture t;
					Textures.RegisterTexture(PanelBackground, new OpenBveApi.Textures.TextureParameters(null, Color24.Blue), out t);
				    OpenBVEGame.RunInRenderThread(() =>
				    {
				        Textures.LoadTexture(t, Textures.OpenGlTextureWrapMode.ClampClamp); 
                    });
					double w = (double)t.Width;
					double h = (double)t.Height;
					SemiHeight = FullHeight - h;
					CreateElement(Train, 0, SemiHeight, w, h, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t, new Color32(255, 255, 255, 255), false);
				}
			}
			// parse lines for rest
			double invfac = Lines.Length == 0 ? Loading.TrainProgressCurrentWeight : Loading.TrainProgressCurrentWeight / (double)Lines.Length;
			for (int i = 0; i < Lines.Length; i++) {
				Loading.TrainProgress = Loading.TrainProgressCurrentSum + invfac * (double)i;
				if ((i & 7) == 0) {
					System.Threading.Thread.Sleep(1);
					if (Loading.Cancel) return;
				}
				if (Lines[i].Length != 0) {
					if (Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal)) {
						string Section = Lines[i].Substring(1, Lines[i].Length - 2).Trim();
						switch (Section.ToLowerInvariant()) {
								// pressuregauge
							case "pressuregauge":
							case "pressuremeter":
							case "pressureindicator":
							case "圧力計":
								{
									int Type = 0;
									Color32[] NeedleColor = new Color32[] { new Color32(0, 0, 0, 255), new Color32(0, 0, 0, 255) };
									int[] NeedleType = new int[] { 0, 0 };
									double CenterX = 0.0, CenterY = 0.0, Radius = 16.0;
									string Background = null, Cover = null;
									double Angle = 0.785398163397449, Minimum = 0.0, Maximum = 1000.0;
									double UnitFactor = 1000.0;
									i++; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal))) {
										int j = Lines[i].IndexOf('='); if (j >= 0) {
											string Key = Lines[i].Substring(0, j).TrimEnd();
											string Value = Lines[i].Substring(j + 1).TrimStart();
											string[] Arguments = GetArguments(Value);
											switch (Key.ToLowerInvariant()) {
												case "type":
												case "形態":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseIntVb6(Arguments[0], out Type)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Type is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Type = 0;
													} else if (Type != 0 & Type != 1) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Type must be either 0 or 1 in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Type = 0;
													} break;
												case "lowerneedle":
												case "lowerhand":
												case "下針":
												case "upperneedle":
												case "upperhand":
												case "上針":
													int k = Key.ToLowerInvariant() == "lowerneedle" | Key.ToLowerInvariant() == "lowerhand" | Key == "下針" ? 0 : 1;
													if (Arguments.Length >= 1 && Arguments[0].Length > 0) {
														switch (Arguments[0].ToLowerInvariant()) {
															case "bc":
															case "ブレーキシリンダ":
																NeedleType[k] = 1; break;
															case "sap":
															case "直通管":
																NeedleType[k] = 2; break;
															case "bp":
															case "ブレーキ管":
															case "制動管":
																NeedleType[k] = 3; break;
															case "er":
															case "釣り合い空気溜め":
															case "釣り合い空気ダメ":
															case "つりあい空気溜め":
															case "ツリアイ空気ダメ":
																NeedleType[k] = 4; break;
															case "mr":
															case "元空気溜め":
															case "元空気ダメ":
																NeedleType[k] = 5; break;
															default:
																{
																	int a; if (!Interface.TryParseIntVb6(Arguments[0], out a)) {
																		Interface.AddMessage(Interface.MessageType.Error, false, "Subject is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
																		a = 0;
																	}
																	NeedleType[k] = a;
																} break;
														}
													}
													int r = 0, g = 0, b = 0;
													if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Interface.TryParseIntVb6(Arguments[1], out r)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "RedValue is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														r = 0;
													} else if (r < 0 | r > 255) {
														Interface.AddMessage(Interface.MessageType.Error, false, "RedValue is required to be within the range from 0 to 255 in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														r = r < 0 ? 0 : 255;
													}
													if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Interface.TryParseIntVb6(Arguments[2], out g)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "GreenValue is invalid in " + Key + " in " + Section + Key + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														g = 0;
													} else if (g < 0 | g > 255) {
														Interface.AddMessage(Interface.MessageType.Error, false, "GreenValue is required to be within the range from 0 to 255 in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														g = g < 0 ? 0 : 255;
													}
													if (Arguments.Length >= 4 && Arguments[3].Length > 0 && !Interface.TryParseIntVb6(Arguments[3], out b)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "BlueValue is invalid in " + Key + " in " + Section + Key + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														b = 0;
													} else if (b < 0 | b > 255) {
														Interface.AddMessage(Interface.MessageType.Error, false, "BlueValue is required to be within the range from 0 to 255 in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														b = b < 0 ? 0 : 255;
													}
													NeedleColor[k] = new Color32((byte)r, (byte)g, (byte)b, 255);
													break;
												case "center":
												case "中心":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out CenterX)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "X is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														CenterX = 0.0;
													} else if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[1], out CenterY)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Y is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														CenterY = 0.0;
													} break;
												case "radius":
												case "半径":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out Radius)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "ValueInPixels is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Radius = 1.0;
													} break;
												case "background":
												case "背景":
													if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
													if (Interface.ContainsInvalidPathChars(Value)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "FileName contains illegal characters in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else {
														Background = OpenBveApi.Path.CombineFile(TrainPath, Value);
														if (!System.IO.File.Exists(Background)) {
															Interface.AddMessage(Interface.MessageType.Error, true, "FileName " + Background + " could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															Background = null;
														}
													}
													break;
												case "cover":
												case "ふた":
													if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
													if (Interface.ContainsInvalidPathChars(Value)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "FileName contains illegal characters in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else {
														Cover = OpenBveApi.Path.CombineFile(TrainPath, Value);
														if (!System.IO.File.Exists(Cover)) {
															Interface.AddMessage(Interface.MessageType.Error, true, "FileName " + Cover + "could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															Cover = null;
														}
													}
													break;
												case "unit":
												case "単位":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0) {
														string a = Arguments[0].ToLowerInvariant();
														int Unit = 0;
														if (a == "kpa") {
															Unit = 0;
														} else if (a == "kgf/cm2" | a == "kgf/cm^2" | a == "kg/cm2" | a == "kg/cm^2") {
															Unit = 1;
														} else if (!Interface.TryParseIntVb6(Arguments[0], out Unit)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															Unit = 0;
														} else if (Unit != 0 & Unit != 1) {
															Interface.AddMessage(Interface.MessageType.Error, false, "Value must be either 0 or 1 in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															Unit = 0;
														}
														if (Unit == 1) {
															UnitFactor = 98066.5;
														} else {
															UnitFactor = 1000.0;
														}
													} break;
												case "maximum":
												case "最大":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out Maximum)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "PressureValue is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Maximum = 1000.0;
													} break;
												case "minimum":
												case "最小":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out Minimum)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "PressureValue is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Minimum = 0.0;
													} break;
												case "angle":
												case "角度":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out Angle)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "ValueInDegrees is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Angle = 0.785398163397449;
													} else {
														Angle *= 0.0174532925199433;
													} break;
											}
										} i++;
									} i--;
									// units
									Minimum *= UnitFactor;
									Maximum *= UnitFactor;
									// background
									if (Background != null) {
										Textures.Texture t;
										Textures.RegisterTexture(Background, new OpenBveApi.Textures.TextureParameters(null, Color24.Blue), out t);
									    OpenBVEGame.RunInRenderThread(() =>
									    {
									        Textures.LoadTexture(t, Textures.OpenGlTextureWrapMode.ClampClamp);
									    });
										double w = (double)t.Width;
										double h = (double)t.Height;
										CreateElement(Train, CenterX - 0.5 * w, CenterY + SemiHeight - 0.5 * h, w, h, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 3.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t, new Color32(255, 255, 255, 255), false);
									}
									// cover
									if (Cover != null) {
										Textures.Texture t;
										Textures.RegisterTexture(Cover, new OpenBveApi.Textures.TextureParameters(null, Color24.Blue), out t);
									    OpenBVEGame.RunInRenderThread(() =>
									    {
									        Textures.LoadTexture(t, Textures.OpenGlTextureWrapMode.ClampClamp);
									    });
										double w = (double)t.Width;
										double h = (double)t.Height;
										CreateElement(Train, CenterX - 0.5 * w, CenterY + SemiHeight - 0.5 * h, w, h, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 6.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t, new Color32(255, 255, 255, 255), false);
									}
									if (Type == 0) {
										// needles
										for (int k = 0; k < 2; k++) {
											if (NeedleType[k] != 0) {
												string Folder = Program.FileSystem.GetDataFolder("Compatibility");
												string File = OpenBveApi.Path.CombineFile(Folder, k == 0 ? "needle_pressuregauge_lower.png" : "needle_pressuregauge_upper.png");
												Textures.Texture t;
												Textures.RegisterTexture(File, out t);
											    OpenBVEGame.RunInRenderThread(() =>
											    {
											        Textures.LoadTexture(t, Textures.OpenGlTextureWrapMode.ClampClamp);
											    });
												double w = (double)t.Width;
												double h = (double)t.Height;
												int j = CreateElement(Train, CenterX - Radius * w / h, CenterY + SemiHeight - Radius, 2.0 * Radius * w / h, 2.0 * Radius, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - (double)(4 + k) * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t, NeedleColor[k], false);
												Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZDirection = new Vector3(0.0, 0.0, -1.0);
												Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateXDirection = new Vector3(1.0, 0.0, 0.0);
												Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateYDirection = Vector3.Cross(Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZDirection, Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateXDirection);
												double c0 = (Angle * (Maximum - Minimum) - 2.0 * Minimum * Math.PI) / (Maximum - Minimum) + Math.PI;
												double c1 = 2.0 * (Math.PI - Angle) / (Maximum - Minimum);
												string Variable = "0";
												switch (NeedleType[k]) {
														case 1: Variable = "brakecylinder"; break;
														case 2: Variable = "straightairpipe"; break;
														case 3: Variable = "brakepipe"; break;
														case 4: Variable = "equalizingreservoir"; break;
														case 5: Variable = "mainreservoir"; break;
												}
												Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(Variable + " " + c1.ToString(Culture) + " " + c0.ToString(Culture) + " fma");
											}
										}
									} else if (Type == 1) {
										// leds
										if (NeedleType[1] != 0) {
											int j = CreateElement(Train, CenterX - Radius, CenterY + SemiHeight - Radius, 2.0 * Radius, 2.0 * Radius, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 5.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, null, NeedleColor[1], false);
											double x0 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[0].Coordinates.X;
											double y0 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[0].Coordinates.Y;
											double z0 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[0].Coordinates.Z;
											double x1 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[1].Coordinates.X;
											double y1 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[1].Coordinates.Y;
											double z1 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[1].Coordinates.Z;
											double x2 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[2].Coordinates.X;
											double y2 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[2].Coordinates.Y;
											double z2 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[2].Coordinates.Z;
											double x3 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[3].Coordinates.X;
											double y3 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[3].Coordinates.Y;
											double z3 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[3].Coordinates.Z;
											double cx = 0.25 * (x0 + x1 + x2 + x3);
											double cy = 0.25 * (y0 + y1 + y2 + y3);
											double cz = 0.25 * (z0 + z1 + z2 + z3);
											World.Vertex[] vertices = new World.Vertex[11];
											int[][] faces = new int[][] {
												new int[] { 0, 1, 2 },
												new int[] { 0, 3, 4 },
												new int[] { 0, 5, 6 },
												new int[] { 0, 7, 8 },
												new int[] { 0, 9, 10 }
											};
											Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh = new World.Mesh(vertices, faces, NeedleColor[1]);
											Train.Cars[Train.DriverCar].CarSections[0].Elements[j].LEDClockwiseWinding = true;
											Train.Cars[Train.DriverCar].CarSections[0].Elements[j].LEDInitialAngle = Angle - 2.0 * Math.PI;
											Train.Cars[Train.DriverCar].CarSections[0].Elements[j].LEDLastAngle = 2.0 * Math.PI - Angle;
											Train.Cars[Train.DriverCar].CarSections[0].Elements[j].LEDVectors = new Vector3[] {
												new Vector3(x0, y0, z0),
												new Vector3(x1, y1, z1),
												new Vector3(x2, y2, z2),
												new Vector3(x3, y3, z3),
												new Vector3(cx, cy, cz)
											};
											double c0 = (Angle * (Maximum - Minimum) - 2.0 * Minimum * Math.PI) / (Maximum - Minimum);
											double c1 = 2.0 * (Math.PI - Angle) / (Maximum - Minimum);
											string Variable;
											switch (NeedleType[1]) {
													case 1: Variable = "brakecylinder"; break;
													case 2: Variable = "straightairpipe"; break;
													case 3: Variable = "brakepipe"; break;
													case 4: Variable = "equalizingreservoir"; break;
													case 5: Variable = "mainreservoir"; break;
													default: Variable = "0"; break;
											}
											Train.Cars[Train.DriverCar].CarSections[0].Elements[j].LEDFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(Variable + " " + c1.ToString(Culture) + " " + c0.ToString(Culture) + " fma");
										}
									}
								} break;
								// speedometer
							case "speedometer":
							case "speedindicator":
							case "速度計":
								{
									int Type = 0;
									Color32 Needle = new Color32(255, 255, 255, 255);
									bool NeedleOverridden = false;
									double CenterX = 0.0, CenterY = 0.0, Radius = 16.0;
									string Background = null, Cover = null, Atc = null;
									double Angle = 1.0471975511966, Maximum = 33.3333333333333, AtcRadius = 0.0;
									i++; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal))) {
										int j = Lines[i].IndexOf('='); if (j >= 0) {
											string Key = Lines[i].Substring(0, j).TrimEnd();
											string Value = Lines[i].Substring(j + 1).TrimStart();
											string[] Arguments = GetArguments(Value);
											switch (Key.ToLowerInvariant()) {
												case "type":
												case "形態":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseIntVb6(Arguments[0], out Type)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Type = 0;
													} else if (Type != 0 & Type != 1) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Value must be either 0 or 1 in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Type = 0;
													} break;
												case "background":
												case "背景":
													if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
													if (Interface.ContainsInvalidPathChars(Value)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "FileName contains illegal characters in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else {
														Background = OpenBveApi.Path.CombineFile(TrainPath, Value);
														if (!System.IO.File.Exists(Background)) {
															Interface.AddMessage(Interface.MessageType.Error, true, "FileName " + Background + " could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															Background = null;
														}
													}
													break;
												case "needle":
												case "hand":
												case "針":
													{
														int r = 0, g = 0, b = 0;
														if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseIntVb6(Arguments[0], out r)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "RedValue is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															r = 255;
														} else if (r < 0 | r > 255) {
															Interface.AddMessage(Interface.MessageType.Error, false, "RedValue is required to be within the range from 0 to 255 in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															r = r < 0 ? 0 : 255;
														}
														if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Interface.TryParseIntVb6(Arguments[1], out g)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "GreenValue is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															g = 255;
														} else if (g < 0 | g > 255) {
															Interface.AddMessage(Interface.MessageType.Error, false, "GreenValue is required to be within the range from 0 to 255 in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															g = g < 0 ? 0 : 255;
														}
														if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Interface.TryParseIntVb6(Arguments[2], out b)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "BlueValue is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															b = 255;
														} else if (b < 0 | b > 255) {
															Interface.AddMessage(Interface.MessageType.Error, false, "BlueValue is required to be within the range from 0 to 255 in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															b = b < 0 ? 0 : 255;
														}
														Needle = new Color32((byte)r, (byte)g, (byte)b, 255);
														NeedleOverridden = true;
													} break;
												case "cover":
												case "ふた":
													if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
													Cover = OpenBveApi.Path.CombineFile(TrainPath, Value);
													if (!System.IO.File.Exists(Cover)) {
														Interface.AddMessage(Interface.MessageType.Error, true, "FileName" + Cover + " could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Cover = null;
													} break;
												case "atc":
													if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
													Atc = OpenBveApi.Path.CombineFile(TrainPath, Value);
													if (!System.IO.File.Exists(Atc)) {
														Interface.AddMessage(Interface.MessageType.Error, true, "FileName" + Atc + " could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Atc = null;
													} break;
												case "atcradius":
												case "atc半径":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out AtcRadius)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "ValueInPixels is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														AtcRadius = 0.0;
													} break;
												case "center":
												case "中心":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out CenterX)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "X is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														CenterX = 0.0;
													} else if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[1], out CenterY)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Y is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														CenterY = 0.0;
													} break;
												case "radius":
												case "半径":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out Radius)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "ValueInPixels is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Radius = 0.0;
													} break;
												case "angle":
												case "角度":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out Angle)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "ValueInDegrees is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Angle = 1.0471975511966;
													} else {
														Angle *= 0.0174532925199433;
													} break;
												case "maximum":
												case "最大":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out Maximum)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "SpeedValue is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Maximum = 33.3333333333333;
													} else {
														Maximum *= 0.277777777777778;
													} break;
											}
										} i++;
									} i--;
									if (Background != null) {
										// background/led
										Textures.Texture t;
										Textures.RegisterTexture(Background, new OpenBveApi.Textures.TextureParameters(null, Color24.Blue), out t);
									    OpenBVEGame.RunInRenderThread(() =>
									    {
									        Textures.LoadTexture(t, Textures.OpenGlTextureWrapMode.ClampClamp);
									    });
										double w = (double)t.Width;
										double h = (double)t.Height;
										CreateElement(Train, CenterX - 0.5 * w, CenterY + SemiHeight - 0.5 * h, w, h, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 3.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t, new Color32(255, 255, 255, 255), false);
									}
									if (Cover != null) {
										// cover
										Textures.Texture t;
										Textures.RegisterTexture(Cover, new OpenBveApi.Textures.TextureParameters(null, Color24.Blue), out t);
									    OpenBVEGame.RunInRenderThread(() =>
									    {
									        Textures.LoadTexture(t, Textures.OpenGlTextureWrapMode.ClampClamp);
									    });
										double w = (double)t.Width;
										double h = (double)t.Height;
										CreateElement(Train, CenterX - 0.5 * w, CenterY + SemiHeight - 0.5 * h, w, h, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 6.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t, new Color32(255, 255, 255, 255), false);
									}
									if (Atc != null) {
										// atc
										int w, h;
										Program.CurrentHost.QueryTextureDimensions(Atc, out w, out h);
										if (w > 0 & h > 0) {
											int n = w / h;
											int k = -1;
											for (int j = 0; j < n; j++) {
												double s; switch (j) {
														case 1: s = 0.0; break;
														case 2: s = 15.0; break;
														case 3: s = 25.0; break;
														case 4: s = 45.0; break;
														case 5: s = 55.0; break;
														case 6: s = 65.0; break;
														case 7: s = 75.0; break;
														case 8: s = 90.0; break;
														case 9: s = 100.0; break;
														case 10: s = 110.0; break;
														case 11: s = 120.0; break;
														default: s = -1.0; break;
												} s *= 0.277777777777778;
												double a; if (s >= 0.0) {
													a = 2.0 * s * (Math.PI - Angle) / Maximum + Angle + Math.PI;
												} else {
													a = Math.PI;
												}
												double x = CenterX - 0.5 * h + Math.Sin(a) * AtcRadius;
												double y = CenterY - 0.5 * h - Math.Cos(a) * AtcRadius + SemiHeight;
												Textures.Texture t;
												Textures.RegisterTexture(Atc, new OpenBveApi.Textures.TextureParameters(new OpenBveApi.Textures.TextureClipRegion(j * h, 0, h, h), Color24.Blue), out t);
											    OpenBVEGame.RunInRenderThread(() =>
											    {
											        Textures.LoadTexture(t, Textures.OpenGlTextureWrapMode.ClampClamp);
											    });
												if (j == 0) {
													k = CreateElement(Train, x, y, (double)h, (double)h, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 4.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t, new Color32(255, 255, 255, 255), false);
												} else {
													CreateElement(Train, x, y, (double)h, (double)h, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 4.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t, new Color32(255, 255, 255, 255), true);
												}
											}
											Train.Cars[Train.DriverCar].CarSections[0].Elements[k].StateFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation("271 pluginstate");
										}
									}
									if (Type == 0) {
										// needle
										string Folder = Program.FileSystem.GetDataFolder("Compatibility");
										string File = OpenBveApi.Path.CombineFile(Folder, "needle_speedometer.png");
										Textures.Texture t;
										Textures.RegisterTexture(File, out t);
									    OpenBVEGame.RunInRenderThread(() =>
									    {
									        Textures.LoadTexture(t, Textures.OpenGlTextureWrapMode.ClampClamp);
									    });
										double w = (double)t.Width;
										double h = (double)t.Height;
										int j = CreateElement(Train, CenterX - Radius * w / h, CenterY + SemiHeight - Radius, 2.0 * Radius * w / h, 2.0 * Radius, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 5.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t, Needle, false);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZDirection = new Vector3(0.0, 0.0, -1.0);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateXDirection = new Vector3(1.0, 0.0, 0.0);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateYDirection = Vector3.Cross(Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZDirection, Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateXDirection);
										double c0 = Angle + Math.PI;
										double c1 = 2.0 * (Math.PI - Angle) / Maximum;
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation("speedometer abs " + c1.ToString(Culture) + " " + c0.ToString(Culture) + " fma");
									} else if (Type == 1) {
										// led
										if (!NeedleOverridden) Needle = new Color32(0, 0, 0, 255);
										int j = CreateElement(Train, CenterX - Radius, CenterY + SemiHeight - Radius, 2.0 * Radius, 2.0 * Radius, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 5.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, null, Needle, false);
										double x0 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[0].Coordinates.X;
										double y0 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[0].Coordinates.Y;
										double z0 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[0].Coordinates.Z;
										double x1 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[1].Coordinates.X;
										double y1 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[1].Coordinates.Y;
										double z1 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[1].Coordinates.Z;
										double x2 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[2].Coordinates.X;
										double y2 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[2].Coordinates.Y;
										double z2 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[2].Coordinates.Z;
										double x3 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[3].Coordinates.X;
										double y3 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[3].Coordinates.Y;
										double z3 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[3].Coordinates.Z;
										double cx = 0.25 * (x0 + x1 + x2 + x3);
										double cy = 0.25 * (y0 + y1 + y2 + y3);
										double cz = 0.25 * (z0 + z1 + z2 + z3);
										World.Vertex[] vertices = new World.Vertex[11];
										int[][] faces = new int[][] {
											new int[] { 0, 1, 2 },
											new int[] { 0, 3, 4 },
											new int[] { 0, 5, 6 },
											new int[] { 0, 7, 8 },
											new int[] { 0, 9, 10 }
										};
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh = new World.Mesh(vertices, faces, Needle);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].LEDClockwiseWinding = true;
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].LEDInitialAngle = Angle - 2.0 * Math.PI;
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].LEDLastAngle = 2.0 * Math.PI - Angle;
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].LEDVectors = new Vector3[] {
											new Vector3(x0, y0, z0),
											new Vector3(x1, y1, z1),
											new Vector3(x2, y2, z2),
											new Vector3(x3, y3, z3),
											new Vector3(cx, cy, cz)
										};
										double c0 = Angle;
										double c1 = 2.0 * (Math.PI - Angle) / Maximum;
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].LEDFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation("speedometer abs " + c1.ToString(Culture) + " " + c0.ToString(Culture) + " fma");
									}
								} break;
								// digitalindicator
							case "digitalindicator":
								{
									string Number = null;
									double CornerX = 0.0, CornerY = 0.0;
									int Width = 0, Height = 0;
									double UnitFactor = 3.6;
									i++; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal))) {
										int j = Lines[i].IndexOf('='); if (j >= 0) {
											string Key = Lines[i].Substring(0, j).TrimEnd();
											string Value = Lines[i].Substring(j + 1).TrimStart();
											string[] Arguments = GetArguments(Value);
											switch (Key.ToLowerInvariant()) {
												case "number":
													if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
													if (Interface.ContainsInvalidPathChars(Value)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "FileName contains illegal characters in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else {
														Number = OpenBveApi.Path.CombineFile(TrainPath, Value);
														if (!System.IO.File.Exists(Number)) {
															Interface.AddMessage(Interface.MessageType.Error, true, "FileName " + Number + " could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															Number = null;
														}
													}
													break;
												case "corner":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out CornerX)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Left is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														CornerX = 0.0;
													} else if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[1], out CornerY)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Top is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														CornerY = 0.0;
													} break;
												case "size":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseIntVb6(Arguments[0], out Width)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Width is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Width = 0;
													} else if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Interface.TryParseIntVb6(Arguments[1], out Height)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Height is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Height = 0;
													} break;
												case "unit":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0) {
														string a = Arguments[0].ToLowerInvariant();
														int Unit = 0;
														if (a == "km/h") {
															Unit = 0;
														} else if (a == "mph") {
															Unit = 1;
														} else if (a == "m/s") {
															Unit = 2;
														} else if (!Interface.TryParseIntVb6(Arguments[0], out Unit)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															Unit = 0;
														} else if (Unit < 0 | Unit > 2) {
															Interface.AddMessage(Interface.MessageType.Error, false, "Value must be between 0 and 2 in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															Unit = 0;
														}
														if (Unit == 1) {
															UnitFactor = 2.2369362920544;
														} else if (Unit == 2) {
															UnitFactor = 1.0;
														} else {
															UnitFactor = 3.6;
														}
													} break;
											}
										} i++;
									} i--;
									if (Number == null) {
										Interface.AddMessage(Interface.MessageType.Error, false, "Number is required to be specified in " + Section + " in " + FileName);
									}
									if (Width <= 0) {
										Interface.AddMessage(Interface.MessageType.Error, false, "Width is required to be specified in " + Section + " in " + FileName);
									}
									if (Height <= 0) {
										Interface.AddMessage(Interface.MessageType.Error, false, "Height is required to be specified in " + Section + " in " + FileName);
									}
									if (Number != null & Width > 0 & Height > 0) {
										int w, h;
										Program.CurrentHost.QueryTextureDimensions(Number, out w, out h);
										if (w > 0 & h > 0) {
											//Generate an error message rather than crashing if the clip region is invalid
											if (Width > w)
											{
												Width = w;
												Interface.AddMessage(Interface.MessageType.Warning, false, "Clip region width was greater than the texture width " + Section + " in " + FileName);
											}
											if (Height > h)
											{
												Height = h;
												Interface.AddMessage(Interface.MessageType.Warning, false, "Clip region height was greater than the texture height " + Section + " in " + FileName);
											}
											int n = h / Height;
											Textures.Texture[] t = new Textures.Texture[n];
											for (int j = 0; j < n; j++) {
												Textures.RegisterTexture(Number, new OpenBveApi.Textures.TextureParameters(new OpenBveApi.Textures.TextureClipRegion(w - Width, j * Height, Width, Height), Color24.Blue), out t[j]);
												//TextureManager.UseTexture(t[j], TextureManager.UseMode.Normal);
											}
											{ // hundreds
												int k = -1;
												for (int j = 0; j < n; j++) {
													if (j == 0) {
														k = CreateElement(Train, CornerX, CornerY + SemiHeight, (double)Width, (double)Height, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 7.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t[j], new Color32(255, 255, 255, 255), false);
													} else {
														CreateElement(Train, CornerX, CornerY + SemiHeight, (double)Width, (double)Height, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 7.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t[j], new Color32(255, 255, 255, 255), true);
													}
												}
												Train.Cars[Train.DriverCar].CarSections[0].Elements[k].StateFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation("speedometer abs " + UnitFactor.ToString(Culture) + " * ~ 100 >= <> 100 quotient 10 mod 10 ?");
											}
											{ // tens
												int k = -1;
												for (int j = 0; j < n; j++) {
													if (j == 0) {
														k = CreateElement(Train, CornerX + (double)Width, CornerY + SemiHeight, (double)Width, (double)Height, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 7.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t[j], new Color32(255, 255, 255, 255), false);
													} else {
														CreateElement(Train, CornerX + (double)Width, CornerY + SemiHeight, (double)Width, (double)Height, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 7.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t[j], new Color32(255, 255, 255, 255), true);
													}
												}
												Train.Cars[Train.DriverCar].CarSections[0].Elements[k].StateFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation("speedometer abs " + UnitFactor.ToString(Culture) + " * ~ 10 >= <> 10 quotient 10 mod 10 ?");
											}
											{ // ones
												int k = -1;
												for (int j = 0; j < n; j++) {
													if (j == 0) {
														k = CreateElement(Train, CornerX + 2.0 * (double)Width, CornerY + SemiHeight, (double)Width, (double)Height, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 7.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t[j], new Color32(255, 255, 255, 255), false);
													} else {
														CreateElement(Train, CornerX + 2.0 * (double)Width, CornerY + SemiHeight, (double)Width, (double)Height, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 7.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t[j], new Color32(255, 255, 255, 255), true);
													}
												}
												Train.Cars[Train.DriverCar].CarSections[0].Elements[k].StateFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation("speedometer abs " + UnitFactor.ToString(Culture) + " * floor 10 mod");
											}
										}
									}
								} break;
								// pilotlamp
							case "pilotlamp":
							case "知らせ灯":
								{
									double CornerX = 0.0, CornerY = 0.0;
									string TurnOn = null, TurnOff = null;
									i++; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal))) {
										int j = Lines[i].IndexOf('='); if (j >= 0) {
											string Key = Lines[i].Substring(0, j).TrimEnd();
											string Value = Lines[i].Substring(j + 1).TrimStart();
											string[] Arguments = GetArguments(Value);
											switch (Key.ToLowerInvariant()) {
												case "turnon":
												case "点灯":
													if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
													if (Interface.ContainsInvalidPathChars(Value)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "FileName contains illegal characters in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else {
														TurnOn = OpenBveApi.Path.CombineFile(TrainPath, Value);
														if (!System.IO.File.Exists(TurnOn)) {
															Interface.AddMessage(Interface.MessageType.Error, true, "FileName" + TurnOn + " could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															TurnOn = null;
														}
													}
													break;
												case "turnoff":
												case "消灯":
													if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
													if (Interface.ContainsInvalidPathChars(Value)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "FileName contains illegal characters in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else {
														TurnOff = OpenBveApi.Path.CombineFile(TrainPath, Value);
														if (!System.IO.File.Exists(TurnOff)) {
															Interface.AddMessage(Interface.MessageType.Error, true, "FileName" + TurnOff + " could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															TurnOff = null;
														}
													}
													break;
												case "corner":
												case "左上":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out CornerX)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Left is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														CornerX = 0.0;
													} else if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[1], out CornerY)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Top is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														CornerY = 0.0;
													} break;
											}
										} i++;
									} i--;
									if (TurnOn != null & TurnOff != null) {
										Textures.Texture t0, t1;
										Textures.RegisterTexture(TurnOn, new OpenBveApi.Textures.TextureParameters(null, Color24.Blue), out t0);
										Textures.RegisterTexture(TurnOff, new OpenBveApi.Textures.TextureParameters(null, Color24.Blue), out t1);
									    OpenBVEGame.RunInRenderThread(() =>
									    {
									        Textures.LoadTexture(t0, Textures.OpenGlTextureWrapMode.ClampClamp);
									        Textures.LoadTexture(t1, Textures.OpenGlTextureWrapMode.ClampClamp);
									    });
										double w = (double)t0.Width;
										double h = (double)t0.Height;
										int j = CreateElement(Train, CornerX, CornerY + SemiHeight, w, h, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 2.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t0, new Color32(255, 255, 255, 255), false);
										w = (double)t1.Width;
										h = (double)t1.Height;
										CreateElement(Train, CornerX, CornerY + SemiHeight, w, h, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 2.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t1, new Color32(255, 255, 255, 255), true);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].StateFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation("doors 0 !=");
									}
								} break;
								// watch
							case "watch":
							case "時計":
								{
									Color32 Needle = new Color32(0, 0, 0, 255);
									double CenterX = 0.0, CenterY = 0.0, Radius = 16.0;
									string Background = null;
									i++; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal))) {
										int j = Lines[i].IndexOf('='); if (j >= 0) {
											string Key = Lines[i].Substring(0, j).TrimEnd();
											string Value = Lines[i].Substring(j + 1).TrimStart();
											string[] Arguments = GetArguments(Value);
											switch (Key.ToLowerInvariant()) {
												case "background":
												case "背景":
													if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
													if (Interface.ContainsInvalidPathChars(Value)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "FileName contains illegal characters in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else {
														Background = OpenBveApi.Path.CombineFile(TrainPath, Value);
														if (!System.IO.File.Exists(Background)) {
															Interface.AddMessage(Interface.MessageType.Error, true, "FileName" + Background + " could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															Background = null;
														}
													}
													break;
												case "needle":
												case "hand":
												case "針":
													{
														int r = 0, g = 0, b = 0;
														if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseIntVb6(Arguments[0], out r)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "RedValue is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															r = 0;
														} else if (r < 0 | r > 255) {
															Interface.AddMessage(Interface.MessageType.Error, false, "RedValue is required to be within the range from 0 to 255 in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															r = r < 0 ? 0 : 255;
														}
														if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Interface.TryParseIntVb6(Arguments[1], out g)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "GreenValue is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															g = 0;
														} else if (g < 0 | g > 255) {
															Interface.AddMessage(Interface.MessageType.Error, false, "GreenValue is required to be within the range from 0 to 255 in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															g = g < 0 ? 0 : 255;
														}
														if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Interface.TryParseIntVb6(Arguments[2], out b)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "BlueValue is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															b = 0;
														} else if (b < 0 | b > 255) {
															Interface.AddMessage(Interface.MessageType.Error, false, "BlueValue is required to be within the range from 0 to 255 in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															b = b < 0 ? 0 : 255;
														}
														Needle = new Color32((byte)r, (byte)g, (byte)b, 255);
													} break;
												case "center":
												case "中心":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out CenterX)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "X is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														CenterX = 0.0;
													} else if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[1], out CenterY)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Y is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														CenterY = 0.0;
													} break;
												case "radius":
												case "半径":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out Radius)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Radius = 16.0;
													} break;
											}
										} i++;
									} i--;
									if (Background != null) {
										Textures.Texture t;
										Textures.RegisterTexture(Background, new OpenBveApi.Textures.TextureParameters(null, Color24.Blue), out t);
									    OpenBVEGame.RunInRenderThread(() =>
									    {
									        Textures.LoadTexture(t, Textures.OpenGlTextureWrapMode.ClampClamp);
									    });
										double w = (double)t.Width;
										double h = (double)t.Height;
										CreateElement(Train, CenterX - 0.5 * w, CenterY + SemiHeight - 0.5 * h, w, h, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 3.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t, new Color32(255, 255, 255, 255), false);
									}
									string Folder = Program.FileSystem.GetDataFolder("Compatibility");
									{ // hour
										string File = OpenBveApi.Path.CombineFile(Folder, "needle_hour.png");
										Textures.Texture t;
										Textures.RegisterTexture(File, out t);
									    OpenBVEGame.RunInRenderThread(() =>
									    {
									        Textures.LoadTexture(t, Textures.OpenGlTextureWrapMode.ClampClamp);
									    });
										double w = (double)t.Width;
										double h = (double)t.Height;
										int j = CreateElement(Train, CenterX - Radius * w / h, CenterY + SemiHeight - Radius, 2.0 * Radius * w / h, 2.0 * Radius, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 4.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t, Needle, false);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZDirection = new Vector3(0.0, 0.0, -1.0);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateXDirection = new Vector3(1.0, 0.0, 0.0);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateYDirection = Vector3.Cross(Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZDirection, Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateXDirection);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation("time 0.000277777777777778 * floor 0.523598775598298 *");
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZDamping = new ObjectManager.Damping(20.0, 0.4);
									}
									{ // minute
										string File = OpenBveApi.Path.CombineFile(Folder, "needle_minute.png");
										Textures.Texture t;
										Textures.RegisterTexture(File, out t);
									    OpenBVEGame.RunInRenderThread(() =>
									    {
									        Textures.LoadTexture(t, Textures.OpenGlTextureWrapMode.ClampClamp);
									    });
										double w = (double)t.Width;
										double h = (double)t.Height;
										int j = CreateElement(Train, CenterX - Radius * w / h, CenterY + SemiHeight - Radius, 2.0 * Radius * w / h, 2.0 * Radius, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 5.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t, Needle, false);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZDirection = new Vector3(0.0, 0.0, -1.0);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateXDirection = new Vector3(1.0, 0.0, 0.0);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateYDirection = Vector3.Cross(Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZDirection, Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateXDirection);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation("time 0.0166666666666667 * floor 0.10471975511966 *");
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZDamping = new ObjectManager.Damping(20.0, 0.4);
									}
									{ // second
										string File = OpenBveApi.Path.CombineFile(Folder, "needle_second.png");
										Textures.Texture t;
										Textures.RegisterTexture(File, out t);
									    OpenBVEGame.RunInRenderThread(() =>
									    {
									        Textures.LoadTexture(t, Textures.OpenGlTextureWrapMode.ClampClamp);
									    });
										double w = (double)t.Width;
										double h = (double)t.Height;
										int j = CreateElement(Train, CenterX - Radius * w / h, CenterY + SemiHeight - Radius, 2.0 * Radius * w / h, 2.0 * Radius, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - 6.0 * StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t, Needle, false);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZDirection = new Vector3(0.0, 0.0, -1.0);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateXDirection = new Vector3(1.0, 0.0, 0.0);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateYDirection = Vector3.Cross(Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZDirection, Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateXDirection);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation("time floor 0.10471975511966 *");
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZDamping = new ObjectManager.Damping(20.0, 0.4);
									}
								} break;
								// brakeindicator
							case "brakeindicator":
								{
									double CornerX = 0.0, CornerY = 0.0;
									string Image = null;
									int Width = 0;
									i++; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal))) {
										int j = Lines[i].IndexOf('='); if (j >= 0) {
											string Key = Lines[i].Substring(0, j).TrimEnd();
											string Value = Lines[i].Substring(j + 1).TrimStart();
											string[] Arguments = GetArguments(Value);
											switch (Key.ToLowerInvariant()) {
												case "image":
													if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
													if (Interface.ContainsInvalidPathChars(Value)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "FileName contains illegal characters in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else {
														Image = OpenBveApi.Path.CombineFile(TrainPath, Value);
														if (!System.IO.File.Exists(Image)) {
															Interface.AddMessage(Interface.MessageType.Error, true, "FileName " + Image + " could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															Image = null;
														}
													}
													break;
												case "corner":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out CornerX)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Left is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														CornerX = 0.0;
													} else if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[1], out CornerY)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Top is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														CornerY = 0.0;
													} break;
												case "width":
													if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseIntVb6(Arguments[0], out Width)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Width is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Width = 1;
													} else if (Width <= 0) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Width is expected to be positive in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Width = 1;
													} break;
											}
										} i++;
									} i--;
									if (Image == null) {
										Interface.AddMessage(Interface.MessageType.Error, false, "Image is required to be specified in " + Section + " in " + FileName);
									}
									if (Width <= 0) {
										Interface.AddMessage(Interface.MessageType.Error, false, "Width is required to be specified in " + Section + " in " + FileName);
									}
									if (Image != null & Width > 0) {
										int w, h;
										Program.CurrentHost.QueryTextureDimensions(Image, out w, out h);
										if (w > 0 & h > 0) {
											int n = w / Width;
											int k = -1;
											for (int j = 0; j < n; j++) {
												Textures.Texture t;
												OpenBveApi.Textures.TextureClipRegion clip = new OpenBveApi.Textures.TextureClipRegion(j * Width, 0, Width, h);
												Textures.RegisterTexture(Image, new OpenBveApi.Textures.TextureParameters(clip, Color24.Blue), out t);
												//TextureManager.UseTexture(t, TextureManager.UseMode.Normal);
												if (j == 0) {
													k = CreateElement(Train, CornerX, CornerY + SemiHeight, (double)Width, (double)h, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t, new Color32(255, 255, 255, 255), false);
												} else {
													CreateElement(Train, CornerX, CornerY + SemiHeight, (double)Width, (double)h, FullWidth, FullHeight, WorldLeft, WorldTop, WorldWidth, WorldHeight, WorldZ + EyeDistance - StackDistance, Train.Cars[Train.DriverCar].DriverX, Train.Cars[Train.DriverCar].DriverY, Train.Cars[Train.DriverCar].DriverZ, t, new Color32(255, 255, 255, 255), true);
												}
											}
											if (Train.Cars[Train.DriverCar].Specs.BrakeType == TrainManager.CarBrakeType.AutomaticAirBrake) {
												int maxpow = Train.Specs.MaximumPowerNotch;
												int em = maxpow + 3;
												Train.Cars[Train.DriverCar].CarSections[0].Elements[k].StateFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation("emergencyBrake " + em.ToString(Culture) + " brakeNotch 0 > " + maxpow.ToString(Culture) + " BrakeNotch + " + maxpow.ToString(Culture) + " powerNotch - ? ?");
											} else {
												if (Train.Specs.HasHoldBrake) {
													int em = Train.Specs.MaximumPowerNotch + 2 + Train.Specs.MaximumBrakeNotch;
													int maxpow = Train.Specs.MaximumPowerNotch;
													int maxpowp1 = maxpow + 1;
													Train.Cars[Train.DriverCar].CarSections[0].Elements[k].StateFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation("emergencyBrake " + em.ToString(Culture) + " holdBrake " + maxpowp1.ToString(Culture) + " brakeNotch 0 > brakeNotch " + maxpowp1.ToString(Culture) + " + " + maxpow.ToString(Culture) + " powerNotch - ? ? ?");
												} else {
													int em = Train.Specs.MaximumPowerNotch + 1 + Train.Specs.MaximumBrakeNotch;
													int maxpow = Train.Specs.MaximumPowerNotch;
													Train.Cars[Train.DriverCar].CarSections[0].Elements[k].StateFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation("emergencyBrake " + em.ToString(Culture) + " brakeNotch 0 > brakeNotch " + maxpow.ToString(Culture) + " + " + maxpow.ToString(Culture) + " powerNotch - ? ?");
												}
											}
										}
									}
								} break;
						}
					}
				}
			}
		}
Exemple #10
0
		// create element
		private static int CreateElement(TrainManager.Train Train, double Left, double Top, double Width, double Height, double FullWidth, double FullHeight, double WorldLeft, double WorldTop, double WorldWidth, double WorldHeight, double WorldZ, double DriverX, double DriverY, double DriverZ, Textures.Texture Texture, Color32 Color, bool AddStateToLastElement) {
			// create object
			ObjectManager.StaticObject Object = new ObjectManager.StaticObject();
			Vector3[] v = new Vector3[4];
			double sx = 0.5 * WorldWidth * Width / FullWidth;
			double sy = 0.5 * WorldHeight * Height / FullHeight;
			v[0] = new Vector3(-sx, -sy, 0);
			v[1] = new Vector3(-sx, sy, 0);
			v[2] = new Vector3(sx, sy, 0);
			v[3] = new Vector3(sx, -sy, 0);
			World.Vertex t0 = new World.Vertex(v[0], new Vector2(0.0f, 1.0f));
			World.Vertex t1 = new World.Vertex(v[1], new Vector2(0.0f, 0.0f));
			World.Vertex t2 = new World.Vertex(v[2], new Vector2(1.0f, 0.0f));
			World.Vertex t3 = new World.Vertex(v[3], new Vector2(1.0f, 1.0f));
			Object.Mesh.Vertices = new World.Vertex[] { t0, t1, t2, t3 };
			Object.Mesh.Faces = new World.MeshFace[] { new World.MeshFace(new int[] { 0, 1, 2, 3 }) };
			Object.Mesh.Materials = new World.MeshMaterial[1];
			Object.Mesh.Materials[0].Flags = Texture != null ? (byte)World.MeshMaterial.TransparentColorMask : (byte)0;
			Object.Mesh.Materials[0].Color = Color;
			Object.Mesh.Materials[0].TransparentColor = new Color24(0, 0, 255);
			Object.Mesh.Materials[0].DaytimeTexture = Texture;
			Object.Mesh.Materials[0].NighttimeTexture = null;
			Object.Dynamic = true;
			// calculate offset
			Vector3 o;
			o.X = WorldLeft + sx + WorldWidth * Left / FullWidth;
			o.Y = WorldTop - sy - WorldHeight * Top / FullHeight;
			o.Z = WorldZ;
			// add object
			if (AddStateToLastElement) {
				int n = Train.Cars[Train.DriverCar].CarSections[0].Elements.Length - 1;
				int j = Train.Cars[Train.DriverCar].CarSections[0].Elements[n].States.Length;
				Array.Resize<ObjectManager.AnimatedObjectState>(ref Train.Cars[Train.DriverCar].CarSections[0].Elements[n].States, j + 1);
				Train.Cars[Train.DriverCar].CarSections[0].Elements[n].States[j].Position = o;
				Train.Cars[Train.DriverCar].CarSections[0].Elements[n].States[j].Object = Object;
				return n;
			} else {
				int n = Train.Cars[Train.DriverCar].CarSections[0].Elements.Length;
				Array.Resize<ObjectManager.AnimatedObject>(ref Train.Cars[Train.DriverCar].CarSections[0].Elements, n + 1);
				Train.Cars[Train.DriverCar].CarSections[0].Elements[n] = new ObjectManager.AnimatedObject();
				Train.Cars[Train.DriverCar].CarSections[0].Elements[n].States = new ObjectManager.AnimatedObjectState[1];
				Train.Cars[Train.DriverCar].CarSections[0].Elements[n].States[0].Position = o;
				Train.Cars[Train.DriverCar].CarSections[0].Elements[n].States[0].Object = Object;
				Train.Cars[Train.DriverCar].CarSections[0].Elements[n].CurrentState = 0;
				Train.Cars[Train.DriverCar].CarSections[0].Elements[n].ObjectIndex = ObjectManager.CreateDynamicObject();
				ObjectManager.Objects[Train.Cars[Train.DriverCar].CarSections[0].Elements[n].ObjectIndex] = ObjectManager.CloneObject(Object);
				return n;
			}
		}
Exemple #11
0
			/// <summary>Creates a mesh consisting of the specified vertices, faces and color.</summary>
			/// <param name="Vertices">The vertices used.</param>
			/// <param name="FaceVertices">A list of faces represented by a list of references to vertices.</param>
			/// <param name="Color">The color to be applied on all of the faces.</param>
			internal Mesh(Vertex[] Vertices, int[][] FaceVertices, Color32 Color) {
				this.Vertices = Vertices;
				this.Materials = new MeshMaterial[1];
				this.Materials[0].Color = Color;
				this.Faces = new MeshFace[FaceVertices.Length];
				for (int i = 0; i < FaceVertices.Length; i++) {
					this.Faces[i] = new MeshFace(FaceVertices[i]);
				}
			}
Exemple #12
0
			/// <summary>Creates a mesh consisting of one face, which is represented by individual vertices, and a color.</summary>
			/// <param name="Vertices">The vertices that make up one face.</param>
			/// <param name="Color">The color to be applied on the face.</param>
			internal Mesh(Vertex[] Vertices, Color32 Color) {
				this.Vertices = Vertices;
				this.Materials = new MeshMaterial[1];
				this.Materials[0].Color = Color;
				this.Faces = new MeshFace[1];
				this.Faces[0].Material = 0;
				this.Faces[0].Vertices = new MeshFaceVertex[Vertices.Length];
				for (int i = 0; i < Vertices.Length; i++) {
					this.Faces[0].Vertices[i].Index = (ushort)i;
				}
			}
Exemple #13
0
		// parse panel config
		internal static void ParsePanel2Config(string TrainPath, System.Text.Encoding Encoding, TrainManager.Train Train)
		{
			// read lines
			System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture;
			string FileName = OpenBveApi.Path.CombineFile(TrainPath, "panel2.cfg");
			string[] Lines = System.IO.File.ReadAllLines(FileName, Encoding);
			for (int i = 0; i < Lines.Length; i++) {
				Lines[i] = Lines[i].Trim();
				int j = Lines[i].IndexOf(';');
				if (j >= 0) {
					Lines[i] = Lines[i].Substring(0, j).TrimEnd();
				}
			}
			// initialize
			double DriverX = Train.Cars[Train.DriverCar].DriverX;
			double DriverY = Train.Cars[Train.DriverCar].DriverY;
			double DriverZ = Train.Cars[Train.DriverCar].DriverZ;
			double PanelResolution = 1024.0;
			double PanelLeft = 0.0, PanelRight = 1024.0;
			double PanelTop = 0.0, PanelBottom = 1024.0;
			double PanelCenterX = 0.0, PanelCenterY = 512.0;
			double PanelOriginX = 0.0, PanelOriginY = 512.0;
			double PanelBitmapWidth = 1024.0, PanelBitmapHeight = 1024.0;
			string PanelDaytimeImage = null;
			string PanelNighttimeImage = null;
			Color24 PanelTransparentColor = new Color24(0, 0, 255);
			// parse lines for panel
			for (int i = 0; i < Lines.Length; i++) {
				if (Lines[i].Length > 0) {
					if (Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal)) {
						string Section = Lines[i].Substring(1, Lines[i].Length - 2).Trim();
						switch (Section.ToLowerInvariant()) {
								// panel
							case "this":
								i++; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal))) {
									int j = Lines[i].IndexOf('='); if (j >= 0) {
										string Key = Lines[i].Substring(0, j).TrimEnd();
										string Value = Lines[i].Substring(j + 1).TrimStart();
										switch (Key.ToLowerInvariant()) {
											case "resolution":
												double pr = 0.0;
												if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out pr)) {
													Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
												}
												if (pr > 100)
												{
													PanelResolution = pr;
												}
												else
												{
													//Parsing very low numbers (Probable typos) for the panel resolution causes some very funky graphical bugs
 													//Cap the minimum panel resolution at 100px wide (BVE1 panels are 480px wide, so this is probably a safe minimum)
													Interface.AddMessage(Interface.MessageType.Error, false, "A panel resolution of less than 10px was given at line " + (i + 1).ToString(Culture) + " in " + FileName);
												}
												break;
											case "left":
												if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out PanelLeft)) {
													Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line" + (i + 1).ToString(Culture) + " in " + FileName);
												} break;
											case "right":
												if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out PanelRight)) {
													Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
												} break;
											case "top":
												if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out PanelTop)) {
													Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
												} break;
											case "bottom":
												if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out PanelBottom)) {
													Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
												} break;
											case "daytimeimage":
												if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
												if (Interface.ContainsInvalidPathChars(Value)) {
													Interface.AddMessage(Interface.MessageType.Error, false, "FileName contains illegal characters in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
												} else {
													PanelDaytimeImage = OpenBveApi.Path.CombineFile(TrainPath, Value);
													if (!System.IO.File.Exists(PanelDaytimeImage)) {
														Interface.AddMessage(Interface.MessageType.Error, true, "FileName " + PanelDaytimeImage + " could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														PanelDaytimeImage = null;
													}
												}
												break;
											case "nighttimeimage":
												if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
												if (Interface.ContainsInvalidPathChars(Value)) {
													Interface.AddMessage(Interface.MessageType.Error, false, "FileName contains illegal characters in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
												} else {
													PanelNighttimeImage = OpenBveApi.Path.CombineFile(TrainPath, Value);
													if (!System.IO.File.Exists(PanelNighttimeImage)) {
														Interface.AddMessage(Interface.MessageType.Error, true, "FileName " + PanelNighttimeImage + " could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														PanelNighttimeImage = null;
													}
												}
												break;
											case "transparentcolor":
												if (Value.Length != 0 && !Interface.TryParseHexColor(Value, out PanelTransparentColor)) {
													Interface.AddMessage(Interface.MessageType.Error, false, "HexColor is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
												} break;
											case "center":
												{
													int k = Value.IndexOf(',');
													if (k >= 0) {
														string a = Value.Substring(0, k).TrimEnd();
														string b = Value.Substring(k + 1).TrimStart();
														if (a.Length != 0 && !Interface.TryParseDoubleVb6(a, out PanelCenterX)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "X is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														}
														if (b.Length != 0 && !Interface.TryParseDoubleVb6(b, out PanelCenterY)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "Y is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														}
													} else {
														Interface.AddMessage(Interface.MessageType.Error, false, "Two arguments are expected in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												}
											case "origin":
												{
													int k = Value.IndexOf(',');
													if (k >= 0) {
														string a = Value.Substring(0, k).TrimEnd();
														string b = Value.Substring(k + 1).TrimStart();
														if (a.Length != 0 && !Interface.TryParseDoubleVb6(a, out PanelOriginX)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "X is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														}
														if (b.Length != 0 && !Interface.TryParseDoubleVb6(b, out PanelOriginY)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "Y is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														}
													} else {
														Interface.AddMessage(Interface.MessageType.Error, false, "Two arguments are expected in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												}
										}
									} i++;
								} i--; break;
						}
					}
				}
			}
			{ // camera restriction
				double WorldWidth, WorldHeight;
				if (Screen.Width >= Screen.Height) {
					WorldWidth = 2.0 * Math.Tan(0.5 * World.HorizontalViewingAngle) * EyeDistance;
					WorldHeight = WorldWidth / World.AspectRatio;
				} else {
					WorldHeight = 2.0 * Math.Tan(0.5 * World.VerticalViewingAngle) * EyeDistance / World.AspectRatio;
					WorldWidth = WorldHeight * World.AspectRatio;
				}
				double x0 = (PanelLeft - PanelCenterX) / PanelResolution;
				double x1 = (PanelRight - PanelCenterX) / PanelResolution;
				double y0 = (PanelCenterY - PanelBottom) / PanelResolution * World.AspectRatio;
				double y1 = (PanelCenterY - PanelTop) / PanelResolution * World.AspectRatio;
				World.CameraRestrictionBottomLeft = new Vector3(x0 * WorldWidth, y0 * WorldHeight, EyeDistance);
				World.CameraRestrictionTopRight = new Vector3(x1 * WorldWidth, y1 * WorldHeight, EyeDistance);
				Train.Cars[Train.DriverCar].DriverYaw = Math.Atan((PanelCenterX - PanelOriginX) * WorldWidth / PanelResolution);
				Train.Cars[Train.DriverCar].DriverPitch = Math.Atan((PanelOriginY - PanelCenterY) * WorldWidth / PanelResolution);
			}
			// create panel
			if (PanelDaytimeImage != null) {
				if (!System.IO.File.Exists(PanelDaytimeImage)) {
					Interface.AddMessage(Interface.MessageType.Error, true, "The daytime panel bitmap could not be found in " + FileName);
					PanelDaytimeImage = null;
				} else {
					Textures.Texture tday;
					Textures.RegisterTexture(PanelDaytimeImage, new OpenBveApi.Textures.TextureParameters(null, new Color24(PanelTransparentColor.R, PanelTransparentColor.G, PanelTransparentColor.B)), out tday);
					Textures.Texture tnight = null;
					if (PanelNighttimeImage != null) {
						if (!System.IO.File.Exists(PanelNighttimeImage)) {
							Interface.AddMessage(Interface.MessageType.Error, true, "The nighttime panel bitmap could not be found in " + FileName);
							PanelNighttimeImage = null;
						} else {
							Textures.RegisterTexture(PanelNighttimeImage, new OpenBveApi.Textures.TextureParameters(null, new Color24(PanelTransparentColor.R, PanelTransparentColor.G, PanelTransparentColor.B)), out tnight);
						}
					}
					OpenBVEGame.RunInRenderThread(() =>
					{
						Textures.LoadTexture(tday, Textures.OpenGlTextureWrapMode.ClampClamp);
					});
					PanelBitmapWidth = (double)tday.Width;
					PanelBitmapHeight = (double)tday.Height;
					CreateElement(Train, 0.0, 0.0, PanelBitmapWidth, PanelBitmapHeight, 0.5, 0.5, 0.0, PanelResolution, PanelLeft, PanelRight, PanelTop, PanelBottom, PanelBitmapWidth, PanelBitmapHeight, PanelCenterX, PanelCenterY, PanelOriginX, PanelOriginY, DriverX, DriverY, DriverZ, tday, tnight, new Color32(255, 255, 255, 255), false);
				}
			}
			// parse lines for rest
			double invfac = Lines.Length == 0 ? Loading.TrainProgressCurrentWeight : Loading.TrainProgressCurrentWeight / (double)Lines.Length;
			for (int i = 0; i < Lines.Length; i++) {
				Loading.TrainProgress = Loading.TrainProgressCurrentSum + invfac * (double)i;
				if ((i & 7) == 0) {
					System.Threading.Thread.Sleep(1);
					if (Loading.Cancel) return;
				}
				if (Lines[i].Length > 0) {
					if (Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal)) {
						string Section = Lines[i].Substring(1, Lines[i].Length - 2).Trim();
						switch (Section.ToLowerInvariant()) {
								// pilotlamp
							case "pilotlamp":
								{
									string Subject = "true";
									double LocationX = 0.0, LocationY = 0.0;
									string DaytimeImage = null, NighttimeImage = null;
									Color24 TransparentColor = new Color24(0, 0, 255);
									int Layer = 0;
									i++; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal))) {
										int j = Lines[i].IndexOf('=');
										if (j >= 0) {
											string Key = Lines[i].Substring(0, j).TrimEnd();
											string Value = Lines[i].Substring(j + 1).TrimStart();
											switch (Key.ToLowerInvariant()) {
												case "subject":
													Subject = Value;
													break;
												case "location":
													int k = Value.IndexOf(',');
													if (k >= 0) {
														string a = Value.Substring(0, k).TrimEnd();
														string b = Value.Substring(k + 1).TrimStart();
														if (a.Length != 0 && !Interface.TryParseDoubleVb6(a, out LocationX)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "Left is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														}
														if (b.Length != 0 && !Interface.TryParseDoubleVb6(b, out LocationY)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "Top is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														}
													} else {
														Interface.AddMessage(Interface.MessageType.Error, false, "Two arguments are expected in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "daytimeimage":
													if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
													if (Interface.ContainsInvalidPathChars(Value)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "FileName contains illegal characters in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else {
														DaytimeImage = OpenBveApi.Path.CombineFile(TrainPath, Value);
														if (!System.IO.File.Exists(DaytimeImage)) {
															Interface.AddMessage(Interface.MessageType.Error, true, "FileName " + DaytimeImage + " could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															DaytimeImage = null;
														}
													}
													break;
												case "nighttimeimage":
													if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
													if (Interface.ContainsInvalidPathChars(Value)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "FileName contains illegal characters in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else {
														NighttimeImage = OpenBveApi.Path.CombineFile(TrainPath, Value);
														if (!System.IO.File.Exists(NighttimeImage)) {
															Interface.AddMessage(Interface.MessageType.Error, true, "FileName " + NighttimeImage + " could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															NighttimeImage = null;
														}
													}
													break;
												case "transparentcolor":
													if (Value.Length != 0 && !Interface.TryParseHexColor(Value, out TransparentColor)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "HexColor is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "layer":
													if (Value.Length != 0 && !Interface.TryParseIntVb6(Value, out Layer)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "LayerIndex is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
											}
										} i++;
									} i--;
									if (DaytimeImage == null) {
										Interface.AddMessage(Interface.MessageType.Error, false, "DaytimeImage is required to be specified in " + Section + " in " + FileName);
									}
									// create element
									if (DaytimeImage != null) {
										Textures.Texture tday;
										Textures.RegisterTexture(DaytimeImage, new OpenBveApi.Textures.TextureParameters(null, new Color24(TransparentColor.R, TransparentColor.G, TransparentColor.B)), out tday);
										Textures.Texture tnight = null;
										if (NighttimeImage != null) {
											Textures.RegisterTexture(NighttimeImage, new OpenBveApi.Textures.TextureParameters(null, new Color24(TransparentColor.R, TransparentColor.G, TransparentColor.B)), out tnight);
										}
										OpenBVEGame.RunInRenderThread(() =>
										{
											Textures.LoadTexture(tday, Textures.OpenGlTextureWrapMode.ClampClamp);
										});
										int w = tday.Width;
										int h = tday.Height;
										int j = CreateElement(Train, LocationX, LocationY, w, h, 0.5, 0.5, (double)Layer * StackDistance, PanelResolution, PanelLeft, PanelRight, PanelTop, PanelBottom, PanelBitmapWidth, PanelBitmapHeight, PanelCenterX, PanelCenterY, PanelOriginX, PanelOriginY, DriverX, DriverY, DriverZ, tday, tnight, new Color32(255, 255, 255, 255), false);
										string f = GetStackLanguageFromSubject(Train, Subject, Section + " in " + FileName);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].StateFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(f + " 1 == --");
									}
								} break;
								// needle
							case "needle":
								{
									string Subject = "true";
									double LocationX = 0.0, LocationY = 0.0;
									string DaytimeImage = null, NighttimeImage = null;
									Color32 Color = new Color32(255, 255, 255, 255);
									Color24 TransparentColor = new Color24(0, 0, 255);
									double OriginX = -1.0, OriginY = -1.0;
									bool OriginDefined = false;
									double Layer = 0.0, Radius = 0.0;
									double InitialAngle = -2.0943951023932, LastAngle = 2.0943951023932;
									double Minimum = 0.0, Maximum = 1000.0;
									double NaturalFrequency = -1.0, DampingRatio = -1.0;
									bool Backstop = false;
									i++; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal))) {
										int j = Lines[i].IndexOf('=');
										if (j >= 0) {
											string Key = Lines[i].Substring(0, j).TrimEnd();
											string Value = Lines[i].Substring(j + 1).TrimStart();
											switch (Key.ToLowerInvariant()) {
												case "subject":
													Subject = Value;
													break;
												case "location":
													{
														int k = Value.IndexOf(',');
														if (k >= 0) {
															string a = Value.Substring(0, k).TrimEnd();
															string b = Value.Substring(k + 1).TrimStart();
															if (a.Length != 0 && !Interface.TryParseDoubleVb6(a, out LocationX)) {
																Interface.AddMessage(Interface.MessageType.Error, false, "CenterX is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															}
															if (b.Length != 0 && !Interface.TryParseDoubleVb6(b, out LocationY)) {
																Interface.AddMessage(Interface.MessageType.Error, false, "CenterY is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															}
														} else {
															Interface.AddMessage(Interface.MessageType.Error, false, "Two arguments are expected in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														}
													} break;
												case "radius":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out Radius)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "ValueInPixels is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else if (Radius == 0.0) {
														Interface.AddMessage(Interface.MessageType.Error, false, "ValueInPixels is expected to be non-zero in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Radius = 16.0;
													} break;
												case "daytimeimage":
													if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
													if (Interface.ContainsInvalidPathChars(Value)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "FileName contains illegal characters in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else {
														DaytimeImage = OpenBveApi.Path.CombineFile(TrainPath, Value);
														if (!System.IO.File.Exists(DaytimeImage)) {
															Interface.AddMessage(Interface.MessageType.Error, true, "FileName " + DaytimeImage + " could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															DaytimeImage = null;
														}
													}
													break;
												case "nighttimeimage":
													if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
													if (Interface.ContainsInvalidPathChars(Value)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "FileName contains illegal characters in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else {
														NighttimeImage = OpenBveApi.Path.CombineFile(TrainPath, Value);
														if (!System.IO.File.Exists(NighttimeImage)) {
															Interface.AddMessage(Interface.MessageType.Error, true, "FileName " + NighttimeImage + " could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															NighttimeImage = null;
														}
													}
													break;
												case "color":
													if (Value.Length != 0 && !Interface.TryParseHexColor(Value, out Color)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "HexColor is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "transparentcolor":
													if (Value.Length != 0 && !Interface.TryParseHexColor(Value, out TransparentColor)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "HexColor is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "origin":
													{
														int k = Value.IndexOf(',');
														if (k >= 0) {
															string a = Value.Substring(0, k).TrimEnd();
															string b = Value.Substring(k + 1).TrimStart();
															if (a.Length != 0 && !Interface.TryParseDoubleVb6(a, out OriginX)) {
																Interface.AddMessage(Interface.MessageType.Error, false, "X is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															}
															if (b.Length != 0 && !Interface.TryParseDoubleVb6(b, out OriginY)) {
																Interface.AddMessage(Interface.MessageType.Error, false, "Y is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
																OriginX = -OriginX;
															}
															OriginDefined = true;
														} else {
															Interface.AddMessage(Interface.MessageType.Error, false, "Two arguments are expected in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														}
													} break;
												case "initialangle":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out InitialAngle)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "ValueInDegrees is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "lastangle":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out LastAngle)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "ValueInDegrees is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "minimum":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out Minimum)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "maximum":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out Maximum)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "naturalfreq":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out NaturalFrequency)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else if (NaturalFrequency < 0.0) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Value is expected to be non-negative in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														NaturalFrequency = -NaturalFrequency;
													} break;
												case "dampingratio":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out DampingRatio)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else if (DampingRatio < 0.0) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Value is expected to be non-negative in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														DampingRatio = -DampingRatio;
													} break;
												case "layer":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out Layer)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "LayerIndex is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "backstop":
													if (Value.Length != 0 && Value.ToLowerInvariant() == "true" || Value == "1")
													{
														Backstop = true;
													}
													break;
											}
										} i++;
									} i--;
									if (DaytimeImage == null) {
										Interface.AddMessage(Interface.MessageType.Error, false, "DaytimeImage is required to be specified in " + Section + " in " + FileName);
									}
									// create element
									if (DaytimeImage != null)
									{
										Textures.Texture tday;
										Textures.RegisterTexture(DaytimeImage,
											new OpenBveApi.Textures.TextureParameters(null,
												new Color24(TransparentColor.R, TransparentColor.G, TransparentColor.B)), out tday);
										Textures.Texture tnight = null;
										if (NighttimeImage != null)
										{
											Textures.RegisterTexture(NighttimeImage,
												new OpenBveApi.Textures.TextureParameters(null,
													new Color24(TransparentColor.R, TransparentColor.G, TransparentColor.B)), out tnight);
										}
										OpenBVEGame.RunInRenderThread(() =>
										{
											Textures.LoadTexture(tday, Textures.OpenGlTextureWrapMode.ClampClamp);
										});
										double w = (double)tday.Width;
										double h = (double)tday.Height;
										if (!OriginDefined) {
											OriginX = 0.5 * w;
											OriginY = 0.5 * h;
										}
										double ox = OriginX / w;
										double oy = OriginY / h;
										double n = Radius == 0.0 | OriginY == 0.0 ? 1.0 : Radius / OriginY;
										double nx = n * w;
										double ny = n * h;
										int j = CreateElement(Train, LocationX - ox * nx, LocationY - oy * ny, nx, ny, ox, oy, (double)Layer * StackDistance, PanelResolution, PanelLeft, PanelRight, PanelTop, PanelBottom, PanelBitmapWidth, PanelBitmapHeight, PanelCenterX, PanelCenterY, PanelOriginX, PanelOriginY, DriverX, DriverY, DriverZ, tday, tnight, Color, false);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZDirection = new Vector3(0.0, 0.0, -1.0);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateXDirection = new Vector3(1.0, 0.0, 0.0);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateYDirection = Vector3.Cross(Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZDirection, Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateXDirection);
										string f;
										switch (Subject.ToLowerInvariant()) {
											case "hour":
												f = "0.000277777777777778 time * floor";
												break;
											case "min":
												f = "0.0166666666666667 time * floor";
												break;
											case "sec":
												f = "time floor";
												break;
											default:
												f = GetStackLanguageFromSubject(Train, Subject, Section + " in " + FileName);
												break;
										}
										//Convert angles from degrees to radians
										InitialAngle *= 0.0174532925199433;
										LastAngle *= 0.0174532925199433;
										double a0 = (InitialAngle * Maximum - LastAngle * Minimum) / (Maximum - Minimum);
										double a1 = (LastAngle - InitialAngle) / (Maximum - Minimum);
										f += " " + a1.ToString(Culture) + " * " + a0.ToString(Culture) + " +";
										if (NaturalFrequency >= 0.0 & DampingRatio >= 0.0) {
											Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZDamping = new ObjectManager.Damping(NaturalFrequency, DampingRatio);
										}
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(f);
										if (Backstop)
										{
											Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZFunction.Minimum = InitialAngle;
											Train.Cars[Train.DriverCar].CarSections[0].Elements[j].RotateZFunction.Maximum = LastAngle;
										}
									}
								} break;
							case "lineargauge":
									{
									string Subject = "true";
									int Width = 0;
									Vector2 Direction = new Vector2(1,0);
									double LocationX = 0.0, LocationY = 0.0;
									string DaytimeImage = null, NighttimeImage = null;
									double Minimum = 0.0, Maximum = 0.0;
									Color24 TransparentColor = new Color24(0, 0, 255);
									int Layer = 0;
									i++; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal))) {
										int j = Lines[i].IndexOf('=');
										if (j >= 0) {
											string Key = Lines[i].Substring(0, j).TrimEnd();
											string Value = Lines[i].Substring(j + 1).TrimStart();
											switch (Key.ToLowerInvariant()) {
												case "subject":
													Subject = Value;
													break;
												case "location":
													int k = Value.IndexOf(',');
													if (k >= 0) {
														string a = Value.Substring(0, k).TrimEnd();
														string b = Value.Substring(k + 1).TrimStart();
														if (a.Length != 0 && !Interface.TryParseDoubleVb6(a, out LocationX)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "Left is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														}
														if (b.Length != 0 && !Interface.TryParseDoubleVb6(b, out LocationY)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "Top is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														}
													} else {
														Interface.AddMessage(Interface.MessageType.Error, false, "Two arguments are expected in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "minimum":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out Minimum))
													{
														Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "maximum":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out Maximum))
													{
														Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "width":
													if (Value.Length != 0 && !Interface.TryParseIntVb6(Value, out Width))
													{
														Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													}
													break;
												case "direction":
													{
														string[] s = Value.Split(',');
														if (s.Length == 2)
														{
															double x, y;
															if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out x))
															{
																Interface.AddMessage(Interface.MessageType.Error, false, "X is invalid in LinearGauge Direction at line " + (i + 1).ToString(Culture) + " in file " + FileName);
															}
															else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out y))
															{
																Interface.AddMessage(Interface.MessageType.Error, false, "Y is invalid in  LinearGauge Direction at line " + (i + 1).ToString(Culture) + " in file " + FileName);
															}
															else
															{
																Direction = new Vector2(x, y);
															}
														}
														else
														{
															Interface.AddMessage(Interface.MessageType.Error, false, "Exactly 2 arguments are expected in LinearGauge Direction at line " + (i + 1).ToString(Culture) + " in file " + FileName);
														}
													}
													break;
												case "daytimeimage":
													if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
													if (Interface.ContainsInvalidPathChars(Value)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "FileName contains illegal characters in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else {
														DaytimeImage = OpenBveApi.Path.CombineFile(TrainPath, Value);
														if (!System.IO.File.Exists(DaytimeImage)) {
															Interface.AddMessage(Interface.MessageType.Error, true, "FileName " + DaytimeImage + " could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															DaytimeImage = null;
														}
													}
													break;
												case "nighttimeimage":
													if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
													if (Interface.ContainsInvalidPathChars(Value)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "FileName contains illegal characters in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else {
														NighttimeImage = OpenBveApi.Path.CombineFile(TrainPath, Value);
														if (!System.IO.File.Exists(NighttimeImage)) {
															Interface.AddMessage(Interface.MessageType.Error, true, "FileName " + NighttimeImage + " could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															NighttimeImage = null;
														}
													}
													break;
												case "transparentcolor":
													if (Value.Length != 0 && !Interface.TryParseHexColor(Value, out TransparentColor)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "HexColor is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "layer":
													if (Value.Length != 0 && !Interface.TryParseIntVb6(Value, out Layer)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "LayerIndex is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
											}
										} i++;
									} i--;
									if (DaytimeImage == null) {
										Interface.AddMessage(Interface.MessageType.Error, false, "DaytimeImage is required to be specified in " + Section + " in " + FileName);
									}
									// create element
									if (DaytimeImage != null) {
										Textures.Texture tday;
										Textures.RegisterTexture(DaytimeImage, new OpenBveApi.Textures.TextureParameters(null, new Color24(TransparentColor.R, TransparentColor.G, TransparentColor.B)), out tday);
										Textures.Texture tnight = null;
										if (NighttimeImage != null) {
											Textures.RegisterTexture(NighttimeImage, new OpenBveApi.Textures.TextureParameters(null, new Color24(TransparentColor.R, TransparentColor.G, TransparentColor.B)), out tnight);
										}
										OpenBVEGame.RunInRenderThread(() =>
										{
											Textures.LoadTexture(tday, Textures.OpenGlTextureWrapMode.ClampClamp);
										});
										int w = tday.Width;
										int h = tday.Height;
										int j = CreateElement(Train, LocationX, LocationY, w, h, 0.5, 0.5, (double)Layer * StackDistance, PanelResolution, PanelLeft, PanelRight, PanelTop, PanelBottom, PanelBitmapWidth, PanelBitmapHeight, PanelCenterX, PanelCenterY, PanelOriginX, PanelOriginY, DriverX, DriverY, DriverZ, tday, tnight, new Color32(255, 255, 255, 255), false);
										if (Maximum < Minimum)
										{
											Interface.AddMessage(Interface.MessageType.Error, false, "Maximum value must be greater than minimum value " + Section + " in " + FileName);
											break;
										}
										string tf = GetInfixFunction(Train, Subject, Minimum, Maximum, Width, tday.Width, Section + " in " + FileName);
										if (tf != String.Empty)
										{
											Train.Cars[Train.DriverCar].CarSections[0].Elements[j].TextureShiftXDirection = Direction;
											Train.Cars[Train.DriverCar].CarSections[0].Elements[j].TextureShiftXFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(tf);
										}
									}
								} break;
								// digitalnumber
							case "digitalnumber":
								{
									string Subject = "true";
									double LocationX = 0.0, LocationY = 0.0;
									string DaytimeImage = null, NighttimeImage = null;
									Color24 TransparentColor = new Color24(0, 0, 255);
									double Layer = 0.0; int Interval = 0;
									i++; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal))) {
										int j = Lines[i].IndexOf('=');
										if (j >= 0) {
											string Key = Lines[i].Substring(0, j).TrimEnd();
											string Value = Lines[i].Substring(j + 1).TrimStart();
											switch (Key.ToLowerInvariant()) {
												case "subject":
													Subject = Value;
													break;
												case "location":
													int k = Value.IndexOf(',');
													if (k >= 0) {
														string a = Value.Substring(0, k).TrimEnd();
														string b = Value.Substring(k + 1).TrimStart();
														if (a.Length != 0 && !Interface.TryParseDoubleVb6(a, out LocationX)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "Left is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														}
														if (b.Length != 0 && !Interface.TryParseDoubleVb6(b, out LocationY)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "Top is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														}
													} else {
														Interface.AddMessage(Interface.MessageType.Error, false, "Two arguments are expected in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "daytimeimage":
													if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
													if (Interface.ContainsInvalidPathChars(Value)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "FileName contains illegal characters in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else {
														DaytimeImage = OpenBveApi.Path.CombineFile(TrainPath, Value);
														if (!System.IO.File.Exists(DaytimeImage)) {
															Interface.AddMessage(Interface.MessageType.Error, true, "FileName " + DaytimeImage + " could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															DaytimeImage = null;
														}
													}
													break;
												case "nighttimeimage":
													if (!System.IO.Path.HasExtension(Value)) Value += ".bmp";
													if (Interface.ContainsInvalidPathChars(Value)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "FileName contains illegal characters in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else {
														NighttimeImage = OpenBveApi.Path.CombineFile(TrainPath, Value);
														if (!System.IO.File.Exists(NighttimeImage)) {
															Interface.AddMessage(Interface.MessageType.Error, true, "FileName " + NighttimeImage + " could not be found in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
															NighttimeImage = null;
														}
													}
													break;
												case "transparentcolor":
													if (Value.Length != 0 && !Interface.TryParseHexColor(Value, out TransparentColor)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "HexColor is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "interval":
													if (Value.Length != 0 && !Interface.TryParseIntVb6(Value, out Interval)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Height is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else if (Interval <= 0) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Height is expected to be non-negative in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "layer":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out Layer)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "LayerIndex is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
											}
										} i++;
									} i--;
									if (DaytimeImage == null) {
										Interface.AddMessage(Interface.MessageType.Error, false, "DaytimeImage is required to be specified in " + Section + " in " + FileName);
									}
									if (Interval <= 0) {
										Interface.AddMessage(Interface.MessageType.Error, false, "Interval is required to be specified in " + Section + " in " + FileName);
									}
									// create element
									if (DaytimeImage != null & Interval > 0) {
										int wday, hday;
										Program.CurrentHost.QueryTextureDimensions(DaytimeImage, out wday, out hday);
										if (wday > 0 & hday > 0) {
											int nday = hday / Interval;
											Textures.Texture[] tday = new Textures.Texture[nday];
											Textures.Texture[] tnight;
											for (int k = 0; k < nday; k++) {
												Textures.RegisterTexture(DaytimeImage, new OpenBveApi.Textures.TextureParameters(new OpenBveApi.Textures.TextureClipRegion(0, k * Interval, wday, Interval), new Color24(TransparentColor.R, TransparentColor.G, TransparentColor.B)), out tday[k]);
											}
											if (NighttimeImage != null) {
												int wnight, hnight;
												Program.CurrentHost.QueryTextureDimensions(NighttimeImage, out wnight, out hnight);
												int nnight = hnight / Interval;
												if (nnight > nday) nnight = nday;
												tnight = new Textures.Texture[nday];
												for (int k = 0; k < nnight; k++) {
													Textures.RegisterTexture(NighttimeImage, new OpenBveApi.Textures.TextureParameters(new OpenBveApi.Textures.TextureClipRegion(0, k * Interval, wday, Interval), new Color24(TransparentColor.R, TransparentColor.G, TransparentColor.B)), out tnight[k]);
												}
												for (int k = nnight; k < nday; k++) {
													tnight[k] = null;
												}
											} else {
												tnight = new Textures.Texture[nday];
												for (int k = 0; k < nday; k++) {
													tnight[k] = null;
												}
											}
											int j = -1;
											for (int k = 0; k < tday.Length; k++) {
												int l = CreateElement(Train, LocationX, LocationY, (double)wday, (double)Interval, 0.5, 0.5, (double)Layer * StackDistance, PanelResolution, PanelLeft, PanelRight, PanelTop, PanelBottom, PanelBitmapWidth, PanelBitmapHeight, PanelCenterX, PanelCenterY, PanelOriginX, PanelOriginY, DriverX, DriverY, DriverZ, tday[k], tnight[k], new Color32(255, 255, 255, 255), k != 0);
												if (k == 0) j = l;
											}
											string f = GetStackLanguageFromSubject(Train, Subject, Section + " in " + FileName);
											Train.Cars[Train.DriverCar].CarSections[0].Elements[j].StateFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(f);
										}
									}
								} break;
								// digitalgauge
							case "digitalgauge":
								{
									string Subject = "true";
									double LocationX = 0.0, LocationY = 0.0;
									Color32 Color = new Color32(0, 0, 0, 255);
									double Radius = 0.0;
									int Layer = 0;
									double InitialAngle = -2.0943951023932, LastAngle = 2.0943951023932;
									double Minimum = 0.0, Maximum = 1000.0;
									double Step = 0.0;
									i++; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal))) {
										int j = Lines[i].IndexOf('=');
										if (j >= 0) {
											string Key = Lines[i].Substring(0, j).TrimEnd();
											string Value = Lines[i].Substring(j + 1).TrimStart();
											switch (Key.ToLowerInvariant()) {
												case "subject":
													Subject = Value;
													break;
												case "location":
													int k = Value.IndexOf(',');
													if (k >= 0) {
														string a = Value.Substring(0, k).TrimEnd();
														string b = Value.Substring(k + 1).TrimStart();
														if (a.Length != 0 && !Interface.TryParseDoubleVb6(a, out LocationX)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "CenterX is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														}
														if (b.Length != 0 && !Interface.TryParseDoubleVb6(b, out LocationY)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "CenterY is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														}
													} else {
														Interface.AddMessage(Interface.MessageType.Error, false, "Two arguments are expected in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "radius":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out Radius)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "ValueInPixels is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else if (Radius == 0.0) {
														Interface.AddMessage(Interface.MessageType.Error, false, "ValueInPixels is expected to be non-zero in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														Radius = 16.0;
													} break;
												case "color":
													if (Value.Length != 0 && !Interface.TryParseHexColor(Value, out Color)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "HexColor is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "initialangle":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out InitialAngle)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "ValueInDegrees is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else {
														InitialAngle *= 0.0174532925199433;
													} break;
												case "lastangle":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out LastAngle)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "ValueInDegrees is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else {
														LastAngle *= 0.0174532925199433;
													} break;
												case "minimum":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out Minimum)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "maximum":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out Maximum)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "step":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out Step)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "layer":
													if (Value.Length != 0 && !Interface.TryParseIntVb6(Value, out Layer)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "LayerIndex is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
											}
										} i++;
									} i--;
									if (Radius == 0.0) {
										Interface.AddMessage(Interface.MessageType.Error, false, "Radius is required to be non-zero in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
									}
									if (Minimum == Maximum) {
										Interface.AddMessage(Interface.MessageType.Error, false, "Minimum and Maximum must not be equal in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
										Radius = 0.0;
									}
									if (Math.Abs(InitialAngle - LastAngle) > 6.28318531) {
										Interface.AddMessage(Interface.MessageType.Warning, false, "The absolute difference between InitialAngle and LastAngle exceeds 360 degrees in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
									}
									if (Radius != 0.0) {
										// create element
										int j = CreateElement(Train, LocationX - Radius, LocationY - Radius, 2.0 * Radius, 2.0 * Radius, 0.5, 0.5, (double)Layer * StackDistance, PanelResolution, PanelLeft, PanelRight, PanelTop, PanelBottom, PanelBitmapWidth, PanelBitmapHeight, PanelCenterX, PanelCenterY, PanelOriginX, PanelOriginY, DriverX, DriverY, DriverZ, null, null, Color, false);
										InitialAngle = InitialAngle + Math.PI;
										LastAngle = LastAngle + Math.PI;
										double x0 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[0].Coordinates.X;
										double y0 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[0].Coordinates.Y;
										double z0 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[0].Coordinates.Z;
										double x1 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[1].Coordinates.X;
										double y1 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[1].Coordinates.Y;
										double z1 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[1].Coordinates.Z;
										double x2 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[2].Coordinates.X;
										double y2 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[2].Coordinates.Y;
										double z2 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[2].Coordinates.Z;
										double x3 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[3].Coordinates.X;
										double y3 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[3].Coordinates.Y;
										double z3 = Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh.Vertices[3].Coordinates.Z;
										double cx = 0.25 * (x0 + x1 + x2 + x3);
										double cy = 0.25 * (y0 + y1 + y2 + y3);
										double cz = 0.25 * (z0 + z1 + z2 + z3);
										World.Vertex[] vertices = new World.Vertex[11];
										int[][] faces = new int[][] {
											new int[] { 0, 1, 2 },
											new int[] { 0, 3, 4 },
											new int[] { 0, 5, 6 },
											new int[] { 0, 7, 8 },
											new int[] { 0, 9, 10 }
										};
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].States[0].Object.Mesh = new World.Mesh(vertices, faces, Color);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].LEDClockwiseWinding = InitialAngle <= LastAngle;
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].LEDInitialAngle = InitialAngle;
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].LEDLastAngle = LastAngle;
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].LEDVectors = new Vector3[] {
											new Vector3(x0, y0, z0),
											new Vector3(x1, y1, z1),
											new Vector3(x2, y2, z2),
											new Vector3(x3, y3, z3),
											new Vector3(cx, cy, cz)
										};
										string f = GetStackLanguageFromSubject(Train, Subject, Section + " in " + FileName);
										double a0 = (InitialAngle * Maximum - LastAngle * Minimum) / (Maximum - Minimum);
										double a1 = (LastAngle - InitialAngle) / (Maximum - Minimum);
										if (Step == 1.0) {
											f += " floor";
										} else if (Step != 0.0) {
											string s = (1.0 / Step).ToString(Culture);
											string t = Step.ToString(Culture);
											f += " " + s + " * floor " + t + " *";
										}
										f += " " + a1.ToString(Culture) + " " + a0.ToString(Culture) + " fma";
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].LEDFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(f);
									} else {
										Interface.AddMessage(Interface.MessageType.Error, false, "Radius is required to be specified in " + Section + " in " + FileName);
									}
								} break;
								// timetable
							case "timetable":
								{
									double LocationX = 0.0, LocationY = 0.0;
									double Width = 0.0, Height = 0.0;
									//We read the transparent color for the timetable from the config file, but it is never used
									//TODO: Fix or depreciate??
									Color24 TransparentColor = new Color24(0, 0, 255);
									double Layer = 0.0;
									i++; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal))) {
										int j = Lines[i].IndexOf('=');
										if (j >= 0) {
											string Key = Lines[i].Substring(0, j).TrimEnd();
											string Value = Lines[i].Substring(j + 1).TrimStart();
											switch (Key.ToLowerInvariant()) {
												case "location":
													int k = Value.IndexOf(',');
													if (k >= 0) {
														string a = Value.Substring(0, k).TrimEnd();
														string b = Value.Substring(k + 1).TrimStart();
														if (a.Length != 0 && !Interface.TryParseDoubleVb6(a, out LocationX)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "X is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														}
														if (b.Length != 0 && !Interface.TryParseDoubleVb6(b, out LocationY)) {
															Interface.AddMessage(Interface.MessageType.Error, false, "Y is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
														}
													} else {
														Interface.AddMessage(Interface.MessageType.Error, false, "Two arguments are expected in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "width":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out Width)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "ValueInPixels is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else if (Width <= 0.0) {
														Interface.AddMessage(Interface.MessageType.Error, false, "ValueInPixels is required to be positive in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "height":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out Height)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "ValueInPixels is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} else if (Height <= 0.0) {
														Interface.AddMessage(Interface.MessageType.Error, false, "ValueInPixels is required to be positive in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "transparentcolor":
													if (Value.Length != 0 && !Interface.TryParseHexColor(Value, out TransparentColor)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "HexColor is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
												case "layer":
													if (Value.Length != 0 && !Interface.TryParseDoubleVb6(Value, out Layer)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "LayerIndex is invalid in " + Key + " in " + Section + " at line " + (i + 1).ToString(Culture) + " in " + FileName);
													} break;
											}
										} i++;
									} i--;
									// create element
									if (Width <= 0.0) {
										Interface.AddMessage(Interface.MessageType.Error, false, "Width is required to be specified in " + Section + " in " + FileName);
									}
									if (Height <= 0.0) {
										Interface.AddMessage(Interface.MessageType.Error, false, "Height is required to be specified in " + Section + " in " + FileName);
									}
									if (Width > 0.0 & Height > 0.0) {
										int j = CreateElement(Train, LocationX, LocationY, Width, Height, 0.5, 0.5, (double)Layer * StackDistance, PanelResolution, PanelLeft, PanelRight, PanelTop, PanelBottom, PanelBitmapWidth, PanelBitmapHeight, PanelCenterX, PanelCenterY, PanelOriginX, PanelOriginY, DriverX, DriverY, DriverZ, null, null, new Color32(255, 255, 255, 255), false);
										Train.Cars[Train.DriverCar].CarSections[0].Elements[j].StateFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation("timetable");
										Timetable.AddObjectForCustomTimetable(Train.Cars[Train.DriverCar].CarSections[0].Elements[j]);
									}
								} break;
						}
					}
				}
			}
		}
Exemple #14
0
		private static int CreateElement(TrainManager.Train Train, double Left, double Top, double Width, double Height, double RelativeRotationCenterX, double RelativeRotationCenterY, double Distance, double PanelResolution, double PanelLeft, double PanelRight, double PanelTop, double PanelBottom, double PanelBitmapWidth, double PanelBitmapHeight, double PanelCenterX, double PanelCenterY, double PanelOriginX, double PanelOriginY, double DriverX, double DriverY, double DriverZ, Textures.Texture DaytimeTexture, Textures.Texture NighttimeTexture, Color32 Color, bool AddStateToLastElement) {
			double WorldWidth, WorldHeight;
			if (Screen.Width >= Screen.Height) {
				WorldWidth = 2.0 * Math.Tan(0.5 * World.HorizontalViewingAngle) * EyeDistance;
				WorldHeight = WorldWidth / World.AspectRatio;
			} else {
				WorldHeight = 2.0 * Math.Tan(0.5 * World.VerticalViewingAngle) * EyeDistance / World.AspectRatio;
				WorldWidth = WorldHeight * World.AspectRatio;
			}
			double x0 = Left / PanelResolution;
			double x1 = (Left + Width) / PanelResolution;
			double y0 = (PanelBottom - Top) / PanelResolution * World.AspectRatio;
			double y1 = (PanelBottom - (Top + Height)) / PanelResolution * World.AspectRatio;
			double xd = 0.5 - PanelCenterX / PanelResolution;
			x0 += xd; x1 += xd;
			double yt = PanelBottom - PanelResolution / World.AspectRatio;
			double yd = (PanelCenterY - yt) / (PanelBottom - yt) - 0.5;
			y0 += yd; y1 += yd;
			x0 = (x0 - 0.5) * WorldWidth;
			x1 = (x1 - 0.5) * WorldWidth;
			y0 = (y0 - 0.5) * WorldHeight;
			y1 = (y1 - 0.5) * WorldHeight;
			double xm = x0 * (1.0 - RelativeRotationCenterX) + x1 * RelativeRotationCenterX;
			double ym = y0 * (1.0 - RelativeRotationCenterY) + y1 * RelativeRotationCenterY;
			Vector3[] v = new Vector3[4];
			v[0] = new Vector3(x0 - xm, y1 - ym, 0);
			v[1] = new Vector3(x0 - xm, y0 - ym, 0);
			v[2] = new Vector3(x1 - xm, y0 - ym, 0);
			v[3] = new Vector3(x1 - xm, y1 - ym, 0);
			World.Vertex t0 = new World.Vertex(v[0], new Vector2(0.0f, 1.0f));
			World.Vertex t1 = new World.Vertex(v[1], new Vector2(0.0f, 0.0f));
			World.Vertex t2 = new World.Vertex(v[2], new Vector2(1.0f, 0.0f));
			World.Vertex t3 = new World.Vertex(v[3], new Vector2(1.0f, 1.0f));
			ObjectManager.StaticObject Object = new ObjectManager.StaticObject();
			Object.Mesh.Vertices = new World.Vertex[] { t0, t1, t2, t3 };
			Object.Mesh.Faces = new World.MeshFace[] { new World.MeshFace(new int[] { 0, 1, 2, 3 }) };
			Object.Mesh.Materials = new World.MeshMaterial[1];
			Object.Mesh.Materials[0].Flags = (byte)(DaytimeTexture != null ? World.MeshMaterial.TransparentColorMask : 0);
			Object.Mesh.Materials[0].Color = Color;
			Object.Mesh.Materials[0].TransparentColor = new Color24(0, 0, 255);
			Object.Mesh.Materials[0].DaytimeTexture = DaytimeTexture;
			Object.Mesh.Materials[0].NighttimeTexture = NighttimeTexture;
			Object.Dynamic = true;
			// calculate offset
			Vector3 o;
			o.X = xm + DriverX;
			o.Y = ym + DriverY;
			o.Z = EyeDistance - Distance + DriverZ;
			// add object
			if (AddStateToLastElement) {
				int n = Train.Cars[Train.DriverCar].CarSections[0].Elements.Length - 1;
				int j = Train.Cars[Train.DriverCar].CarSections[0].Elements[n].States.Length;
				Array.Resize<ObjectManager.AnimatedObjectState>(ref Train.Cars[Train.DriverCar].CarSections[0].Elements[n].States, j + 1);
				Train.Cars[Train.DriverCar].CarSections[0].Elements[n].States[j].Position = o;
				Train.Cars[Train.DriverCar].CarSections[0].Elements[n].States[j].Object = Object;
				return n;
			} else {
				int n = Train.Cars[Train.DriverCar].CarSections[0].Elements.Length;
				Array.Resize<ObjectManager.AnimatedObject>(ref Train.Cars[Train.DriverCar].CarSections[0].Elements, n + 1);
				Train.Cars[Train.DriverCar].CarSections[0].Elements[n] = new ObjectManager.AnimatedObject();
				Train.Cars[Train.DriverCar].CarSections[0].Elements[n].States = new ObjectManager.AnimatedObjectState[1];
				Train.Cars[Train.DriverCar].CarSections[0].Elements[n].States[0].Position = o;
				Train.Cars[Train.DriverCar].CarSections[0].Elements[n].States[0].Object = Object;
				Train.Cars[Train.DriverCar].CarSections[0].Elements[n].CurrentState = 0;
				Train.Cars[Train.DriverCar].CarSections[0].Elements[n].ObjectIndex = ObjectManager.CreateDynamicObject();
				ObjectManager.Objects[Train.Cars[Train.DriverCar].CarSections[0].Elements[n].ObjectIndex] = ObjectManager.CloneObject(Object);
				return n;
			}
		}
Exemple #15
0
		internal static bool TryParseHexColor(string Expression, out Color32 Color) {
			if (Expression.StartsWith("#")) {
				string a = Expression.Substring(1).TrimStart();
				int x; if (int.TryParse(a, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out x)) {
					int r = (x >> 16) & 0xFF;
					int g = (x >> 8) & 0xFF;
					int b = x & 0xFF;
					if (r >= 0 & r <= 255 & g >= 0 & g <= 255 & b >= 0 & b <= 255) {
						Color = new Color32((byte)r, (byte)g, (byte)b, 255);
						return true;
					} else {
						Color = new Color32(0, 0, 255, 255);
						return false;
					}
				} else {
					Color = new Color32(0, 0, 255, 255);
					return false;
				}
			} else {
				Color = new Color32(0, 0, 255, 255);
				return false;
			}
		}