using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml; using System.IO; namespace Toolbox.Library.Collada { public class ColladaWriter : IDisposable { //Based around this nice Writer https://raw.githubusercontent.com/Ploaj/SSBHLib/master/CrossMod/IO/DAEWriter.cs //With adjustments and expanded usage. private XmlTextWriter Writer; private DAE.ExportSettings Settings; private DAE.Version Version; private List> GeometrySources = new List>(); private Dictionary AttributeIdList = new Dictionary(); private Dictionary, List>> GeometryControllers = new Dictionary, List>>(); //Keep a dictionary of names and assigned id names private Dictionary MeshIdList = new Dictionary(); private Dictionary> MaterialIdList = new Dictionary>(); private Dictionary MeshSkinIdList = new Dictionary(); private List Joints = new List(); public string CurrentGeometryID; public string CurrentMaterial; public bool Optimize = false; public ColladaWriter(string fileName, DAE.ExportSettings settings) { Settings = settings; Version = settings.FileVersion; Writer = new XmlTextWriter(fileName, Encoding.UTF8) { Formatting = Formatting.Indented, Indentation = 2, }; System.Globalization.CultureInfo customCulture = (System.Globalization.CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone(); customCulture.NumberFormat.NumberDecimalSeparator = "."; System.Threading.Thread.CurrentThread.CurrentCulture = customCulture; WriteDAEHeader(); } public void WriteDAEHeader() { Writer.WriteStartDocument(); Writer.WriteStartElement("COLLADA"); Writer.WriteAttributeString("xmlns", "http://www.collada.org/2005/11/COLLADASchema"); Writer.WriteAttributeString("version", $"{Version.Major}.{Version.Minor}.{Version.Micro}"); } public void WriteAsset() { Writer.WriteStartElement("asset"); Writer.WriteEndElement(); } public void WriteFileSettings() { Writer.WriteStartElement("asset"); { Writer.WriteStartElement("contributor"); Writer.WriteElementString("authoring_tool", System.Windows.Forms.Application.ProductName); Writer.WriteEndElement(); Writer.WriteStartElement("created"); Writer.WriteString(DateTime.UtcNow.ToString("s", System.Globalization.CultureInfo.InvariantCulture) + "Z"); Writer.WriteEndElement(); Writer.WriteStartElement("modified"); Writer.WriteString(DateTime.UtcNow.ToString("s", System.Globalization.CultureInfo.InvariantCulture) + "Z"); Writer.WriteEndElement(); Writer.WriteStartElement("unit"); if (Settings.Preset == ProgramPreset.MAX) { Writer.WriteAttributeString("meter", "1"); Writer.WriteAttributeString("name", "centimeter"); } else { Writer.WriteAttributeString("meter", "0.01"); Writer.WriteAttributeString("name", "centimeter"); } Writer.WriteEndElement(); Writer.WriteStartElement("up_axis"); Writer.WriteString("Y_UP"); Writer.WriteEndElement(); } Writer.WriteEndElement(); } public void WriteLibraryImages(string[] textureNames = null) { Writer.WriteStartElement("library_images"); for (int i = 0; i < textureNames?.Length; i++) { Writer.WriteStartElement("image"); Writer.WriteAttributeString("id", textureNames[i]); Writer.WriteStartElement("init_from"); Writer.WriteString($"{Settings.ImageFolder}{textureNames[i]}{Settings.ImageExtension}"); Writer.WriteEndElement(); Writer.WriteEndElement(); } Writer.WriteEndElement(); } public void WriteLibraryMaterials(List materials) { Writer.WriteStartElement("library_materials"); for (int i = 0; i < materials?.Count; i++) { Writer.WriteStartElement("material"); Writer.WriteAttributeString("id", materials[i].Name); Writer.WriteStartElement("instance_effect"); Writer.WriteAttributeString("url", "#Effect_" + materials[i].Name); Writer.WriteEndElement(); Writer.WriteEndElement(); } Writer.WriteEndElement(); } public void WriteLibraryEffects(List materials) { Writer.WriteStartElement("library_effects"); for (int i = 0; i < materials?.Count; i++) { Writer.WriteStartElement("effect"); Writer.WriteAttributeString("id", $"Effect_{materials[i].Name}"); Writer.WriteStartElement("profile_COMMON"); { for (int t = 0; t < materials[i].Textures.Count; t++) { var tex = materials[i].Textures[t]; //Here we write 2 things. Surface then sampler info //Surface info Writer.WriteStartElement("newparam"); Writer.WriteAttributeString("sid", $"surface_{materials[i].Name}-{tex.Type}"); Writer.WriteStartElement("surface"); Writer.WriteAttributeString("type", "2D"); { Writer.WriteStartElement("init_from"); Writer.WriteString(tex.Name); Writer.WriteEndElement(); Writer.WriteStartElement("format"); //Formats are uncompressed types. //Todo these should be adjusted for DDS Writer.WriteString("A8R8G8B8"); Writer.WriteEndElement(); } Writer.WriteEndElement(); Writer.WriteEndElement(); { //Sampler info Writer.WriteStartElement("newparam"); Writer.WriteAttributeString("sid", $"sampler_{materials[i].Name}-{tex.Type}"); Writer.WriteStartElement("sampler2D"); Writer.WriteStartElement("source"); Writer.WriteString($"surface_{materials[i].Name}-{tex.Type}"); Writer.WriteEndElement(); //Add sampler wrap modes Writer.WriteStartElement("wrap_s"); Writer.WriteString(tex.WrapModeS.ToString()); Writer.WriteEndElement(); Writer.WriteStartElement("wrap_t"); Writer.WriteString(tex.WrapModeT.ToString()); Writer.WriteEndElement(); Writer.WriteEndElement(); Writer.WriteEndElement(); } } //Now write texture info //Todo support PBR workflow Writer.WriteStartElement("technique"); Writer.WriteAttributeString("sid", "common"); Writer.WriteStartElement("phong"); bool HasSpecular = false; bool HasEmission = false; bool HasDiffuse = false; for (int t = 0; t < materials[i].Textures.Count; t++) { var tex = materials[i].Textures[t]; if (tex.Type == PhongTextureType.diffuse) HasDiffuse = true; if (tex.Type == PhongTextureType.emission) HasEmission = true; if (tex.Type == PhongTextureType.specular) HasSpecular = true; Writer.WriteStartElement($"{tex.Type}"); Writer.WriteStartElement("texture"); Writer.WriteAttributeString("texture", $"sampler_{materials[i].Name}-{tex.Type}"); Writer.WriteAttributeString("texcoord", $"{tex.TextureChannel}"); Writer.WriteEndElement(); Writer.WriteEndElement(); } if (!HasDiffuse) WriteMaterialColor("diffuse", new float[] { 1, 1, 1, 1 }); if (!HasEmission) WriteMaterialColor("emission", new float[] { 0, 0, 0, 1 }); if (!HasSpecular) WriteMaterialColor("specular", new float[] { 0, 0, 0, 1 }); Writer.WriteEndElement(); Writer.WriteEndElement(); } Writer.WriteEndElement(); Writer.WriteEndElement(); } Writer.WriteEndElement(); } public void WriteMaterialColor(string type, float[] color) { Writer.WriteStartElement(type); Writer.WriteStartElement("color"); Writer.WriteAttributeString("sid", type); Writer.WriteString(string.Join(" ", color)); Writer.WriteEndElement(); Writer.WriteEndElement(); } public void StartLibraryGeometries() { Writer.WriteStartElement("library_geometries"); } public void StartGeometry(string name) { CurrentGeometryID = GetUniqueID($"{name}-mesh"); MeshSkinIdList.Add(CurrentGeometryID, ""); MeshIdList.Add(CurrentGeometryID, name); Writer.WriteStartElement("geometry"); Writer.WriteAttributeString("id", CurrentGeometryID); Writer.WriteAttributeString("name", name); Writer.WriteStartElement("mesh"); } private void OptimizeSource(T[] values, TriangleList[] triangleLists, int stride, out T[] newValues, out TriangleList[] newIndices) where T : struct { var optimizedIndices = new List(); var optimizedValues = new List(); // Use a special class to store values, so we can have the performance benefits of a dictionary. var vertexBank = new Dictionary, uint>(); for (int t = 0; t < triangleLists.Length; t++) { for (int i = 0; i < triangleLists[t].Indices.Count; i++) { var vertexValues = new ValueContainer(GetVertexValues(values, triangleLists[t].Indices.ToArray(), stride, i)); if (!vertexBank.ContainsKey(vertexValues)) { uint index = (uint)vertexBank.Count; optimizedIndices.Add(index); vertexBank.Add(vertexValues, index); optimizedValues.AddRange(vertexValues.Values); } else { optimizedIndices.Add(vertexBank[vertexValues]); } } triangleLists[t].Indices = optimizedIndices; } newIndices = triangleLists; newValues = optimizedValues.ToArray(); } private static T[] GetVertexValues(T[] values, uint[] indices, int stride, int i) { var vertexValues = new T[stride]; for (int j = 0; j < stride; j++) vertexValues[j] = values[indices[i] * stride + j]; return vertexValues; } public void WriteGeometrySource(string name, SemanticType semantic, float[] values, TriangleList[] indices, int set = -1) { int stride = GetStride(semantic); if (Optimize) OptimizeSource(values, indices, stride, out values, out indices); string sourceid = GetUniqueID(name + "-" + semantic.ToString().ToLower()); Writer.WriteStartElement("source"); Writer.WriteAttributeString("id", sourceid); Writer.WriteStartElement("float_array"); string FloatArrayID = GetUniqueID(name + "-" + semantic.ToString().ToLower() + "-array"); Writer.WriteAttributeString("id", FloatArrayID); Writer.WriteAttributeString("count", values.Length.ToString()); Writer.WriteString(string.Join(" ", values)); Writer.WriteEndElement(); Writer.WriteStartElement("technique_common"); { Writer.WriteStartElement("accessor"); Writer.WriteAttributeString("source", $"#{FloatArrayID}"); Writer.WriteAttributeString("count", (values.Length / stride).ToString()); Writer.WriteAttributeString("stride", stride.ToString()); if (semantic == SemanticType.NORMAL || semantic == SemanticType.POSITION) { WriteParam("X", "float"); WriteParam("Y", "float"); WriteParam("Z", "float"); } if (semantic == SemanticType.TEXCOORD) { WriteParam("S", "float"); WriteParam("T", "float"); } if (semantic == SemanticType.COLOR) { WriteParam("R", "float"); WriteParam("G", "float"); WriteParam("B", "float"); WriteParam("A", "float"); } Writer.WriteEndElement(); } Writer.WriteEndElement(); Writer.WriteEndElement(); GeometrySources.Add(new Tuple(sourceid, semantic, indices, set)); } private int GetStride(SemanticType semantic) { if (semantic == SemanticType.COLOR) return 4; else if (semantic == SemanticType.TEXCOORD) return 2; else if (semantic == SemanticType.JOINT) return 1; else if (semantic == SemanticType.INV_BIND_MATRIX) return 16; else if (semantic == SemanticType.WEIGHT) return 1; else return 3; } private void WriteParam(string Name, string Type) { Writer.WriteStartElement("param"); Writer.WriteAttributeString("name", Name); Writer.WriteAttributeString("type", Type); Writer.WriteEndElement(); } public void EndGeometryMesh() { Tuple Position = null; foreach (var v in GeometrySources) { if (v.Item2 == SemanticType.POSITION) { Position = v; break; } } // can only write vertex information if there is at least position data if (Position != null) { // vertices string verticesid = GetUniqueID(Position.Item1.Replace("position", "vertex")); Writer.WriteStartElement("vertices"); Writer.WriteAttributeString("id", verticesid); WriteInput(Position.Item2.ToString(), Position.Item1); Writer.WriteEndElement(); List triangleMaterials = new List(); // triangles int triIndex = 0; foreach (TriangleList triangleList in Position.Item3) { Writer.WriteStartElement("triangles"); if (triangleList.Material != "") { Writer.WriteAttributeString("material", $"{triangleList.Material}"); triangleMaterials.Add(triangleList.Material); } else if (CurrentMaterial != "") { Writer.WriteAttributeString("material", $"{CurrentMaterial}"); triangleMaterials.Add(CurrentMaterial); CurrentMaterial = ""; } Writer.WriteAttributeString("count", (triangleList.Indices.Count / 3).ToString()); WriteInput("VERTEX", $"{verticesid}", 0); int offset = 1; foreach (var v in GeometrySources) if (v.Item2 != SemanticType.POSITION) { WriteInput(v.Item2.ToString(), $"{v.Item1}", offset++, v.Item4); } // write p StringBuilder p = new StringBuilder(); for (int i = 0; i < triangleList.Indices.Count; i++) { p.Append(triangleList.Indices[i] + " "); foreach (var v in GeometrySources) if (v.Item2 != SemanticType.POSITION) p.Append(v.Item3[triIndex].Indices[i] + " "); } Writer.WriteStartElement("p"); Writer.WriteString(p.ToString()); Writer.WriteEndElement(); Writer.WriteEndElement(); triIndex++; } MaterialIdList.Add(CurrentGeometryID, triangleMaterials); } GeometrySources.Clear(); Writer.WriteEndElement(); Writer.WriteEndElement(); } public void WriteInput(string semantic, string sourceid, int offset = -1, int set = -1) { Writer.WriteStartElement("input"); Writer.WriteAttributeString("semantic", semantic); Writer.WriteAttributeString("source", $"#{sourceid}"); if (offset != -1) Writer.WriteAttributeString("offset", offset.ToString()); if (set != -1) Writer.WriteAttributeString("set", set.ToString()); Writer.WriteEndElement(); } /// /// A function for automatically creating and handling the controllers /// The Joints must be added before this function is used /// /// A list of arrays of bone indices for each vertex /// A list that contains an array of weights per vertex public void AttachGeometryController(List BoneIndices, List Weights) { GeometryControllers.Add(CurrentGeometryID, new Tuple, List>(BoneIndices, Weights)); } /// /// Ends Writing the Geometry Section /// public void EndGeometrySection() { Writer.WriteEndElement(); if (Joints?.Count > 0) CreateControllerSection(); CreateVisualNodeSection(); } private void RecursivlyWriteJoints(Joint joint) { Writer.WriteStartElement("node"); Writer.WriteAttributeString("id", $"Armature_{joint.Name}"); Writer.WriteAttributeString("name", joint.Name); Writer.WriteAttributeString("sid", joint.Name); Writer.WriteAttributeString("type", "JOINT"); Writer.WriteStartElement("matrix"); Writer.WriteAttributeString("sid", "transform"); Writer.WriteString(string.Join(" ", joint.Transform)); Writer.WriteEndElement(); foreach (var child in GetChildren(joint)) { RecursivlyWriteJoints(child); } Writer.WriteEndElement(); } private Joint[] GetChildren(Joint j) { int parentindex = Joints.IndexOf(j); List Children = new List(); foreach (var child in Joints) { if (child.ParentIndex == parentindex) Children.Add(child); } return Children.ToArray(); } /// /// Starts the library controller section /// public void BeginLibraryControllers() { Writer.WriteStartElement("library_controllers"); } /// /// automatically generates the controller nodes /// private void CreateControllerSection() { BeginLibraryControllers(); foreach (var v in GeometryControllers.Keys) { WriteLibraryController(v); } EndLibraryControllers(); } /// /// Adds a new joint to the default skeletal tree /// public void AddJoint(string name, string parentName, float[] Transform, float[] InvWorldTransform) { Joint j = new Joint(); j.Name = name; j.Transform = Transform; j.BindPose = InvWorldTransform; foreach (var joint in Joints) if (joint.Name.Equals(parentName)) j.ParentIndex = Joints.IndexOf(joint); Joints.Add(j); } public void WriteLibraryController(string Name) { if (!GeometryControllers.ContainsKey(Name)) return; var BoneWeight = GeometryControllers[Name]; string SkinID = $"Armature_{Name}"; MeshSkinIdList[Name] = SkinID; Writer.WriteStartElement("controller"); Writer.WriteAttributeString("id", SkinID); Writer.WriteStartElement("skin"); Writer.WriteAttributeString("source", $"#{Name}"); { Writer.WriteStartElement("bind_shape_matrix"); Writer.WriteString("1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1"); Writer.WriteEndElement(); List meshJoints = new List(); List indexTable = new List(); //Go through each bone index and find each rigged joint the mesh uses for (int i = 0; i < BoneWeight.Item1.Count; i++) { for (int j = 0; j < BoneWeight.Item1[i].Length; j++) { int index = BoneWeight.Item1[i][j]; if (index < Joints.Count && index != -1 && !meshJoints.Contains(Joints[index])) meshJoints.Add(Joints[index]); } } //Create a lookup table to find the joint list from all the joints that get indexed for (int i = 0; i < Joints.Count; i++) { indexTable.Add(meshJoints.IndexOf(Joints[i])); } object[] BoneNames = new string[meshJoints.Count]; object[] InvBinds = new object[meshJoints.Count * 16]; for (int i = 0; i < BoneNames.Length; i++) { BoneNames[i] = meshJoints[i].Name; for (int j = 0; j < 16; j++) { InvBinds[i * 16 + j] = meshJoints[i].BindPose[j]; } } var Weights = new List(); var WeightIndices = new List(); foreach (var v in BoneWeight.Item2) { foreach (var w in v) { int index = Weights.IndexOf(w); if (index == -1) { WeightIndices.Add(Weights.Count); Weights.Add(w); } else { WeightIndices.Add(index); } } } string Jointid = WriteSkinSource(Name, SemanticType.JOINT, BoneNames); string Bindid = WriteSkinSource(Name, SemanticType.INV_BIND_MATRIX, InvBinds); string Weightid = WriteSkinSource(Name, SemanticType.WEIGHT, Weights.ToArray()); Writer.WriteStartElement("joints"); WriteInput(SemanticType.JOINT.ToString(), Jointid); WriteInput(SemanticType.INV_BIND_MATRIX.ToString(), Bindid); Writer.WriteEndElement(); Writer.WriteStartElement("vertex_weights"); Writer.WriteAttributeString("count", BoneWeight.Item1.Count.ToString()); WriteInput(SemanticType.JOINT.ToString(), Jointid, 0); WriteInput(SemanticType.WEIGHT.ToString(), Weightid, 1); // now writing out the counts and such... //vcount { StringBuilder values = new StringBuilder(); foreach (var v in BoneWeight.Item1) { values.Append($"{v.Length} "); } Writer.WriteStartElement("vcount"); Writer.WriteString(values.ToString()); Writer.WriteEndElement(); } //v { StringBuilder values = new StringBuilder(); int weightindexcount = 0; for (int i = 0; i < BoneWeight.Item1.Count; i++) { for (int j = 0; j < BoneWeight.Item1[i].Length; j++) { values.Append($"{indexTable[BoneWeight.Item1[i][j]]} {WeightIndices[weightindexcount++]} "); } } Writer.WriteStartElement("v"); Writer.WriteString(values.ToString()); Writer.WriteEndElement(); } Writer.WriteEndElement(); } Writer.WriteEndElement(); Writer.WriteEndElement(); } /// /// Writes a skin source accessor for the current skin controller /// /// /// /// /// the id of the newly created skin source public string WriteSkinSource(string Name, SemanticType Semantic, object[] Values) { int Stride = GetStride(Semantic); string sourceid = GetUniqueID(Name + "-" + Semantic.ToString().ToLower()); Writer.WriteStartElement("source"); Writer.WriteAttributeString("id", sourceid); Writer.WriteStartElement(Semantic == SemanticType.JOINT ? "Name_array" : "float_array"); string FloatArrayID = GetUniqueID(Name + "-" + Semantic.ToString().ToLower() + "-array"); Writer.WriteAttributeString("id", FloatArrayID); Writer.WriteAttributeString("count", Values.Length.ToString()); Writer.WriteString(string.Join(" ", Values)); Writer.WriteEndElement(); Writer.WriteStartElement("technique_common"); { Writer.WriteStartElement("accessor"); Writer.WriteAttributeString("source", $"#{FloatArrayID}"); Writer.WriteAttributeString("count", (Values.Length / Stride).ToString()); Writer.WriteAttributeString("stride", Stride.ToString()); if (Semantic == SemanticType.JOINT) { WriteParam("JOINT", "name"); } if (Semantic == SemanticType.INV_BIND_MATRIX) { WriteParam("TRANSFORM", "float4x4"); } if (Semantic == SemanticType.WEIGHT) { WriteParam("WEIGHT", "float"); } Writer.WriteEndElement(); } Writer.WriteEndElement(); Writer.WriteEndElement(); return sourceid; } /// /// Ends the Library Controller Section /// public void EndLibraryControllers() { Writer.WriteEndElement(); } public void BeginVisualNodeSection() { Writer.WriteStartElement("library_visual_scenes"); Writer.WriteStartElement("visual_scene"); Writer.WriteAttributeString("id", "Scene"); Writer.WriteAttributeString("name", "Scene"); } public void CreateVisualNodeSection() { BeginVisualNodeSection(); Writer.WriteStartElement("node"); Writer.WriteAttributeString("id", "Armature"); Writer.WriteAttributeString("name", "Armature"); Writer.WriteAttributeString("type", "NODE"); Writer.WriteStartElement("matrix"); Writer.WriteAttributeString("sid", "transform"); Writer.WriteString("1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1"); Writer.WriteEndElement(); foreach (var joint in Joints) if (joint.ParentIndex == -1) RecursivlyWriteJoints(joint); Writer.WriteEndElement(); // write geometry nodes foreach (var m in MeshSkinIdList) { Writer.WriteStartElement("node"); Writer.WriteAttributeString("id", MeshIdList[m.Key]); Writer.WriteAttributeString("name", MeshIdList[m.Key]); Writer.WriteAttributeString("type", "NODE"); if (m.Value.Equals("")) { Writer.WriteStartElement("instance_geometry"); Writer.WriteAttributeString("url", $"#{m.Key}"); Writer.WriteAttributeString("name", MeshIdList[m.Key]); if (MaterialIdList.ContainsKey(m.Key)) { Writer.WriteStartElement("bind_material"); Writer.WriteStartElement("technique_common"); foreach (var mat in MaterialIdList[m.Key]) { Writer.WriteStartElement("instance_material"); Writer.WriteAttributeString("symbol", mat); Writer.WriteAttributeString("target", "#" + mat); Writer.WriteEndElement(); WriteChannel(0); WriteChannel(1); WriteChannel(2); } Writer.WriteEndElement(); Writer.WriteEndElement(); } Writer.WriteEndElement(); } else { Writer.WriteStartElement("instance_controller"); Writer.WriteAttributeString("url", $"#{m.Value}"); Writer.WriteStartElement("skeleton"); Writer.WriteString("#Armature_" + Joints[0].Name); Writer.WriteEndElement(); if (MaterialIdList.ContainsKey(m.Key)) { Writer.WriteStartElement("bind_material"); Writer.WriteStartElement("technique_common"); foreach (var mat in MaterialIdList[m.Key]) { Writer.WriteStartElement("instance_material"); Writer.WriteAttributeString("symbol", mat); Writer.WriteAttributeString("target", "#" + mat); Writer.WriteEndElement(); WriteChannel(0); WriteChannel(1); WriteChannel(2); } Writer.WriteEndElement(); Writer.WriteEndElement(); } Writer.WriteEndElement(); } Writer.WriteEndElement(); } EndVisualNodeSection(); } public void WriteChannel(int index) { Writer.WriteStartElement("bind_vertex_input"); Writer.WriteAttributeString("semantic", $"CHANNEL{index}"); Writer.WriteAttributeString("input_semantic", "TEXCOORD"); Writer.WriteAttributeString("input_set", index.ToString()); Writer.WriteEndElement(); } public void WriteNode(string Name) { Writer.WriteStartElement("node"); Writer.WriteAttributeString("id", Name); Writer.WriteAttributeString("name", Name); Writer.WriteAttributeString("type", "NODE"); Writer.WriteEndElement(); } public void EndVisualNodeSection() { Writer.WriteEndElement(); Writer.WriteEndElement(); Writer.WriteStartElement("scene"); Writer.WriteStartElement("instance_visual_scene"); Writer.WriteAttributeString("url", "#Scene"); Writer.WriteEndElement(); Writer.WriteEndElement(); } public string GetUniqueID(string id) { string name = id.Replace(" ", string.Empty); if (AttributeIdList.ContainsKey(name)) { AttributeIdList[name]++; return $"{name}_{AttributeIdList[name]}";// } else { AttributeIdList.Add(name, 0); return name; } } public void Dispose() { Writer?.WriteEndElement(); Writer?.WriteEndDocument(); Writer?.Close(); } private static void WriteFloats(object[] values) { } private class ValueContainer : IEquatable> where T : struct { public T[] Values { get; } private readonly int hashCode; public ValueContainer(T[] values) { Values = values; hashCode = ((System.Collections.IStructuralEquatable)Values).GetHashCode(EqualityComparer.Default); } public override bool Equals(object obj) { return Equals(obj as ValueContainer); } public bool Equals(ValueContainer other) { // Compare precalculated hash first for performance reasons. // The entire sequence needs to be compared to resolve collisions. return other != null && hashCode == other.hashCode && Enumerable.SequenceEqual(Values, other.Values); } public override int GetHashCode() { return hashCode; } } } public class Material { public string Name { get; set; } public List Textures = new List(); } public class Geometry { public string Name { get; set; } public Material Material { get; set; } public List Position = new List(); public List Normal = new List(); public List UV0 = new List(); public List UV1 = new List(); public List UV2 = new List(); public List UV3 = new List(); public List Color = new List(); public List BoneIndices = new List(); public List BoneWeights = new List(); public bool HasNormals = false; public bool HasUV0 = false; public bool HasUV1 = false; public bool HasUV2 = false; public bool HasUV3 = false; public bool HasColors = false; public bool HasBoneIndices = false; public bool HasWeights = false; } public enum PhongTextureType { diffuse, specular, emission, bump, } public enum TEXCOORD { CHANNEL0, CHANNEL1, CHANNEL2, CHANNEL3, } public enum SemanticType { None, POSITION, VERTEX, NORMAL, TEXCOORD, COLOR, WEIGHT, JOINT, INV_BIND_MATRIX, TEXTANGENT, TEXBINORMAL } public enum ProgramPreset { NONE, MAX, MAYA, BLENDER, } public enum SamplerWrapMode { NONE, WRAP, MIRROR, CLAMP, BORDER, } public class TextureMap { public TEXCOORD TextureChannel { get; set; } public string Name { get; set; } public PhongTextureType Type { get; set; } public SamplerWrapMode WrapModeS = SamplerWrapMode.WRAP; public SamplerWrapMode WrapModeT = SamplerWrapMode.WRAP; public TextureMap() { TextureChannel = TEXCOORD.CHANNEL0; Type = PhongTextureType.diffuse; Name = ""; } } public class TriangleList { public string Material = ""; public List Indices = new List(); } public class Joint { public string Name; public int ParentIndex = -1; public float[] Transform; public float[] BindPose; } }