/// <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 }); }
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); }
/// <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; }
/* * /// <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); }
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; }
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); }
/// <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); }
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); }