// 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>
        /// <param name="LoadMode">The texture load mode.</param>
        /// <returns>The collection of animated objects.</returns>
        internal static ObjectManager.AnimatedObjectCollection ReadObject(string FileName, System.Text.Encoding Encoding, ObjectManager.ObjectLoadMode LoadMode)
        {
            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(Interface.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 = new Vector3(0.0, 0.0, 0.0);
                        ObjectManager.UnifiedObject[] obj = new OpenBve.ObjectManager.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(Interface.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(Interface.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(Interface.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(Interface.MessageType.Error, false, "Exactly 3 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        }
                                    } break;

                                    default:
                                        Interface.AddMessage(Interface.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(Interface.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 <ObjectManager.UnifiedObject>(ref obj, obj.Length << 1);
                                            }
                                            obj[objCount] = ObjectManager.LoadObject(file, Encoding, LoadMode, false, false, false);
                                            objCount++;
                                        }
                                        else
                                        {
                                            Interface.AddMessage(Interface.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 ObjectManager.StaticObject)
                                {
                                    ObjectManager.StaticObject s = (ObjectManager.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();
                                    ObjectManager.AnimatedObjectState aos = new ObjectManager.AnimatedObjectState
                                    {
                                        Object   = s,
                                        Position = position
                                    };
                                    a.States = new ObjectManager.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 ObjectManager.AnimatedObjectState[] {},
                            CurrentState           = -1,
                            TranslateXDirection    = new Vector3(1.0, 0.0, 0.0),
                            TranslateYDirection    = new Vector3(0.0, 1.0, 0.0),
                            TranslateZDirection    = new Vector3(0.0, 0.0, 1.0),
                            RotateXDirection       = new Vector3(1.0, 0.0, 0.0),
                            RotateYDirection       = new Vector3(0.0, 1.0, 0.0),
                            RotateZDirection       = new Vector3(0.0, 0.0, 1.0),
                            TextureShiftXDirection = new Vector2(1.0, 0.0),
                            TextureShiftYDirection = new Vector2(0.0, 1.0),
                            RefreshRate            = 0.0,
                            ObjectIndex            = -1
                        };
                        Vector3  Position          = new Vector3(0.0, 0.0, 0.0);
                        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(Interface.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(Interface.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(Interface.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(Interface.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(Interface.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(Interface.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(Interface.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(Interface.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  = FunctionScripts.GetPostfixNotationFromInfixNotation(b);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(Interface.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(Interface.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(Interface.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(Interface.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(Interface.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 = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

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

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

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

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

                                    case "translatezfunctionrpn":
                                        try {
                                            Result.Objects[ObjectCount].TranslateZFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(Interface.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(Interface.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(Interface.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(Interface.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(Interface.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(Interface.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 = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

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

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

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

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

                                    case "rotatezfunctionrpn":
                                        try {
                                            Result.Objects[ObjectCount].RotateZFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(Interface.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(Interface.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(Interface.MessageType.Error, false, "DampingRatio is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                            }
                                            else if (nf <= 0.0)
                                            {
                                                Interface.AddMessage(Interface.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(Interface.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 ObjectManager.Damping(nf, dr);
                                                    break;

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

                                                case "rotatezdamping":
                                                    Result.Objects[ObjectCount].RotateZDamping = new ObjectManager.Damping(nf, dr);
                                                    break;
                                                }
                                            }
                                        }
                                        else
                                        {
                                            Interface.AddMessage(Interface.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(Interface.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(Interface.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(Interface.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 = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        } break;

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

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

                                    case "textureshiftyfunctionrpn":
                                        try {
                                            Result.Objects[ObjectCount].TextureShiftYFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
                                        } catch (Exception ex) {
                                            Interface.AddMessage(Interface.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(Interface.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(Interface.MessageType.Error, false, "Value is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        }
                                        else if (r < 0.0)
                                        {
                                            Interface.AddMessage(Interface.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(Interface.MessageType.Error, false, "The attribute " + a + " is not supported at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                        break;
                                    }
                                }
                                else
                                {
                                    Interface.AddMessage(Interface.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 = FunctionScripts.GetFunctionScriptFromPostfixNotation(StateFunctionRpn);
                                } catch (Exception ex) {
                                    Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in StateFunction at line " + (StateFunctionLine + 1).ToString(Culture) + " in file " + FileName);
                                }
                            }
                            Result.Objects[ObjectCount].States = new ObjectManager.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 = new Vector3(0.0, 0.0, 0.0);
                                if (StateFiles[k] != null)
                                {
                                    Result.Objects[ObjectCount].States[k].Object = ObjectManager.LoadStaticObject(StateFiles[k], Encoding, LoadMode, false, ForceTextureRepeatX, ForceTextureRepeatY);
                                    if (Result.Objects[ObjectCount].States[k].Object != null)
                                    {
                                        Result.Objects[ObjectCount].States[k].Object.Dynamic = true;
                                    }
                                }
                                else
                                {
                                    Result.Objects[ObjectCount].States[k].Object = null;
                                }
                                for (int j = 0; j < Result.Objects[ObjectCount].States.Length; j++)
                                {
                                    Result.Objects[ObjectCount].States[j].Position = Position;
                                }
                            }
                        }
                        else
                        {
                            Result.Objects[ObjectCount].States = new ObjectManager.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(Interface.MessageType.Information, false, "Animated objects containing sounds are only supported in openBVE v1.5.2.4+");
                            Interface.AddMessage(Interface.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(Interface.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);
        }
Example #2
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>
		/// <param name="LoadMode">The texture load mode.</param>
		/// <returns>The collection of animated objects.</returns>
		internal static ObjectManager.AnimatedObjectCollection ReadObject(string FileName, System.Text.Encoding Encoding, ObjectManager.ObjectLoadMode LoadMode)
		{
			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(';');
				//Trim out comments
				Lines[i] = j >= 0 ? Lines[i].Substring(0, j).Trim() : Lines[i].Trim();
				//Test whether RPN functions have been used
				rpnUsed = Lines[i].IndexOf("functionrpn", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			if (rpnUsed)
			{
				Interface.AddMessage(Interface.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 = new Vector3(0.0, 0.0, 0.0);
								ObjectManager.UnifiedObject[] obj = new OpenBve.ObjectManager.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(Interface.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(Interface.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(Interface.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(Interface.MessageType.Error, false, "Exactly 3 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
														}
													} break;
												default:
													Interface.AddMessage(Interface.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 (Interface.ContainsInvalidPathChars(Lines[i]))
											{
												Interface.AddMessage(Interface.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<ObjectManager.UnifiedObject>(ref obj, obj.Length << 1);
													}
													obj[objCount] = ObjectManager.LoadObject(file, Encoding, LoadMode, false, false, false);
													objCount++;
												}
												else
												{
													Interface.AddMessage(Interface.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 ObjectManager.StaticObject)
										{
											ObjectManager.StaticObject s = (ObjectManager.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();
											ObjectManager.AnimatedObjectState aos = new ObjectManager.AnimatedObjectState
											{
												Object = s,
												Position = position
											};
											a.States = new ObjectManager.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 ObjectManager.AnimatedObjectState[] {},
									CurrentState = -1,
									TranslateXDirection = new Vector3(1.0, 0.0, 0.0),
									TranslateYDirection = new Vector3(0.0, 1.0, 0.0),
									TranslateZDirection = new Vector3(0.0, 0.0, 1.0),
									RotateXDirection = new Vector3(1.0, 0.0, 0.0),
									RotateYDirection = new Vector3(0.0, 1.0, 0.0),
									RotateZDirection = new Vector3(0.0, 0.0, 1.0),
									TextureShiftXDirection = new Vector2(1.0, 0.0),
									TextureShiftYDirection = new Vector2(0.0, 1.0),
									RefreshRate = 0.0,
									ObjectIndex = -1
								};
								Vector3 Position = new Vector3(0.0, 0.0, 0.0);
								double RotateX = 0;
								bool StaticXRotation = false;
								double RotateY = 0;
								bool StaticYRotation = false;
								double RotateZ = 0;
								bool StaticZRotation = false;
								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(Interface.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(Interface.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(Interface.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(Interface.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];
															bool NullObject = true;
															for (int k = 0; k < s.Length; k++)
															{
																s[k] = s[k].Trim();
																if (s[k].Length == 0)
																{
																	Interface.AddMessage(Interface.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 (Interface.ContainsInvalidPathChars(s[k]))
																{
																	Interface.AddMessage(Interface.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(Interface.MessageType.Error, true, "File " + StateFiles[k] + " not found in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
																		StateFiles[k] = null;
																	}
																}
																if (StateFiles[k] != null)
																{
																	NullObject = false;
																}	
															}
															if (NullObject == true)
															{
																Interface.AddMessage(Interface.MessageType.Error, false, "None of the specified files were found in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
																return null;
															}
														}
														else
														{
															Interface.AddMessage(Interface.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 = FunctionScripts.GetPostfixNotationFromInfixNotation(b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.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(Interface.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(Interface.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(Interface.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(Interface.MessageType.Error, false, "Exactly 3 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
														}
													} break;
												case "translatexfunction":
													try
													{
														double X;
														if (double.TryParse(b, NumberStyles.Float, Culture, out X))
														{
															Position.X = X;
															//A function script must be evaluated every frame, no matter if it is a constant value
															//If we add this to the position instead, this gives a minor speedup
															break;
														}
														Result.Objects[ObjectCount].TranslateXFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
													} break;
												case "translatexscript":
													try
													{
														Result.Objects[ObjectCount].TranslateXScriptFile = OpenBveApi.Path.CombineDirectory(System.IO.Path.GetDirectoryName(FileName), b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
													} break;
												case "translateyfunction":
													try
													{
														double Y;
														if (double.TryParse(b, NumberStyles.Float, Culture, out Y))
														{
															Position.Y = Y;
															//A function script must be evaluated every frame, no matter if it is a constant value
															//If we add this to the position instead, this gives a minor speedup
															break;
														}
														Result.Objects[ObjectCount].TranslateYFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
													} break;
												case "translateyscript":
													try
													{
														Result.Objects[ObjectCount].TranslateYScriptFile = OpenBveApi.Path.CombineDirectory(System.IO.Path.GetDirectoryName(FileName), b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
													} break;
												case "translatezfunction":
													try
													{
														double Z;
														if (double.TryParse(b, NumberStyles.Float, Culture, out Z))
														{
															Position.Z = Z;
															//A function script must be evaluated every frame, no matter if it is a constant value
															//If we add this to the position instead, this gives a minor speedup
															break;
														}
														Result.Objects[ObjectCount].TranslateZFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
													} break;
												case "translatezscript":
													try
													{
														Result.Objects[ObjectCount].TranslateZScriptFile = OpenBveApi.Path.CombineDirectory(System.IO.Path.GetDirectoryName(FileName), b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
													} break;
												case "trackfollowerfunction":
													try
													{
														Result.Objects[ObjectCount].TrackFollowerFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
													} break;
												case "axles":
													try
													{
														double FrontAxlePosition;
														double RearAxlePosition;
														var splitValue = b.Split(',');
														Double.TryParse(splitValue[0], out FrontAxlePosition);
														Double.TryParse(splitValue[1], out RearAxlePosition);
														if (FrontAxlePosition > RearAxlePosition)
														{
															Result.Objects[ObjectCount].FrontAxlePosition = FrontAxlePosition;
															Result.Objects[ObjectCount].RearAxlePosition = RearAxlePosition;
														}
														else if (FrontAxlePosition < RearAxlePosition)
														{
															Interface.AddMessage(Interface.MessageType.Error, false,"Rear is expected to be less than Front in " + a + " at line " + (i + 1).ToString(Culture) + " in file " +FileName);
														}
														else
														{
															Interface.AddMessage(Interface.MessageType.Error, false, "Rear must not equal Front in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
														}

													}
													catch(Exception ex)
													{
														Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
													}
													break;
													/*
													 * RPN Functions were added by Michelle, and she stated that they should not be used other than in debugging
													 * Not aware of any uses, but these should stay there anyway
													 * 
													 */
												case "translatexfunctionrpn":
													try
													{
														Result.Objects[ObjectCount].TranslateXFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
													} break;
												case "translateyfunctionrpn":
													try
													{
														Result.Objects[ObjectCount].TranslateYFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
													} break;
												case "translatezfunctionrpn":
													try
													{
														Result.Objects[ObjectCount].TranslateZFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.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(Interface.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(Interface.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(Interface.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(Interface.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(Interface.MessageType.Error, false, "Exactly 3 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
														}
													} break;
												case "rotatexfunction":
													try
													{
														if (double.TryParse(b, NumberStyles.Float, Culture, out RotateX))
														{
															//A function script must be evaluated every frame, no matter if it is a constant value
															//If we add this to the position instead, this gives a minor speedup
															StaticXRotation = true;
														}
														Result.Objects[ObjectCount].RotateXFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
													} break;
												case "rotateyfunction":
													try
													{
														if (double.TryParse(b, NumberStyles.Float, Culture, out RotateY))
														{
															StaticYRotation = true;
														}
														Result.Objects[ObjectCount].RotateYFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
													} break;
												case "rotatezfunction":
													try
													{
														if (double.TryParse(b, NumberStyles.Float, Culture, out RotateZ))
														{
															StaticZRotation = true;
														}
														Result.Objects[ObjectCount].RotateZFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
													} break;
												case "rotatexfunctionrpn":
													try
													{
														Result.Objects[ObjectCount].RotateXFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
													} break;
												case "rotateyfunctionrpn":
													try
													{
														Result.Objects[ObjectCount].RotateYFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
													} break;
												case "rotatezfunctionrpn":
													try
													{
														Result.Objects[ObjectCount].RotateZFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.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(Interface.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(Interface.MessageType.Error, false, "DampingRatio is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
															}
															else if (nf <= 0.0)
															{
																Interface.AddMessage(Interface.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(Interface.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 ObjectManager.Damping(nf, dr);
																		break;
																	case "rotateydamping":
																		Result.Objects[ObjectCount].RotateYDamping = new ObjectManager.Damping(nf, dr);
																		break;
																	case "rotatezdamping":
																		Result.Objects[ObjectCount].RotateZDamping = new ObjectManager.Damping(nf, dr);
																		break;
																}
															}
														}
														else
														{
															Interface.AddMessage(Interface.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(Interface.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(Interface.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(Interface.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 = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
													} break;
												case "textureshiftyfunction":
													try
													{
														Result.Objects[ObjectCount].TextureShiftYFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
													} break;
												case "textureshiftxfunctionrpn":
													try
													{
														Result.Objects[ObjectCount].TextureShiftXFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
													} break;
												case "textureshiftyfunctionrpn":
													try
													{
														Result.Objects[ObjectCount].TextureShiftYFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
													}
													catch (Exception ex)
													{
														Interface.AddMessage(Interface.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(Interface.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(Interface.MessageType.Error, false, "Value is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
														}
														else if (r < 0.0)
														{
															Interface.AddMessage(Interface.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(Interface.MessageType.Error, false, "The attribute " + a + " is not supported at line " + (i + 1).ToString(Culture) + " in file " + FileName);
													break;
											}
										}
										else
										{
											Interface.AddMessage(Interface.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 = FunctionScripts.GetFunctionScriptFromPostfixNotation(StateFunctionRpn);
										}
										catch (Exception ex)
										{
											Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in StateFunction at line " + (StateFunctionLine + 1).ToString(Culture) + " in file " + FileName);
										}
									}
									Result.Objects[ObjectCount].States = new ObjectManager.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.Y != 0.0;
									bool ForceTextureRepeatY = Result.Objects[ObjectCount].TextureShiftXFunction != null & Result.Objects[ObjectCount].TextureShiftXDirection.X != 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 = new Vector3(0.0, 0.0, 0.0);
										if (StateFiles[k] != null)
										{
											Result.Objects[ObjectCount].States[k].Object = ObjectManager.LoadStaticObject(StateFiles[k], Encoding, LoadMode, false, ForceTextureRepeatX, ForceTextureRepeatY);
											if (Result.Objects[ObjectCount].States[k].Object != null)
											{
												Result.Objects[ObjectCount].States[k].Object.Dynamic = true;
											}
										}
										else
										{
											Result.Objects[ObjectCount].States[k].Object = null;
										}
										
									}
									for (int j = 0; j < Result.Objects[ObjectCount].States.Length; j++)
									{

										//Rotate X
										if (Result.Objects[ObjectCount].States[j].Object == null)
										{
											continue;
										}
										for (int l = 0; l < Result.Objects[ObjectCount].States[j].Object.Mesh.Vertices.Length; l++)
										{
											//Apply position
											Result.Objects[ObjectCount].States[j].Position = Position;
											//Test whether the object contains non static rotation functions
											//If so, the results may be off so don't optimise
											if (!StaticXRotation)
											{
												if (Result.Objects[ObjectCount].RotateXFunction != null)
												{
													break;
												}
											}
											if (!StaticYRotation)
											{
												if (Result.Objects[ObjectCount].RotateYFunction != null)
												{
													break;
												}
											}
											if (!StaticZRotation)
											{
												if (Result.Objects[ObjectCount].RotateZFunction != null)
												{
													break;
												}
											}
											if (StaticXRotation)
											{
												double x = Result.Objects[ObjectCount].RotateXDirection.X;
												double y = Result.Objects[ObjectCount].RotateXDirection.Y;
												double z = Result.Objects[ObjectCount].RotateXDirection.Z;
												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 (RotateX != 0.0)
												{
													t = 1.0 / Math.Sqrt(t);
													x *= t;
													y *= t;
													z *= t;
												}
												double cosX = Math.Cos(RotateX);
												double sinX = Math.Sin(RotateX);
												World.Rotate(ref Result.Objects[ObjectCount].States[j].Object.Mesh.Vertices[l].Coordinates, x, y, z, cosX, sinX);
												Result.Objects[ObjectCount].RotateXFunction = null;
											}
											if (StaticYRotation)
											{
												double x = Result.Objects[ObjectCount].RotateYDirection.X;
												double y = Result.Objects[ObjectCount].RotateYDirection.Y;
												double z = Result.Objects[ObjectCount].RotateYDirection.Z;
												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 (RotateY != 0.0)
												{
													t = 1.0 / Math.Sqrt(t);
													x *= t;
													y *= t;
													z *= t;
												}
												double cosY = Math.Cos(RotateY);
												double sinY = Math.Sin(RotateY);
												World.Rotate(ref Result.Objects[ObjectCount].States[j].Object.Mesh.Vertices[l].Coordinates, x, y, z, cosY, sinY);
												Result.Objects[ObjectCount].RotateYFunction = null;
											}
											if (StaticZRotation)
											{
												double x = Result.Objects[ObjectCount].RotateZDirection.X;
												double y = Result.Objects[ObjectCount].RotateZDirection.Y;
												double z = Result.Objects[ObjectCount].RotateZDirection.Z;
												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 (RotateZ != 0.0)
												{
													t = 1.0 / Math.Sqrt(t);
													x *= t;
													y *= t;
													z *= t;
												}
												double cosZ = Math.Cos(RotateZ);
												double sinZ = Math.Sin(RotateZ);
												World.Rotate(ref Result.Objects[ObjectCount].States[j].Object.Mesh.Vertices[l].Coordinates, x, y, z, cosZ, sinZ);
												Result.Objects[ObjectCount].RotateZFunction = null;
											}

											
										}
									}
								}
								else
								{
									Result.Objects[ObjectCount].States = new ObjectManager.AnimatedObjectState[] { };
								}
								ObjectCount++;
							}
							break;
						default:
							Interface.AddMessage(Interface.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;
		}
 // 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>
 /// <param name="LoadMode">The texture load mode.</param>
 /// <returns>The collection of animated objects.</returns>
 internal static ObjectManager.AnimatedObjectCollection ReadObject(string FileName, System.Text.Encoding Encoding, ObjectManager.ObjectLoadMode LoadMode)
 {
     System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture;
     ObjectManager.AnimatedObjectCollection Result = new ObjectManager.AnimatedObjectCollection();
     Result.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 (Program.CurrentProgramType == Program.ProgramType.ObjectViewer | Program.CurrentProgramType == Program.ProgramType.RouteViewer) {
             if (Lines[i].IndexOf("functionrpn", StringComparison.OrdinalIgnoreCase) >= 0) {
                 rpnUsed = true;
             }
         }
     }
     if (rpnUsed) {
         Interface.AddMessage(Interface.MessageType.Warning, false, "An animated object file contains non-official RPN functions. 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++;
                         World.Vector3D position = new World.Vector3D(0.0, 0.0, 0.0);
                         ObjectManager.UnifiedObject[] obj = new OpenBve.ObjectManager.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(Interface.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(Interface.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(Interface.MessageType.Error, false, "Z is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                                     } else {
                                                         position = new World.Vector3D(x, y, z);
                                                     }
                                                 } else {
                                                     Interface.AddMessage(Interface.MessageType.Error, false, "Exactly 3 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                                 }
                                             } break;
                                         default:
                                             Interface.AddMessage(Interface.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 (Interface.ContainsInvalidPathChars(Lines[i])) {
                                         Interface.AddMessage(Interface.MessageType.Error, false, Lines[i] + " contains illegal characters at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                     } else {
                                         string file = Interface.GetCombinedFileName(Folder, Lines[i]);
                                         if (System.IO.File.Exists(file)) {
                                             if (obj.Length == objCount) {
                                                 Array.Resize<ObjectManager.UnifiedObject>(ref obj, obj.Length << 1);
                                             }
                                             obj[objCount] = ObjectManager.LoadObject(file, Encoding, LoadMode, false, false, false);
                                             objCount++;
                                         } else {
                                             Interface.AddMessage(Interface.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 ObjectManager.StaticObject) {
                                     ObjectManager.StaticObject s = (ObjectManager.StaticObject)obj[j];
                                     s.Dynamic = 1;
                                     if (ObjectCount >= Result.Objects.Length) {
                                         Array.Resize<ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length << 1);
                                     }
                                     ObjectManager.AnimatedObject a = new ObjectManager.AnimatedObject();
                                     ObjectManager.AnimatedObjectState aos = new ObjectManager.AnimatedObjectState();
                                     aos.Object = s;
                                     aos.Position = position;
                                     a.States = new ObjectManager.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();
                         Result.Objects[ObjectCount].States = new ObjectManager.AnimatedObjectState[] { };
                         Result.Objects[ObjectCount].CurrentState = -1;
                         Result.Objects[ObjectCount].TranslateXDirection = new World.Vector3D(1.0, 0.0, 0.0);
                         Result.Objects[ObjectCount].TranslateYDirection = new World.Vector3D(0.0, 1.0, 0.0);
                         Result.Objects[ObjectCount].TranslateZDirection = new World.Vector3D(0.0, 0.0, 1.0);
                         Result.Objects[ObjectCount].RotateXDirection = new World.Vector3D(1.0, 0.0, 0.0);
                         Result.Objects[ObjectCount].RotateYDirection = new World.Vector3D(0.0, 1.0, 0.0);
                         Result.Objects[ObjectCount].RotateZDirection = new World.Vector3D(0.0, 0.0, 1.0);
                         Result.Objects[ObjectCount].TextureShiftXDirection = new World.Vector2D(1.0, 0.0);
                         Result.Objects[ObjectCount].TextureShiftYDirection = new World.Vector2D(0.0, 1.0);
                         Result.Objects[ObjectCount].RefreshRate = 0.0;
                         Result.Objects[ObjectCount].ObjectIndex = -1;
                         World.Vector3D Position = new World.Vector3D(0.0, 0.0, 0.0);
                         string[] StateFiles = null;
                         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(Interface.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(Interface.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(Interface.MessageType.Error, false, "Z is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                                     } else {
                                                         Position = new World.Vector3D(x, y, z);
                                                     }
                                                 } else {
                                                     Interface.AddMessage(Interface.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 (Interface.ContainsInvalidPathChars(s[k])) {
                                                             Interface.AddMessage(Interface.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] = Interface.GetCombinedFileName(Folder, s[k]);
                                                             if (!System.IO.File.Exists(StateFiles[k])) {
                                                                 Interface.AddMessage(Interface.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(Interface.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 {
                                                 Result.Objects[ObjectCount].StateFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
                                             } catch (Exception ex) {
                                                 Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                             } break;
                                         case "statefunctionrpn":
                                             try {
                                                 Result.Objects[ObjectCount].StateFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
                                             } catch (Exception ex) {
                                                 Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                             } 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(Interface.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(Interface.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(Interface.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 World.Vector3D(x, y, z);
                                                                 break;
                                                             case "translateydirection":
                                                                 Result.Objects[ObjectCount].TranslateYDirection = new World.Vector3D(x, y, z);
                                                                 break;
                                                             case "translatezdirection":
                                                                 Result.Objects[ObjectCount].TranslateZDirection = new World.Vector3D(x, y, z);
                                                                 break;
                                                         }
                                                     }
                                                 } else {
                                                     Interface.AddMessage(Interface.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 = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
                                             } catch (Exception ex) {
                                                 Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                             } break;
                                         case "translateyfunction":
                                             try {
                                                 Result.Objects[ObjectCount].TranslateYFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
                                             } catch (Exception ex) {
                                                 Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                             } break;
                                         case "translatezfunction":
                                             try {
                                                 Result.Objects[ObjectCount].TranslateZFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
                                             } catch (Exception ex) {
                                                 Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                             } break;
                                         case "translatexfunctionrpn":
                                             try {
                                                 Result.Objects[ObjectCount].TranslateXFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
                                             } catch (Exception ex) {
                                                 Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                             } break;
                                         case "translateyfunctionrpn":
                                             try {
                                                 Result.Objects[ObjectCount].TranslateYFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
                                             } catch (Exception ex) {
                                                 Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                             } break;
                                         case "translatezfunctionrpn":
                                             try {
                                                 Result.Objects[ObjectCount].TranslateZFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
                                             } catch (Exception ex) {
                                                 Interface.AddMessage(Interface.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(Interface.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(Interface.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(Interface.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(Interface.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 World.Vector3D(x, y, z);
                                                                 break;
                                                             case "rotateydirection":
                                                                 Result.Objects[ObjectCount].RotateYDirection = new World.Vector3D(x, y, z);
                                                                 break;
                                                             case "rotatezdirection":
                                                                 Result.Objects[ObjectCount].RotateZDirection = new World.Vector3D(x, y, z);
                                                                 break;
                                                         }
                                                     }
                                                 } else {
                                                     Interface.AddMessage(Interface.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 = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
                                             } catch (Exception ex) {
                                                 Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                             } break;
                                         case "rotateyfunction":
                                             try {
                                                 Result.Objects[ObjectCount].RotateYFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
                                             } catch (Exception ex) {
                                                 Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                             } break;
                                         case "rotatezfunction":
                                             try {
                                                 Result.Objects[ObjectCount].RotateZFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
                                             } catch (Exception ex) {
                                                 Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                             } break;
                                         case "rotatexfunctionrpn":
                                             try {
                                                 Result.Objects[ObjectCount].RotateXFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
                                             } catch (Exception ex) {
                                                 Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                             } break;
                                         case "rotateyfunctionrpn":
                                             try {
                                                 Result.Objects[ObjectCount].RotateYFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
                                             } catch (Exception ex) {
                                                 Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                             } break;
                                         case "rotatezfunctionrpn":
                                             try {
                                                 Result.Objects[ObjectCount].RotateZFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
                                             } catch (Exception ex) {
                                                 Interface.AddMessage(Interface.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(Interface.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(Interface.MessageType.Error, false, "DampingRatio is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                                     } else if (nf <= 0.0) {
                                                         Interface.AddMessage(Interface.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(Interface.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 ObjectManager.Damping(nf, dr);
                                                                 break;
                                                             case "rotateydamping":
                                                                 Result.Objects[ObjectCount].RotateYDamping = new ObjectManager.Damping(nf, dr);
                                                                 break;
                                                             case "rotatezdamping":
                                                                 Result.Objects[ObjectCount].RotateZDamping = new ObjectManager.Damping(nf, dr);
                                                                 break;
                                                         }
                                                     }
                                                 } else {
                                                     Interface.AddMessage(Interface.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(Interface.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(Interface.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 World.Vector2D(x, y);
                                                                 break;
                                                             case "textureshiftydirection":
                                                                 Result.Objects[ObjectCount].TextureShiftYDirection = new World.Vector2D(x, y);
                                                                 break;
                                                         }
                                                     }
                                                 } else {
                                                     Interface.AddMessage(Interface.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 = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
                                             } catch (Exception ex) {
                                                 Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                             } break;
                                         case "textureshiftyfunction":
                                             try {
                                                 Result.Objects[ObjectCount].TextureShiftYFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b);
                                             } catch (Exception ex) {
                                                 Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                             } break;
                                         case "textureshiftxfunctionrpn":
                                             try {
                                                 Result.Objects[ObjectCount].TextureShiftXFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
                                             } catch (Exception ex) {
                                                 Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                             } break;
                                         case "textureshiftyfunctionrpn":
                                             try {
                                                 Result.Objects[ObjectCount].TextureShiftYFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b);
                                             } catch (Exception ex) {
                                                 Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                             } break;
                                         case "refreshrate":
                                             {
                                                 double r;
                                                 if (!double.TryParse(b, System.Globalization.NumberStyles.Float, Culture, out r)) {
                                                     Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                                 } else if (r < 0.0) {
                                                     Interface.AddMessage(Interface.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(Interface.MessageType.Error, false, "The attribute " + a + " is not supported at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                             break;
                                     }
                                 } else {
                                     Interface.AddMessage(Interface.MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName);
                                     return null;
                                 }
                             }
                             i++;
                         }
                         i--;
                         if (StateFiles != null) {
                             Result.Objects[ObjectCount].States = new ObjectManager.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.Y != 0.0;
                             bool ForceTextureRepeatY = Result.Objects[ObjectCount].TextureShiftXFunction != null & Result.Objects[ObjectCount].TextureShiftXDirection.X != 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 = new World.Vector3D(0.0, 0.0, 0.0);
                                 if (StateFiles[k] != null) {
                                     Result.Objects[ObjectCount].States[k].Object = ObjectManager.LoadStaticObject(StateFiles[k], Encoding, LoadMode, false, ForceTextureRepeatX, ForceTextureRepeatY);
                                     Result.Objects[ObjectCount].States[k].Object.Dynamic = 1;
                                 } else {
                                     Result.Objects[ObjectCount].States[k].Object = null;
                                 }
                                 for (int j = 0; j < Result.Objects[ObjectCount].States.Length; j++) {
                                     Result.Objects[ObjectCount].States[j].Position = Position;
                                 }
                             }
                         } else {
                             Result.Objects[ObjectCount].States = new ObjectManager.AnimatedObjectState[] { };
                         }
                         ObjectCount++;
                     }
                     break;
                 default:
                     Interface.AddMessage(Interface.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;
 }
Example #4
0
        internal static ObjectManager.AnimatedObjectCollection ReadObject(string FileName, System.Text.Encoding Encoding, ObjectManager.ObjectLoadMode LoadMode, Vector3 Rotation)
        {
            XmlDocument currentXML = new XmlDocument();

            ObjectManager.AnimatedObjectCollection Result = new ObjectManager.AnimatedObjectCollection
            {
                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.Ordinal) != -1)
                    {
                        //Loksim parser tolerates multiple quotes, strict XML does not
                        Lines[i] = Lines[i].Replace("\"\"", "\"");
                    }
                    while (Lines[i].IndexOf("  ", StringComparison.Ordinal) != -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(Interface.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)
            {
                ObjectManager.UnifiedObject[] obj = new OpenBve.ObjectManager.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(Interface.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 = FunctionScripts.GetPostfixNotationFromInfixNotation(GetAnimatedFunction(attribute.Value, false));
                                                    break;

                                                case "HideOn":
                                                    //Defines when the object should be hidden
                                                    Object.FunctionScript = FunctionScripts.GetPostfixNotationFromInfixNotation(GetAnimatedFunction(attribute.Value, true));
                                                    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
                    ObjectManager.StaticObject staticObject = new ObjectManager.StaticObject
                    {
                        Mesh = new World.Mesh
                        {
                            Vertices  = new World.Vertex[0],
                            Faces     = new World.MeshFace[0],
                            Materials = new World.MeshMaterial[0]
                        }
                    };
                    for (int i = 0; i < CurrentObjects.Length; i++)
                    {
                        if (CurrentObjects[i] == null || string.IsNullOrEmpty(CurrentObjects[i].Name))
                        {
                            continue;
                        }
                        ObjectManager.StaticObject             Object         = null;
                        ObjectManager.AnimatedObjectCollection AnimatedObject = null;
                        try {
                            if (CurrentObjects[i].Name.ToLowerInvariant().EndsWith(".l3dgrp"))
                            {
                                AnimatedObject = ReadObject(CurrentObjects[i].Name, Encoding, LoadMode, CurrentObjects[i].Rotation);
                            }
                            else if (CurrentObjects[i].Name.ToLowerInvariant().EndsWith(".l3dobj"))
                            {
                                Object = Ls3DObjectParser.ReadObject(CurrentObjects[i].Name, LoadMode, 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(Interface.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 <ObjectManager.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();
                                ObjectManager.AnimatedObjectState aos = new ObjectManager.AnimatedObjectState
                                {
                                    Object   = Object,
                                    Position = CurrentObjects[i].Position,
                                };
                                a.States           = new ObjectManager.AnimatedObjectState[] { aos };
                                Result.Objects[aL] = a;
                                Result.Objects[aL].StateFunction =
                                    FunctionScripts.GetFunctionScriptFromPostfixNotation(CurrentObjects[i].FunctionScript + " 1 == --");
                            }
                            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 ObjectManager.AnimatedObjectState[0];
                                }
                            }
                        }
                    }
                    if (staticObject != null)
                    {
                        Array.Resize <ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length + 1);
                        ObjectManager.AnimatedObject      a   = new ObjectManager.AnimatedObject();
                        ObjectManager.AnimatedObjectState aos = new ObjectManager.AnimatedObjectState
                        {
                            Object = staticObject,
                        };
                        a.States = new ObjectManager.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);
        }
Example #5
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="LoadMode">The object load mode</param>
        /// <returns>A new animated object collection, containing the GruppenObject's meshes etc.</returns>
        internal static ObjectManager.AnimatedObjectCollection ReadObject(string FileName, System.Text.Encoding Encoding, ObjectManager.ObjectLoadMode LoadMode)
        {
            XmlDocument currentXML = new XmlDocument();

            //May need to be changed to use de-DE
            System.Globalization.CultureInfo       Culture = System.Globalization.CultureInfo.InvariantCulture;
            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");
                            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("\"\"") != -1)
                    {
                        //Loksim parser tolerates multiple quotes, strict XML does not
                        Lines[i] = Lines[i].Replace("\"\"", "\"");
                    }
                    while (Lines[i].IndexOf("  ") != -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(Interface.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)
            {
                ObjectManager.UnifiedObject[] obj = new OpenBve.ObjectManager.UnifiedObject[0];
                XmlNodeList DocumentNodes         = currentXML.DocumentElement.SelectNodes("/GRUPPENOBJECT");
                if (DocumentNodes != null)
                {
                    foreach (XmlNode outerNode in DocumentNodes)
                    {
                        if (outerNode.HasChildNodes)
                        {
                            foreach (XmlNode node in outerNode.ChildNodes)
                            {
                                if (node.Name == "Object" && node.HasChildNodes)
                                {
                                    foreach (XmlNode childNode in node.ChildNodes)
                                    {
                                        if (childNode.Name == "Props" && childNode.Attributes != null)
                                        {
                                            GruppenObject Object = new GruppenObject();
                                            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(Interface.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(';');

                                                    double.TryParse(SplitRotation[0], out Object.Rotation.X);
                                                    double.TryParse(SplitRotation[1], out Object.Rotation.Y);
                                                    double.TryParse(SplitRotation[2], out Object.Rotation.Z);
                                                    break;

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

                                                case "HideOn":
                                                    //Defines when the object should be hidden
                                                    Object.FunctionScript = FunctionScripts.GetPostfixNotationFromInfixNotation(GetAnimatedFunction(attribute.Value, true));
                                                    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
                    for (int i = 0; i < CurrentObjects.Length; i++)
                    {
                        if (CurrentObjects[i] == null || string.IsNullOrEmpty(CurrentObjects[i].Name))
                        {
                            continue;
                        }
                        var Object = (ObjectManager.StaticObject)ObjectManager.LoadObject(CurrentObjects[i].Name, Encoding, LoadMode, false, false, false, CurrentObjects[i].Rotation);
                        if (Object != null)
                        {
                            Array.Resize <ObjectManager.UnifiedObject>(ref obj, obj.Length + 1);
                            obj[obj.Length - 1] = Object;

                            Array.Resize <ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length + 1);
                            ObjectManager.AnimatedObject      a   = new ObjectManager.AnimatedObject();
                            ObjectManager.AnimatedObjectState aos = new ObjectManager.AnimatedObjectState
                            {
                                Object   = Object,
                                Position = CurrentObjects[i].Position,
                            };
                            a.States          = new ObjectManager.AnimatedObjectState[] { aos };
                            Result.Objects[i] = a;
                            if (!string.IsNullOrEmpty(CurrentObjects[i].FunctionScript))
                            {
                                Result.Objects[i].StateFunction =
                                    FunctionScripts.GetFunctionScriptFromPostfixNotation(CurrentObjects[i].FunctionScript + " 1 == --");
                            }
                        }
                    }
                }
                return(Result);
            }
            //Didn't find an acceptable XML object
            //Probably will cause things to throw an absolute wobbly somewhere....
            return(null);
        }
Example #6
0
        internal static ObjectManager.AnimatedObjectCollection ReadObject(string FileName, System.Text.Encoding Encoding,
                                                                          ObjectManager.ObjectLoadMode LoadMode)
        {
            XmlDocument currentXML = new XmlDocument();

            //May need to be changed to use de-DE
            System.Globalization.CultureInfo       Culture = System.Globalization.CultureInfo.InvariantCulture;
            ObjectManager.AnimatedObjectCollection Result  = new ObjectManager.AnimatedObjectCollection();
            Result.Objects = new ObjectManager.AnimatedObject[0];
            int ObjectCount = 0;

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

            GruppenObject[] CurrentObjects = new GruppenObject[0];
            //Check for null
            if (currentXML.DocumentElement != null)
            {
                ObjectManager.UnifiedObject[] obj = new OpenBve.ObjectManager.UnifiedObject[0];
                XmlNodeList DocumentNodes         = currentXML.DocumentElement.SelectNodes("/GRUPPENOBJECT");
                if (DocumentNodes != null)
                {
                    foreach (XmlNode outerNode in DocumentNodes)
                    {
                        if (outerNode.HasChildNodes)
                        {
                            foreach (XmlNode node in outerNode.ChildNodes)
                            {
                                if (node.Name == "Object" && node.HasChildNodes)
                                {
                                    foreach (XmlNode childNode in node.ChildNodes)
                                    {
                                        if (childNode.Name == "Props" && childNode.Attributes != null)
                                        {
                                            Array.Resize <GruppenObject>(ref CurrentObjects, CurrentObjects.Length + 1);
                                            GruppenObject Object = new GruppenObject();
                                            foreach (XmlAttribute attribute in childNode.Attributes)
                                            {
                                                switch (attribute.Name)
                                                {
                                                case "Name":
                                                    string ObjectFile = OpenBveApi.Path.CombineFile(BaseDir, attribute.Value);

                                                    Object.Name = ObjectFile;
                                                    ObjectCount++;
                                                    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(';');

                                                    double.TryParse(SplitRotation[0], out Object.RotationX);
                                                    double.TryParse(SplitRotation[1], out Object.RotationY);
                                                    double.TryParse(SplitRotation[2], out Object.RotationZ);
                                                    break;
                                                }
                                            }
                                            CurrentObjects[CurrentObjects.Length - 1] = Object;
                                        }
                                    }
                                }
                            }
                        }
                    }

                    //We've loaded the XML references, now load the objects into memory
                    for (int i = 0; i < CurrentObjects.Length; i++)
                    {
                        var Object = ObjectManager.LoadObject(CurrentObjects[i].Name, Encoding, LoadMode, false, false, false, CurrentObjects[i].RotationX, CurrentObjects[i].RotationY, CurrentObjects[i].RotationZ);
                        if (Object != null)
                        {
                            Array.Resize <ObjectManager.UnifiedObject>(ref obj, obj.Length + 1);
                            obj[obj.Length - 1] = Object;
                        }
                    }
                    for (int j = 0; j < obj.Length; j++)
                    {
                        if (obj[j] != null)
                        {
                            Array.Resize <ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length + 1);
                            if (obj[j] is ObjectManager.StaticObject)
                            {
                                ObjectManager.StaticObject s = (ObjectManager.StaticObject)obj[j];
                                s.Dynamic = true;
                                ObjectManager.AnimatedObject      a   = new ObjectManager.AnimatedObject();
                                ObjectManager.AnimatedObjectState aos = new ObjectManager.AnimatedObjectState
                                {
                                    Object   = s,
                                    Position = CurrentObjects[j].Position
                                };
                                a.States          = new ObjectManager.AnimatedObjectState[] { aos };
                                Result.Objects[j] = 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++)
                                {
                                    for (int h = 0; h < a.Objects[k].States.Length; h++)
                                    {
                                        a.Objects[k].States[h].Position.X += CurrentObjects[j].Position.X;
                                        a.Objects[k].States[h].Position.Y += CurrentObjects[j].Position.Y;
                                        a.Objects[k].States[h].Position.Z += CurrentObjects[j].Position.Z;
                                    }
                                    Result.Objects[j] = a.Objects[k];
                                    ObjectCount++;
                                }
                            }
                        }
                    }
                }
                return(Result);
            }
            //Didn't find an acceptable XML object
            //Probably will cause things to throw an absolute wobbly somewhere....
            return(null);
        }
Example #7
0
        internal static ObjectManager.AnimatedObjectCollection ReadObject(string FileName, System.Text.Encoding Encoding,
            ObjectManager.ObjectLoadMode LoadMode)
        {
            XmlDocument currentXML = new XmlDocument();
            //May need to be changed to use de-DE
            System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture;
            ObjectManager.AnimatedObjectCollection Result = new ObjectManager.AnimatedObjectCollection();
            Result.Objects = new ObjectManager.AnimatedObject[0];
            int ObjectCount = 0;
            currentXML.Load(FileName);
            string BaseDir = System.IO.Path.GetDirectoryName(FileName);

            GruppenObject[] CurrentObjects = new GruppenObject[0];
            //Check for null
            if (currentXML.DocumentElement != null)
            {
                ObjectManager.UnifiedObject[] obj = new OpenBve.ObjectManager.UnifiedObject[0];
                XmlNodeList DocumentNodes = currentXML.DocumentElement.SelectNodes("/GRUPPENOBJECT");
                if (DocumentNodes != null)
                {
                    foreach (XmlNode outerNode in DocumentNodes)
                    {
                        if (outerNode.HasChildNodes)
                        {
                            foreach (XmlNode node in outerNode.ChildNodes)
                            {
                                if (node.Name == "Object" && node.HasChildNodes)
                                {
                                    foreach (XmlNode childNode in node.ChildNodes)
                                    {
                                        if (childNode.Name == "Props" && childNode.Attributes != null)
                                        {
                                            Array.Resize<GruppenObject>(ref CurrentObjects, CurrentObjects.Length + 1);
                                            GruppenObject Object = new GruppenObject();
                                            foreach (XmlAttribute attribute in childNode.Attributes)
                                            {
                                                switch (attribute.Name)
                                                {
                                                    case "Name":
                                                        string ObjectFile = OpenBveApi.Path.CombineFile(BaseDir,attribute.Value);
                                                        
                                                        Object.Name = ObjectFile;
                                                        ObjectCount++;
                                                        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(';');

                                                        double.TryParse(SplitRotation[0], out Object.RotationX);
                                                        double.TryParse(SplitRotation[1], out Object.RotationY);
                                                        double.TryParse(SplitRotation[2], out Object.RotationZ);
                                                        break;
                                                }
                                            }
                                            CurrentObjects[CurrentObjects.Length - 1] = Object;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    
                    //We've loaded the XML references, now load the objects into memory
                    for (int i = 0; i < CurrentObjects.Length; i++)
                    {
                        var Object = ObjectManager.LoadObject(CurrentObjects[i].Name, Encoding, LoadMode, false, false, false, CurrentObjects[i].RotationX, CurrentObjects[i].RotationY, CurrentObjects[i].RotationZ);
                        if (Object != null)
                        {
                            Array.Resize<ObjectManager.UnifiedObject>(ref obj, obj.Length +1);
                            obj[obj.Length - 1] = Object;
                        }
                    }
                    for (int j = 0; j < obj.Length; j++)
                    {
                        if (obj[j] != null)
                        {
                            Array.Resize<ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length + 1);
                            if (obj[j] is ObjectManager.StaticObject)
                            {
                                ObjectManager.StaticObject s = (ObjectManager.StaticObject) obj[j];
                                s.Dynamic = true;
                                ObjectManager.AnimatedObject a = new ObjectManager.AnimatedObject();
                                ObjectManager.AnimatedObjectState aos = new ObjectManager.AnimatedObjectState
                                {
                                    Object = s,
                                    Position = CurrentObjects[j].Position
                                };
                                a.States = new ObjectManager.AnimatedObjectState[] {aos};
                                Result.Objects[j] = 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++)
                                {
                                    for (int h = 0; h < a.Objects[k].States.Length; h++)
                                    {
                                        a.Objects[k].States[h].Position.X += CurrentObjects[j].Position.X;
                                        a.Objects[k].States[h].Position.Y += CurrentObjects[j].Position.Y;
                                        a.Objects[k].States[h].Position.Z += CurrentObjects[j].Position.Z;
                                    }
                                    Result.Objects[j] = a.Objects[k];
                                    ObjectCount++;
                                }
                            }
                        }
                    }
                }
                return Result;
            }
            //Didn't find an acceptable XML object
            //Probably will cause things to throw an absolute wobbly somewhere....
            return null;
        }