Represents a 3D vector of System.Single coordinates.
 // read object
 /// <summary>Loads a CSV or B3D 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="ForceTextureRepeat">Whether to force TextureWrapMode.Repeat for referenced textures.</param>
 /// <returns>The object loaded.</returns>
 internal static ObjectManager.StaticObject ReadObject(string FileName, System.Text.Encoding Encoding, ObjectManager.ObjectLoadMode LoadMode, bool ForceTextureRepeatX, bool ForceTextureRepeatY)
 {
     System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture;
     bool IsB3D = string.Equals(System.IO.Path.GetExtension(FileName), ".b3d", StringComparison.OrdinalIgnoreCase);
     // initialize 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[] { };
     // read lines
     string[] Lines = System.IO.File.ReadAllLines(FileName, Encoding);
     // parse lines
     MeshBuilder Builder = new MeshBuilder();
     World.Vector3Df[] Normals = new World.Vector3Df[4];
     for (int i = 0; i < Lines.Length; i++) {
         {
             // strip away comments
             int j = Lines[i].IndexOf(';');
             if (j >= 0) {
                 Lines[i] = Lines[i].Substring(0, j);
             }
         }
         // collect arguments
         string[] Arguments = Lines[i].Split(new char[] { ',' }, StringSplitOptions.None);
         for (int j = 0; j < Arguments.Length; j++) {
             Arguments[j] = Arguments[j].Trim();
         }
         {
             // remove unused arguments at the end of the chain
             int j;
             for (j = Arguments.Length - 1; j >= 0; j--) {
                 if (Arguments[j].Length != 0) break;
             }
             Array.Resize<string>(ref Arguments, j + 1);
         }
         // style
         string Command;
         if (IsB3D & Arguments.Length != 0) {
             // b3d
             int j = Arguments[0].IndexOf(' ');
             if (j >= 0) {
                 Command = Arguments[0].Substring(0, j).TrimEnd();
                 Arguments[0] = Arguments[0].Substring(j + 1).TrimStart();
             } else {
                 Command = Arguments[0];
                 if (Arguments.Length != 1) {
                     Interface.AddMessage(Interface.MessageType.Error, false, "Invalid syntax at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                 }
                 Arguments = new string[] { };
             }
         } else if (Arguments.Length != 0) {
             // csv
             Command = Arguments[0];
             for (int j = 0; j < Arguments.Length - 1; j++) {
                 Arguments[j] = Arguments[j + 1];
             }
             Array.Resize<string>(ref Arguments, Arguments.Length - 1);
         } else {
             // empty
             Command = null;
         }
         // parse terms
         if (Command != null) {
             string cmd = Command.ToLowerInvariant();
             switch(cmd) {
                 case "createmeshbuilder":
                 case "[meshbuilder]":
                     {
                         if (cmd == "createmeshbuilder" & IsB3D) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "CreateMeshBuilder is not a supported command - did you mean [MeshBuilder]? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         } else if (cmd == "[meshbuilder]" & !IsB3D) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "[MeshBuilder] is not a supported command - did you mean CreateMeshBuilder? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         if (Arguments.Length > 0) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "0 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         ApplyMeshBuilder(ref Object, Builder, LoadMode, ForceTextureRepeatX, ForceTextureRepeatY);
                         Builder = new MeshBuilder();
                         Normals = new World.Vector3Df[4];
                     } break;
                 case "addvertex":
                 case "vertex":
                     {
                         if (cmd == "addvertex" & IsB3D) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "AddVertex is not a supported command - did you mean Vertex? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         } else if (cmd == "vertex" & !IsB3D) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "Vertex is not a supported command - did you mean AddVertex? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         if (Arguments.Length > 6) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "At most 6 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         double vx = 0.0, vy = 0.0, vz = 0.0;
                         if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out vx)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument vX in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             vx = 0.0;
                         }
                         if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[1], out vy)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument vY in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             vy = 0.0;
                         }
                         if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[2], out vz)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument vZ in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             vz = 0.0;
                         }
                         double nx = 0.0, ny = 0.0, nz = 0.0;
                         if (Arguments.Length >= 4 && Arguments[3].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[3], out nx)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument nX in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             nx = 0.0;
                         }
                         if (Arguments.Length >= 5 && Arguments[4].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[4], out ny)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument nY in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             ny = 0.0;
                         }
                         if (Arguments.Length >= 6 && Arguments[5].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[5], out nz)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument nZ in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             nz = 0.0;
                         }
                         World.Normalize(ref nx, ref ny, ref 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 Vector3(vx, vy, vz);
                         Normals[Builder.Vertices.Length - 1] = new World.Vector3Df((float)nx, (float)ny, (float)nz);
                     } break;
                 case "addface":
                 case "addface2":
                 case "face":
                 case "face2":
                     {
                         if (IsB3D) {
                             if (cmd == "addface") {
                                 Interface.AddMessage(Interface.MessageType.Warning, false, "AddFace is not a supported command - did you mean Face? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             } else if (cmd == "addface2") {
                                 Interface.AddMessage(Interface.MessageType.Warning, false, "AddFace2 is not a supported command - did you mean Face2? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             }
                         } else {
                             if (cmd == "face") {
                                 Interface.AddMessage(Interface.MessageType.Warning, false, "Face is not a supported command - did you mean AddFace? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             } else if (cmd == "face2") {
                                 Interface.AddMessage(Interface.MessageType.Warning, false, "Face2 is not a supported command - did you mean AddFace2? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             }
                         }
                         if (Arguments.Length < 3) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "At least 3 arguments are required in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         } else {
                             bool q = true;
                             int[] a = new int[Arguments.Length];
                             for (int j = 0; j < Arguments.Length; j++) {
                                 if (!Interface.TryParseIntVb6(Arguments[j], out a[j])) {
                                     Interface.AddMessage(Interface.MessageType.Error, false, "v" + j.ToString(Culture) + " is invalid in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                     q = false;
                                     break;
                                 } else if (a[j] < 0 | a[j] >= Builder.Vertices.Length) {
                                     Interface.AddMessage(Interface.MessageType.Error, false, "v" + j.ToString(Culture) + " references a non-existing vertex in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                     q = false;
                                     break;
                                 } else if (a[j] > 65535) {
                                     Interface.AddMessage(Interface.MessageType.Error, false, "v" + j.ToString(Culture) + " indexes a vertex above 65535 which is not currently supported in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                     q = false;
                                     break;
                                 }
                             }
                             if (q) {
                                 int f = Builder.Faces.Length;
                                 Array.Resize<World.MeshFace>(ref Builder.Faces, f + 1);
                                 Builder.Faces[f] = new World.MeshFace();
                                 Builder.Faces[f].Vertices = new World.MeshFaceVertex[Arguments.Length];
                                 while (Builder.Vertices.Length > Normals.Length) {
                                     Array.Resize<World.Vector3Df>(ref Normals, Normals.Length << 1);
                                 }
                                 for (int j = 0; j < Arguments.Length; j++) {
                                     Builder.Faces[f].Vertices[j].Index = (ushort)a[j];
                                     Builder.Faces[f].Vertices[j].Normal = Normals[a[j]];
                                 }
                                 if (cmd == "addface2" | cmd == "face2") {
                                     Builder.Faces[f].Flags = (byte)World.MeshFace.Face2Mask;
                                 }
                             }
                         }
                     } break;
                 case "cube":
                     {
                         if (Arguments.Length > 3) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         double x = 0.0;
                         if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out x)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument HalfWidth in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             x = 1.0;
                         }
                         double y = x, z = x;
                         if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[1], out y)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument HalfHeight in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             y = 1.0;
                         }
                         if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[2], out z)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument HalfDepth in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             z = 1.0;
                         }
                         CreateCube(ref Builder, x, y, z);
                     } break;
                 case "cylinder":
                     {
                         if (Arguments.Length > 4) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "At most 4 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         int n = 8;
                         if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseIntVb6(Arguments[0], out n)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument n in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             n = 8;
                         }
                         if (n < 2) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "n is expected to be at least 2 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             n = 8;
                         }
                         double r1 = 0.0, r2 = 0.0, h = 1.0;
                         if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[1], out r1)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument UpperRadius in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             r1 = 1.0;
                         }
                         if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[2], out r2)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument LowerRadius in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             r2 = 1.0;
                         }
                         if (Arguments.Length >= 4 && Arguments[3].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[3], out h)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument Height in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             h = 1.0;
                         }
                         CreateCylinder(ref Builder, n, r1, r2, h);
                     } break;
                 case "translate":
                 case "translateall":
                     {
                         if (Arguments.Length > 3) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         double x = 0.0, y = 0.0, z = 0.0;
                         if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out x)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument X in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             x = 0.0;
                         }
                         if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[1], out y)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument Y in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             y = 0.0;
                         }
                         if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[2], out z)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument Z in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             z = 0.0;
                         }
                         ApplyTranslation(Builder, x, y, z);
                         if (cmd == "translateall") {
                             ApplyTranslation(Object, x, y, z);
                         }
                     } break;
                 case "scale":
                 case "scaleall":
                     {
                         if (Arguments.Length > 3) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         double x = 1.0, y = 1.0, z = 1.0;
                         if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out x)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument X in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             x = 1.0;
                         } else if (x == 0.0) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "X is required to be different from zero in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             x = 1.0;
                         }
                         if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[1], out y)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument Y in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             y = 1.0;
                         } else if (y == 0.0) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Y is required to be different from zero in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             y = 1.0;
                         }
                         if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[2], out z)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument Z in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             z = 1.0;
                         } else if (z == 0.0) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Z is required to be different from zero in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             z = 1.0;
                         }
                         ApplyScale(Builder, x, y, z);
                         if (cmd == "scaleall") {
                             ApplyScale(Object, x, y, z);
                         }
                     } break;
                 case "rotate":
                 case "rotateall":
                     {
                         if (Arguments.Length > 4) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "At most 4 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         double x = 0.0, y = 0.0, z = 0.0, a = 0.0;
                         if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out x)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument X in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             x = 0.0;
                         }
                         if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[1], out y)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument Y in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             y = 0.0;
                         }
                         if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[2], out z)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument Z in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             z = 0.0;
                         }
                         if (Arguments.Length >= 4 && Arguments[3].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[3], out a)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument Angle in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             a = 0.0;
                         }
                         double t = x * x + y * y + z * z;
                         if (t == 0.0) {
                             x = 1.0;
                             y = 0.0;
                             z = 0.0;
                             t = 1.0;
                         }
                         if (a != 0.0) {
                             t = 1.0 / Math.Sqrt(t);
                             x *= t;
                             y *= t;
                             z *= t;
                             a *= 0.0174532925199433;
                             ApplyRotation(Builder, x, y, z, a);
                             if (cmd == "rotateall") {
                                 ApplyRotation(Object, x, y, z, a);
                             }
                         }
                     } break;
                 case "shear":
                 case "shearall":
                     {
                         if (Arguments.Length > 7) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "At most 7 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         double dx = 0.0, dy = 0.0, dz = 0.0;
                         double sx = 0.0, sy = 0.0, sz = 0.0;
                         double r = 0.0;
                         if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[0], out dx)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument dX in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             dx = 0.0;
                         }
                         if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[1], out dy)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument dY in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             dy = 0.0;
                         }
                         if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[2], out dz)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument dZ in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             dz = 0.0;
                         }
                         if (Arguments.Length >= 4 && Arguments[3].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[3], out sx)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument sX in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             sx = 0.0;
                         }
                         if (Arguments.Length >= 5 && Arguments[4].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[4], out sy)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument sY in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             sy = 0.0;
                         }
                         if (Arguments.Length >= 6 && Arguments[5].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[5], out sz)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument sZ in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             sz = 0.0;
                         }
                         if (Arguments.Length >= 7 && Arguments[6].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[6], out r)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument Ratio in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             r = 0.0;
                         }
                         World.Normalize(ref dx, ref dy, ref dz);
                         World.Normalize(ref sx, ref sy, ref sz);
                         ApplyShear(Builder, dx, dy, dz, sx, sy, sz, r);
                         if (cmd == "shearall") {
                             ApplyShear(Object, dx, dy, dz, sx, sy, sz, r);
                         }
                     } break;
                 case "generatenormals":
                 case "[texture]":
                     if (cmd == "generatenormals" & IsB3D) {
                         Interface.AddMessage(Interface.MessageType.Warning, false, "GenerateNormals is not a supported command - did you mean [Texture]? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                     } else if (cmd == "[texture]" & !IsB3D) {
                         Interface.AddMessage(Interface.MessageType.Warning, false, "[Texture] is not a supported command - did you mean GenerateNormals? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                     }
                     break;
                 case "setcolor":
                 case "color":
                     {
                         if (cmd == "setcolor" & IsB3D) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "SetColor is not a supported command - did you mean Color? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         } else if (cmd == "color" & !IsB3D) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "Color is not a supported command - did you mean SetColor? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         if (Arguments.Length > 4) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "At most 4 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         int r = 0, g = 0, b = 0, a = 255;
                         if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseIntVb6(Arguments[0], out r)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument Red in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             r = 0;
                         } else if (r < 0 | r > 255) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Red is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + 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, "Invalid argument Green in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             g = 0;
                         } else if (g < 0 | g > 255) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Green is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + 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, "Invalid argument Blue in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             b = 0;
                         } else if (b < 0 | b > 255) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Blue is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             b = b < 0 ? 0 : 255;
                         }
                         if (Arguments.Length >= 4 && Arguments[3].Length > 0 && !Interface.TryParseIntVb6(Arguments[3], out a)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument Alpha in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             a = 255;
                         } else if (a < 0 | a > 255) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Alpha is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             a = a < 0 ? 0 : 255;
                         }
                         int m = Builder.Materials.Length;
                         Array.Resize<Material>(ref Builder.Materials, m << 1);
                         for (int j = m; j < Builder.Materials.Length; j++) {
                             Builder.Materials[j] = new Material(Builder.Materials[j - m]);
                             Builder.Materials[j].Color = new Color32((byte)r, (byte)g, (byte)b, (byte)a);
                             Builder.Materials[j].BlendMode = Builder.Materials[0].BlendMode;
                             Builder.Materials[j].GlowAttenuationData = Builder.Materials[0].GlowAttenuationData;
                             Builder.Materials[j].DaytimeTexture = Builder.Materials[0].DaytimeTexture;
                             Builder.Materials[j].NighttimeTexture = Builder.Materials[0].NighttimeTexture;
                             Builder.Materials[j].TransparentColor = Builder.Materials[0].TransparentColor;
                             Builder.Materials[j].TransparentColorUsed = Builder.Materials[0].TransparentColorUsed;
                         }
                         for (int j = 0; j < Builder.Faces.Length; j++) {
                             Builder.Faces[j].Material += (ushort)m;
                         }
                     } break;
                 case "setemissivecolor":
                 case "emissivecolor":
                     {
                         if (cmd == "setemissivecolor" & IsB3D) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "SetEmissiveColor is not a supported command - did you mean EmissiveColor? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         } else if (cmd == "emissivecolor" & !IsB3D) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "EmissiveColor is not a supported command - did you mean SetEmissiveColor? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         if (Arguments.Length > 3) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         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, "Invalid argument Red in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             r = 0;
                         } else if (r < 0 | r > 255) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Red is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + 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, "Invalid argument Green in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             g = 0;
                         } else if (g < 0 | g > 255) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Green is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + 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, "Invalid argument Blue in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             b = 0;
                         } else if (b < 0 | b > 255) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Blue is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             b = b < 0 ? 0 : 255;
                         }
                         int m = Builder.Materials.Length;
                         Array.Resize<Material>(ref Builder.Materials, m << 1);
                         for (int j = m; j < Builder.Materials.Length; j++) {
                             Builder.Materials[j] = new Material(Builder.Materials[j - m]);
                             Builder.Materials[j].EmissiveColor = new Color24((byte)r, (byte)g, (byte)b);
                             Builder.Materials[j].EmissiveColorUsed = true;
                             Builder.Materials[j].BlendMode = Builder.Materials[0].BlendMode;
                             Builder.Materials[j].GlowAttenuationData = Builder.Materials[0].GlowAttenuationData;
                             Builder.Materials[j].DaytimeTexture = Builder.Materials[0].DaytimeTexture;
                             Builder.Materials[j].NighttimeTexture = Builder.Materials[0].NighttimeTexture;
                             Builder.Materials[j].TransparentColor = Builder.Materials[0].TransparentColor;
                             Builder.Materials[j].TransparentColorUsed = Builder.Materials[0].TransparentColorUsed;
                         }
                         for (int j = 0; j < Builder.Faces.Length; j++) {
                             Builder.Faces[j].Material += (ushort)m;
                         }
                     } break;
                 case "setdecaltransparentcolor":
                 case "transparent":
                     {
                         if (cmd == "setdecaltransparentcolor" & IsB3D) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "SetDecalTransparentColor is not a supported command - did you mean Transparent? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         } else if (cmd == "transparent" & !IsB3D) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "Transparent is not a supported command - did you mean SetDecalTransparentColor? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         if (Arguments.Length > 3) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         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, "Invalid argument Red in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             r = 0;
                         } else if (r < 0 | r > 255) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Red is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + 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, "Invalid argument Green in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             g = 0;
                         } else if (g < 0 | g > 255) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Green is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + 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, "Invalid argument Blue in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             b = 0;
                         } else if (b < 0 | b > 255) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Blue is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             b = b < 0 ? 0 : 255;
                         }
                         for (int j = 0; j < Builder.Materials.Length; j++) {
                             Builder.Materials[j].TransparentColor = new Color24((byte)r, (byte)g, (byte)b);
                             Builder.Materials[j].TransparentColorUsed = true;
                         }
                     } break;
                 case "setblendmode":
                 case "blendmode":
                     {
                         if (cmd == "setblendmode" & IsB3D) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "SetBlendMode is not a supported command - did you mean BlendMode? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         } else if (cmd == "blendmode" & !IsB3D) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "BlendMode is not a supported command - did you mean SetBlendMode? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         if (Arguments.Length > 3) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         World.MeshMaterialBlendMode blendmode = World.MeshMaterialBlendMode.Normal;
                         if (Arguments.Length >= 1 && Arguments[0].Length > 0) {
                             switch (Arguments[0].ToLowerInvariant()) {
                                 case "normal":
                                     blendmode = World.MeshMaterialBlendMode.Normal;
                                     break;
                                 case "additive":
                                     blendmode = World.MeshMaterialBlendMode.Additive;
                                     break;
                                 default:
                                     Interface.AddMessage(Interface.MessageType.Error, false, "The given BlendMode is not supported in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                     blendmode = World.MeshMaterialBlendMode.Normal;
                                     break;
                             }
                         }
                         double glowhalfdistance = 0.0;
                         if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Interface.TryParseDoubleVb6(Arguments[1], out glowhalfdistance)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument GlowHalfDistance in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             glowhalfdistance = 0;
                         }
                         World.GlowAttenuationMode glowmode = World.GlowAttenuationMode.DivisionExponent4;
                         if (Arguments.Length >= 3 && Arguments[2].Length > 0) {
                             switch (Arguments[2].ToLowerInvariant()) {
                                     case "divideexponent2": glowmode = World.GlowAttenuationMode.DivisionExponent2; break;
                                     case "divideexponent4": glowmode = World.GlowAttenuationMode.DivisionExponent4; break;
                                 default:
                                     Interface.AddMessage(Interface.MessageType.Error, false, "The given GlowAttenuationMode is not supported in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                     break;
                             }
                         }
                         for (int j = 0; j < Builder.Materials.Length; j++) {
                             Builder.Materials[j].BlendMode = blendmode;
                             Builder.Materials[j].GlowAttenuationData = World.GetGlowAttenuationData(glowhalfdistance, glowmode);
                         }
                     } break;
                 case "loadtexture":
                 case "load":
                     {
                         if (cmd == "loadtexture" & IsB3D) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "LoadTexture is not a supported command - did you mean Load? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         } else if (cmd == "load" & !IsB3D) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "Load is not a supported command - did you mean LoadTexture? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         if (Arguments.Length > 2) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "At most 2 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         string tday = null, tnight = null;
                         if (Arguments.Length >= 1 && Arguments[0].Length != 0) {
                             if (Interface.ContainsInvalidPathChars(Arguments[0])) {
                                 Interface.AddMessage(Interface.MessageType.Error, false, "DaytimeTexture contains illegal characters in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             } else {
                                 tday = OpenBveApi.Path.CombineFile(System.IO.Path.GetDirectoryName(FileName), Arguments[0]);
                                 if (!System.IO.File.Exists(tday)) {
                                     Interface.AddMessage(Interface.MessageType.Error, true, "DaytimeTexture " + tday + " could not be found in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                     tday = null;
                                 }
                             }
                         }
                         if (Arguments.Length >= 2 && Arguments[1].Length != 0) {
                             if (Arguments[0].Length == 0) {
                                 Interface.AddMessage(Interface.MessageType.Error, true, "DaytimeTexture is required to be specified in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             } else {
                                 if (Interface.ContainsInvalidPathChars(Arguments[1])) {
                                     Interface.AddMessage(Interface.MessageType.Error, false, "NighttimeTexture contains illegal characters in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                 } else {
                                     tnight = OpenBveApi.Path.CombineFile(System.IO.Path.GetDirectoryName(FileName), Arguments[1]);
                                     if (!System.IO.File.Exists(tnight)) {
                                         Interface.AddMessage(Interface.MessageType.Error, true, "The NighttimeTexture " + tnight + " could not be found in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                         tnight = null;
                                     }
                                 }
                             }
                         }
                         for (int j = 0; j < Builder.Materials.Length; j++) {
                             Builder.Materials[j].DaytimeTexture = tday;
                             Builder.Materials[j].NighttimeTexture = tnight;
                         }
                     } break;
                 case "settexturecoordinates":
                 case "coordinates":
                     {
                         if (cmd == "settexturecoordinates" & IsB3D) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "SetTextureCoordinates is not a supported command - did you mean Coordinates? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         } else if (cmd == "coordinates" & !IsB3D) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "Coordinates is not a supported command - did you mean SetTextureCoordinates? - at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         if (Arguments.Length > 3) {
                             Interface.AddMessage(Interface.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                         int j = 0; float x = 0.0f, y = 0.0f;
                         if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Interface.TryParseIntVb6(Arguments[0], out j)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument VertexIndex in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             j = 0;
                         }
                         if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Interface.TryParseFloatVb6(Arguments[1], out x)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument X in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             x = 0.0f;
                         }
                         if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Interface.TryParseFloatVb6(Arguments[2], out y)) {
                             Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument Y in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                             y = 0.0f;
                         }
                         if (j >= 0 & j < Builder.Vertices.Length) {
                             Builder.Vertices[j].TextureCoordinates = new World.Vector2Df(x, y);
                         } else {
                             Interface.AddMessage(Interface.MessageType.Error, false, "VertexIndex references a non-existing vertex in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                         }
                     } break;
                 default:
                     if (Command.Length != 0) {
                         Interface.AddMessage(Interface.MessageType.Error, false, "The command " + Command + " is not supported at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                     }
                     break;
             }
         }
     }
     // finalize object
     ApplyMeshBuilder(ref Object, Builder, LoadMode, ForceTextureRepeatX, ForceTextureRepeatY);
     World.CreateNormals(ref Object.Mesh);
     return Object;
 }
 // create cylinder
 private static void CreateCylinder(ref MeshBuilder Builder, int n, double r1, double r2, double h)
 {
     // parameters
     bool uppercap = r1 > 0.0;
     bool lowercap = r2 > 0.0;
     int m = (uppercap ? 1 : 0) + (lowercap ? 1 : 0);
     r1 = Math.Abs(r1);
     r2 = Math.Abs(r2);
     double ns = h >= 0.0 ? 1.0 : -1.0;
     // initialization
     int v = Builder.Vertices.Length;
     Array.Resize<World.Vertex>(ref Builder.Vertices, v + 2 * n);
     World.Vector3Df[] Normals = new World.Vector3Df[2 * n];
     double d = 2.0 * Math.PI / (double)n;
     double g = 0.5 * h;
     double t = 0.0;
     double a = h != 0.0 ? Math.Atan((r2 - r1) / h) : 0.0;
     double cosa = Math.Cos(a);
     double sina = Math.Sin(a);
     // vertices and normals
     for (int i = 0; i < n; i++) {
         double dx = Math.Cos(t);
         double dz = Math.Sin(t);
         double lx = dx * r2;
         double lz = dz * r2;
         double ux = dx * r1;
         double uz = dz * r1;
         Builder.Vertices[v + 2 * i + 0].Coordinates = new Vector3(ux, g, uz);
         Builder.Vertices[v + 2 * i + 1].Coordinates = new Vector3(lx, -g, lz);
         double nx = dx * ns, ny = 0.0, nz = dz * ns;
         double sx, sy, sz;
         World.Cross(nx, ny, nz, 0.0, 1.0, 0.0, out sx, out sy, out sz);
         World.Rotate(ref nx, ref ny, ref nz, sx, sy, sz, cosa, sina);
         Normals[2 * i + 0] = new World.Vector3Df((float)nx, (float)ny, (float)nz);
         Normals[2 * i + 1] = new World.Vector3Df((float)nx, (float)ny, (float)nz);
         t += d;
     }
     // faces
     int f = Builder.Faces.Length;
     Array.Resize<World.MeshFace>(ref Builder.Faces, f + n + m);
     for (int i = 0; i < n; i++) {
         Builder.Faces[f + i].Flags = 0;
         int i0 = (2 * i + 2) % (2 * n);
         int i1 = (2 * i + 3) % (2 * n);
         int i2 = 2 * i + 1;
         int i3 = 2 * i;
         Builder.Faces[f + i].Vertices = new World.MeshFaceVertex[] { new World.MeshFaceVertex(v + i0, Normals[i0]), new World.MeshFaceVertex(v + i1, Normals[i1]), new World.MeshFaceVertex(v + i2, Normals[i2]), new World.MeshFaceVertex(v + i3, Normals[i3]) };
     }
     for (int i = 0; i < m; i++) {
         Builder.Faces[f + n + i].Vertices = new World.MeshFaceVertex[n];
         for (int j = 0; j < n; j++) {
             if (i == 0 & lowercap) {
                 // lower cap
                 Builder.Faces[f + n + i].Vertices[j] = new World.MeshFaceVertex(v + 2 * j + 1);
             } else {
                 // upper cap
                 Builder.Faces[f + n + i].Vertices[j] = new World.MeshFaceVertex(v + 2 * (n - j - 1));
             }
         }
     }
 }
Пример #3
0
 // reset
 internal static void Reset()
 {
     LoadTexturesImmediately = LoadTextureImmediatelyMode.NotYet;
     Objects = new Object[256];
     ObjectCount = 0;
     StaticOpaque = new ObjectGroup[] { };
     StaticOpaqueForceUpdate = true;
     DynamicOpaque = new ObjectList();
     DynamicAlpha = new ObjectList();
     OverlayOpaque = new ObjectList();
     OverlayAlpha = new ObjectList();
     OptionLighting = true;
     OptionAmbientColor = new Color24(160, 160, 160);
     OptionDiffuseColor = new Color24(160, 160, 160);
     OptionLightPosition = new World.Vector3Df(0.223606797749979f, 0.86602540378444f, -0.447213595499958f);
     OptionLightingResultingAmount = 1.0f;
     OptionClock = false;
     OptionBrakeSystems = false;
 }
Пример #4
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;
        }
Пример #5
0
 private static void RenderBackground(World.Background Data, double dx, double dy, double dz, float Alpha, float scale)
 {
     if (Data.Texture != null && Textures.LoadTexture(Data.Texture, Textures.OpenGlTextureWrapMode.RepeatClamp)) {
         if (LightingEnabled) {
             Gl.glDisable(Gl.GL_LIGHTING);
             LightingEnabled = false;
         }
         if (!TexturingEnabled) {
             Gl.glEnable(Gl.GL_TEXTURE_2D);
             TexturingEnabled = true;
         }
         if (Alpha == 1.0f) {
             if (BlendEnabled) {
                 Gl.glDisable(Gl.GL_BLEND);
                 BlendEnabled = false;
             }
         } else if (!BlendEnabled) {
             Gl.glEnable(Gl.GL_BLEND);
             BlendEnabled = true;
         }
         Gl.glBindTexture(Gl.GL_TEXTURE_2D, Data.Texture.OpenGlTextures[(int)Textures.OpenGlTextureWrapMode.RepeatClamp].Name);
         Gl.glColor4f(1.0f, 1.0f, 1.0f, Alpha);
         float y0, y1;
         if (Data.KeepAspectRatio) {
             int tw = Data.Texture.Width;
             int th = Data.Texture.Height;
             double hh = Math.PI * World.BackgroundImageDistance * (double)th / ((double)tw * (double)Data.Repetition);
             y0 = (float)(-0.5 * hh);
             y1 = (float)(1.5 * hh);
         } else {
             y0 = (float)(-0.125 * World.BackgroundImageDistance);
             y1 = (float)(0.375 * World.BackgroundImageDistance);
         }
         const int n = 32;
         World.Vector3Df[] bottom = new World.Vector3Df[n];
         World.Vector3Df[] top = new World.Vector3Df[n];
         double angleValue = 2.61799387799149 - 3.14159265358979 / (double)n;
         double angleIncrement = 6.28318530717958 / (double)n;
         /*
          * To ensure that the whole background cylinder is rendered inside the viewing frustum,
          * the background is rendered before the scene with z-buffer writes disabled. Then,
          * the actual distance from the camera is irrelevant as long as it is inside the frustum.
          * */
         for (int i = 0; i < n; i++) {
             float x = (float)(World.BackgroundImageDistance * Math.Cos(angleValue));
             float z = (float)(World.BackgroundImageDistance * Math.Sin(angleValue));
             bottom[i] = new World.Vector3Df(scale * x, scale * y0, scale * z);
             top[i] = new World.Vector3Df(scale * x, scale * y1, scale * z);
             angleValue += angleIncrement;
         }
         float textureStart = 0.5f * (float)Data.Repetition / (float)n;
         float textureIncrement = -(float)Data.Repetition / (float)n;
         double textureX = textureStart;
         for (int i = 0; i < n; i++) {
             int j = (i + 1) % n;
             // side wall
             Gl.glBegin(Gl.GL_QUADS);
             Gl.glTexCoord2d(textureX, 0.005f);
             Gl.glVertex3f(top[i].X, top[i].Y, top[i].Z);
             Gl.glTexCoord2d(textureX, 0.995f);
             Gl.glVertex3f(bottom[i].X, bottom[i].Y, bottom[i].Z);
             Gl.glTexCoord2d(textureX + textureIncrement, 0.995f);
             Gl.glVertex3f(bottom[j].X, bottom[j].Y, bottom[j].Z);
             Gl.glTexCoord2d(textureX + textureIncrement, 0.005f);
             Gl.glVertex3f(top[j].X, top[j].Y, top[j].Z);
             Gl.glEnd();
             // top cap
             Gl.glBegin(Gl.GL_TRIANGLES);
             Gl.glTexCoord2d(textureX, 0.005f);
             Gl.glVertex3f(top[i].X, top[i].Y, top[i].Z);
             Gl.glTexCoord2d(textureX + textureIncrement, 0.005f);
             Gl.glVertex3f(top[j].X, top[j].Y, top[j].Z);
             Gl.glTexCoord2d(textureX + 0.5 * textureIncrement, 0.1f);
             Gl.glVertex3f(0.0f, top[i].Y, 0.0f);
             // bottom cap
             Gl.glTexCoord2d(textureX + 0.5 * textureIncrement, 0.9f);
             Gl.glVertex3f(0.0f, bottom[i].Y, 0.0f);
             Gl.glTexCoord2d(textureX + textureIncrement, 0.995f);
             Gl.glVertex3f(bottom[j].X, bottom[j].Y, bottom[j].Z);
             Gl.glTexCoord2d(textureX, 0.995f);
             Gl.glVertex3f(bottom[i].X, bottom[i].Y, bottom[i].Z);
             Gl.glEnd();
             // finish
             textureX += textureIncrement;
         }
         Gl.glDisable(Gl.GL_TEXTURE_2D);
         TexturingEnabled = false;
         if (!BlendEnabled) {
             Gl.glEnable(Gl.GL_BLEND);
             BlendEnabled = true;
         }
     }
 }
Пример #6
0
 // reset
 internal static void Reset()
 {
     LoadTexturesImmediately = LoadTextureImmediatelyMode.NotYet;
     ObjectList = new Object[256];
     ObjectListCount = 0;
     OpaqueList = new ObjectFace[256];
     OpaqueListCount = 0;
     TransparentColorList = new ObjectFace[256];
     TransparentColorListDistance = new double[256];
     TransparentColorListCount = 0;
     AlphaList = new ObjectFace[256];
     AlphaListDistance = new double[256];
     AlphaListCount = 0;
     OverlayList = new ObjectFace[256];
     OverlayListDistance = new double[256];
     OverlayListCount = 0;
     OptionLighting = true;
     OptionAmbientColor = new World.ColorRGB(160, 160, 160);
     OptionDiffuseColor = new World.ColorRGB(160, 160, 160);
     OptionLightPosition = new World.Vector3Df(0.215920077052065f, 0.875724044222352f, -0.431840154104129f);
     OptionLightingResultingAmount = 1.0f;
     GL.Disable(EnableCap.Fog); FogEnabled = false;
 }
Пример #7
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;
		}
Пример #8
0
		private static void RenderBackground(World.Background Data, double dx, double dy, double dz, float Alpha) {
			if (Data.Texture >= 0) {
				int OpenGlTextureIndex = TextureManager.UseTexture(Data.Texture, TextureManager.UseMode.LoadImmediately);
				if (OpenGlTextureIndex > 0) {
					if (LightingEnabled) {
						Gl.glDisable(Gl.GL_LIGHTING);
						LightingEnabled = false;
					}
					if (!TexturingEnabled) {
						Gl.glEnable(Gl.GL_TEXTURE_2D);
						TexturingEnabled = true;
					}
					if (Alpha == 1.0f) {
						if (BlendEnabled) {
							Gl.glDisable(Gl.GL_BLEND);
							BlendEnabled = false;
						}
					} else if (!BlendEnabled) {
						Gl.glEnable(Gl.GL_BLEND);
						BlendEnabled = true;
					}
					Gl.glBindTexture(Gl.GL_TEXTURE_2D, OpenGlTextureIndex);
					Gl.glColor4f(1.0f, 1.0f, 1.0f, Alpha);
					float y0, y1;
					if (Data.KeepAspectRatio) {
						int tw = TextureManager.Textures[Data.Texture].Width;
						int th = TextureManager.Textures[Data.Texture].Height;
						double hh = Math.PI * World.BackgroundImageDistance * (double)th / ((double)tw * (double)Data.Repetition);
						y0 = (float)(-0.5 * hh);
						y1 = (float)(1.5 * hh);
					} else {
						y0 = (float)(-0.125 * World.BackgroundImageDistance);
						y1 = (float)(0.375 * World.BackgroundImageDistance);
					}
					const int n = 32;
					World.Vector3Df[] bottom = new World.Vector3Df[n];
					World.Vector3Df[] top = new World.Vector3Df[n];
					double angleValue = 2.61799387799149 - 3.14159265358979 / (double)n;
					double angleIncrement = 6.28318530717958 / (double)n;
					for (int i = 0; i < n; i++) {
						float x = (float)(World.BackgroundImageDistance * Math.Cos(angleValue));
						float z = (float)(World.BackgroundImageDistance * Math.Sin(angleValue));
						bottom[i] = new World.Vector3Df(x, y0, z);
						top[i] = new World.Vector3Df(x, y1, z);
						angleValue += angleIncrement;
					}
					float textureStart = 0.5f * (float)Data.Repetition / (float)n;
					float textureIncrement = -(float)Data.Repetition / (float)n;
					double textureX = textureStart;
					for (int i = 0; i < n; i++) {
						int j = (i + 1) % n;
						// side wall
						Gl.glBegin(Gl.GL_QUADS);
						Gl.glTexCoord2d(textureX, 0.005f);
						Gl.glVertex3f(top[i].X, top[i].Y, top[i].Z);
						Gl.glTexCoord2d(textureX, 0.995f);
						Gl.glVertex3f(bottom[i].X, bottom[i].Y, bottom[i].Z);
						Gl.glTexCoord2d(textureX + textureIncrement, 0.995f);
						Gl.glVertex3f(bottom[j].X, bottom[j].Y, bottom[j].Z);
						Gl.glTexCoord2d(textureX + textureIncrement, 0.005f);
						Gl.glVertex3f(top[j].X, top[j].Y, top[j].Z);
						Gl.glEnd();
						// top cap
						Gl.glBegin(Gl.GL_TRIANGLES);
						Gl.glTexCoord2d(textureX, 0.005f);
						Gl.glVertex3f(top[i].X, top[i].Y, top[i].Z);
						Gl.glTexCoord2d(textureX + textureIncrement, 0.005f);
						Gl.glVertex3f(top[j].X, top[j].Y, top[j].Z);
						Gl.glTexCoord2d(textureX + 0.5 * textureIncrement, 0.1f);
						Gl.glVertex3f(0.0f, top[i].Y, 0.0f);
						// bottom cap
						Gl.glTexCoord2d(textureX + 0.5 * textureIncrement, 0.9f);
						Gl.glVertex3f(0.0f, bottom[i].Y, 0.0f);
						Gl.glTexCoord2d(textureX + textureIncrement, 0.995f);
						Gl.glVertex3f(bottom[j].X, bottom[j].Y, bottom[j].Z);
						Gl.glTexCoord2d(textureX, 0.995f);
						Gl.glVertex3f(bottom[i].X, bottom[i].Y, bottom[i].Z);
						Gl.glEnd();
						// finish
						textureX += textureIncrement;
					}
					Gl.glDisable(Gl.GL_TEXTURE_2D);
					TexturingEnabled = false;
					if (!BlendEnabled) {
						Gl.glEnable(Gl.GL_BLEND);
						BlendEnabled = true;
					}
				}
			}
		}
Пример #9
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);
        }