예제 #1
0
        /// <summary>
        /// Called by the framework when importing a game asset. This is the method called by XNA when
        /// an asset is to be imported into an object that can be recognized by the Content Pipeline.
        /// </summary>
        /// <param name="filename">Name of a game asset file.</param>
        /// <param name="context">
        /// Contains information for importing a game asset, such as a logger interface.
        /// </param>
        /// <returns>Resulting game asset.</returns>
        public override NodeContent Import(string filename, ContentImporterContext context)
        {
            var wrappedContext   = new ContentPipelineContext(context);
            var identity         = new ContentIdentity(filename);
            var modelDescription = ModelDescription.Load(filename, wrappedContext, false);

            if (modelDescription == null)
            {
                throw new InvalidContentException("Error loading model description.", identity);
            }

            return(new DeferredNodeContent(modelDescription)
            {
                Identity = identity
            });
        }
예제 #2
0
        public static void Split(AnimationContentDictionary animationDictionary, string splitFile, ContentIdentity contentIdentity, ContentProcessorContext context)
        {
            if (animationDictionary == null)
            return;

              if (string.IsNullOrEmpty(splitFile))
            return;

              if (contentIdentity == null)
            throw new ArgumentNullException("contentIdentity");

              if (context == null)
            throw new ArgumentNullException("context");

              if (animationDictionary.Count == 0)
              {
            context.Logger.LogWarning(null, contentIdentity, "The model does not have an animation. Animation splitting is skipped.");
            return;
              }

              if (animationDictionary.Count > 1)
            context.Logger.LogWarning(null, contentIdentity, "The model contains more than 1 animation. The animation splitting is performed on the first animation. Other animations are deleted!");

              // Load XML file.
              splitFile = ContentHelper.FindFile(splitFile, contentIdentity);
              XDocument document = XDocument.Load(splitFile, LoadOptions.SetLineInfo);

              // Let the content pipeline know that we depend on this file and we need to
              // rebuild the content if the file is modified.
              context.AddDependency(splitFile);

              // Parse XML.
              var animationsElement = document.Element("Animations");
              if (animationsElement == null)
              {
            context.Logger.LogWarning(null, contentIdentity, "The animation split file \"{0}\" does not contain an <Animations> root node.", splitFile);
            return;
              }

              var wrappedContext = new ContentPipelineContext(context);
              var splits = ParseAnimationSplitDefinitions(animationsElement, contentIdentity, wrappedContext);
              if (splits == null || splits.Count == 0)
              {
            context.Logger.LogWarning(null, contentIdentity, "The XML file with the animation split definitions is invalid or empty. Animation is not split.");
            return;
              }

              // Split animations.
              Split(animationDictionary, splits, contentIdentity, context);
        }
예제 #3
0
        /// <summary>
        /// Parses the animation split definitions defined by the specified XML element.
        /// </summary>
        /// <param name="animationsElement">The XML element that defines the animation splits.</param>
        /// <param name="contentIdentity">The content identity.</param>
        /// <param name="context">The context.</param>
        /// <returns>The list of animation split definitions.</returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="animationsElement"/>, <paramref name="contentIdentity"/>, or 
        /// <paramref name="context"/> is <see langword="null"/>.
        /// </exception>
        internal static List<AnimationSplitDefinition> ParseAnimationSplitDefinitions(XElement animationsElement, ContentIdentity contentIdentity, ContentPipelineContext context)
        {
            if (animationsElement == null)
            throw new ArgumentNullException("animationsElement");
              if (contentIdentity == null)
            throw new ArgumentNullException("contentIdentity");
              if (context == null)
            throw new ArgumentNullException("context");

              // The frame rate needs to be set, when the splits are defined in frames.
              double? framerate = (double?)animationsElement.Attribute("Framerate");

              // Read all animation splits.
              var splits = new List<AnimationSplitDefinition>();
              foreach (var animationElement in animationsElement.Elements("Animation"))
              {
            var name = animationElement.GetMandatoryAttribute("Name", contentIdentity);

            double? startTime = (double?)animationElement.Attribute("StartTime");
            double? endTime = (double?)animationElement.Attribute("EndTime");

            int? startFrame = (int?)animationElement.Attribute("StartFrame");
            int? endFrame = (int?)animationElement.Attribute("EndFrame");

            bool? addLoopFrame = (bool?)animationsElement.Attribute("AddLoopFrame");

            if (startTime == null && startFrame == null)
            {
              string message = XmlHelper.GetExceptionMessage(animationElement, "The animation element does not contain a valid \"StartTime\" or \"StartFrame\" attribute.");
              throw new InvalidContentException(message, contentIdentity);
            }

            if (endTime == null && endFrame == null)
            {
              string message = XmlHelper.GetExceptionMessage(animationElement, "The animation element does not contain a valid \"EndTime\" or \"EndFrame\" attribute.");
              throw new InvalidContentException(message, contentIdentity);
            }

            if (framerate == null && (startTime == null || endTime == null))
            {
              string message = XmlHelper.GetExceptionMessage(animationsElement, "The animations element must have a <Framerate> element if start and end are specified in frames.");
              throw new InvalidContentException(message, contentIdentity);
            }

            startTime = startTime ?? startFrame.Value / framerate.Value;
            endTime = endTime ?? endFrame.Value / framerate.Value;
            TimeSpan start = new TimeSpan((long)(startTime.Value * TimeSpan.TicksPerSecond));
            TimeSpan end = new TimeSpan((long)(endTime.Value * TimeSpan.TicksPerSecond));

            if (start > end)
            {
              string message = XmlHelper.GetExceptionMessage(animationElement, "Invalid animation element: The start time is larger than the end time.");
              throw new InvalidContentException(message, contentIdentity);
            }

            splits.Add(new AnimationSplitDefinition { Name = name, StartTime = start, EndTime = end, AddLoopFrame = addLoopFrame });
              }

              return splits;
        }
예제 #4
0
        /*
         * /// <summary>
         * /// Gets or sets the value of the <strong>X Axis Rotation</strong> processor parameter.
         * /// </summary>
         * /// <value>The amount of rotation, in degrees, around the x-axis.</value>
         * [DefaultValue(0f)]
         * [DisplayName("X Axis Rotation")]
         * [Description("Rotates the model a specified number of degrees around the x-axis.")]
         * public virtual float RotationX
         * {
         * get { return _rotationX; }
         * set { _rotationX = value; }
         * }
         * private float _rotationX;
         *
         *
         * /// <summary>
         * /// Gets or sets the value of the <strong>Y Axis Rotation</strong> processor parameter.
         * /// </summary>
         * /// <value>The amount of rotation, in degrees, around the y-axis.</value>
         * [DefaultValue(0f)]
         * [DisplayName("Y Axis Rotation")]
         * [Description("Rotates the model a specified number of degrees around the y-axis.")]
         * public virtual float RotationY
         * {
         * get { return _rotationY; }
         * set { _rotationY = value; }
         * }
         * private float _rotationY;
         *
         *
         * /// <summary>
         * /// Gets or sets the value of the <strong>Z Axis Rotation</strong> processor parameter.
         * /// </summary>
         * /// <value>The amount of rotation, in degrees, around the z-axis.</value>
         * [DefaultValue(0f)]
         * [DisplayName("Z Axis Rotation")]
         * [Description("Rotates the model a specified number of degrees around the z-axis.")]
         * public virtual float RotationZ
         * {
         * get { return _rotationZ; }
         * set { _rotationZ = value; }
         * }
         * private float _rotationZ;
         *
         *
         * /// <summary>
         * /// Gets or sets the value of the <strong>Scale</strong> processor parameter.
         * /// </summary>
         * /// <value>The scaling factor to be applied.</value>
         * [DefaultValue(1f)]
         * [DisplayName("Scale")]
         * [Description("Scales the model uniformly along all three axes.")]
         * public virtual float Scale
         * {
         * get { return _scale; }
         * set { _scale = value; }
         * }
         * private float _scale = 1f;
         */
        #endregion


        //--------------------------------------------------------------
        #region Methods
        //--------------------------------------------------------------

        /// <summary>
        /// Converts mesh content to model content.
        /// </summary>
        /// <param name="input">The root node content.</param>
        /// <param name="context">Contains any required custom process parameters.</param>
        /// <returns>The model content.</returns>
        public override DRModelNodeContent Process(NodeContent input, ContentProcessorContext context)
        {
            if (input == null)
            {
                throw new ArgumentNullException("input");
            }
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            // The content processor may write text files. We want to use invariant culture number formats.
            // TODO: Do not set Thread.CurrentThread.CurrentCulture. Make sure that all read/write operations explicitly use InvariantCulture.
            var originalCulture = Thread.CurrentThread.CurrentCulture;

            Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;

            try
            {
                // Uncomment this to launch and attach a debugger.
                //System.Diagnostics.Debugger.Launch();

                // Uncomment this to visualize the content tree.
                //ContentHelper.PrintContentTree(input, context);

                _context = context;

                var delayedNode = input as DeferredNodeContent;
                if (delayedNode != null)
                {
                    // Model description was imported.
                    _modelDescription = delayedNode.ModelDescription;

                    // Load the model.
                    delayedNode.Import(context);
                    _input = input;
                }
                else
                {
                    // The model was imported.
                    _input = input;

                    // Load the model description.
                    var wrappedContext = new ContentPipelineContext(context);
                    if (input.Identity != null && input.Identity.SourceFilename != null)
                    {
                        _modelDescription = ModelDescription.Load(input.Identity.SourceFilename, wrappedContext, CreateMissingModelDescription);
                    }
                }

                if (_modelDescription != null)
                {
                    _modelDescription.Validate(_input, _context);
                }

                ValidateInput();

                // Try to find skeleton root bone.
                _rootBone = MeshHelper.FindSkeleton(input);
                if (_rootBone != null)
                {
#if ANIMATION
                    MergeAnimationFiles();
#endif
                    BakeTransforms(input);
                    TransformModel();
#if ANIMATION
                    BuildSkeleton();
                    BuildAnimations();
#endif
                    SetSkinnedMaterial();
                }
                else
                {
                    TransformModel();
                }

                BuildSceneGraph();
                PrepareMaterials();
                BuildMeshes();
                BuildOccluders();
                CombineLodGroups();
                ValidateOutput();

                _model.Name = Path.GetFileNameWithoutExtension(context.OutputFilename);
            }
            finally
            {
                // Clean up.
                Thread.CurrentThread.CurrentCulture = originalCulture;
            }

            return(_model);
        }
예제 #5
0
        public static ModelDescription Load(string sourceFileName, ContentPipelineContext context, bool createIfMissing)
        {
            if (sourceFileName == null)
            throw new ArgumentNullException("sourceFileName");
              if (sourceFileName.Length == 0)
            throw new ArgumentException("File name must not be empty.", "sourceFileName");
              if (context == null)
            throw new ArgumentNullException("context");

              string fileName = Path.ChangeExtension(sourceFileName, "drmdl");
              if (!File.Exists(fileName))
              {
            // Also try with extension xml, which was used before drmdl.
            fileName = Path.ChangeExtension(sourceFileName, "xml");
            if (!IsModelDescriptionFile(fileName))
            {
              fileName = Path.ChangeExtension(sourceFileName, "drmdl");

              if (!createIfMissing)
              {
            context.Logger.LogImportantMessage(
              "The model description file \"{0}\" is missing. Using default settings.",
              Path.GetFileName(fileName));

            return null;
              }

              // Create default model description file.
              try
              {
            using (var stream = File.CreateText(fileName))
            {
              stream.Write(Properties.Resources.DefaultModelDescription, Path.GetFileName(sourceFileName));
            }
              }
              catch (Exception exception)
              {
            context.Logger.LogImportantMessage(
              "Automatic creation of model description \"{0}\" failed. Using default settings.\nException: {1}",
              fileName, exception.ToString());

            return null;
              }
            }
              }

              context.AddDependency(fileName);

              var modelDescription = new ModelDescription { Identity = new ContentIdentity(fileName) };

              XDocument document;
              try
              {
            document = XDocument.Load(fileName, LoadOptions.SetLineInfo);
              }
              catch (Exception exception)
              {
            string message = string.Format(CultureInfo.InvariantCulture, "Could not load '{0}': {1}", fileName, exception.Message);
            throw new InvalidContentException(message, modelDescription.Identity);
              }

              var modelElement = document.Root;
              if (modelElement == null || modelElement.Name != "Model")
              {
            string message = string.Format(CultureInfo.InvariantCulture, "Root element \"<Model>\" is missing in XML.");
            throw new InvalidContentException(message, modelDescription.Identity);
              }

              // Model attributes.
              modelDescription.Name = (string)modelElement.Attribute("Name") ?? Path.GetFileNameWithoutExtension(fileName);
              modelDescription.FileName = (string)modelElement.Attribute("File") ?? (string)modelElement.Attribute("FileName");
              modelDescription.Importer = (string)modelElement.Attribute("Importer");
              modelDescription.RotationX = (float?)modelElement.Attribute("RotationX") ?? 0.0f;
              modelDescription.RotationY = (float?)modelElement.Attribute("RotationY") ?? 0.0f;
              modelDescription.RotationZ = (float?)modelElement.Attribute("RotationZ") ?? 0.0f;
              modelDescription.Scale = (float?)modelElement.Attribute("Scale") ?? 1.0f;
              modelDescription.GenerateTangentFrames = (bool?)modelElement.Attribute("GenerateTangentFrames") ?? false;
              modelDescription.SwapWindingOrder = (bool?)modelElement.Attribute("SwapWindingOrder") ?? false;
              modelDescription.PremultiplyVertexColors = (bool?)modelElement.Attribute("PremultiplyVertexColors") ?? true;
              modelDescription.MaxDistance = (float?)modelElement.Attribute("MaxDistance") ?? 0.0f;

              var aabbMinimumAttribute = modelElement.Attribute("AabbMinimum");
              var aabbMaximumAttribute = modelElement.Attribute("AabbMaximum");
              if (aabbMinimumAttribute != null && aabbMaximumAttribute != null)
              {
            modelDescription.AabbEnabled = true;
            modelDescription.AabbMinimum = aabbMinimumAttribute.ToVector3(Vector3.Zero, modelDescription.Identity);
            modelDescription.AabbMaximum = aabbMaximumAttribute.ToVector3(Vector3.One, modelDescription.Identity);
              }

              // Mesh elements.
              modelDescription.Meshes = new List<MeshDescription>();
              foreach (var meshElement in modelElement.Elements("Mesh"))
              {
            var meshDescription = new MeshDescription();
            meshDescription.Name = (string)meshElement.Attribute("Name") ?? string.Empty;
            meshDescription.GenerateTangentFrames = (bool?)meshElement.Attribute("GenerateTangentFrames") ?? modelDescription.GenerateTangentFrames;
            meshDescription.MaxDistance = (float?)meshElement.Attribute("MaxDistance") ?? modelDescription.MaxDistance;
            meshDescription.LodDistance = (float?)meshElement.Attribute("LodDistance") ?? 0.0f;

            meshDescription.Submeshes = new List<SubmeshDescription>();
            foreach (var submeshElement in meshElement.Elements("Submesh"))
            {
              var submeshDescription = new SubmeshDescription();
              submeshDescription.GenerateTangentFrames = (bool?)meshElement.Attribute("GenerateTangentFrames") ?? meshDescription.GenerateTangentFrames;
              submeshDescription.Material = (string)submeshElement.Attribute("Material");

              meshDescription.Submeshes.Add(submeshDescription);
            }

            modelDescription.Meshes.Add(meshDescription);
              }

              // Animations element.
              var animationsElement = modelElement.Element("Animations");
              if (animationsElement != null)
              {
            var animationDescription = new AnimationDescription();
            animationDescription.MergeFiles = (string)animationsElement.Attribute("MergeFiles");
            animationDescription.Splits = AnimationSplitter.ParseAnimationSplitDefinitions(animationsElement, modelDescription.Identity, context);
            animationDescription.ScaleCompression = (float?)animationsElement.Attribute("ScaleCompression") ?? -1;
            animationDescription.RotationCompression = (float?)animationsElement.Attribute("RotationCompression") ?? -1;
            animationDescription.TranslationCompression = (float?)animationsElement.Attribute("TranslationCompression") ?? -1;
            animationDescription.AddLoopFrame = (bool?)animationsElement.Attribute("AddLoopFrame");

            modelDescription.Animation = animationDescription;
              }

              return modelDescription;
        }
예제 #6
0
        public static void Split(AnimationContentDictionary animationDictionary, string splitFile, ContentIdentity contentIdentity, ContentProcessorContext context)
        {
            if (animationDictionary == null)
            {
                return;
            }

            if (string.IsNullOrEmpty(splitFile))
            {
                return;
            }

            if (contentIdentity == null)
            {
                throw new ArgumentNullException("contentIdentity");
            }

            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            if (animationDictionary.Count == 0)
            {
                context.Logger.LogWarning(null, contentIdentity, "The model does not have an animation. Animation splitting is skipped.");
                return;
            }

            if (animationDictionary.Count > 1)
            {
                context.Logger.LogWarning(null, contentIdentity, "The model contains more than 1 animation. The animation splitting is performed on the first animation. Other animations are deleted!");
            }

            // Load XML file.
            splitFile = ContentHelper.FindFile(splitFile, contentIdentity);
            XDocument document = XDocument.Load(splitFile, LoadOptions.SetLineInfo);

            // Let the content pipeline know that we depend on this file and we need to
            // rebuild the content if the file is modified.
            context.AddDependency(splitFile);

            // Parse XML.
            var animationsElement = document.Element("Animations");

            if (animationsElement == null)
            {
                context.Logger.LogWarning(null, contentIdentity, "The animation split file \"{0}\" does not contain an <Animations> root node.", splitFile);
                return;
            }

            var wrappedContext = new ContentPipelineContext(context);
            var splits         = ParseAnimationSplitDefinitions(animationsElement, contentIdentity, wrappedContext);

            if (splits == null || splits.Count == 0)
            {
                context.Logger.LogWarning(null, contentIdentity, "The XML file with the animation split definitions is invalid or empty. Animation is not split.");
                return;
            }

            // Split animations.
            Split(animationDictionary, splits, contentIdentity, context);
        }
예제 #7
0
        /// <summary>
        /// Parses the animation split definitions defined by the specified XML element.
        /// </summary>
        /// <param name="animationsElement">The XML element that defines the animation splits.</param>
        /// <param name="contentIdentity">The content identity.</param>
        /// <param name="context">The context.</param>
        /// <returns>The list of animation split definitions.</returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="animationsElement"/>, <paramref name="contentIdentity"/>, or
        /// <paramref name="context"/> is <see langword="null"/>.
        /// </exception>
        internal static List <AnimationSplitDefinition> ParseAnimationSplitDefinitions(XElement animationsElement, ContentIdentity contentIdentity, ContentPipelineContext context)
        {
            if (animationsElement == null)
            {
                throw new ArgumentNullException("animationsElement");
            }
            if (contentIdentity == null)
            {
                throw new ArgumentNullException("contentIdentity");
            }
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            // The frame rate needs to be set, when the splits are defined in frames.
            double?framerate = (double?)animationsElement.Attribute("Framerate");

            // Read all animation splits.
            var splits = new List <AnimationSplitDefinition>();

            foreach (var animationElement in animationsElement.Elements("Animation"))
            {
                var name = animationElement.GetMandatoryAttribute("Name", contentIdentity);

                double?startTime = (double?)animationElement.Attribute("StartTime");
                double?endTime   = (double?)animationElement.Attribute("EndTime");

                int?startFrame = (int?)animationElement.Attribute("StartFrame");
                int?endFrame   = (int?)animationElement.Attribute("EndFrame");

                bool?addLoopFrame = (bool?)animationsElement.Attribute("AddLoopFrame");

                if (startTime == null && startFrame == null)
                {
                    string message = XmlHelper.GetExceptionMessage(animationElement, "The animation element does not contain a valid \"StartTime\" or \"StartFrame\" attribute.");
                    throw new InvalidContentException(message, contentIdentity);
                }

                if (endTime == null && endFrame == null)
                {
                    string message = XmlHelper.GetExceptionMessage(animationElement, "The animation element does not contain a valid \"EndTime\" or \"EndFrame\" attribute.");
                    throw new InvalidContentException(message, contentIdentity);
                }

                if (framerate == null && (startTime == null || endTime == null))
                {
                    string message = XmlHelper.GetExceptionMessage(animationsElement, "The animations element must have a <Framerate> element if start and end are specified in frames.");
                    throw new InvalidContentException(message, contentIdentity);
                }

                startTime = startTime ?? startFrame.Value / framerate.Value;
                endTime   = endTime ?? endFrame.Value / framerate.Value;
                TimeSpan start = new TimeSpan((long)(startTime.Value * TimeSpan.TicksPerSecond));
                TimeSpan end   = new TimeSpan((long)(endTime.Value * TimeSpan.TicksPerSecond));

                if (start > end)
                {
                    string message = XmlHelper.GetExceptionMessage(animationElement, "Invalid animation element: The start time is larger than the end time.");
                    throw new InvalidContentException(message, contentIdentity);
                }

                splits.Add(new AnimationSplitDefinition {
                    Name = name, StartTime = start, EndTime = end, AddLoopFrame = addLoopFrame
                });
            }

            return(splits);
        }
예제 #8
0
        public static ModelDescription Load(string sourceFileName, ContentPipelineContext context, bool createIfMissing)
        {
            if (sourceFileName == null)
            {
                throw new ArgumentNullException("sourceFileName");
            }
            if (sourceFileName.Length == 0)
            {
                throw new ArgumentException("File name must not be empty.", "sourceFileName");
            }
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            string fileName = Path.ChangeExtension(sourceFileName, "drmdl");

            if (!File.Exists(fileName))
            {
                // Also try with extension xml, which was used before drmdl.
                fileName = Path.ChangeExtension(sourceFileName, "xml");
                if (!IsModelDescriptionFile(fileName))
                {
                    fileName = Path.ChangeExtension(sourceFileName, "drmdl");

                    if (!createIfMissing)
                    {
                        context.Logger.LogImportantMessage(
                            "The model description file \"{0}\" is missing. Using default settings.",
                            Path.GetFileName(fileName));

                        return(null);
                    }

                    // Create default model description file.
                    try
                    {
                        using (var stream = File.CreateText(fileName))
                        {
                            stream.Write(Properties.Resources.DefaultModelDescription, Path.GetFileName(sourceFileName));
                        }
                    }
                    catch (Exception exception)
                    {
                        context.Logger.LogImportantMessage(
                            "Automatic creation of model description \"{0}\" failed. Using default settings.\nException: {1}",
                            fileName, exception.ToString());

                        return(null);
                    }
                }
            }

            context.AddDependency(fileName);

            var modelDescription = new ModelDescription {
                Identity = new ContentIdentity(fileName)
            };

            XDocument document;

            try
            {
                document = XDocument.Load(fileName, LoadOptions.SetLineInfo);
            }
            catch (Exception exception)
            {
                string message = string.Format(CultureInfo.InvariantCulture, "Could not load '{0}': {1}", fileName, exception.Message);
                throw new InvalidContentException(message, modelDescription.Identity);
            }

            var modelElement = document.Root;

            if (modelElement == null || modelElement.Name != "Model")
            {
                string message = string.Format(CultureInfo.InvariantCulture, "Root element \"<Model>\" is missing in XML.");
                throw new InvalidContentException(message, modelDescription.Identity);
            }

            // Model attributes.
            modelDescription.Name                    = (string)modelElement.Attribute("Name") ?? Path.GetFileNameWithoutExtension(fileName);
            modelDescription.FileName                = (string)modelElement.Attribute("File") ?? (string)modelElement.Attribute("FileName");
            modelDescription.Importer                = (string)modelElement.Attribute("Importer");
            modelDescription.RotationX               = (float?)modelElement.Attribute("RotationX") ?? 0.0f;
            modelDescription.RotationY               = (float?)modelElement.Attribute("RotationY") ?? 0.0f;
            modelDescription.RotationZ               = (float?)modelElement.Attribute("RotationZ") ?? 0.0f;
            modelDescription.Scale                   = (float?)modelElement.Attribute("Scale") ?? 1.0f;
            modelDescription.GenerateTangentFrames   = (bool?)modelElement.Attribute("GenerateTangentFrames") ?? false;
            modelDescription.SwapWindingOrder        = (bool?)modelElement.Attribute("SwapWindingOrder") ?? false;
            modelDescription.PremultiplyVertexColors = (bool?)modelElement.Attribute("PremultiplyVertexColors") ?? true;
            modelDescription.MaxDistance             = (float?)modelElement.Attribute("MaxDistance") ?? 0.0f;

            var aabbMinimumAttribute = modelElement.Attribute("AabbMinimum");
            var aabbMaximumAttribute = modelElement.Attribute("AabbMaximum");

            if (aabbMinimumAttribute != null && aabbMaximumAttribute != null)
            {
                modelDescription.AabbEnabled = true;
                modelDescription.AabbMinimum = aabbMinimumAttribute.ToVector3(Vector3.Zero, modelDescription.Identity);
                modelDescription.AabbMaximum = aabbMaximumAttribute.ToVector3(Vector3.One, modelDescription.Identity);
            }

            // Mesh elements.
            modelDescription.Meshes = new List <MeshDescription>();
            foreach (var meshElement in modelElement.Elements("Mesh"))
            {
                var meshDescription = new MeshDescription();
                meshDescription.Name = (string)meshElement.Attribute("Name") ?? string.Empty;
                meshDescription.GenerateTangentFrames = (bool?)meshElement.Attribute("GenerateTangentFrames") ?? modelDescription.GenerateTangentFrames;
                meshDescription.MaxDistance           = (float?)meshElement.Attribute("MaxDistance") ?? modelDescription.MaxDistance;
                meshDescription.LodDistance           = (float?)meshElement.Attribute("LodDistance") ?? 0.0f;

                meshDescription.Submeshes = new List <SubmeshDescription>();
                foreach (var submeshElement in meshElement.Elements("Submesh"))
                {
                    var submeshDescription = new SubmeshDescription();
                    submeshDescription.GenerateTangentFrames = (bool?)meshElement.Attribute("GenerateTangentFrames") ?? meshDescription.GenerateTangentFrames;
                    submeshDescription.Material = (string)submeshElement.Attribute("Material");

                    meshDescription.Submeshes.Add(submeshDescription);
                }

                modelDescription.Meshes.Add(meshDescription);
            }

            // Animations element.
            var animationsElement = modelElement.Element("Animations");

            if (animationsElement != null)
            {
                var animationDescription = new AnimationDescription();
                animationDescription.MergeFiles             = (string)animationsElement.Attribute("MergeFiles");
                animationDescription.Splits                 = AnimationSplitter.ParseAnimationSplitDefinitions(animationsElement, modelDescription.Identity, context);
                animationDescription.ScaleCompression       = (float?)animationsElement.Attribute("ScaleCompression") ?? -1;
                animationDescription.RotationCompression    = (float?)animationsElement.Attribute("RotationCompression") ?? -1;
                animationDescription.TranslationCompression = (float?)animationsElement.Attribute("TranslationCompression") ?? -1;
                animationDescription.AddLoopFrame           = (bool?)animationsElement.Attribute("AddLoopFrame");

                modelDescription.Animation = animationDescription;
            }

            return(modelDescription);
        }