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