예제 #1
0
        internal static ObjectManager.AnimatedObjectCollection ReadObject(string fileName)
        {
            MsTsShape shape = new MsTsShape();

            ObjectManager.AnimatedObjectCollection Result = new ObjectManager.AnimatedObjectCollection
            {
                Objects = new ObjectManager.AnimatedObject[4]
            };

            currentFolder = Path.GetDirectoryName(fileName);
            Stream fb = new FileStream(fileName, FileMode.Open, FileAccess.Read);

            byte[] buffer = new byte[34];
            fb.Read(buffer, 0, 2);

            bool unicode = (buffer[0] == 0xFF && buffer[1] == 0xFE);

            string headerString;

            if (unicode)
            {
                fb.Read(buffer, 0, 32);
                headerString = Encoding.Unicode.GetString(buffer, 0, 16);
            }
            else
            {
                fb.Read(buffer, 2, 14);
                headerString = Encoding.ASCII.GetString(buffer, 0, 8);
            }

            // SIMISA@F  means compressed
            // SIMISA@@  means uncompressed
            if (headerString.StartsWith("SIMISA@F"))
            {
                fb = new ZlibStream(fb, CompressionMode.Decompress);
            }
            else if (headerString.StartsWith("\r\nSIMISA"))
            {
                // ie us1rd2l1000r10d.s, we are going to allow this but warn
                Console.Error.WriteLine("Improper header in " + fileName);
                fb.Read(buffer, 0, 4);
            }
            else if (!headerString.StartsWith("SIMISA@@"))
            {
                throw new Exception("Unrecognized shape file header " + headerString + " in " + fileName);
            }

            string subHeader;

            if (unicode)
            {
                fb.Read(buffer, 0, 32);
                subHeader = Encoding.Unicode.GetString(buffer, 0, 16);
            }
            else
            {
                fb.Read(buffer, 0, 16);
                subHeader = Encoding.ASCII.GetString(buffer, 0, 8);
            }
            if (subHeader[7] == 't')
            {
                using (BinaryReader reader = new BinaryReader(fb))
                {
                    byte[] newBytes = reader.ReadBytes((int)(fb.Length - fb.Position));
                    string s;
                    if (unicode)
                    {
                        s = Encoding.Unicode.GetString(newBytes);
                    }
                    else
                    {
                        s = Encoding.ASCII.GetString(newBytes);
                    }

                    s = s.Replace("\r\n", " ").Replace("\n", " ").Replace("\r", " ").Replace("\t", " ").Trim();
                    if (!s.StartsWith("shape", StringComparison.InvariantCultureIgnoreCase))
                    {
                        throw new Exception();                         //Shape definition
                    }
                    TextualBlock block = new TextualBlock(s, KujuTokenID.shape);
                    ParseBlock(block, ref shape);
                }
            }
            else if (subHeader[7] != 'b')
            {
                throw new Exception("Unrecognized subHeader \"" + subHeader + "\" in " + fileName);
            }
            else
            {
                using (BinaryReader reader = new BinaryReader(fb))
                {
                    KujuTokenID currentToken = (KujuTokenID)reader.ReadUInt16();
                    if (currentToken != KujuTokenID.shape)
                    {
                        throw new Exception();                         //Shape definition
                    }
                    reader.ReadUInt16();
                    uint        remainingBytes = reader.ReadUInt32();
                    byte[]      newBytes       = reader.ReadBytes((int)remainingBytes);
                    BinaryBlock block          = new BinaryBlock(newBytes, KujuTokenID.shape);
                    ParseBlock(block, ref shape);
                }
            }
            Array.Resize(ref Result.Objects, shape.totalObjects);
            int idx = 0;

            double[] previousLODs = new double[shape.totalObjects];
            for (int i = 0; i < shape.LODs.Count; i++)
            {
                for (int j = 0; j < shape.LODs[i].subObjects.Count; j++)
                {
                    Result.Objects[idx]        = new ObjectManager.AnimatedObject();
                    Result.Objects[idx].States = new AnimatedObjectState[1];
                    AnimatedObjectState aos = new AnimatedObjectState(null, Vector3.Zero);
                    shape.LODs[i].subObjects[j].Apply(out aos.Object);
                    aos.Position = new Vector3(0, 0, 0);
                    Result.Objects[idx].States[0] = aos;
                    previousLODs[idx]             = shape.LODs[i].viewingDistance;
                    int k = idx;
                    while (k > 0)
                    {
                        if (previousLODs[k] < shape.LODs[i].viewingDistance)
                        {
                            break;
                        }

                        k--;
                    }

                    if (k != 0)
                    {
                        Result.Objects[idx].StateFunction = new FunctionScript(Program.CurrentHost, "if[cameraDistance <" + shape.LODs[i].viewingDistance + ",if[cameraDistance >" + previousLODs[k] + ",0,-1],-1]", true);
                    }
                    else
                    {
                        Result.Objects[idx].StateFunction = new FunctionScript(Program.CurrentHost, "if[cameraDistance <" + shape.LODs[i].viewingDistance + ",0,-1]", true);
                    }

                    idx++;
                }
            }
            return(Result);
        }
예제 #2
0
        /// <summary>Loads a Loksim3D GruppenObject</summary>
        /// <param name="FileName">The filename to load</param>
        /// <param name="Encoding">The text encoding of the containing file (Currently ignored, REMOVE??)</param>
        /// <param name="Rotation">A three-dimemsional vector describing the rotation to be applied</param>
        /// <returns>A new animated object collection, containing the GruppenObject's meshes etc.</returns>
        internal static ObjectManager.AnimatedObjectCollection ReadObject(string FileName, Encoding Encoding, Vector3 Rotation)
        {
            XmlDocument currentXML = new XmlDocument();

            ObjectManager.AnimatedObjectCollection Result = new ObjectManager.AnimatedObjectCollection();
            Result.Objects = new ObjectManager.AnimatedObject[0];
            try
            {
                currentXML.Load(FileName);
            }
            catch (Exception ex)
            {
                //The XML is not strictly valid
                string[] Lines = File.ReadAllLines(FileName);
                using (var stringReader = new StringReader(Lines[0]))
                {
                    var settings = new XmlReaderSettings {
                        ConformanceLevel = ConformanceLevel.Fragment
                    };
                    using (var xmlReader = XmlReader.Create(stringReader, settings))
                    {
                        if (xmlReader.Read())
                        {
                            //Attempt to find the text encoding and re-read the file
                            var result = xmlReader.GetAttribute("encoding");
                            if (result != null)
                            {
                                var e = System.Text.Encoding.GetEncoding(result);
                                Lines = File.ReadAllLines(FileName, e);
                                //Turf out the old encoding, as our string array should now be UTF-8
                                Lines[0] = "<?xml version=\"1.0\"?>";
                            }
                        }
                    }
                }
                for (int i = 0; i < Lines.Length; i++)
                {
                    while (Lines[i].IndexOf("\"\"", StringComparison.InvariantCulture) != -1)
                    {
                        //Loksim parser tolerates multiple quotes, strict XML does not
                        Lines[i] = Lines[i].Replace("\"\"", "\"");
                    }
                    while (Lines[i].IndexOf("  ", StringComparison.InvariantCulture) != -1)
                    {
                        //Replace double-spaces with singles
                        Lines[i] = Lines[i].Replace("  ", " ");
                    }
                }
                bool tryLoad = false;
                try
                {
                    //Horrible hack: Write out our string array to a new memory stream, then load from this stream
                    //Why can't XmlDocument.Load() just take a string array......
                    using (var stream = new MemoryStream())
                    {
                        var sw = new StreamWriter(stream);
                        foreach (var line in Lines)
                        {
                            sw.Write(line);
                            sw.Flush();
                        }
                        sw.Flush();
                        stream.Position = 0;
                        currentXML.Load(stream);
                        tryLoad = true;
                    }
                }
                catch
                {
                    //Generic catch-all clause
                }
                if (!tryLoad)
                {
                    //Pass out the *original* XML error, not anything generated when we've tried to correct it
                    Interface.AddMessage(MessageType.Error, false, "Error parsing Loksim3D XML: " + ex.Message);
                    return(null);
                }
            }

            string BaseDir = System.IO.Path.GetDirectoryName(FileName);

            GruppenObject[] CurrentObjects = new GruppenObject[0];
            //Check for null
            if (currentXML.DocumentElement != null)
            {
                UnifiedObject[] obj           = new UnifiedObject[0];
                XmlNodeList     DocumentNodes = currentXML.DocumentElement.SelectNodes("/GRUPPENOBJECT");
                if (DocumentNodes != null)
                {
                    foreach (XmlNode outerNode in DocumentNodes)
                    {
                        if (outerNode.ChildNodes.OfType <XmlElement>().Any())
                        {
                            foreach (XmlNode node in outerNode.ChildNodes)
                            {
                                if (node.Name == "Object" && node.ChildNodes.OfType <XmlElement>().Any())
                                {
                                    foreach (XmlNode childNode in node.ChildNodes)
                                    {
                                        if (childNode.Name == "Props" && childNode.Attributes != null)
                                        {
                                            GruppenObject Object = new GruppenObject
                                            {
                                                Rotation = Rotation
                                            };
                                            foreach (XmlAttribute attribute in childNode.Attributes)
                                            {
                                                switch (attribute.Name)
                                                {
                                                case "Name":
                                                    string ObjectFile = OpenBveApi.Path.Loksim3D.CombineFile(BaseDir, attribute.Value, Program.FileSystem.LoksimPackageInstallationDirectory);
                                                    if (!File.Exists(ObjectFile))
                                                    {
                                                        Object.Name = null;
                                                        Interface.AddMessage(MessageType.Warning, true, "Ls3d Object file " + attribute.Value + " not found.");
                                                    }
                                                    else
                                                    {
                                                        Object.Name = ObjectFile;
                                                    }
                                                    break;

                                                case "Position":
                                                    string[] SplitPosition = attribute.Value.Split(';');
                                                    double.TryParse(SplitPosition[0], out Object.Position.X);
                                                    double.TryParse(SplitPosition[1], out Object.Position.Y);
                                                    double.TryParse(SplitPosition[2], out Object.Position.Z);
                                                    break;

                                                case "Rotation":
                                                    string[] SplitRotation = attribute.Value.Split(';');
                                                    Vector3  r;
                                                    double.TryParse(SplitRotation[0], out r.X);
                                                    double.TryParse(SplitRotation[1], out r.Y);
                                                    double.TryParse(SplitRotation[2], out r.Z);
                                                    Object.Rotation += r;
                                                    break;

                                                case "ShowOn":
                                                    //Defines when the object should be shown
                                                    Object.FunctionScript = FunctionScriptNotation.GetPostfixNotationFromInfixNotation(GetAnimatedFunction(attribute.Value, false));
                                                    break;

                                                case "HideOn":
                                                    //Defines when the object should be hidden
                                                    Object.FunctionScript = FunctionScriptNotation.GetPostfixNotationFromInfixNotation(GetAnimatedFunction(attribute.Value, true));
                                                    break;

                                                case "FixedDynamicVisibility":
                                                    if (attribute.Value.ToLowerInvariant() == "true")
                                                    {
                                                        Object.FixedDynamicVisibility = true;
                                                    }
                                                    else
                                                    {
                                                        Object.FixedDynamicVisibility = false;
                                                    }
                                                    break;

                                                case "DynamicVisibility":
                                                    if (Object.FixedDynamicVisibility)
                                                    {
                                                        Object.FunctionScript = FunctionScriptNotation.GetPostfixNotationFromInfixNotation(GetDynamicFunction(attribute.Value));
                                                    }
                                                    break;
                                                }
                                            }
                                            if (Object.Name != null)
                                            {
                                                Array.Resize <GruppenObject>(ref CurrentObjects, CurrentObjects.Length + 1);
                                                CurrentObjects[CurrentObjects.Length - 1] = Object;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    //We've loaded the XML references, now load the objects into memory

                    //Single mesh object, containing all static components of the LS3D object
                    //If we use multiples, the Z-sorting throws a wobbly
                    StaticObject staticObject = null;
                    for (int i = 0; i < CurrentObjects.Length; i++)
                    {
                        if (CurrentObjects[i] == null || string.IsNullOrEmpty(CurrentObjects[i].Name))
                        {
                            continue;
                        }
                        StaticObject Object = null;
                        ObjectManager.AnimatedObjectCollection AnimatedObject = null;
                        try
                        {
                            if (CurrentObjects[i].Name.ToLowerInvariant().EndsWith(".l3dgrp"))
                            {
                                AnimatedObject = ReadObject(CurrentObjects[i].Name, Encoding, CurrentObjects[i].Rotation);
                            }
                            else if (CurrentObjects[i].Name.ToLowerInvariant().EndsWith(".l3dobj"))
                            {
                                Object = (StaticObject)ObjectManager.LoadObject(CurrentObjects[i].Name, Encoding, false, false, false, CurrentObjects[i].Rotation);
                            }
                            else
                            {
                                throw new Exception("Format " + System.IO.Path.GetExtension(CurrentObjects[i].Name) + " is not currently supported by the Loksim3D object parser");
                            }
                        }
                        catch (Exception ex) {
                            Interface.AddMessage(MessageType.Error, false, ex.Message);
                        }
                        if (Object != null)
                        {
                            if (!string.IsNullOrEmpty(CurrentObjects[i].FunctionScript))
                            {
                                //If the function script is not empty, this is a new animated object bit
                                Array.Resize <UnifiedObject>(ref obj, obj.Length + 1);
                                obj[obj.Length - 1] = Object;
                                int aL = Result.Objects.Length;
                                Array.Resize <ObjectManager.AnimatedObject>(ref Result.Objects, aL + 1);
                                ObjectManager.AnimatedObject a   = new ObjectManager.AnimatedObject();
                                AnimatedObjectState          aos = new AnimatedObjectState(Object, CurrentObjects[i].Position);
                                a.States           = new AnimatedObjectState[] { aos };
                                Result.Objects[aL] = a;
                                Result.Objects[aL].StateFunction = new FunctionScript(Program.CurrentHost, CurrentObjects[i].FunctionScript + " 1 == --", false);
                            }
                            else
                            {
                                //Otherwise, join to the main static mesh & update co-ords
                                for (int j = 0; j < Object.Mesh.Vertices.Length; j++)
                                {
                                    Object.Mesh.Vertices[j].Coordinates += CurrentObjects[i].Position;
                                }
                                staticObject.JoinObjects(Object);
                            }
                        }
                        else if (AnimatedObject != null)
                        {
                            int rl = Result.Objects.Length;
                            int l  = AnimatedObject.Objects.Length;
                            Array.Resize <ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length + l);
                            for (int o = rl; o < rl + l; o++)
                            {
                                if (AnimatedObject.Objects[o - rl] != null)
                                {
                                    Result.Objects[o] = AnimatedObject.Objects[o - rl].Clone();
                                    for (int si = 0; si < Result.Objects[o].States.Length; si++)
                                    {
                                        Result.Objects[o].States[si].Position += CurrentObjects[i].Position;
                                    }
                                }
                                else
                                {
                                    Result.Objects[o]        = new ObjectManager.AnimatedObject();
                                    Result.Objects[o].States = new AnimatedObjectState[0];
                                }
                            }
                        }
                    }
                    if (staticObject != null)
                    {
                        Array.Resize <ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length + 1);
                        ObjectManager.AnimatedObject a   = new ObjectManager.AnimatedObject();
                        AnimatedObjectState          aos = new AnimatedObjectState(staticObject, Vector3.Zero);
                        a.States = new AnimatedObjectState[] { aos };
                        Result.Objects[Result.Objects.Length - 1] = a;
                    }
                }
                return(Result);
            }
            //Didn't find an acceptable XML object
            //Probably will cause things to throw an absolute wobbly somewhere....
            return(null);
        }
예제 #3
0
        // parse animated object config
        /// <summary>Loads a collection of animated objects 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>
        /// <returns>The collection of animated objects.</returns>
        internal static ObjectManager.AnimatedObjectCollection ReadObject(string FileName, Encoding Encoding)
        {
            System.Globalization.CultureInfo       Culture = System.Globalization.CultureInfo.InvariantCulture;
            ObjectManager.AnimatedObjectCollection Result  = new ObjectManager.AnimatedObjectCollection
            {
                Objects = new ObjectManager.AnimatedObject[4]
            };
            int ObjectCount = 0;

            // load file
            string[] Lines   = System.IO.File.ReadAllLines(FileName, Encoding);
            bool     rpnUsed = false;

            for (int i = 0; i < Lines.Length; i++)
            {
                int j = Lines[i].IndexOf(';');
                if (j >= 0)
                {
                    Lines[i] = Lines[i].Substring(0, j).Trim();
                }
                else
                {
                    Lines[i] = Lines[i].Trim();
                }
                if (Lines[i].IndexOf("functionrpn", StringComparison.OrdinalIgnoreCase) >= 0)
                {
                    rpnUsed = true;
                }
            }
            if (rpnUsed)
            {
                Interface.AddMessage(MessageType.Error, false, "An animated object file contains RPN functions. These were never meant to be used directly, only for debugging. They won't be supported indefinately. Please get rid of them in file " + FileName);
            }
            for (int i = 0; i < Lines.Length; i++)
            {
                if (Lines[i].Length != 0)
                {
                    switch (Lines[i].ToLowerInvariant())
                    {
                    case "[include]":
                    {
                        i++;
                        Vector3         position = Vector3.Zero;
                        UnifiedObject[] obj      = new UnifiedObject[4];
                        int             objCount = 0;
                        while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal)))
                        {
                            if (Lines[i].Length != 0)
                            {
                                int j = Lines[i].IndexOf("=", StringComparison.Ordinal);
                                if (j > 0)
                                {
                                    string a = Lines[i].Substring(0, j).TrimEnd();
                                    string b = Lines[i].Substring(j + 1).TrimStart();
                                    switch (a.ToLowerInvariant())
                                    {
                                    case "position":
                                    {
                                        string[] s = b.Split(',');
                                        if (s.Length == 3)
                                        {
                                            double x, y, z;
                                            if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out x))
                                            {
                                                Interface.AddMessage(MessageType.Error, false, "X is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out y))
                                            {
                                                Interface.AddMessage(MessageType.Error, false, "Y is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else if (!double.TryParse(s[2], System.Globalization.NumberStyles.Float, Culture, out z))
                                            {
                                                Interface.AddMessage(MessageType.Error, false, "Z is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else
                                            {
                                                position = new Vector3(x, y, z);
                                            }
                                        }
                                        else
                                        {
                                            Interface.AddMessage(MessageType.Error, false, "Exactly 3 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        }
                                    } break;

                                    default:
                                        Interface.AddMessage(MessageType.Error, false, "The attribute " + a + " is not supported at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        break;
                                    }
                                }
                                else
                                {
                                    string Folder = System.IO.Path.GetDirectoryName(FileName);
                                    if (Path.ContainsInvalidChars(Lines[i]))
                                    {
                                        Interface.AddMessage(MessageType.Error, false, Lines[i] + " contains illegal characters at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                    }
                                    else
                                    {
                                        string file = OpenBveApi.Path.CombineFile(Folder, Lines[i]);
                                        if (System.IO.File.Exists(file))
                                        {
                                            if (obj.Length == objCount)
                                            {
                                                Array.Resize <UnifiedObject>(ref obj, obj.Length << 1);
                                            }
                                            obj[objCount] = ObjectManager.LoadObject(file, Encoding, false, false, false);
                                            objCount++;
                                        }
                                        else
                                        {
                                            Interface.AddMessage(MessageType.Error, true, "File " + file + " not found at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        }
                                    }
                                }
                            }
                            i++;
                        }
                        i--;
                        for (int j = 0; j < objCount; j++)
                        {
                            if (obj[j] != null)
                            {
                                if (obj[j] is StaticObject)
                                {
                                    StaticObject s = (StaticObject)obj[j];
                                    s.Dynamic = true;
                                    if (ObjectCount >= Result.Objects.Length)
                                    {
                                        Array.Resize <ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length << 1);
                                    }
                                    ObjectManager.AnimatedObject a   = new ObjectManager.AnimatedObject();
                                    AnimatedObjectState          aos = new AnimatedObjectState(s, position);
                                    a.States = new AnimatedObjectState[] { aos };
                                    Result.Objects[ObjectCount] = a;
                                    ObjectCount++;
                                }
                                else if (obj[j] is ObjectManager.AnimatedObjectCollection)
                                {
                                    ObjectManager.AnimatedObjectCollection a = (ObjectManager.AnimatedObjectCollection)obj[j];
                                    for (int k = 0; k < a.Objects.Length; k++)
                                    {
                                        if (ObjectCount >= Result.Objects.Length)
                                        {
                                            Array.Resize <ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length << 1);
                                        }
                                        for (int h = 0; h < a.Objects[k].States.Length; h++)
                                        {
                                            a.Objects[k].States[h].Position.X += position.X;
                                            a.Objects[k].States[h].Position.Y += position.Y;
                                            a.Objects[k].States[h].Position.Z += position.Z;
                                        }
                                        Result.Objects[ObjectCount] = a.Objects[k];
                                        ObjectCount++;
                                    }
                                }
                            }
                        }
                    }
                    break;

                    case "[object]":
                    {
                        i++;
                        if (Result.Objects.Length == ObjectCount)
                        {
                            Array.Resize <ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length << 1);
                        }
                        Result.Objects[ObjectCount] = new ObjectManager.AnimatedObject
                        {
                            States                 = new AnimatedObjectState[] {},
                            CurrentState           = -1,
                            TranslateXDirection    = Vector3.Right,
                            TranslateYDirection    = Vector3.Down,
                            TranslateZDirection    = Vector3.Forward,
                            RotateXDirection       = Vector3.Right,
                            RotateYDirection       = Vector3.Down,
                            RotateZDirection       = Vector3.Forward,
                            TextureShiftXDirection = new Vector2(1.0, 0.0),
                            TextureShiftYDirection = new Vector2(0.0, 1.0),
                            RefreshRate            = 0.0,
                            ObjectIndex            = -1
                        };
                        Vector3  Position          = Vector3.Zero;
                        bool     timetableUsed     = false;
                        string[] StateFiles        = null;
                        string   StateFunctionRpn  = null;
                        int      StateFunctionLine = -1;
                        while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal)))
                        {
                            if (Lines[i].Length != 0)
                            {
                                int j = Lines[i].IndexOf("=", StringComparison.Ordinal);
                                if (j > 0)
                                {
                                    string a = Lines[i].Substring(0, j).TrimEnd();
                                    string b = Lines[i].Substring(j + 1).TrimStart();
                                    switch (a.ToLowerInvariant())
                                    {
                                    case "position":
                                    {
                                        string[] s = b.Split(',');
                                        if (s.Length == 3)
                                        {
                                            double x, y, z;
                                            if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out x))
                                            {
                                                Interface.AddMessage(MessageType.Error, false, "X is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out y))
                                            {
                                                Interface.AddMessage(MessageType.Error, false, "Y is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else if (!double.TryParse(s[2], System.Globalization.NumberStyles.Float, Culture, out z))
                                            {
                                                Interface.AddMessage(MessageType.Error, false, "Z is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else
                                            {
                                                Position = new Vector3(x, y, z);
                                            }
                                        }
                                        else
                                        {
                                            Interface.AddMessage(MessageType.Error, false, "Exactly 3 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        }
                                    } break;

                                    case "states":
                                    {
                                        string[] s = b.Split(',');
                                        if (s.Length >= 1)
                                        {
                                            string Folder = System.IO.Path.GetDirectoryName(FileName);
                                            StateFiles = new string[s.Length];
                                            for (int k = 0; k < s.Length; k++)
                                            {
                                                s[k] = s[k].Trim();
                                                if (s[k].Length == 0)
                                                {
                                                    Interface.AddMessage(MessageType.Error, false, "File" + k.ToString(Culture) + " is an empty string - did you mean something else? - in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                                    StateFiles[k] = null;
                                                }
                                                else if (Path.ContainsInvalidChars(s[k]))
                                                {
                                                    Interface.AddMessage(MessageType.Error, false, "File" + k.ToString(Culture) + " contains illegal characters in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                                    StateFiles[k] = null;
                                                }
                                                else
                                                {
                                                    StateFiles[k] = OpenBveApi.Path.CombineFile(Folder, s[k]);
                                                    if (!System.IO.File.Exists(StateFiles[k]))
                                                    {
                                                        Interface.AddMessage(MessageType.Error, true, "File " + StateFiles[k] + " not found in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                                        StateFiles[k] = null;
                                                    }
                                                }
                                            }
                                        }
                                        else
                                        {
                                            Interface.AddMessage(MessageType.Error, false, "At least one argument is expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            return(null);
                                        }
                                    } break;

                                    case "statefunction":
                                        try {
                                            StateFunctionLine = i;
                                            StateFunctionRpn  = FunctionScriptNotation.GetPostfixNotationFromInfixNotation(b);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

                                    case "statefunctionrpn":
                                    {
                                        StateFunctionLine = i;
                                        StateFunctionRpn  = b;
                                    } break;

                                    case "translatexdirection":
                                    case "translateydirection":
                                    case "translatezdirection":
                                    {
                                        string[] s = b.Split(',');
                                        if (s.Length == 3)
                                        {
                                            double x, y, z;
                                            if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out x))
                                            {
                                                Interface.AddMessage(MessageType.Error, false, "X is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out y))
                                            {
                                                Interface.AddMessage(MessageType.Error, false, "Y is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else if (!double.TryParse(s[2], System.Globalization.NumberStyles.Float, Culture, out z))
                                            {
                                                Interface.AddMessage(MessageType.Error, false, "Z is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else
                                            {
                                                switch (a.ToLowerInvariant())
                                                {
                                                case "translatexdirection":
                                                    Result.Objects[ObjectCount].TranslateXDirection = new Vector3(x, y, z);
                                                    break;

                                                case "translateydirection":
                                                    Result.Objects[ObjectCount].TranslateYDirection = new Vector3(x, y, z);
                                                    break;

                                                case "translatezdirection":
                                                    Result.Objects[ObjectCount].TranslateZDirection = new Vector3(x, y, z);
                                                    break;
                                                }
                                            }
                                        }
                                        else
                                        {
                                            Interface.AddMessage(MessageType.Error, false, "Exactly 3 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        }
                                    } break;

                                    case "translatexfunction":
                                        try {
                                            Result.Objects[ObjectCount].TranslateXFunction = new FunctionScript(Program.CurrentHost, b, true);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

                                    case "translateyfunction":
                                        try {
                                            Result.Objects[ObjectCount].TranslateYFunction = new FunctionScript(Program.CurrentHost, b, true);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

                                    case "translatezfunction":
                                        try {
                                            Result.Objects[ObjectCount].TranslateZFunction = new FunctionScript(Program.CurrentHost, b, true);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

                                    case "translatexfunctionrpn":
                                        try {
                                            Result.Objects[ObjectCount].TranslateXFunction = new FunctionScript(Program.CurrentHost, b, false);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

                                    case "translateyfunctionrpn":
                                        try {
                                            Result.Objects[ObjectCount].TranslateYFunction = new FunctionScript(Program.CurrentHost, b, false);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

                                    case "translatezfunctionrpn":
                                        try {
                                            Result.Objects[ObjectCount].TranslateZFunction = new FunctionScript(Program.CurrentHost, b, false);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

                                    case "rotatexdirection":
                                    case "rotateydirection":
                                    case "rotatezdirection":
                                    {
                                        string[] s = b.Split(',');
                                        if (s.Length == 3)
                                        {
                                            double x, y, z;
                                            if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out x))
                                            {
                                                Interface.AddMessage(MessageType.Error, false, "X is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out y))
                                            {
                                                Interface.AddMessage(MessageType.Error, false, "Y is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else if (!double.TryParse(s[2], System.Globalization.NumberStyles.Float, Culture, out z))
                                            {
                                                Interface.AddMessage(MessageType.Error, false, "Z is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else if (x == 0.0 & y == 0.0 & z == 0.0)
                                            {
                                                Interface.AddMessage(MessageType.Error, false, "The direction indicated by X, Y and Z is expected to be non-zero in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else
                                            {
                                                switch (a.ToLowerInvariant())
                                                {
                                                case "rotatexdirection":
                                                    Result.Objects[ObjectCount].RotateXDirection = new Vector3(x, y, z);
                                                    break;

                                                case "rotateydirection":
                                                    Result.Objects[ObjectCount].RotateYDirection = new Vector3(x, y, z);
                                                    break;

                                                case "rotatezdirection":
                                                    Result.Objects[ObjectCount].RotateZDirection = new Vector3(x, y, z);
                                                    break;
                                                }
                                            }
                                        }
                                        else
                                        {
                                            Interface.AddMessage(MessageType.Error, false, "Exactly 3 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        }
                                    } break;

                                    case "rotatexfunction":
                                        try {
                                            Result.Objects[ObjectCount].RotateXFunction = new FunctionScript(Program.CurrentHost, b, true);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

                                    case "rotateyfunction":
                                        try {
                                            Result.Objects[ObjectCount].RotateYFunction = new FunctionScript(Program.CurrentHost, b, true);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

                                    case "rotatezfunction":
                                        try {
                                            Result.Objects[ObjectCount].RotateZFunction = new FunctionScript(Program.CurrentHost, b, true);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

                                    case "rotatexfunctionrpn":
                                        try {
                                            Result.Objects[ObjectCount].RotateXFunction = new FunctionScript(Program.CurrentHost, b, false);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

                                    case "rotateyfunctionrpn":
                                        try {
                                            Result.Objects[ObjectCount].RotateYFunction = new FunctionScript(Program.CurrentHost, b, false);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

                                    case "rotatezfunctionrpn":
                                        try {
                                            Result.Objects[ObjectCount].RotateZFunction = new FunctionScript(Program.CurrentHost, b, false);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

                                    case "rotatexdamping":
                                    case "rotateydamping":
                                    case "rotatezdamping":
                                    {
                                        string[] s = b.Split(',');
                                        if (s.Length == 2)
                                        {
                                            double nf, dr;
                                            if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out nf))
                                            {
                                                Interface.AddMessage(MessageType.Error, false, "NaturalFrequency is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out dr))
                                            {
                                                Interface.AddMessage(MessageType.Error, false, "DampingRatio is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else if (nf <= 0.0)
                                            {
                                                Interface.AddMessage(MessageType.Error, false, "NaturalFrequency is expected to be positive in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else if (dr <= 0.0)
                                            {
                                                Interface.AddMessage(MessageType.Error, false, "DampingRatio is expected to be positive in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else
                                            {
                                                switch (a.ToLowerInvariant())
                                                {
                                                case "rotatexdamping":
                                                    Result.Objects[ObjectCount].RotateXDamping = new Damping(nf, dr);
                                                    break;

                                                case "rotateydamping":
                                                    Result.Objects[ObjectCount].RotateYDamping = new Damping(nf, dr);
                                                    break;

                                                case "rotatezdamping":
                                                    Result.Objects[ObjectCount].RotateZDamping = new Damping(nf, dr);
                                                    break;
                                                }
                                            }
                                        }
                                        else
                                        {
                                            Interface.AddMessage(MessageType.Error, false, "Exactly 2 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        }
                                    } break;

                                    case "textureshiftxdirection":
                                    case "textureshiftydirection":
                                    {
                                        string[] s = b.Split(',');
                                        if (s.Length == 2)
                                        {
                                            double x, y;
                                            if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out x))
                                            {
                                                Interface.AddMessage(MessageType.Error, false, "X is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out y))
                                            {
                                                Interface.AddMessage(MessageType.Error, false, "Y is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else
                                            {
                                                switch (a.ToLowerInvariant())
                                                {
                                                case "textureshiftxdirection":
                                                    Result.Objects[ObjectCount].TextureShiftXDirection = new Vector2(x, y);
                                                    break;

                                                case "textureshiftydirection":
                                                    Result.Objects[ObjectCount].TextureShiftYDirection = new Vector2(x, y);
                                                    break;
                                                }
                                            }
                                        }
                                        else
                                        {
                                            Interface.AddMessage(MessageType.Error, false, "Exactly 2 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        }
                                    } break;

                                    case "textureshiftxfunction":
                                        try {
                                            Result.Objects[ObjectCount].TextureShiftXFunction = new FunctionScript(Program.CurrentHost, b, true);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

                                    case "textureshiftyfunction":
                                        try {
                                            Result.Objects[ObjectCount].TextureShiftYFunction = new FunctionScript(Program.CurrentHost, b, true);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

                                    case "textureshiftxfunctionrpn":
                                        try {
                                            Result.Objects[ObjectCount].TextureShiftXFunction = new FunctionScript(Program.CurrentHost, b, false);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

                                    case "textureshiftyfunctionrpn":
                                        try {
                                            Result.Objects[ObjectCount].TextureShiftYFunction = new FunctionScript(Program.CurrentHost, b, false);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

                                    case "textureoverride":
                                        switch (b.ToLowerInvariant())
                                        {
                                        case "none":
                                            break;

                                        case "timetable":
                                            if (!timetableUsed)
                                            {
                                                Timetable.AddObjectForCustomTimetable(Result.Objects[ObjectCount]);
                                                timetableUsed = true;
                                            }
                                            break;

                                        default:
                                            Interface.AddMessage(MessageType.Error, false, "Unrecognized value in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            break;
                                        }
                                        break;

                                    case "refreshrate":
                                    {
                                        double r;
                                        if (!double.TryParse(b, System.Globalization.NumberStyles.Float, Culture, out r))
                                        {
                                            Interface.AddMessage(MessageType.Error, false, "Value is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        }
                                        else if (r < 0.0)
                                        {
                                            Interface.AddMessage(MessageType.Error, false, "Value is expected to be non-negative in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        }
                                        else
                                        {
                                            Result.Objects[ObjectCount].RefreshRate = r;
                                        }
                                    } break;

                                    default:
                                        Interface.AddMessage(MessageType.Error, false, "The attribute " + a + " is not supported at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        break;
                                    }
                                }
                                else
                                {
                                    Interface.AddMessage(MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                    return(null);
                                }
                            }
                            i++;
                        }
                        i--;
                        if (StateFiles != null)
                        {
                            // create the object
                            if (timetableUsed)
                            {
                                if (StateFunctionRpn != null)
                                {
                                    StateFunctionRpn = "timetable 0 == " + StateFunctionRpn + " -1 ?";
                                }
                                else
                                {
                                    StateFunctionRpn = "timetable";
                                }
                            }
                            if (StateFunctionRpn != null)
                            {
                                try {
                                    Result.Objects[ObjectCount].StateFunction = new FunctionScript(Program.CurrentHost, StateFunctionRpn, false);
                                } catch (Exception ex) {
                                    Interface.AddMessage(MessageType.Error, false, ex.Message + " in StateFunction at line " + (StateFunctionLine + 1).ToString(Culture) + " in file " + FileName);
                                }
                            }
                            Result.Objects[ObjectCount].States = new AnimatedObjectState[StateFiles.Length];
                            bool ForceTextureRepeatX = Result.Objects[ObjectCount].TextureShiftXFunction != null & Result.Objects[ObjectCount].TextureShiftXDirection.X != 0.0 |
                                                       Result.Objects[ObjectCount].TextureShiftYFunction != null & Result.Objects[ObjectCount].TextureShiftYDirection.X != 0.0;
                            bool ForceTextureRepeatY = Result.Objects[ObjectCount].TextureShiftXFunction != null & Result.Objects[ObjectCount].TextureShiftXDirection.Y != 0.0 |
                                                       Result.Objects[ObjectCount].TextureShiftYFunction != null & Result.Objects[ObjectCount].TextureShiftYDirection.Y != 0.0;
                            for (int k = 0; k < StateFiles.Length; k++)
                            {
                                Result.Objects[ObjectCount].States[k].Position = Vector3.Zero;
                                if (StateFiles[k] != null)
                                {
                                    Result.Objects[ObjectCount].States[k].Object = ObjectManager.LoadStaticObject(StateFiles[k], Encoding, false, ForceTextureRepeatX, ForceTextureRepeatY);
                                    if (Result.Objects[ObjectCount].States[k].Object != null)
                                    {
                                        Result.Objects[ObjectCount].States[k].Position       = Position;
                                        Result.Objects[ObjectCount].States[k].Object.Dynamic = true;
                                        for (int l = 0; l < Result.Objects[ObjectCount].States[k].Object.Mesh.Materials.Length; l++)
                                        {
                                            if (ForceTextureRepeatX && ForceTextureRepeatY)
                                            {
                                                Result.Objects[ObjectCount].States[k].Object.Mesh.Materials[l].WrapMode = OpenGlTextureWrapMode.RepeatRepeat;
                                            }
                                            else if (ForceTextureRepeatX)
                                            {
                                                switch (Result.Objects[ObjectCount].States[k].Object.Mesh.Materials[l].WrapMode)
                                                {
                                                case OpenGlTextureWrapMode.ClampRepeat:
                                                    Result.Objects[ObjectCount].States[k].Object.Mesh.Materials[l].WrapMode = OpenGlTextureWrapMode.RepeatRepeat;
                                                    break;

                                                case OpenGlTextureWrapMode.ClampClamp:
                                                    Result.Objects[ObjectCount].States[k].Object.Mesh.Materials[l].WrapMode = OpenGlTextureWrapMode.RepeatClamp;
                                                    break;
                                                }
                                            }
                                            else if (ForceTextureRepeatY)
                                            {
                                                switch (Result.Objects[ObjectCount].States[k].Object.Mesh.Materials[l].WrapMode)
                                                {
                                                case OpenGlTextureWrapMode.RepeatClamp:
                                                    Result.Objects[ObjectCount].States[k].Object.Mesh.Materials[l].WrapMode = OpenGlTextureWrapMode.RepeatRepeat;
                                                    break;

                                                case OpenGlTextureWrapMode.ClampClamp:
                                                    Result.Objects[ObjectCount].States[k].Object.Mesh.Materials[l].WrapMode = OpenGlTextureWrapMode.ClampRepeat;
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                }
                                else
                                {
                                    Result.Objects[ObjectCount].States[k].Object = null;
                                }
                            }
                        }
                        else
                        {
                            Result.Objects[ObjectCount].States = new AnimatedObjectState[] { };
                        }
                        ObjectCount++;
                    }
                    break;

                    case "[sound]":
                    case "[statechangesound]":
                        //Only show the sound nag once per route, otherwise this could cause spam...
                        if (!Program.SoundError)
                        {
                            Interface.AddMessage(MessageType.Information, false, "Animated objects containing sounds are only supported in openBVE v1.5.2.4+");
                            Interface.AddMessage(MessageType.Information, false, "Object Viewer does not support sounds. Please use the main game to test these!");
                            Program.SoundError = true;
                        }
                        i++;
                        while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal)))
                        {
                            i++;
                        }
                        break;

                    default:
                        Interface.AddMessage(MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                        return(null);
                    }
                }
            }
            Array.Resize <ObjectManager.AnimatedObject>(ref Result.Objects, ObjectCount);
            return(Result);
        }