/* * Material.cs * Authors: August Zinsser * Benjamin Nitschke (based on his tutorial project SkinningWithColladaModelsInXna at abi.exdream.com) * * Copyright August Zinsser 2007 * This program is distributed under the terms of the GNU General Public License */ using System; using System.Collections.Generic; using System.Text; using System.Xml; using System.Globalization; using Microsoft.Xna.Framework.Graphics; namespace Pina3D { /// /// Material class for Xna materials used for ColladaModels. Consists of standard surface properies like ambient color, /// diffuse texture, normal maps, etc. Shaders use these properties when rendering ColladaModels. /// public class Material : IDisposable { public static readonly Color Invisible = new Color(0, 0, 0, 0); protected string mName = ""; // Name of this material (only used for ColladaModel/Renderable) protected Color mDiffuseColor = Invisible; // Shader parameter protected Color mAmbientColor = Invisible; // '' protected Color mSpecularColor = Invisible; // '' protected Color mIncandescenceColor = Invisible; // '' protected float mSpecularPower = 0f; // '' protected Texture mDiffuseMap = null; // Diffuse map texture protected Texture mNormalMap = null; // '' (Normal) protected Texture mDiffuseMap1 = null; // (Optional) additional diffuse map protected Texture mDiffuseMap2 = null; // '' protected Texture mDiffuseMap3 = null; // '' protected bool mDiffuseMapEnabled = true; // True if the material should render with its Diffuse Map (if the map is not null) protected bool mNormalMapEnabled = true; // '' (Normal) /// /// Outputs the internal settings of this material (for debugging purposes) /// /// public string UsageSettings { get { // Output enabled settings string retStr = "Enabled Maps: "; if (mDiffuseMapEnabled) retStr += "Diffuse - "; if (mNormalMapEnabled) retStr += "Normal - "; // Remove the last " - " retStr = retStr.Substring(0, retStr.Length - 3); // Output null-ness of associated data retStr += "\nData Loaded: "; if (mDiffuseMap != null) retStr += "DiffuseMap - "; if (mDiffuseMap1 != null) retStr += "DiffuseMap1 - "; if (mDiffuseMap2 != null) retStr += "DiffuseMap2 - "; if (mDiffuseMap3 != null) retStr += "DiffuseMap3 - "; if (mNormalMap != null) retStr += "NormalMap: - "; // Remove the last " - " retStr = retStr.Substring(0, retStr.Length - 3); return retStr; } } public float SpecularPower { set { mSpecularPower = value; } get { return mSpecularPower; } } public Color AmbientColor { set { mAmbientColor = value; } get { return mAmbientColor; } } public Color DiffuseColor { set { mDiffuseColor = value; } get { return mDiffuseColor; } } public Color SpecularColor { set { mSpecularColor = value; } get { return mSpecularColor; } } public Color IncandescenceColor { set { mIncandescenceColor = value; } get { return mIncandescenceColor; } } /// /// The standard diffuse map /// public Texture DiffuseMap { set { mDiffuseMap = value; } get { return mDiffuseMap; } } public Texture NormalMap { set { mNormalMap = value; } get { return mNormalMap; } } /// /// An extra diffuse map to use for multi-textured materials /// public Texture DiffuseMap1 { set { mDiffuseMap1 = value; } get { return mDiffuseMap1; } } /// /// An extra diffuse map to use for multi-textured materials /// public Texture DiffuseMap2 { set { mDiffuseMap2 = value; } get { return mDiffuseMap2; } } /// /// An extra diffuse map to use for multi-textured materials /// public Texture DiffuseMap3 { set { mDiffuseMap3 = value; } get { return mDiffuseMap3; } } /// /// True if the diffuse map is both enabled and not null /// public bool UseDiffuseMap { get { return mDiffuseMapEnabled && mDiffuseMap != null; } } /// /// True if the normal map is both enabled and not null /// public bool UseNormalMap { get { return mNormalMapEnabled && mNormalMap != null; } } /// /// True if the standard diffuse map is enabled and not null and at least 1 other diffuse map is not null /// public bool UseMultipleDiffuseMaps { get { return mDiffuseMapEnabled && mDiffuseMap != null && (mDiffuseMap1 != null || mDiffuseMap2 != null || mDiffuseMap3 != null); } } /// /// Create material, using default values. /// public Material() { } /// /// Create material. Pass null to use default values. /// public Material(Color? ambientColor, Color? diffuseColor, Color? specularColor, Texture diffuseMap, Texture normalMap) { if (ambientColor.HasValue) mAmbientColor = ambientColor.Value; if (diffuseColor.HasValue) mDiffuseColor = diffuseColor.Value; if (specularColor.HasValue) mSpecularColor = specularColor.Value; if (diffuseMap != null) mDiffuseMap = diffuseMap; if (normalMap != null) mNormalMap = normalMap; // Use defaults for the remaining values } /// /// Load a material from inside an effect node for loading collada models /// /// Effect node /// Textures /// Effects ids internal Material(XmlNode effectNode, Dictionary textures) { mName = XmlHelper.GetXmlAttribute(effectNode, "id"); // Build a reference dictionary to hold which nodes refer to which other nodes which eventually refer to a loaded texture Dictionary referenceChain = new Dictionary(); // Get this effect instance's profile node XmlNode profileNode = XmlHelper.GetChildNode(effectNode, "profile_COMMON"); // Load each supported parameter XmlNodeList paramNodes = profileNode.ChildNodes; foreach (XmlNode paramNode in paramNodes) { string key = XmlHelper.GetXmlAttribute(paramNode, "sid"); string value = null; // Extract "init_from" flags from surfaces XmlNode surfaceNode = XmlHelper.GetChildNode(paramNode, "surface"); if (surfaceNode != null) { XmlNode initFromNode = XmlHelper.GetChildNode(surfaceNode, "init_from"); value = initFromNode.InnerText; } // Extract "source" flags from samplers XmlNode samplerNode = XmlHelper.GetChildNode(paramNode, "sampler2D"); if (samplerNode != null) { XmlNode sourceNode = XmlHelper.GetChildNode(samplerNode, "source"); value = sourceNode.InnerText; } if (value != null) { referenceChain.Add(key, value); } } // Now load the technique's parameters (the values we're REALLY after) XmlNode techniqueNode = XmlHelper.GetChildNode(profileNode, "technique"); XmlNode shaderNode = techniqueNode.ChildNodes[0]; XmlNodeList shaderParams = shaderNode.ChildNodes; foreach (XmlNode shaderParam in shaderParams) { float[] vec; // params may have color and/or texture nodes XmlNode colorNode = XmlHelper.GetChildNode(shaderParam, "color"); XmlNode textureNode = XmlHelper.GetChildNode(shaderParam, "texture"); switch (shaderParam.Name) { case "ambient": if (colorNode != null) { vec = StringHelper.ConvertStringToFloatArray(colorNode.InnerText); mAmbientColor = ColorFromFloatArray(vec); } break; case "emission": if (colorNode != null) { vec = StringHelper.ConvertStringToFloatArray(colorNode.InnerText); mIncandescenceColor = ColorFromFloatArray(vec); } break; case "diffuse": if (colorNode != null) { vec = StringHelper.ConvertStringToFloatArray(colorNode.InnerText); mDiffuseColor = ColorFromFloatArray(vec); } if (textureNode != null) { string textureID = XmlHelper.GetXmlAttribute(textureNode, "texture"); string textureKey = FindTextureKeyFromReferenceChain(textureID, referenceChain); if (textureID != textureKey) mDiffuseMap = textures[textureKey]; mDiffuseColor = Color.Black; } break; } } } /// /// Tells renderers to ignore or use the diffuse map /// public void SetDiffuseMapEnabled(bool state) { mDiffuseMapEnabled = state; } /// /// Tells renderers to ignore or use the normal map /// public void SetNormalMapEnabled(bool state) { mNormalMapEnabled = state; } /// /// Dispose /// public void Dispose() { if (mDiffuseMap != null) mDiffuseMap.Dispose(); if (mNormalMap != null) mNormalMap.Dispose(); } /// /// From float array with color components ranging from 0 to 1 and in rgba /// order, accepts arrays with length 3 and 4, default alpha value is 1.0f /// No array boundcheck is performed. /// private Color ColorFromFloatArray(float[] setColor) { byte r, g, b, a = 255; r = (byte)(setColor[0] * 255); g = (byte)(setColor[1] * 255); b = (byte)(setColor[2] * 255); if (setColor.Length == 4) a = (byte)(setColor[3] * 255); return new Color(r, g, b, a); } /// /// Searches for the specified key and returns then recursively checks that value as the key for more values /// until a value is found that is not a key. Returns the inputted if there is not reference to it. /// /// /// private string FindTextureKeyFromReferenceChain(string startKey, Dictionary referenceChain) { string curKey = startKey; while (referenceChain.ContainsKey(curKey)) { curKey = referenceChain[curKey]; } return curKey; } } }