Represents a vertex consisting of 3D coordinates and 2D texture coordinates.
示例#1
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;
						}
					}
				}
			}
		}
示例#2
0
        /// <summary>Loads a Loksim3D object from a file.</summary>
        /// <param name="FileName">The text file to load the animated object from. Must be an absolute file name.</param>
        /// <param name="LoadMode">The texture load mode.</param>
        /// <param name="Rotation">The rotation to be applied</param>
        /// <returns>The object loaded.</returns>
        internal static ObjectManager.StaticObject ReadObject(string FileName, ObjectManager.ObjectLoadMode LoadMode, Vector3 Rotation)
        {
            string      BaseDir    = System.IO.Path.GetDirectoryName(FileName);
            XmlDocument currentXML = new XmlDocument();

            //Initialise the object
            ObjectManager.StaticObject Object = new ObjectManager.StaticObject();
            Object.Mesh.Faces     = new World.MeshFace[] { };
            Object.Mesh.Materials = new World.MeshMaterial[] { };
            Object.Mesh.Vertices  = new World.Vertex[] { };
            MeshBuilder Builder = new MeshBuilder();

            Vector3[] Normals         = new Vector3[4];
            bool      PropertiesFound = false;

            World.Vertex[] tempVertices       = new World.Vertex[0];
            Vector3[]      tempNormals        = new Vector3[0];
            Color24        transparentColor   = new Color24();
            string         tday               = null;
            string         tnight             = null;
            string         transtex           = null;
            bool           TransparencyUsed   = false;
            bool           TransparentTypSet  = false;
            bool           FirstPxTransparent = false;
            Color24        FirstPxColor       = new Color24();
            bool           Face2              = false;
            int            TextureWidth       = 0;
            int            TextureHeight      = 0;

            if (File.Exists(FileName))
            {
                try
                {
                    currentXML.Load(FileName);
                }
                catch
                {
                    return(null);
                }
            }
            else
            {
                Interface.AddMessage(Interface.MessageType.Error, false, "Loksim3D object " + FileName + " does not exist.");
                return(null);
            }
            //Check for null
            if (currentXML.DocumentElement != null)
            {
                XmlNodeList DocumentNodes = currentXML.DocumentElement.SelectNodes("/OBJECT");
                //Check this file actually contains Loksim3D object nodes
                if (DocumentNodes != null)
                {
                    foreach (XmlNode outerNode in DocumentNodes)
                    {
                        if (outerNode.ChildNodes.OfType <XmlElement>().Any())
                        {
                            foreach (XmlNode node in outerNode.ChildNodes)
                            {
                                //I think there should only be one properties node??
                                //Need better format documentation
                                if (node.Name == "Props" && PropertiesFound == false)
                                {
                                    if (node.Attributes != null)
                                    {
                                        //Our node has child nodes, therefore this properties node should be valid
                                        //Needs better validation
                                        PropertiesFound = true;
                                        foreach (XmlAttribute attribute in node.Attributes)
                                        {
                                            switch (attribute.Name)
                                            {
                                            //Sets the texture
                                            //Loksim3D objects only support daytime textures
                                            case "Texture":
                                                tday = OpenBveApi.Path.Loksim3D.CombineFile(BaseDir, attribute.Value, Program.FileSystem.LoksimPackageInstallationDirectory);
                                                if (!File.Exists(tday))
                                                {
                                                    Interface.AddMessage(Interface.MessageType.Warning, true, "Ls3d Texture file " + attribute.Value + " not found.");
                                                    break;
                                                }
                                                try
                                                {
                                                    using (Bitmap TextureInformation = new Bitmap(tday))
                                                    {
                                                        TextureWidth  = TextureInformation.Width;
                                                        TextureHeight = TextureInformation.Height;
                                                        Color color = TextureInformation.GetPixel(0, 0);
                                                        FirstPxColor = new Color24(color.R, color.G, color.B);
                                                    }
                                                }
                                                catch
                                                {
                                                    Interface.AddMessage(Interface.MessageType.Error, true,
                                                                         "An error occured loading daytime texture " + tday +
                                                                         " in file " + FileName);
                                                    tday = null;
                                                }
                                                break;

                                            //Defines whether the texture uses transparency
                                            //May be omitted
                                            case "Transparent":
                                                if (TransparentTypSet)
                                                {
                                                    //Appears to be ignored with TransparentTyp set
                                                    continue;
                                                }
                                                if (attribute.Value == "TRUE")
                                                {
                                                    TransparencyUsed = true;
                                                    transparentColor = new Color24(0, 0, 0);
                                                }
                                                break;

                                            case "TransTexture":
                                                if (string.IsNullOrEmpty(attribute.Value))
                                                {
                                                    //Empty....
                                                    continue;
                                                }
                                                transtex = OpenBveApi.Path.Loksim3D.CombineFile(BaseDir, attribute.Value, Program.FileSystem.LoksimPackageInstallationDirectory);
                                                if (!File.Exists(transtex))
                                                {
                                                    Interface.AddMessage(Interface.MessageType.Error, true, "AlphaTexture " + transtex + " could not be found in file " + FileName);
                                                    transtex = null;
                                                }
                                                break;

                                            //Sets the transparency type
                                            case "TransparentTyp":
                                                TransparentTypSet = true;
                                                switch (attribute.Value)
                                                {
                                                case "0":
                                                    //Transparency is disabled
                                                    TransparencyUsed = false;
                                                    break;

                                                case "1":
                                                    //Transparency is solid black
                                                    TransparencyUsed   = true;
                                                    transparentColor   = new Color24(0, 0, 0);
                                                    FirstPxTransparent = false;
                                                    break;

                                                case "2":
                                                    //Transparency is the color at Pixel 1,1
                                                    TransparencyUsed   = true;
                                                    FirstPxTransparent = true;
                                                    break;

                                                case "3":
                                                case "4":
                                                    //This is used when transparency is used with an alpha bitmap
                                                    TransparencyUsed   = false;
                                                    FirstPxTransparent = false;
                                                    break;

                                                case "5":
                                                    //Use the alpha channel from the image, so we don't need to do anything fancy
                                                    //TODO: (Low priority) Check what happens in Loksim itself when an image uses the Alpha channel, but doesn't actually specify type 5
                                                    break;

                                                default:
                                                    Interface.AddMessage(Interface.MessageType.Error, false, "Unrecognised transparency type " + attribute.Value + " detected in " + attribute.Name + " in Loksim3D object file " + FileName);
                                                    break;
                                                }
                                                break;

                                            //Sets whether the rears of the faces are to be drawn
                                            case "Drawrueckseiten":
                                                if (attribute.Value == "TRUE" || string.IsNullOrEmpty(attribute.Value))
                                                {
                                                    Face2 = true;
                                                }
                                                else
                                                {
                                                    Face2 = false;
                                                }
                                                break;

                                                /*
                                                 * MISSING PROPERTIES:
                                                 * AutoRotate - Rotate with tracks?? LS3D presumably uses a 3D world system.
                                                 * Beleuchtet- Translates as illuminated. Presume something to do with lighting? - What emissive color?
                                                 * FileAuthor
                                                 * FileInfo
                                                 * FilePicture
                                                 */
                                            }
                                        }
                                    }
                                }
                                //The point command is eqivilant to a vertex
                                else if (node.Name == "Point" && node.ChildNodes.OfType <XmlElement>().Any())
                                {
                                    foreach (XmlNode childNode in node.ChildNodes)
                                    {
                                        if (childNode.Name == "Props" && childNode.Attributes != null)
                                        {
                                            //Vertex
                                            double vx = 0.0, vy = 0.0, vz = 0.0;
                                            //Normals
                                            double nx = 0.0, ny = 0.0, nz = 0.0;
                                            foreach (XmlAttribute attribute in childNode.Attributes)
                                            {
                                                switch (attribute.Name)
                                                {
                                                //Sets the vertex normals
                                                case "Normal":
                                                    string[] NormalPoints = attribute.Value.Split(';');
                                                    if (!double.TryParse(NormalPoints[0], out nx))
                                                    {
                                                        Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument nX in " + attribute.Name + " in Loksim3D object file " + FileName);
                                                    }
                                                    if (!double.TryParse(NormalPoints[1], out ny))
                                                    {
                                                        Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument nY in " + attribute.Name + " in Loksim3D object file " + FileName);
                                                    }
                                                    if (!double.TryParse(NormalPoints[2], out nz))
                                                    {
                                                        Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument nZ in " + attribute.Name + " in Loksim3D object file " + FileName);
                                                    }
                                                    break;

                                                //Sets the vertex 3D co-ordinates
                                                case "Vekt":
                                                    string[] VertexPoints = attribute.Value.Split(';');
                                                    if (!double.TryParse(VertexPoints[0], out vx))
                                                    {
                                                        Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument vX in " + attribute.Name + " in Loksim3D object file " + FileName);
                                                    }
                                                    if (!double.TryParse(VertexPoints[1], out vy))
                                                    {
                                                        Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument yY in " + attribute.Name + " in Loksim3D object file " + FileName);
                                                    }
                                                    if (!double.TryParse(VertexPoints[2], out vz))
                                                    {
                                                        Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument vZ in " + attribute.Name + " in Loksim3D object file " + FileName);
                                                    }
                                                    break;
                                                }
                                            }
                                            World.Normalize(ref nx, ref ny, ref nz);
                                            //Resize temp arrays
                                            Array.Resize <World.Vertex>(ref tempVertices, tempVertices.Length + 1);
                                            Array.Resize <Vector3>(ref tempNormals, tempNormals.Length + 1);
                                            //Add vertex and normals to temp array
                                            tempVertices[tempVertices.Length - 1].Coordinates = new Vector3(vx, vy, vz);
                                            tempNormals[tempNormals.Length - 1] = new Vector3((float)nx, (float)ny, (float)nz);
                                            Array.Resize <World.Vertex>(ref Builder.Vertices, Builder.Vertices.Length + 1);
                                            while (Builder.Vertices.Length >= Normals.Length)
                                            {
                                                Array.Resize <Vector3>(ref Normals, Normals.Length << 1);
                                            }
                                            Builder.Vertices[Builder.Vertices.Length - 1].Coordinates = new Vector3(vx, vy, vz);
                                            Normals[Builder.Vertices.Length - 1] = new Vector3((float)nx, (float)ny, (float)nz);
                                        }
                                    }
                                }
                                //The Flaeche command creates a face
                                else if (node.Name == "Flaeche" && node.ChildNodes.OfType <XmlElement>().Any())
                                {
                                    foreach (XmlNode childNode in node.ChildNodes)
                                    {
                                        if (childNode.Name == "Props" && childNode.Attributes != null)
                                        {
                                            //Defines the verticies in this face
                                            //**NOTE**: A vertex may appear in multiple faces with different texture co-ordinates
                                            if (childNode.Attributes["Points"] != null)
                                            {
                                                string[] Verticies = childNode.Attributes["Points"].Value.Split(';');
                                                int      f         = Builder.Faces.Length;
                                                //Add 1 to the length of the face array
                                                Array.Resize <World.MeshFace>(ref Builder.Faces, f + 1);
                                                Builder.Faces[f] = new World.MeshFace();
                                                //Create the vertex array for the face
                                                Builder.Faces[f].Vertices = new World.MeshFaceVertex[Verticies.Length];
                                                while (Builder.Vertices.Length > Normals.Length)
                                                {
                                                    Array.Resize <Vector3>(ref Normals,
                                                                           Normals.Length << 1);
                                                }
                                                //Run through the vertices list and grab from the temp array

                                                int smallestX = TextureWidth;
                                                int smallestY = TextureHeight;
                                                for (int j = 0; j < Verticies.Length; j++)
                                                {
                                                    //This is the position of the vertex in the temp array
                                                    int currentVertex;
                                                    if (!int.TryParse(Verticies[j], out currentVertex))
                                                    {
                                                        Interface.AddMessage(Interface.MessageType.Error, false, Verticies[j] + " does not parse to a valid Vertex in " + node.Name + " in Loksim3D object file " + FileName);
                                                        continue;
                                                    }
                                                    //Add one to the actual vertex array
                                                    Array.Resize <World.Vertex>(ref Builder.Vertices, Builder.Vertices.Length + 1);
                                                    //Set coordinates
                                                    Builder.Vertices[Builder.Vertices.Length - 1].Coordinates = tempVertices[currentVertex].Coordinates;
                                                    //Set the vertex index
                                                    Builder.Faces[f].Vertices[j].Index = (ushort)(Builder.Vertices.Length - 1);
                                                    //Set the normals
                                                    Builder.Faces[f].Vertices[j].Normal = tempNormals[currentVertex];
                                                    //Now deal with the texture
                                                    //Texture mapping points are in pixels X,Y and are relative to the face in question rather than the vertex
                                                    if (childNode.Attributes["Texture"] != null)
                                                    {
                                                        string[] TextureCoords = childNode.Attributes["Texture"].Value.Split(';');
                                                        Vector2  currentCoords;
                                                        float    OpenBVEWidth;
                                                        float    OpenBVEHeight;
                                                        string[] splitCoords = TextureCoords[j].Split(',');
                                                        if (!float.TryParse(splitCoords[0], out OpenBVEWidth))
                                                        {
                                                            Interface.AddMessage(Interface.MessageType.Error, false, "Invalid texture width specified in " + node.Name + " in Loksim3D object file " + FileName);
                                                            continue;
                                                        }
                                                        if (!float.TryParse(splitCoords[1], out OpenBVEHeight))
                                                        {
                                                            Interface.AddMessage(Interface.MessageType.Error, false, "Invalid texture height specified in " + node.Name + " in Loksim3D object file " + FileName);
                                                            continue;
                                                        }
                                                        if (OpenBVEWidth <= smallestX && OpenBVEHeight <= smallestY)
                                                        {
                                                            //Clamp texture width and height
                                                            smallestX = (int)OpenBVEWidth;
                                                            smallestY = (int)OpenBVEHeight;
                                                        }
                                                        if (TextureWidth != 0 && TextureHeight != 0)
                                                        {
                                                            //Calculate openBVE co-ords
                                                            currentCoords.X = (OpenBVEWidth / TextureWidth);
                                                            currentCoords.Y = (OpenBVEHeight / TextureHeight);
                                                        }
                                                        else
                                                        {
                                                            //Invalid, so just return zero
                                                            currentCoords.X = 0;
                                                            currentCoords.Y = 0;
                                                        }
                                                        Builder.Vertices[Builder.Vertices.Length - 1].TextureCoordinates = currentCoords;
                                                    }
                                                }
                                                if (Face2)
                                                {
                                                    //Add face2 flag if required
                                                    Builder.Faces[f].Flags = (byte)World.MeshFace.Face2Mask;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                //Apply rotation

                /*
                 * NOTES:
                 * No rotation order is specified
                 * The rotation string in a .l3dgrp file is ordered Y, Z, X    ??? Can't find a good reason for this ???
                 * Rotations must still be performed in X,Y,Z order to produce correct results
                 */

                if (Rotation.Z != 0.0)
                {
                    //Convert to radians
                    Rotation.Z *= 0.0174532925199433;
                    //Apply rotation
                    ApplyRotation(Builder, 1, 0, 0, Rotation.Z);
                }


                if (Rotation.X != 0.0)
                {
                    //This is actually the Y-Axis rotation
                    //Convert to radians
                    Rotation.X *= 0.0174532925199433;
                    //Apply rotation
                    ApplyRotation(Builder, 0, 1, 0, Rotation.X);
                }
                if (Rotation.Y != 0.0)
                {
                    //This is actually the X-Axis rotation
                    //Convert to radians
                    Rotation.Y *= 0.0174532925199433;
                    //Apply rotation
                    ApplyRotation(Builder, 0, 0, 1, Rotation.Y);
                }


                //These files appear to only have one texture defined
                //Therefore import later- May have to change
                if (File.Exists(tday))
                {
                    for (int j = 0; j < Builder.Materials.Length; j++)
                    {
                        Builder.Materials[j].DaytimeTexture      = tday;
                        Builder.Materials[j].NighttimeTexture    = tnight;
                        Builder.Materials[j].TransparencyTexture = transtex;
                    }
                }
                if (TransparencyUsed == true)
                {
                    for (int j = 0; j < Builder.Materials.Length; j++)
                    {
                        Builder.Materials[j].TransparentColor     = FirstPxTransparent ? FirstPxColor : transparentColor;
                        Builder.Materials[j].TransparentColorUsed = true;
                    }
                }
            }
            ApplyMeshBuilder(ref Object, Builder, LoadMode, false, false);
            Object.Mesh.CreateNormals();
            return(Object);
        }
示例#3
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;
			}
		}
示例#4
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;
						}
					}
				}
			}
		}
示例#5
0
        // read object
        /// <summary>Loads a Loksim3D object from a file.</summary>
        /// <param name="FileName">The text file to load the animated object from. Must be an absolute file name.</param>
        /// <param name="Encoding">The encoding the file is saved in. If the file uses a byte order mark, the encoding indicated by the byte order mark is used and the Encoding parameter is ignored.</param>
        /// <param name="LoadMode">The texture load mode.</param>
        /// <param name="ForceTextureRepeatX">Whether to force TextureWrapMode.Repeat for the X-axis</param>
        /// /// <param name="ForceTextureRepeatY">Whether to force TextureWrapMode.Repeat for the Y-axis</param>
        /// <returns>The object loaded.</returns>
        internal static ObjectManager.StaticObject ReadObject(string FileName, System.Text.Encoding Encoding,ObjectManager.ObjectLoadMode LoadMode, bool ForceTextureRepeatX, bool ForceTextureRepeatY, double RotationX, double RotationY, double RotationZ)
        {
            XmlDocument currentXML = new XmlDocument();
            //May need to be changed to use de-DE
            System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture;
            //Initialise the object
            ObjectManager.StaticObject Object = new ObjectManager.StaticObject();
			Object.Mesh.Faces = new World.MeshFace[] { };
			Object.Mesh.Materials = new World.MeshMaterial[] { };
			Object.Mesh.Vertices = new World.Vertex[] { };
            MeshBuilder Builder = new MeshBuilder();
			World.Vector3Df[] Normals = new World.Vector3Df[4];
            bool PropertiesFound = false;

            World.Vertex[] tempVertices = new World.Vertex[0];
            World.Vector3Df[] tempNormals = new World.Vector3Df[0];
            World.ColorRGB transparentColor = new World.ColorRGB();
            string tday = null;
            string tnight = null;
            bool TransparencyUsed = false;
            bool Face2 = false;
            int TextureWidth = 0;
            int TextureHeight = 0;
            if (File.Exists(FileName))
            {
                currentXML.Load(FileName);
            }
            else
            {
                return null;
            }
            //Check for null
            if (currentXML.DocumentElement != null)
            {
                XmlNodeList DocumentNodes = currentXML.DocumentElement.SelectNodes("/OBJECT");
                //Check this file actually contains Loksim3D object nodes
                if (DocumentNodes != null)
                {
                    foreach (XmlNode outerNode in DocumentNodes)
                    {
                        if (outerNode.HasChildNodes)
                        {
                            foreach (XmlNode node in outerNode.ChildNodes)
                            {
                                //I think there should only be one properties node??
                                //Need better format documentation
                                if (node.Name == "Props" && PropertiesFound == false)
                                {
                                    if (node.Attributes != null)
                                    {
                                        //Our node has child nodes, therefore this properties node should be valid
                                        //Needs better validation
                                        PropertiesFound = true;
                                        foreach (XmlAttribute attribute in node.Attributes)
                                        {
                                            switch (attribute.Name)
                                            {
                                                //Sets the texture
                                                //Loksim3D objects only support daytime textures
                                                case "Texture":
                                                    tday = OpenBveApi.Path.CombineFile(System.IO.Path.GetDirectoryName(FileName), attribute.Value);
                                                    if (File.Exists(tday))
                                                    {
                                                        try
                                                        {
                                                            using (Bitmap TextureInformation = new Bitmap(tday))
                                                            {
                                                                TextureWidth = TextureInformation.Width;
                                                                TextureHeight = TextureInformation.Height;
                                                                Color color = TextureInformation.GetPixel(1, 1);
                                                                transparentColor = new World.ColorRGB((byte) color.R, (byte) color.G, (byte) color.B);
                                                            }
                                                        }
                                                        catch
                                                        {
                                                            Interface.AddMessage(Interface.MessageType.Error, true, "An error occured loading daytime texture " + tday + " in file " + FileName);
                                                            tday = null;
                                                        }
                                                    }
                                                    else
                                                    {
                                                        Interface.AddMessage(Interface.MessageType.Error, true, "DaytimeTexture " + tday + " could not be found in file " + FileName);
                                                    }
                                                    break;
                                                //Defines whether the texture uses transparency
                                                //May be omitted
                                                case "Transparent":
                                                    if (attribute.Value == "TRUE")
                                                    {
                                                        TransparencyUsed = true;
                                                    }
                                                    else
                                                    {
                                                        TransparencyUsed = false;
                                                    }
                                                    break;
                                                //Sets the transparency type
                                                case "TransparentTyp":
                                                    switch (attribute.Value)
                                                    {
                                                        case "0":
                                                            //Transparency is disabled
                                                            TransparencyUsed = false;
                                                            break;
                                                        case "1":
                                                            //Transparency is solid black
                                                            TransparencyUsed = true;
                                                            transparentColor = new World.ColorRGB(0,0,0);
                                                            break;
                                                        case "2":
                                                            //Transparency is the color at Pixel 1,1
                                                            TransparencyUsed = true;
                                                            break;
                                                        case "3":
                                                            //This is used when transparency is used with an alpha bitmap
                                                            //Not currently supported
                                                            TransparencyUsed = false;
                                                            break;
                                                    }
                                                    break;
                                                //Sets whether the rears of the faces are to be drawn
                                                case "Drawrueckseiten":
                                                    Face2 = true;
                                                    break;

                                                /*
                                         * MISSING PROPERTIES:
                                         * AutoRotate - Rotate with tracks?? LS3D presumably uses a 3D world system.
                                         * Beleuchtet- Translates as illuminated. Presume something to do with lighting? - What emissive color?
                                         * FileAuthor
                                         * FileInfo
                                         * FilePicture
                                         */
                                            }
                                        }
                                    }
                                }
                                //The point command is eqivilant to a vertex
                                else if (node.Name == "Point" && node.HasChildNodes)
                                {
                                    foreach (XmlNode childNode in node.ChildNodes)
                                    {
                                        if (childNode.Name == "Props" && childNode.Attributes != null)
                                        {
                                            //Vertex
                                            double vx = 0.0, vy = 0.0, vz = 0.0;
                                            //Normals
                                            double nx = 0.0, ny = 0.0, nz = 0.0;
                                            foreach (XmlAttribute attribute in childNode.Attributes)
                                            {
                                                switch (attribute.Name)
                                                {
                                                    //Sets the vertex normals
                                                    case "Normal":
                                                        string[] NormalPoints = attribute.Value.Split(';');
                                                        double.TryParse(NormalPoints[0], out nx);
                                                        double.TryParse(NormalPoints[1], out ny);
                                                        double.TryParse(NormalPoints[2], out nz);
                                                        break;
                                                    //Sets the vertex 3D co-ordinates
                                                    case "Vekt":
                                                        string[] VertexPoints = attribute.Value.Split(';');
                                                        double.TryParse(VertexPoints[0], out vx);
                                                        double.TryParse(VertexPoints[1], out vy);
                                                        double.TryParse(VertexPoints[2], out vz);
                                                        break;
                                                }
                                            }
                                            World.Normalize(ref nx, ref ny, ref nz);

                                            {
                                                //Resize temp arrays
                                                Array.Resize<World.Vertex>(ref tempVertices, tempVertices.Length + 1);
                                                Array.Resize<World.Vector3Df>(ref tempNormals, tempNormals.Length + 1);
                                                //Add vertex and normals to temp array
                                                tempVertices[tempVertices.Length - 1].Coordinates = new World.Vector3D(vx, vy, vz);
                                                tempNormals[tempNormals.Length - 1] = new World.Vector3Df((float)nx, (float)ny, (float)nz);
                                            }

                                            Array.Resize<World.Vertex>(ref Builder.Vertices, Builder.Vertices.Length + 1);
                                            while (Builder.Vertices.Length >= Normals.Length)
                                            {
                                                Array.Resize<World.Vector3Df>(ref Normals, Normals.Length << 1);
                                            }
                                            Builder.Vertices[Builder.Vertices.Length - 1].Coordinates = new World.Vector3D(vx, vy, vz);
                                            Normals[Builder.Vertices.Length - 1] = new World.Vector3Df((float) nx, (float) ny, (float) nz);
                                        }
                                    }
                                }
                                //The Flaeche command creates a face
                                else if (node.Name == "Flaeche" && node.HasChildNodes)
                                {
                                    foreach (XmlNode childNode in node.ChildNodes)
                                    {
                                        if (childNode.Name == "Props" && childNode.Attributes != null)
                                        {
                                            //Defines the verticies in this face
                                            //**NOTE**: A vertex may appear in multiple faces with different texture co-ordinates
                                            if (childNode.Attributes["Points"] != null)
                                            {
                                                string[] Verticies = childNode.Attributes["Points"].Value.Split(';');
                                                int f = Builder.Faces.Length;
                                                //Add 1 to the length of the face array
                                                Array.Resize<World.MeshFace>(ref Builder.Faces, f + 1);
                                                Builder.Faces[f] = new World.MeshFace();
                                                //Create the vertex array for the face
                                                Builder.Faces[f].Vertices = new World.MeshFaceVertex[Verticies.Length];
                                                while (Builder.Vertices.Length > Normals.Length)
                                                {
                                                    Array.Resize<World.Vector3Df>(ref Normals,
                                                        Normals.Length << 1);
                                                }
                                                //Run through the vertices list and grab from the temp array
                                                for (int j = 0; j < Verticies.Length; j++)
                                                {
                                                    //This is the position of the vertex in the temp array
                                                    int currentVertex;
                                                    int.TryParse(Verticies[j], out currentVertex);
                                                    //Add one to the actual vertex array
                                                    Array.Resize<World.Vertex>(ref Builder.Vertices, Builder.Vertices.Length + 1);
                                                    //Set coordinates
                                                    Builder.Vertices[Builder.Vertices.Length - 1].Coordinates = tempVertices[currentVertex].Coordinates;
                                                    //Set the vertex index
                                                    Builder.Faces[f].Vertices[j].Index = (ushort)(Builder.Vertices.Length - 1);
                                                    //Set the normals
                                                    Builder.Faces[f].Vertices[j].Normal = tempNormals[currentVertex];
                                                    //Now deal with the texture
                                                    //Texture mapping points are in pixels X,Y and are relative to the face in question rather than the vertex
                                                    if (childNode.Attributes["Texture"] != null)
                                                    {
                                                        string[] TextureCoords = childNode.Attributes["Texture"].Value.Split(';');
                                                        World.Vector2Df currentCoords;
                                                        float OpenBVEWidth;
                                                        float OpenBVEHeight;
                                                        string[] splitCoords = TextureCoords[j].Split(',');
                                                        float.TryParse(splitCoords[0], out OpenBVEWidth);
                                                        float.TryParse(splitCoords[1], out OpenBVEHeight);
                                                        if (TextureWidth != 0 && TextureHeight != 0)
                                                        {
                                                            currentCoords.X = (OpenBVEWidth / TextureWidth);
                                                            currentCoords.Y = (OpenBVEHeight / TextureHeight);
                                                        }
                                                        else
                                                        {
                                                            currentCoords.X = 0;
                                                            currentCoords.Y = 0;
                                                        }
                                                        Builder.Vertices[Builder.Vertices.Length - 1].TextureCoordinates = currentCoords;
                                                    }
                                                    if (Face2)
                                                    {
                                                        Builder.Faces[f].Flags = (byte) World.MeshFace.Face2Mask;
                                                    }
                                                }
                                            }

                                        }
                                    }
                                }

                            }
                        }
                    }
                }
                
                //Apply rotation
                /*
                 * NOTES:
                 * No rotation order is specified
                 * The rotation string in a .l3dgrp file is ordered Y, X, Z    ??? Can't find a good reason for this ???
                 * Rotations must still be performed in X,Y,Z order to produce correct results
                 */

                if (RotationX != 0.0)
                {
                    //This is actually the Y-Axis rotation
                    //Convert to radians
                    RotationX *= 0.0174532925199433;
                    //Apply rotation
                    ApplyRotation(Builder, 0, 1, 0, RotationX);
                }
                if (RotationY != 0.0)
                {
                    //This is actually the X-Axis rotation
                    //Convert to radians
                    RotationY *= 0.0174532925199433;
                    //Apply rotation
                    ApplyRotation(Builder, 1, 0, 0, RotationY);
                }
                if (RotationZ != 0.0)
                {
                    //Convert to radians
                    RotationZ *= 0.0174532925199433;
                    //Apply rotation
                    ApplyRotation(Builder, 0, 0, 1, RotationZ);
                }
                
                //These files appear to only have one texture defined
                //Therefore import later- May have to change
                if (File.Exists(tday))
                {
                    for (int j = 0; j < Builder.Materials.Length; j++)
                    {
                        Builder.Materials[j].DaytimeTexture = tday;
                        Builder.Materials[j].NighttimeTexture = tnight;
                    }
                }
                if (TransparencyUsed == true)
                {
                    for (int j = 0; j < Builder.Materials.Length; j++)
                    {
                        Builder.Materials[j].TransparentColor = transparentColor;
                        Builder.Materials[j].TransparentColorUsed = true;
                    }
                }
                 
            }
            ApplyMeshBuilder(ref Object, Builder, LoadMode, ForceTextureRepeatX, ForceTextureRepeatY);
            World.CreateNormals(ref Object.Mesh);
            return Object;
        }
示例#6
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;
			}
		}
示例#7
0
        /// <summary>Loads a Wavefront object from a file.</summary>
        /// <param name="FileName">The text file to load the animated object from. Must be an absolute file name.</param>
        /// <param name="Encoding">The encoding the file is saved in. If the file uses a byte order mark, the encoding indicated by the byte order mark is used and the Encoding parameter is ignored.</param>
        /// <param name="LoadMode">The texture load mode.</param>
        /// <param name="ForceTextureRepeatX">Whether to force TextureWrapMode.Repeat for referenced textures on the X-axis</param>
        /// <param name="ForceTextureRepeatY">Whether to force TextureWrapMode.Repeat for referenced textures on the Y-axis</param>
        /// <returns>The object loaded.</returns>
        internal static ObjectManager.StaticObject ReadObject(string FileName, System.Text.Encoding Encoding, ObjectManager.ObjectLoadMode LoadMode, bool ForceTextureRepeatX, bool ForceTextureRepeatY)
        {
            ObjectManager.StaticObject Object = new ObjectManager.StaticObject
            {
                Mesh =
                {
                    Faces     = new World.MeshFace[]     { },
                    Materials = new World.MeshMaterial[] { },
                    Vertices  = new World.Vertex[]       { }
                }
            };

            MeshBuilder Builder = new MeshBuilder();

            /*
             * Temporary arrays
             */
            List <Vector3> tempVertices = new List <Vector3>();
            List <Vector3> tempNormals  = new List <Vector3>();
            List <Vector2> tempCoords   = new List <Vector2>();

            Material[] TempMaterials = new Material[0];
            //Stores the current material
            int currentMaterial = -1;

            //Read the contents of the file
            string[] Lines = File.ReadAllLines(FileName);

            //Preprocess
            for (int i = 0; i < Lines.Length; i++)
            {
                // Strip hash comments
                int c = Lines[i].IndexOf("#", StringComparison.Ordinal);
                if (c >= 0)
                {
                    Lines[i] = Lines[i].Substring(0, c);
                }
                // collect arguments
                List <string> Arguments = new List <string>(Lines[i].Split(new char[] { ' ', '\t' }, StringSplitOptions.None));
                for (int j = Arguments.Count - 1; j >= 0; j--)
                {
                    Arguments[j] = Arguments[j].Trim();
                    if (Arguments[j] == string.Empty)
                    {
                        Arguments.RemoveAt(j);
                    }
                }
                if (Arguments.Count == 0)
                {
                    continue;
                }
                switch (Arguments[0].ToLowerInvariant())
                {
                case "v":
                    //Vertex
                    Vector3 vertex = new Vector3();
                    if (!double.TryParse(Arguments[1], out vertex.X))
                    {
                        Interface.AddMessage(Interface.MessageType.Warning, false, "Invalid X co-ordinate in Vertex at Line " + i);
                    }
                    if (!double.TryParse(Arguments[2], out vertex.Y))
                    {
                        Interface.AddMessage(Interface.MessageType.Warning, false, "Invalid Y co-ordinate in Vertex at Line " + i);
                    }
                    if (!double.TryParse(Arguments[3], out vertex.Z))
                    {
                        Interface.AddMessage(Interface.MessageType.Warning, false, "Invalid Z co-ordinate in Vertex at Line " + i);
                    }
                    tempVertices.Add(vertex);
                    break;

                case "vt":
                    //Vertex texture co-ords
                    Vector2 coords = new Vector2();
                    if (!double.TryParse(Arguments[1], out coords.X))
                    {
                        Interface.AddMessage(Interface.MessageType.Warning, false, "Invalid X co-ordinate in Texture Co-ordinates at Line " + i);
                    }
                    if (!double.TryParse(Arguments[2], out coords.Y))
                    {
                        Interface.AddMessage(Interface.MessageType.Warning, false, "Invalid X co-ordinate in Texture Co-Ordinates at Line " + i);
                    }
                    //Wavefront obj texture co-ords Y axis appear inverted v.s. BVE standard
                    coords.Y = -coords.Y;
                    tempCoords.Add(coords);
                    break;

                case "vn":
                    Vector3 normal = new Vector3();
                    if (!double.TryParse(Arguments[1], out normal.X))
                    {
                        Interface.AddMessage(Interface.MessageType.Warning, false, "Invalid X co-ordinate in Vertex Normal at Line " + i);
                    }
                    if (!double.TryParse(Arguments[2], out normal.Y))
                    {
                        Interface.AddMessage(Interface.MessageType.Warning, false, "Invalid Y co-ordinate in Vertex Normal at Line " + i);
                    }
                    if (!double.TryParse(Arguments[3], out normal.Z))
                    {
                        Interface.AddMessage(Interface.MessageType.Warning, false, "Invalid Z co-ordinate in Vertex Normal at Line " + i);
                    }
                    tempNormals.Add(normal);
                    //Vertex normals
                    break;

                case "vp":
                    //Parameter space verticies, not supported
                    throw new NotSupportedException("Parameter space verticies are not supported by this parser");

                case "f":
                    //Creates a new face

                    //Create the temp list to hook out the vertices
                    List <World.Vertex> vertices = new List <World.Vertex>();
                    List <Vector3>      normals  = new List <Vector3>();
                    for (int f = 1; f < Arguments.Count; f++)
                    {
                        World.Vertex newVertex     = new World.Vertex();
                        string[]     faceArguments = Arguments[f].Split(new char[] { '/' }, StringSplitOptions.None);
                        int          idx;
                        if (!int.TryParse(faceArguments[0], out idx) || idx > tempVertices.Count)
                        {
                            Interface.AddMessage(Interface.MessageType.Warning, false, "Invalid Vertex index in Face " + f + " at Line " + i);
                            continue;
                        }
                        newVertex.Coordinates = tempVertices[idx - 1];
                        if (faceArguments.Length <= 1)
                        {
                            normals.Add(new Vector3());
                        }
                        else
                        {
                            if (!int.TryParse(faceArguments[1], out idx) || idx > tempCoords.Count)
                            {
                                if (!string.IsNullOrEmpty(faceArguments[1]))
                                {
                                    Interface.AddMessage(Interface.MessageType.Warning, false, "Invalid Texture Co-ordinate index in Face " + f + " at Line " + i);
                                }
                                newVertex.TextureCoordinates = new Vector2();
                            }
                            else
                            {
                                newVertex.TextureCoordinates = tempCoords[idx - 1];
                            }
                        }
                        if (faceArguments.Length <= 2)
                        {
                            normals.Add(new Vector3());
                        }
                        else
                        {
                            if (!int.TryParse(faceArguments[2], out idx) || idx > tempNormals.Count)
                            {
                                if (!string.IsNullOrEmpty(faceArguments[2]))
                                {
                                    Interface.AddMessage(Interface.MessageType.Warning, false, "Invalid Vertex Normal index in Face " + f + " at Line " + i);
                                }
                                normals.Add(new Vector3());
                            }
                            else
                            {
                                normals.Add(tempNormals[idx - 1]);
                            }
                        }
                        vertices.Add(newVertex);
                    }
                    World.MeshFaceVertex[] Vertices = new World.MeshFaceVertex[vertices.Count];
                    for (int k = 0; k < vertices.Count; k++)
                    {
                        int v = Builder.Vertices.FindIndex(a => a == vertices[k]);
                        if (v != -1)
                        {
                            Vertices[k].Index = (ushort)v;
                        }
                        else
                        {
                            Builder.Vertices.Add(vertices[k]);
                            Vertices[k].Index = (ushort)(Builder.Vertices.Count - 1);
                        }

                        Vertices[k].Normal = normals[k];
                    }
                    Builder.Faces.Add(currentMaterial == -1 ? new World.MeshFace(Vertices, 0) : new World.MeshFace(Vertices, (ushort)currentMaterial));
                    break;

                case "g":
                    //Starts a new face group and (normally) applies a new texture
                    ApplyMeshBuilder(ref Object, Builder);
                    Builder = new MeshBuilder();
                    break;

                case "s":
                    /*
                     * Changes the smoothing group applied to these vertexes:
                     * 0- Disabled (Overriden by Vertex normals)
                     * Otherwise appears to be a bitmask (32 available groups)
                     * whereby faces within the same groups have their normals averaged
                     * to appear smooth joins
                     *
                     * Not really supported at the minute, probably requires the engine
                     * twiddling to deliberately support specifiying the shading type for a face
                     *
                     */
                    break;

                case "mtllib":
                    //Loads the library of materials used by this file
                    string MaterialsPath = OpenBveApi.Path.CombineFile(Path.GetDirectoryName(FileName), Arguments[1]);
                    if (File.Exists(MaterialsPath))
                    {
                        LoadMaterials(MaterialsPath, ref TempMaterials);
                    }
                    break;

                case "usemtl":
                    for (int m = 0; m < TempMaterials.Length; m++)
                    {
                        if (TempMaterials[m].Key.ToLowerInvariant() == Arguments[1].ToLowerInvariant())
                        {
                            bool mf = false;
                            for (int k = 0; k < Builder.Materials.Length; k++)
                            {
                                if (Builder.Materials[k].Key.ToLowerInvariant() == Arguments[1].ToLowerInvariant())
                                {
                                    mf = true;
                                    currentMaterial = k;
                                    break;
                                }
                            }
                            if (!mf)
                            {
                                Array.Resize(ref Builder.Materials, Builder.Materials.Length + 1);
                                Builder.Materials[Builder.Materials.Length - 1] = TempMaterials[m];
                                currentMaterial = Builder.Materials.Length - 1;
                            }
                            break;
                        }
                        if (m == TempMaterials.Length)
                        {
                            Interface.AddMessage(Interface.MessageType.Error, true, "Material " + Arguments[1] + " was not found.");
                            currentMaterial = -1;
                        }
                    }
                    break;

                default:
                    Interface.AddMessage(Interface.MessageType.Warning, false, "Unrecognised command " + Arguments[0]);
                    break;
                }
            }
            ApplyMeshBuilder(ref Object, Builder);
            Object.Mesh.CreateNormals();
            return(Object);
        }
示例#8
0
		// process structure
		private static bool ProcessStructure(string FileName, Structure Structure, out ObjectManager.StaticObject Object, ObjectManager.ObjectLoadMode LoadMode, bool ForceTextureRepeatX, bool ForceTextureRepeatY) {
			System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture;
			Object = new ObjectManager.StaticObject();
			Object.Mesh.Faces = new World.MeshFace[] { };
			Object.Mesh.Materials = new World.MeshMaterial[] { };
			Object.Mesh.Vertices = new World.Vertex[] { };
			// file
			for (int i = 0; i < Structure.Data.Length; i++) {
				Structure f = Structure.Data[i] as Structure;
				if (f == null) {
					Interface.AddMessage(Interface.MessageType.Error, false, "Top-level inlined arguments are invalid in x object file " + FileName);
					return false;
				}
				switch (f.Name) {
					case "Mesh":
						{
							// mesh
							if (f.Data.Length < 4) {
								Interface.AddMessage(Interface.MessageType.Error, false, "Mesh is expected to have at least 4 arguments in x object file " + FileName);
								return false;
							} else if (!(f.Data[0] is int)) {
								Interface.AddMessage(Interface.MessageType.Error, false, "nVertices is expected to be a DWORD in Mesh in x object file " + FileName);
								return false;
							} else if (!(f.Data[1] is Structure[])) {
								Interface.AddMessage(Interface.MessageType.Error, false, "vertices[nVertices] is expected to be a Vector array in Mesh in x object file " + FileName);
								return false;
							} else if (!(f.Data[2] is int)) {
								Interface.AddMessage(Interface.MessageType.Error, false, "nFaces is expected to be a DWORD in Mesh in x object file " + FileName);
								return false;
							} else if (!(f.Data[3] is Structure[])) {
								Interface.AddMessage(Interface.MessageType.Error, false, "faces[nFaces] is expected to be a MeshFace array in Mesh in x object file " + FileName);
								return false;
							}
							int nVertices = (int)f.Data[0];
							if (nVertices < 0) {
								Interface.AddMessage(Interface.MessageType.Error, false, "nVertices is expected to be non-negative in Mesh in x object file " + FileName);
								return false;
							}
							Structure[] vertices = (Structure[])f.Data[1];
							if (nVertices != vertices.Length) {
								Interface.AddMessage(Interface.MessageType.Error, false, "nVertices does not match with the length of array vertices in Mesh in x object file " + FileName);
								return false;
							}
							int nFaces = (int)f.Data[2];
							if (nFaces < 0) {
								Interface.AddMessage(Interface.MessageType.Error, false, "nFaces is expected to be non-negative in Mesh in x object file " + FileName);
								return false;
							}
							Structure[] faces = (Structure[])f.Data[3];
							if (nFaces != faces.Length) {
								Interface.AddMessage(Interface.MessageType.Error, false, "nFaces does not match with the length of array faces in Mesh in x object file " + FileName);
								return false;
							}
							// collect vertices
							World.Vertex[] Vertices = new World.Vertex[nVertices];
							for (int j = 0; j < nVertices; j++) {
								if (vertices[j].Name != "Vector") {
									Interface.AddMessage(Interface.MessageType.Error, false, "vertices[" + j.ToString(Culture) + "] is expected to be of template Vertex in Mesh in x object file " + FileName);
									return false;
								} else if (vertices[j].Data.Length != 3) {
									Interface.AddMessage(Interface.MessageType.Error, false, "vertices[" + j.ToString(Culture) + "] is expected to have 3 arguments in Mesh in x object file " + FileName);
									return false;
								} else if (!(vertices[j].Data[0] is double)) {
									Interface.AddMessage(Interface.MessageType.Error, false, "x is expected to be a float in vertices[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName);
									return false;
								} else if (!(vertices[j].Data[1] is double)) {
									Interface.AddMessage(Interface.MessageType.Error, false, "y is expected to be a float in vertices[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName);
									return false;
								} else if (!(vertices[j].Data[2] is double)) {
									Interface.AddMessage(Interface.MessageType.Error, false, "z is expected to be a float in vertices[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName);
									return false;
								}
								double x = (double)vertices[j].Data[0];
								double y = (double)vertices[j].Data[1];
								double z = (double)vertices[j].Data[2];
								Vertices[j].Coordinates = new World.Vector3D(x, y, z);
							}
							// collect faces
							int[][] Faces = new int[nFaces][];
							World.Vector3Df[][] FaceNormals = new World.Vector3Df[nFaces][];
							int[] FaceMaterials = new int[nFaces];
							for (int j = 0; j < nFaces; j++) {
								FaceMaterials[j] = -1;
							}
							for (int j = 0; j < nFaces; j++) {
								if (faces[j].Name != "MeshFace") {
									Interface.AddMessage(Interface.MessageType.Error, false, "faces[" + j.ToString(Culture) + "] is expected to be of template MeshFace in Mesh in x object file " + FileName);
									return false;
								} else if (faces[j].Data.Length != 2) {
									Interface.AddMessage(Interface.MessageType.Error, false, "face[" + j.ToString(Culture) + "] is expected to have 2 arguments in Mesh in x object file " + FileName);
									return false;
								} else if (!(faces[j].Data[0] is int)) {
									Interface.AddMessage(Interface.MessageType.Error, false, "nFaceVertexIndices is expected to be a DWORD in face[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName);
									return false;
								} else if (!(faces[j].Data[1] is int[])) {
									Interface.AddMessage(Interface.MessageType.Error, false, "faceVertexIndices[nFaceVertexIndices] is expected to be a DWORD array in face[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName);
									return false;
								}
								int nFaceVertexIndices = (int)faces[j].Data[0];
								if (nFaceVertexIndices < 0) {
									Interface.AddMessage(Interface.MessageType.Error, false, "nFaceVertexIndices is expected to be non-negative in MeshFace in Mesh in x object file " + FileName);
									return false;
								}
								int[] faceVertexIndices = (int[])faces[j].Data[1];
								if (nFaceVertexIndices != faceVertexIndices.Length) {
									Interface.AddMessage(Interface.MessageType.Error, false, "nFaceVertexIndices does not match with the length of array faceVertexIndices in face[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName);
									return false;
								}
								Faces[j] = new int[nFaceVertexIndices];
								FaceNormals[j] = new World.Vector3Df[nFaceVertexIndices];
								for (int k = 0; k < nFaceVertexIndices; k++) {
									if (faceVertexIndices[k] < 0 | faceVertexIndices[k] >= nVertices) {
										Interface.AddMessage(Interface.MessageType.Error, false, "faceVertexIndices[" + k.ToString(Culture) + "] does not reference a valid vertex in face[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName);
										return false;
									}
									Faces[j][k] = faceVertexIndices[k];
									FaceNormals[j][k] = new World.Vector3Df(0.0f, 0.0f, 0.0f);
								}
							}
							// collect additional templates
							Material[] Materials = new Material[] { };
							for (int j = 4; j < f.Data.Length; j++) {
								Structure g = f.Data[j] as Structure;
								if (g == null) {
									Interface.AddMessage(Interface.MessageType.Error, false, "Unexpected inlined argument encountered in Mesh in x object file " + FileName);
									return false;
								}
								switch (g.Name) {
									case "MeshMaterialList":
										{
											// meshmateriallist
											if (g.Data.Length < 3) {
												Interface.AddMessage(Interface.MessageType.Error, false, "MeshMaterialList is expected to have at least 3 arguments in Mesh in x object file " + FileName);
												return false;
											} else if (!(g.Data[0] is int)) {
												Interface.AddMessage(Interface.MessageType.Error, false, "nMaterials is expected to be a DWORD in MeshMaterialList in Mesh in x object file " + FileName);
												return false;
											} else if (!(g.Data[1] is int)) {
												Interface.AddMessage(Interface.MessageType.Error, false, "nFaceIndexes is expected to be a DWORD in MeshMaterialList in Mesh in x object file " + FileName);
												return false;
											} else if (!(g.Data[2] is int[])) {
												Interface.AddMessage(Interface.MessageType.Error, false, "faceIndexes[nFaceIndexes] is expected to be a DWORD array in MeshMaterialList in Mesh in x object file " + FileName);
												return false;
											}
											int nMaterials = (int)g.Data[0];
											if (nMaterials < 0) {
												Interface.AddMessage(Interface.MessageType.Error, false, "nMaterials is expected to be non-negative in MeshMaterialList in Mesh in x object file " + FileName);
												return false;
											}
											int nFaceIndexes = (int)g.Data[1];
											if (nFaceIndexes < 0) {
												Interface.AddMessage(Interface.MessageType.Error, false, "nFaceIndexes is expected to be non-negative in MeshMaterialList in Mesh in x object file " + FileName);
												return false;
											} else if (nFaceIndexes > nFaces) {
												Interface.AddMessage(Interface.MessageType.Error, false, "nFaceIndexes does not reference valid faces in MeshMaterialList in Mesh in x object file " + FileName);
												return false;
											}
											int[] faceIndexes = (int[])g.Data[2];
											if (nFaceIndexes != faceIndexes.Length) {
												Interface.AddMessage(Interface.MessageType.Error, false, "nFaceIndexes does not match with the length of array faceIndexes in face[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName);
												return false;
											}
											for (int k = 0; k < nFaceIndexes; k++) {
												if (faceIndexes[k] < 0 | faceIndexes[k] >= nMaterials) {
													Interface.AddMessage(Interface.MessageType.Error, false, "faceIndexes[" + k.ToString(Culture) + "] does not reference a valid Material template in MeshMaterialList in Mesh in x object file " + FileName);
													return false;
												}
											}
											// collect material templates
											int mn = Materials.Length;
											Array.Resize<Material>(ref Materials, mn + nMaterials);
											for (int k = 0; k < nMaterials; k++) {
												Materials[mn + k].faceColor = new World.ColorRGBA(255, 255, 255, 255);
												Materials[mn + k].specularColor = new World.ColorRGB(0, 0, 0);
												Materials[mn + k].emissiveColor = new World.ColorRGB(0, 0, 0);
												Materials[mn + k].TextureFilename = null;
											}
											int MaterialIndex = mn;
											for (int k = 3; k < g.Data.Length; k++) {
												Structure h = g.Data[k] as Structure;
												if (h == null) {
													Interface.AddMessage(Interface.MessageType.Error, false, "Unexpected inlined argument encountered in MeshMaterialList in Mesh in x object file " + FileName);
													return false;
												} else if (h.Name != "Material") {
													Interface.AddMessage(Interface.MessageType.Error, false, "Material template expected in MeshMaterialList in Mesh in x object file " + FileName);
													return false;
												} else {
													// material
													if (h.Data.Length < 4) {
														Interface.AddMessage(Interface.MessageType.Error, false, "Material is expected to have at least 4 arguments in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													} else if (!(h.Data[0] is Structure)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "faceColor is expected to be a ColorRGBA in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													} else if (!(h.Data[1] is double)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "power is expected to be a float in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													} else if (!(h.Data[2] is Structure)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "specularColor is expected to be a ColorRGBA in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													} else if (!(h.Data[3] is Structure)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "emissiveColor is expected to be a ColorRGBA in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													}
													Structure faceColor = (Structure)h.Data[0];
													Structure specularColor = (Structure)h.Data[2];
													Structure emissiveColor = (Structure)h.Data[3];
													double red, green, blue, alpha;
													// collect face color
													if (faceColor.Name != "ColorRGBA") {
														Interface.AddMessage(Interface.MessageType.Error, false, "faceColor is expected to be a ColorRGBA in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													} else if (faceColor.Data.Length != 4) {
														Interface.AddMessage(Interface.MessageType.Error, false, "faceColor is expected to have 4 arguments in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													} else if (!(faceColor.Data[0] is double)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "red is expected to be a float in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													} else if (!(faceColor.Data[1] is double)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "green is expected to be a float in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													} else if (!(faceColor.Data[2] is double)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "blue is expected to be a float in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													} else if (!(faceColor.Data[3] is double)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "alpha is expected to be a float in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													}
													red = (double)faceColor.Data[0];
													green = (double)faceColor.Data[1];
													blue = (double)faceColor.Data[2];
													alpha = (double)faceColor.Data[3];
													if (red < 0.0 | red > 1.0) {
														Interface.AddMessage(Interface.MessageType.Error, false, "red is expected to be in the range from 0.0 to 1.0 in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														red = red < 0.5 ? 0.0 : 1.0;
													}
													if (green < 0.0 | green > 1.0) {
														Interface.AddMessage(Interface.MessageType.Error, false, "green is expected to be in the range from 0.0 to 1.0 in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														green = green < 0.5 ? 0.0 : 1.0;
													}
													if (blue < 0.0 | blue > 1.0) {
														Interface.AddMessage(Interface.MessageType.Error, false, "blue is expected to be in the range from 0.0 to 1.0 in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														blue = blue < 0.5 ? 0.0 : 1.0;
													}
													if (alpha < 0.0 | alpha > 1.0) {
														Interface.AddMessage(Interface.MessageType.Error, false, "alpha is expected to be in the range from 0.0 to 1.0 in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														alpha = alpha < 0.5 ? 0.0 : 1.0;
													}
													Materials[MaterialIndex].faceColor = new World.ColorRGBA((byte)Math.Round(255.0 * red), (byte)Math.Round(255.0 * green), (byte)Math.Round(255.0 * blue), (byte)Math.Round(255.0 * alpha));
													// collect specular color
													if (specularColor.Name != "ColorRGB") {
														Interface.AddMessage(Interface.MessageType.Error, false, "specularColor is expected to be a ColorRGB in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													} else if (specularColor.Data.Length != 3) {
														Interface.AddMessage(Interface.MessageType.Error, false, "specularColor is expected to have 3 arguments in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													} else if (!(specularColor.Data[0] is double)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "red is expected to be a float in specularColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													} else if (!(specularColor.Data[1] is double)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "green is expected to be a float in specularColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													} else if (!(specularColor.Data[2] is double)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "blue is expected to be a float in specularColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													}
													red = (double)specularColor.Data[0];
													green = (double)specularColor.Data[1];
													blue = (double)specularColor.Data[2];
													if (red < 0.0 | red > 1.0) {
														Interface.AddMessage(Interface.MessageType.Error, false, "red is expected to be in the range from 0.0 to 1.0 in specularColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														red = red < 0.5 ? 0.0 : 1.0;
													}
													if (green < 0.0 | green > 1.0) {
														Interface.AddMessage(Interface.MessageType.Error, false, "green is expected to be in the range from 0.0 to 1.0 in specularColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														green = green < 0.5 ? 0.0 : 1.0;
													}
													if (blue < 0.0 | blue > 1.0) {
														Interface.AddMessage(Interface.MessageType.Error, false, "blue is expected to be in the range from 0.0 to 1.0 in specularColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														blue = blue < 0.5 ? 0.0 : 1.0;
													}
													Materials[MaterialIndex].specularColor = new World.ColorRGB((byte)Math.Round(255.0 * red), (byte)Math.Round(255.0 * green), (byte)Math.Round(255.0 * blue));
													// collect emissive color
													if (emissiveColor.Name != "ColorRGB") {
														Interface.AddMessage(Interface.MessageType.Error, false, "emissiveColor is expected to be a ColorRGBA in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													} else if (emissiveColor.Data.Length != 3) {
														Interface.AddMessage(Interface.MessageType.Error, false, "emissiveColor is expected to have 3 arguments in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													} else if (!(emissiveColor.Data[0] is double)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "red is expected to be a float in emissiveColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													} else if (!(emissiveColor.Data[1] is double)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "green is expected to be a float in emissiveColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													} else if (!(emissiveColor.Data[2] is double)) {
														Interface.AddMessage(Interface.MessageType.Error, false, "blue is expected to be a float in emissiveColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														return false;
													}
													red = (double)emissiveColor.Data[0];
													green = (double)emissiveColor.Data[1];
													blue = (double)emissiveColor.Data[2];
													if (red < 0.0 | red > 1.0) {
														Interface.AddMessage(Interface.MessageType.Error, false, "red is expected to be in the range from 0.0 to 1.0 in emissiveColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														red = red < 0.5 ? 0.0 : 1.0;
													}
													if (green < 0.0 | green > 1.0) {
														Interface.AddMessage(Interface.MessageType.Error, false, "green is expected to be in the range from 0.0 to 1.0 in emissiveColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														green = green < 0.5 ? 0.0 : 1.0;
													}
													if (blue < 0.0 | blue > 1.0) {
														Interface.AddMessage(Interface.MessageType.Error, false, "blue is expected to be in the range from 0.0 to 1.0 in emissiveColor in Material in MeshMaterialList in Mesh in x object file " + FileName);
														blue = blue < 0.5 ? 0.0 : 1.0;
													}
													Materials[MaterialIndex].emissiveColor = new World.ColorRGB((byte)Math.Round(255.0 * red), (byte)Math.Round(255.0 * green), (byte)Math.Round(255.0 * blue));
													// collect additional templates
													for (int l = 4; l < h.Data.Length; l++) {
														Structure e = h.Data[l] as Structure;
														if (e == null) {
															Interface.AddMessage(Interface.MessageType.Error, false, "Unexpected inlined argument encountered in Material in MeshMaterialList in Mesh in x object file " + FileName);
															return false;
														}
														switch (e.Name) {
															case "TextureFilename":
																{
																	// texturefilename
																	if (e.Data.Length != 1) {
																		Interface.AddMessage(Interface.MessageType.Error, false, "filename is expected to have 1 argument in TextureFilename in Material in MeshMaterialList in Mesh in x object file " + FileName);
																		return false;
																	} else if (!(e.Data[0] is string)) {
																		Interface.AddMessage(Interface.MessageType.Error, false, "filename is expected to be a string in TextureFilename in Material in MeshMaterialList in Mesh in x object file " + FileName);
																		return false;
																	}
																	string filename = (string)e.Data[0];
																	if (Interface.ContainsInvalidPathChars(filename)) {
																		Interface.AddMessage(Interface.MessageType.Error, false, "filename contains illegal characters in TextureFilename in Material in MeshMaterialList in Mesh in x object file " + FileName);
																	} else {
																		string File = OpenBveApi.Path.CombineFile(System.IO.Path.GetDirectoryName(FileName), filename);
																		if (System.IO.File.Exists(File)) {
																			Materials[MaterialIndex].TextureFilename = File;
																		} else {
																			Interface.AddMessage(Interface.MessageType.Error, true, "The texture file " + File + " could not be found in TextureFilename in Material in MeshMaterialList in Mesh in x object file " + FileName);
																		}
																	}
																} break;
															default:
																// unknown
																Interface.AddMessage(Interface.MessageType.Warning, false, "Unsupported template " + e.Name + " encountered in MeshMaterialList in Mesh in x object file " + FileName);
																break;
														}
													}
													// finish
													MaterialIndex++;
												}
											} if (MaterialIndex != mn + nMaterials) {
												Interface.AddMessage(Interface.MessageType.Error, false, "nMaterials does not match the number of Material templates encountered in Material in MeshMaterialList in Mesh in x object file " + FileName);
												return false;
											}
											// assign materials
											for (int k = 0; k < nFaceIndexes; k++) {
												FaceMaterials[k] = faceIndexes[k];
											}
											if (nMaterials != 0) {
												for (int k = 0; k < nFaces; k++) {
													if (FaceMaterials[k] == -1) {
														FaceMaterials[k] = 0;
													}
												}
											}
										} break;
									case "MeshTextureCoords":
										{
											// meshtexturecoords
											if (g.Data.Length != 2) {
												Interface.AddMessage(Interface.MessageType.Error, false, "MeshTextureCoords is expected to have 2 arguments in Mesh in x object file " + FileName);
												return false;
											} else if (!(g.Data[0] is int)) {
												Interface.AddMessage(Interface.MessageType.Error, false, "nTextureCoords is expected to be a DWORD in MeshTextureCoords in Mesh in x object file " + FileName);
												return false;
											} else if (!(g.Data[1] is Structure[])) {
												Interface.AddMessage(Interface.MessageType.Error, false, "textureCoords[nTextureCoords] is expected to be a Coords2d array in MeshTextureCoords in Mesh in x object file " + FileName);
												return false;
											}
											int nTextureCoords = (int)g.Data[0];
											Structure[] textureCoords = (Structure[])g.Data[1];
											if (nTextureCoords < 0 | nTextureCoords > nVertices) {
												Interface.AddMessage(Interface.MessageType.Error, false, "nTextureCoords does not reference valid vertices in MeshTextureCoords in Mesh in x object file " + FileName);
												return false;
											}
											for (int k = 0; k < nTextureCoords; k++) {
												if (textureCoords[k].Name != "Coords2d") {
													Interface.AddMessage(Interface.MessageType.Error, false, "textureCoords[" + k.ToString(Culture) + "] is expected to be a Coords2d in MeshTextureCoords in Mesh in x object file " + FileName);
													return false;
												} else if (textureCoords[k].Data.Length != 2) {
													Interface.AddMessage(Interface.MessageType.Error, false, "textureCoords[" + k.ToString(Culture) + "] is expected to have 2 arguments in MeshTextureCoords in Mesh in x object file " + FileName);
													return false;
												} else if (!(textureCoords[k].Data[0] is double)) {
													Interface.AddMessage(Interface.MessageType.Error, false, "u is expected to be a float in textureCoords[" + k.ToString(Culture) + "] in MeshTextureCoords in Mesh in x object file " + FileName);
													return false;
												} else if (!(textureCoords[k].Data[1] is double)) {
													Interface.AddMessage(Interface.MessageType.Error, false, "v is expected to be a float in textureCoords[" + k.ToString(Culture) + "] in MeshTextureCoords in Mesh in x object file " + FileName);
													return false;
												}
												double u = (double)textureCoords[k].Data[0];
												double v = (double)textureCoords[k].Data[1];
												Vertices[k].TextureCoordinates = new World.Vector2Df((float)u, (float)v);
											}
										} break;
									case "MeshNormals":
										{
											// meshnormals
											if (g.Data.Length != 4) {
												Interface.AddMessage(Interface.MessageType.Error, false, "MeshNormals is expected to have 4 arguments in Mesh in x object file " + FileName);
												return false;
											} else if (!(g.Data[0] is int)) {
												Interface.AddMessage(Interface.MessageType.Error, false, "nNormals is expected to be a DWORD in MeshNormals in Mesh in x object file " + FileName);
												return false;
											} else if (!(g.Data[1] is Structure[])) {
												Interface.AddMessage(Interface.MessageType.Error, false, "normals is expected to be a Vector array in MeshNormals in Mesh in x object file " + FileName);
												return false;
											} else if (!(g.Data[2] is int)) {
												Interface.AddMessage(Interface.MessageType.Error, false, "nFaceNormals is expected to be a DWORD in MeshNormals in Mesh in x object file " + FileName);
												return false;
											} else if (!(g.Data[3] is Structure[])) {
												Interface.AddMessage(Interface.MessageType.Error, false, "faceNormals is expected to be a MeshFace array in MeshNormals in Mesh in x object file " + FileName);
												return false;
											}
											int nNormals = (int)g.Data[0];
											if (nNormals < 0) {
												Interface.AddMessage(Interface.MessageType.Error, false, "nNormals is expected to be non-negative in MeshNormals in Mesh in x object file " + FileName);
												return false;
											}
											Structure[] normals = (Structure[])g.Data[1];
											if (nNormals != normals.Length) {
												Interface.AddMessage(Interface.MessageType.Error, false, "nNormals does not match with the length of array normals in MeshNormals in Mesh in x object file " + FileName);
												return false;
											}
											int nFaceNormals = (int)g.Data[2];
											if (nFaceNormals < 0 | nFaceNormals > nFaces) {
												Interface.AddMessage(Interface.MessageType.Error, false, "nNormals does not reference valid vertices in MeshNormals in Mesh in x object file " + FileName);
												return false;
											}
											Structure[] faceNormals = (Structure[])g.Data[3];
											if (nFaceNormals != faceNormals.Length) {
												Interface.AddMessage(Interface.MessageType.Error, false, "nFaceNormals does not match with the length of array faceNormals in MeshNormals in Mesh in x object file " + FileName);
												return false;
											}
											// collect normals
											World.Vector3Df[] Normals = new World.Vector3Df[nNormals];
											for (int k = 0; k < nNormals; k++) {
												if (normals[k].Name != "Vector") {
													Interface.AddMessage(Interface.MessageType.Error, false, "normals[" + k.ToString(Culture) + "] is expected to be of template Vertex in MeshNormals in Mesh in x object file " + FileName);
													return false;
												} else if (normals[k].Data.Length != 3) {
													Interface.AddMessage(Interface.MessageType.Error, false, "normals[" + k.ToString(Culture) + "] is expected to have 3 arguments in MeshNormals in Mesh in x object file " + FileName);
													return false;
												} else if (!(normals[k].Data[0] is double)) {
													Interface.AddMessage(Interface.MessageType.Error, false, "x is expected to be a float in normals[" + k.ToString(Culture) + "] in MeshNormals in Mesh in x object file " + FileName);
													return false;
												} else if (!(normals[k].Data[1] is double)) {
													Interface.AddMessage(Interface.MessageType.Error, false, "y is expected to be a float in normals[" + k.ToString(Culture) + " ]in MeshNormals in Mesh in x object file " + FileName);
													return false;
												} else if (!(normals[k].Data[2] is double)) {
													Interface.AddMessage(Interface.MessageType.Error, false, "z is expected to be a float in normals[" + k.ToString(Culture) + "] in MeshNormals in Mesh in x object file " + FileName);
													return false;
												}
												double x = (double)normals[k].Data[0];
												double y = (double)normals[k].Data[1];
												double z = (double)normals[k].Data[2];
												World.Normalize(ref x, ref y, ref z);
												Normals[k] = new World.Vector3Df((float)x, (float)y, (float)z);
											}
											// collect faces
											for (int k = 0; k < nFaceNormals; k++) {
												if (faceNormals[k].Name != "MeshFace") {
													Interface.AddMessage(Interface.MessageType.Error, false, "faceNormals[" + k.ToString(Culture) + "] is expected to be of template MeshFace in MeshNormals in Mesh in x object file " + FileName);
													return false;
												} else if (faceNormals[k].Data.Length != 2) {
													Interface.AddMessage(Interface.MessageType.Error, false, "faceNormals[" + k.ToString(Culture) + "] is expected to have 2 arguments in MeshNormals in Mesh in x object file " + FileName);
													return false;
												} else if (!(faceNormals[k].Data[0] is int)) {
													Interface.AddMessage(Interface.MessageType.Error, false, "nFaceVertexIndices is expected to be a DWORD in faceNormals[" + k.ToString(Culture) + "] in MeshNormals in Mesh in x object file " + FileName);
													return false;
												} else if (!(faceNormals[k].Data[1] is int[])) {
													Interface.AddMessage(Interface.MessageType.Error, false, "faceVertexIndices[nFaceVertexIndices] is expected to be a DWORD array in faceNormals[" + k.ToString(Culture) + "] in MeshNormals in Mesh in x object file " + FileName);
													return false;
												}
												int nFaceVertexIndices = (int)faceNormals[k].Data[0];
												if (nFaceVertexIndices < 0 | nFaceVertexIndices > Faces[k].Length) {
													Interface.AddMessage(Interface.MessageType.Error, false, "nFaceVertexIndices does not reference a valid vertex in MeshFace in MeshNormals in Mesh in x object file " + FileName);
													return false;
												}
												int[] faceVertexIndices = (int[])faceNormals[k].Data[1];
												if (nFaceVertexIndices != faceVertexIndices.Length) {
													Interface.AddMessage(Interface.MessageType.Error, false, "nFaceVertexIndices does not match with the length of array faceVertexIndices in faceNormals[" + k.ToString(Culture) + "] in MeshFace in MeshNormals in Mesh in x object file " + FileName);
													return false;
												}
												for (int l = 0; l < nFaceVertexIndices; l++) {
													if (faceVertexIndices[l] < 0 | faceVertexIndices[l] >= nNormals) {
														Interface.AddMessage(Interface.MessageType.Error, false, "faceVertexIndices[" + l.ToString(Culture) + "] does not reference a valid normal in faceNormals[" + k.ToString(Culture) + "] in MeshFace in MeshNormals in Mesh in x object file " + FileName);
														return false;
													}
													FaceNormals[k][l] = Normals[faceVertexIndices[l]];
												}
											}
										} break;
									default:
										// unknown
										Interface.AddMessage(Interface.MessageType.Warning, false, "Unsupported template " + g.Name + " encountered in Mesh in x object file " + FileName);
										break;

								}
							}
							// default material
							if (Materials.Length == 0) {
								Materials = new Material[1];
								Materials[0].faceColor = new World.ColorRGBA(255, 255, 255, 255);
								Materials[0].emissiveColor = new World.ColorRGB(0, 0, 0);
								Materials[0].specularColor = new World.ColorRGB(0, 0, 0);
								Materials[0].TextureFilename = null;
								for (int j = 0; j < nFaces; j++) {
									FaceMaterials[j] = 0;
								}
							}
							// create mesh
							int mf = Object.Mesh.Faces.Length;
							int mm = Object.Mesh.Materials.Length;
							int mv = Object.Mesh.Vertices.Length;
							Array.Resize<World.MeshFace>(ref Object.Mesh.Faces, mf + nFaces);
							Array.Resize<World.MeshMaterial>(ref Object.Mesh.Materials, mm + Materials.Length);
							Array.Resize<World.Vertex>(ref Object.Mesh.Vertices, mv + Vertices.Length);
							for (int j = 0; j < Materials.Length; j++) {
								bool emissive = Materials[j].emissiveColor.R != 0 | Materials[j].emissiveColor.G != 0 | Materials[j].emissiveColor.B != 0;
								bool transparent;
								if (Materials[j].TextureFilename != null) {
									TextureManager.TextureWrapMode WrapX, WrapY;
									if (ForceTextureRepeatX) {
										WrapX = TextureManager.TextureWrapMode.Repeat;
									} else {
										WrapX = TextureManager.TextureWrapMode.ClampToEdge;
									}
									if (ForceTextureRepeatY) {
										WrapY = TextureManager.TextureWrapMode.Repeat;
									} else {
										WrapY = TextureManager.TextureWrapMode.ClampToEdge;
									}
									if (WrapX != TextureManager.TextureWrapMode.Repeat | WrapY != TextureManager.TextureWrapMode.Repeat) {
										for (int k = 0; k < nFaces; k++) {
											for (int h = 0; h < Faces[k].Length; h++) {
												if (Vertices[Faces[k][h]].TextureCoordinates.X < 0.0 | Vertices[Faces[k][h]].TextureCoordinates.X > 1.0) {
													WrapX = TextureManager.TextureWrapMode.Repeat;
												}
												if (Vertices[Faces[k][h]].TextureCoordinates.Y < 0.0 | Vertices[Faces[k][h]].TextureCoordinates.Y > 1.0) {
													WrapY = TextureManager.TextureWrapMode.Repeat;
												}
											}
										}
									}
									int tday = TextureManager.RegisterTexture(Materials[j].TextureFilename, new World.ColorRGB(0, 0, 0), 1, TextureManager.TextureLoadMode.Normal, WrapX, WrapY, LoadMode != ObjectManager.ObjectLoadMode.Normal, 0, 0, 0, 0);
									Object.Mesh.Materials[mm + j].DaytimeTextureIndex = tday;
									transparent = true;
								} else {
									Object.Mesh.Materials[mm + j].DaytimeTextureIndex = -1;
									transparent = false;
								}
								Object.Mesh.Materials[mm + j].Flags = (byte)((transparent ? World.MeshMaterial.TransparentColorMask : 0) | (emissive ? World.MeshMaterial.EmissiveColorMask : 0));
								Object.Mesh.Materials[mm + j].Color = Materials[j].faceColor;
								Object.Mesh.Materials[mm + j].TransparentColor = new World.ColorRGB(0, 0, 0);
								Object.Mesh.Materials[mm + j].EmissiveColor = Materials[j].emissiveColor;
								Object.Mesh.Materials[mm + j].NighttimeTextureIndex = -1;
								Object.Mesh.Materials[mm + j].BlendMode = World.MeshMaterialBlendMode.Normal;
								Object.Mesh.Materials[mm + j].GlowAttenuationData = 0;
							}
							for (int j = 0; j < nFaces; j++) {
								Object.Mesh.Faces[mf + j].Material = (ushort)FaceMaterials[j];
								Object.Mesh.Faces[mf + j].Vertices = new World.MeshFaceVertex[Faces[j].Length];
								for (int k = 0; k < Faces[j].Length; k++) {
									Object.Mesh.Faces[mf + j].Vertices[mv + k] = new World.MeshFaceVertex(mv + Faces[j][k], FaceNormals[j][k]);
								}
							}
							for (int j = 0; j < Vertices.Length; j++) {
								Object.Mesh.Vertices[mv + j] = Vertices[j];
							}
							break;
						}
					case "Header":
						break;
					default:
						// unknown
						Interface.AddMessage(Interface.MessageType.Warning, false, "Unsupported template " + f.Name + " encountered in x object file " + FileName);
						break;
				}
			}
			// return
			World.CreateNormals(ref Object.Mesh);
			return true;
		}
示例#9
0
        internal static void RenderBackground(BackgroundManager.BackgroundObject Object)
        {
            if (!TexturingEnabled)
            {
                GL.Enable(EnableCap.Texture2D);
                TexturingEnabled = true;
            }
            int Mat = -1;

            for (int i = 0; i < Object.ObjectBackground.Mesh.Faces.Length; i++)
            {
                int m = Object.ObjectBackground.Mesh.Faces[i].Material;
                if (m != Mat)
                {
                    Textures.OpenGlTextureWrapMode wrap = Textures.OpenGlTextureWrapMode.ClampClamp;
                    for (int v = 0; v < Object.ObjectBackground.Mesh.Vertices.Length; v++)
                    {
                        if (Object.ObjectBackground.Mesh.Vertices[v].TextureCoordinates.X <0.0f | Object.ObjectBackground.Mesh.Vertices[v].TextureCoordinates.X> 1.0f)
                        {
                            wrap |= Textures.OpenGlTextureWrapMode.RepeatClamp;
                        }
                        if (Object.ObjectBackground.Mesh.Vertices[v].TextureCoordinates.Y <0.0f | Object.ObjectBackground.Mesh.Vertices[v].TextureCoordinates.Y> 1.0f)
                        {
                            wrap |= Textures.OpenGlTextureWrapMode.ClampRepeat;
                        }
                    }

                    if (Object.ObjectBackground.Mesh.Materials[m].DaytimeTexture != null)
                    {
                        Textures.LoadTexture(Object.ObjectBackground.Mesh.Materials[m].DaytimeTexture, wrap);
                        GL.BindTexture(TextureTarget.Texture2D, Object.ObjectBackground.Mesh.Materials[m].DaytimeTexture.OpenGlTextures[(int)wrap].Name);
                    }
                }
                int FaceType = Object.ObjectBackground.Mesh.Faces[i].Flags & World.MeshFace.FaceTypeMask;
                switch (FaceType)
                {
                case World.MeshFace.FaceTypeTriangles:
                    GL.Begin(PrimitiveType.Triangles);
                    break;

                case World.MeshFace.FaceTypeTriangleStrip:
                    GL.Begin(PrimitiveType.TriangleStrip);
                    break;

                case World.MeshFace.FaceTypeQuads:
                    GL.Begin(PrimitiveType.Quads);
                    break;

                case World.MeshFace.FaceTypeQuadStrip:
                    GL.Begin(PrimitiveType.QuadStrip);
                    break;

                default:
                    GL.Begin(PrimitiveType.Polygon);
                    break;
                }

                for (int j = 0; j < Object.ObjectBackground.Mesh.Faces[i].Vertices.Length; j++)
                {
                    GL.Color4(inv255 * (float)Object.ObjectBackground.Mesh.Materials[m].Color.R * 1.0f, inv255 * Object.ObjectBackground.Mesh.Materials[m].Color.G * 1.0f, inv255 * (float)Object.ObjectBackground.Mesh.Materials[m].Color.B * 1.0f, inv255 * (float)Object.ObjectBackground.Mesh.Materials[m].Color.A);
                    World.Vertex v = Object.ObjectBackground.Mesh.Vertices[Object.ObjectBackground.Mesh.Faces[i].Vertices[j].Index];
                    GL.TexCoord2(v.TextureCoordinates.X, v.TextureCoordinates.Y);
                    GL.Vertex3(v.Coordinates.X, v.Coordinates.Y, v.Coordinates.Z);
                }
                GL.End();
            }
        }
示例#10
0
        // read object
        /// <summary>Loads a Loksim3D object from a file.</summary>
        /// <param name="FileName">The text file to load the animated object from. Must be an absolute file name.</param>
        /// <param name="Encoding">The encoding the file is saved in. If the file uses a byte order mark, the encoding indicated by the byte order mark is used and the Encoding parameter is ignored.</param>
        /// <param name="LoadMode">The texture load mode.</param>
        /// <param name="ForceTextureRepeatX">Whether to force TextureWrapMode.Repeat for the X-axis</param>
        /// /// <param name="ForceTextureRepeatY">Whether to force TextureWrapMode.Repeat for the Y-axis</param>
        /// <returns>The object loaded.</returns>
        internal static ObjectManager.StaticObject ReadObject(string FileName, System.Text.Encoding Encoding, ObjectManager.ObjectLoadMode LoadMode, bool ForceTextureRepeatX, bool ForceTextureRepeatY, double RotationX, double RotationY, double RotationZ)
        {
            XmlDocument currentXML = new XmlDocument();

            //May need to be changed to use de-DE
            System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture;
            //Initialise the object
            ObjectManager.StaticObject Object = new ObjectManager.StaticObject();
            Object.Mesh.Faces     = new World.MeshFace[] { };
            Object.Mesh.Materials = new World.MeshMaterial[] { };
            Object.Mesh.Vertices  = new World.Vertex[] { };
            MeshBuilder Builder = new MeshBuilder();

            World.Vector3Df[] Normals = new World.Vector3Df[4];
            bool PropertiesFound      = false;

            World.Vertex[]    tempVertices     = new World.Vertex[0];
            World.Vector3Df[] tempNormals      = new World.Vector3Df[0];
            World.ColorRGB    transparentColor = new World.ColorRGB();
            string            tday             = null;
            string            tnight           = null;
            bool TransparencyUsed = false;
            bool Face2            = false;
            int  TextureWidth     = 0;
            int  TextureHeight    = 0;

            if (File.Exists(FileName))
            {
                currentXML.Load(FileName);
            }
            else
            {
                return(null);
            }
            //Check for null
            if (currentXML.DocumentElement != null)
            {
                XmlNodeList DocumentNodes = currentXML.DocumentElement.SelectNodes("/OBJECT");
                //Check this file actually contains Loksim3D object nodes
                if (DocumentNodes != null)
                {
                    foreach (XmlNode outerNode in DocumentNodes)
                    {
                        if (outerNode.HasChildNodes)
                        {
                            foreach (XmlNode node in outerNode.ChildNodes)
                            {
                                //I think there should only be one properties node??
                                //Need better format documentation
                                if (node.Name == "Props" && PropertiesFound == false)
                                {
                                    if (node.Attributes != null)
                                    {
                                        //Our node has child nodes, therefore this properties node should be valid
                                        //Needs better validation
                                        PropertiesFound = true;
                                        foreach (XmlAttribute attribute in node.Attributes)
                                        {
                                            switch (attribute.Name)
                                            {
                                            //Sets the texture
                                            //Loksim3D objects only support daytime textures
                                            case "Texture":
                                                tday = OpenBveApi.Path.CombineFile(System.IO.Path.GetDirectoryName(FileName), attribute.Value);
                                                if (File.Exists(tday))
                                                {
                                                    try
                                                    {
                                                        using (Bitmap TextureInformation = new Bitmap(tday))
                                                        {
                                                            TextureWidth  = TextureInformation.Width;
                                                            TextureHeight = TextureInformation.Height;
                                                            Color color = TextureInformation.GetPixel(1, 1);
                                                            transparentColor = new World.ColorRGB((byte)color.R, (byte)color.G, (byte)color.B);
                                                        }
                                                    }
                                                    catch
                                                    {
                                                        Interface.AddMessage(Interface.MessageType.Error, true, "An error occured loading daytime texture " + tday + " in file " + FileName);
                                                        tday = null;
                                                    }
                                                }
                                                else
                                                {
                                                    Interface.AddMessage(Interface.MessageType.Error, true, "DaytimeTexture " + tday + " could not be found in file " + FileName);
                                                }
                                                break;

                                            //Defines whether the texture uses transparency
                                            //May be omitted
                                            case "Transparent":
                                                if (attribute.Value == "TRUE")
                                                {
                                                    TransparencyUsed = true;
                                                }
                                                else
                                                {
                                                    TransparencyUsed = false;
                                                }
                                                break;

                                            //Sets the transparency type
                                            case "TransparentTyp":
                                                switch (attribute.Value)
                                                {
                                                case "0":
                                                    //Transparency is disabled
                                                    TransparencyUsed = false;
                                                    break;

                                                case "1":
                                                    //Transparency is solid black
                                                    TransparencyUsed = true;
                                                    transparentColor = new World.ColorRGB(0, 0, 0);
                                                    break;

                                                case "2":
                                                    //Transparency is the color at Pixel 1,1
                                                    TransparencyUsed = true;
                                                    break;

                                                case "3":
                                                    //This is used when transparency is used with an alpha bitmap
                                                    //Not currently supported
                                                    TransparencyUsed = false;
                                                    break;
                                                }
                                                break;

                                            //Sets whether the rears of the faces are to be drawn
                                            case "Drawrueckseiten":
                                                Face2 = true;
                                                break;

                                                /*
                                                 * MISSING PROPERTIES:
                                                 * AutoRotate - Rotate with tracks?? LS3D presumably uses a 3D world system.
                                                 * Beleuchtet- Translates as illuminated. Presume something to do with lighting? - What emissive color?
                                                 * FileAuthor
                                                 * FileInfo
                                                 * FilePicture
                                                 */
                                            }
                                        }
                                    }
                                }
                                //The point command is eqivilant to a vertex
                                else if (node.Name == "Point" && node.HasChildNodes)
                                {
                                    foreach (XmlNode childNode in node.ChildNodes)
                                    {
                                        if (childNode.Name == "Props" && childNode.Attributes != null)
                                        {
                                            //Vertex
                                            double vx = 0.0, vy = 0.0, vz = 0.0;
                                            //Normals
                                            double nx = 0.0, ny = 0.0, nz = 0.0;
                                            foreach (XmlAttribute attribute in childNode.Attributes)
                                            {
                                                switch (attribute.Name)
                                                {
                                                //Sets the vertex normals
                                                case "Normal":
                                                    string[] NormalPoints = attribute.Value.Split(';');
                                                    double.TryParse(NormalPoints[0], out nx);
                                                    double.TryParse(NormalPoints[1], out ny);
                                                    double.TryParse(NormalPoints[2], out nz);
                                                    break;

                                                //Sets the vertex 3D co-ordinates
                                                case "Vekt":
                                                    string[] VertexPoints = attribute.Value.Split(';');
                                                    double.TryParse(VertexPoints[0], out vx);
                                                    double.TryParse(VertexPoints[1], out vy);
                                                    double.TryParse(VertexPoints[2], out vz);
                                                    break;
                                                }
                                            }
                                            World.Normalize(ref nx, ref ny, ref nz);

                                            {
                                                //Resize temp arrays
                                                Array.Resize <World.Vertex>(ref tempVertices, tempVertices.Length + 1);
                                                Array.Resize <World.Vector3Df>(ref tempNormals, tempNormals.Length + 1);
                                                //Add vertex and normals to temp array
                                                tempVertices[tempVertices.Length - 1].Coordinates = new World.Vector3D(vx, vy, vz);
                                                tempNormals[tempNormals.Length - 1] = new World.Vector3Df((float)nx, (float)ny, (float)nz);
                                            }

                                            Array.Resize <World.Vertex>(ref Builder.Vertices, Builder.Vertices.Length + 1);
                                            while (Builder.Vertices.Length >= Normals.Length)
                                            {
                                                Array.Resize <World.Vector3Df>(ref Normals, Normals.Length << 1);
                                            }
                                            Builder.Vertices[Builder.Vertices.Length - 1].Coordinates = new World.Vector3D(vx, vy, vz);
                                            Normals[Builder.Vertices.Length - 1] = new World.Vector3Df((float)nx, (float)ny, (float)nz);
                                        }
                                    }
                                }
                                //The Flaeche command creates a face
                                else if (node.Name == "Flaeche" && node.HasChildNodes)
                                {
                                    foreach (XmlNode childNode in node.ChildNodes)
                                    {
                                        if (childNode.Name == "Props" && childNode.Attributes != null)
                                        {
                                            //Defines the verticies in this face
                                            //**NOTE**: A vertex may appear in multiple faces with different texture co-ordinates
                                            if (childNode.Attributes["Points"] != null)
                                            {
                                                string[] Verticies = childNode.Attributes["Points"].Value.Split(';');
                                                int      f         = Builder.Faces.Length;
                                                //Add 1 to the length of the face array
                                                Array.Resize <World.MeshFace>(ref Builder.Faces, f + 1);
                                                Builder.Faces[f] = new World.MeshFace();
                                                //Create the vertex array for the face
                                                Builder.Faces[f].Vertices = new World.MeshFaceVertex[Verticies.Length];
                                                while (Builder.Vertices.Length > Normals.Length)
                                                {
                                                    Array.Resize <World.Vector3Df>(ref Normals,
                                                                                   Normals.Length << 1);
                                                }
                                                //Run through the vertices list and grab from the temp array
                                                for (int j = 0; j < Verticies.Length; j++)
                                                {
                                                    //This is the position of the vertex in the temp array
                                                    int currentVertex;
                                                    int.TryParse(Verticies[j], out currentVertex);
                                                    //Add one to the actual vertex array
                                                    Array.Resize <World.Vertex>(ref Builder.Vertices, Builder.Vertices.Length + 1);
                                                    //Set coordinates
                                                    Builder.Vertices[Builder.Vertices.Length - 1].Coordinates = tempVertices[currentVertex].Coordinates;
                                                    //Set the vertex index
                                                    Builder.Faces[f].Vertices[j].Index = (ushort)(Builder.Vertices.Length - 1);
                                                    //Set the normals
                                                    Builder.Faces[f].Vertices[j].Normal = tempNormals[currentVertex];
                                                    //Now deal with the texture
                                                    //Texture mapping points are in pixels X,Y and are relative to the face in question rather than the vertex
                                                    if (childNode.Attributes["Texture"] != null)
                                                    {
                                                        string[]        TextureCoords = childNode.Attributes["Texture"].Value.Split(';');
                                                        World.Vector2Df currentCoords;
                                                        float           OpenBVEWidth;
                                                        float           OpenBVEHeight;
                                                        string[]        splitCoords = TextureCoords[j].Split(',');
                                                        float.TryParse(splitCoords[0], out OpenBVEWidth);
                                                        float.TryParse(splitCoords[1], out OpenBVEHeight);
                                                        if (TextureWidth != 0 && TextureHeight != 0)
                                                        {
                                                            currentCoords.X = (OpenBVEWidth / TextureWidth);
                                                            currentCoords.Y = (OpenBVEHeight / TextureHeight);
                                                        }
                                                        else
                                                        {
                                                            currentCoords.X = 0;
                                                            currentCoords.Y = 0;
                                                        }
                                                        Builder.Vertices[Builder.Vertices.Length - 1].TextureCoordinates = currentCoords;
                                                    }
                                                    if (Face2)
                                                    {
                                                        Builder.Faces[f].Flags = (byte)World.MeshFace.Face2Mask;
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                //Apply rotation

                /*
                 * NOTES:
                 * No rotation order is specified
                 * The rotation string in a .l3dgrp file is ordered Y, X, Z    ??? Can't find a good reason for this ???
                 * Rotations must still be performed in X,Y,Z order to produce correct results
                 */

                if (RotationX != 0.0)
                {
                    //This is actually the Y-Axis rotation
                    //Convert to radians
                    RotationX *= 0.0174532925199433;
                    //Apply rotation
                    ApplyRotation(Builder, 0, 1, 0, RotationX);
                }
                if (RotationY != 0.0)
                {
                    //This is actually the X-Axis rotation
                    //Convert to radians
                    RotationY *= 0.0174532925199433;
                    //Apply rotation
                    ApplyRotation(Builder, 1, 0, 0, RotationY);
                }
                if (RotationZ != 0.0)
                {
                    //Convert to radians
                    RotationZ *= 0.0174532925199433;
                    //Apply rotation
                    ApplyRotation(Builder, 0, 0, 1, RotationZ);
                }

                //These files appear to only have one texture defined
                //Therefore import later- May have to change
                if (File.Exists(tday))
                {
                    for (int j = 0; j < Builder.Materials.Length; j++)
                    {
                        Builder.Materials[j].DaytimeTexture   = tday;
                        Builder.Materials[j].NighttimeTexture = tnight;
                    }
                }
                if (TransparencyUsed == true)
                {
                    for (int j = 0; j < Builder.Materials.Length; j++)
                    {
                        Builder.Materials[j].TransparentColor     = transparentColor;
                        Builder.Materials[j].TransparentColorUsed = true;
                    }
                }
            }
            ApplyMeshBuilder(ref Object, Builder, LoadMode, ForceTextureRepeatX, ForceTextureRepeatY);
            World.CreateNormals(ref Object.Mesh);
            return(Object);
        }