using System; using System.Collections.ObjectModel; using System.Collections.Generic; using System.Runtime.Serialization; using System.Xml.Serialization; using System.Xml; using System.IO; namespace StatedAnim { public class StatedAnimObject { Int32 _NodeHandle; public Int32 NodeHandle { get { return _NodeHandle; } set { _NodeHandle = value; this.Changed(this, new EventArgs()); } } String _Name; public String Name { get { return _Name; } set { _Name = value; this.Changed(this, new EventArgs()); } } String _State; public String State { get { return _State; } set { _State = value; this.Changed(this, new EventArgs()); } } public StatedAnimObject(String Name, String State, Int32 NodeHandle) { this._NodeHandle = NodeHandle; this._Name = Name; this._State = State; } public event EventHandler Changed; } public class StatedAnimObjectList : ObservableCollection { public event EventHandler ContentChanged; public void AddRegister(StatedAnimObject Input) { Input.Changed += new EventHandler(this.OnChanged); base.Add(Input); } void OnChanged(object s, EventArgs e) { this.ContentChanged(s, e); } public Boolean HandleExists(Int32 Handle) { foreach (var SubStatedAnimObject in this.Items) { if (SubStatedAnimObject.NodeHandle == Handle) { return true; } } return false; } } //These classes help with the organisation of SkinnedObjects //SO = SkinnedObject [DataContract] public class SOOrganiser { public static Int32 ThresholdStep = 1; [DataMember] public List SODataList = new List(); public Int32 GetBoneCount() { Int32 ReturnBoneCount = 0; foreach (var SubSOData in SODataList) { ReturnBoneCount += SubSOData.BoneCount; } return ReturnBoneCount; } public SOData[] Process() { //Sort the SODataList by size (material number then vert count - so we assign the largest and most complex first) SortSODataList(); var CondensedSODataList = new List(); //We keep on going through the list untill all of them have clumped into logical chunks //In this pass we are looking for 100% matches. while (SODataList.Count > 0) { //Create an SODataSet for this pass var CurrentSOData = SODataList[0]; SODataList.Remove(CurrentSOData); //List to store any SOData objects that were processed on this cycle List ProcessedSODataList = new List(); //Cycle through each SOData and each SODataSet foreach (SOData iSOData in SODataList) { //Check to see if the current SODataSet can take the current SOData if (CurrentSOData.CanCombine(iSOData)) { //If it can then get how well the materials match Single CurrentPercentage = CurrentSOData.GetPercentageMatch(iSOData); if (CurrentPercentage == 100) { //If we have a perfect match CurrentSOData.Combine(iSOData); //Log that we have process this SOData and break from the loop to move onto the next SOData object ProcessedSODataList.Add(iSOData); } } } if (ProcessedSODataList.Count != 0) { //Remove all the SOData objects we processed foreach (SOData ProcessedSOData in ProcessedSODataList) { SODataList.Remove(ProcessedSOData); } } CondensedSODataList.Add(CurrentSOData); } //By this point we should have a list of n SODataSets //We now need to combine these as efficently as possible Int32 PercentageThreshold = 100; while (PercentageThreshold >= 0) { var SuccessfullCombines = 0; for (var i = 0; i < CondensedSODataList.Count; i++) { //Pop the next SOData off the stack var TargetSOData = CondensedSODataList[i]; foreach (var SubSOData in CondensedSODataList) { if (SubSOData != TargetSOData && TargetSOData.CanCombine(SubSOData)) { //If it can then get how well the materials match Single CurrentPercentage = TargetSOData.GetPercentageMatch(SubSOData); if (CurrentPercentage >= PercentageThreshold) { //If we have a decent match then merge it in. TargetSOData.Combine(SubSOData); //Log that we have processed this SOData and break from the loop to move onto the next SOData object CondensedSODataList.Remove(SubSOData); SuccessfullCombines += 1; break; } } } } //If we didn't manage any combines on this pass then reduce the threshold if (SuccessfullCombines == 0) { PercentageThreshold -= 1; } } return CondensedSODataList.ToArray(); } void SortSODataList() { SODataList.Sort ( delegate(SOData R, SOData L) { //First sort by materials if (R.MaterialHashTable.Count > L.MaterialHashTable.Count) { return -1; } if (R.MaterialHashTable.Count < L.MaterialHashTable.Count) { return 1; } //Then by vert count if (R.VertCount > L.VertCount) { return -1; } if (R.VertCount < L.VertCount) { return 1; } return 0; } ); } //void GetBaseObjects() //{ // //Start the dataset list // SODataSetList = new List(); // //First we start by looking for the 10 largest SOData objects which have as little // //in relation with all the other SDData objects as possible. // Int32 CurrentThreshold = 0; // while (SODataSetList.Count < SODataSetLimit) // { // //Go through each SOData object and see if it fits below the current percentage threshold // SOData CurrentBaseSOData = null; // foreach (SOData SubSOData in SODataList) // { // Single HighestPercentageMatch = GetHighestPercentageMatch(SubSOData); // if (HighestPercentageMatch <= CurrentThreshold) // { // CurrentBaseSOData = SubSOData; // break; // } // } // if (CurrentBaseSOData == null) // { // //If we couldn't find one we'll up the threshold. // CurrentThreshold += ThresholdStep; // } // else // { // //Add our new base SO data object and merge it // SODataSetList.Add(new SODataSet(CurrentBaseSOData)); // SODataList.Remove(CurrentBaseSOData); // } // } //} //Returns the highest percentage match against all datasets //Single GetHighestPercentageMatch(SOData InputSOData) //{ // Single HighestPercentage = 0; // foreach (SODataSet iSODataSet in SODataSetList) // { // Single CurrentPercentage = iSODataSet.GetPercentageMatch(InputSOData); // if (CurrentPercentage > HighestPercentage) // { // HighestPercentage = CurrentPercentage; // } // } // return HighestPercentage; //} public static void Write(String FilePath, SOOrganiser OutputSOOrganiser) { var serializer = new DataContractSerializer(typeof(SOOrganiser)); var WriterSettings = new XmlWriterSettings { Indent = true }; using (var Writer = XmlWriter.Create(FilePath, WriterSettings)) { serializer.WriteObject(Writer, OutputSOOrganiser); } } public static SOOrganiser Read(String FilePath) { var serializer = new DataContractSerializer(typeof(SOOrganiser)); using (var Reader = XmlReader.Create(FilePath)) { return (SOOrganiser)serializer.ReadObject(Reader); } } } //public class SODataSet //{ // SOData MasterSOData = new SOData(); // public List SODataGuidList = new List(); // public Guid[] GUIDArray // { // get { return SODataGuidList.ToArray(); } // } // public SODataSet(SOData InitialSOData) // { // Combine(InitialSOData); // } // public Single GetPercentageMatch(SOData InputSOData) // { // return MasterSOData.GetPercentageMatch(InputSOData); // } // public Boolean CanCombine(SOData InputSOData) // { // return (MasterSOData.CanCombine(InputSOData)); // } // public void Combine(SOData InputSOData) // { // MasterSOData.Combine(InputSOData); // SODataGuidList.Add(InputSOData.SourceGUID); // } // public override string ToString() // { // return String.Format("[{0}][{1}][{2}]", MasterSOData.FaceCount, MasterSOData.BoneCount, MasterSOData.MaterialHashTable.Count); // } //} public class SOData { internal SOData() {} public SOData(Guid SourceGUID) { SourceGUIDList.Add(SourceGUID); } static Int32 MaximumFaceCount = 21840; static Int32 MaximumVertCount = 63999; static Int32 MaximumBoneCount = 128; static Int32 MaximumMaterialCount = 48; [DataMember] public Dictionary MaterialHashTable = new Dictionary(); [DataMember] public List SourceGUIDList = new List(); //Exposed for MXS public Guid[] SourceGUIDArray { get { return SourceGUIDList.ToArray(); } } [DataMember] public Int32 FaceCount = 0; [DataMember] public Int32 VertCount = 0; [DataMember] public Int32 BoneCount = 0; public void Combine(SOData InputSOData) { this.FaceCount += InputSOData.FaceCount; this.VertCount += InputSOData.VertCount; this.BoneCount += InputSOData.BoneCount; SourceGUIDList.AddRange(InputSOData.SourceGUIDList); foreach (var MaterialHash in InputSOData.MaterialHashTable) { Append(MaterialHash.Key, MaterialHash.Value); } } //Check that the input SOCombineData can combine with this SOCombineData instance. public Boolean CanCombine(SOData InputSOData) { if (InputSOData.VertCount + this.VertCount < MaximumVertCount && InputSOData.BoneCount + this.BoneCount < MaximumBoneCount) { Int32 UniqueMaterialCount = MaterialHashTable.Count; foreach (Int32 MaterialHash in InputSOData.MaterialHashTable.Keys) { if (this.MaterialHashTable.ContainsKey(MaterialHash)) { if (this.MaterialHashTable[MaterialHash] + InputSOData.MaterialHashTable[MaterialHash] > MaximumFaceCount) { return false; } } else { UniqueMaterialCount += 1; } } if (UniqueMaterialCount > MaximumMaterialCount) { return false; } else { return true; } } return false; } //Returns how much the shaders of the input InputSOData matches this SOData instance public Single GetPercentageMatch(SOData InputSOData) { Single TotalElements = 0; Single MatchingElements = 0; foreach (Int32 MaterialHash in InputSOData.MaterialHashTable.Keys) { if (this.MaterialHashTable.ContainsKey(MaterialHash)) { MatchingElements += 1; } TotalElements += 1; } if (MatchingElements == 0 || TotalElements == 0) { return 0.0f; } return (MatchingElements / TotalElements * 100); } //Similar to the default 'Add' but if the dictionary already contains the material hash it will simply add the faces to that public void Append(Int32 MaterialHash, Int32 FaceCount) { if (MaterialHashTable.ContainsKey(MaterialHash)) { MaterialHashTable[MaterialHash] += FaceCount; } else { MaterialHashTable.Add(MaterialHash, FaceCount); } } } }