439 lines
15 KiB
C#
Executable File
439 lines
15 KiB
C#
Executable File
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<EventArgs> Changed;
|
|
}
|
|
|
|
public class StatedAnimObjectList : ObservableCollection<StatedAnimObject>
|
|
{
|
|
public event EventHandler<EventArgs> ContentChanged;
|
|
|
|
public void AddRegister(StatedAnimObject Input)
|
|
{
|
|
Input.Changed += new EventHandler<EventArgs>(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<SOData> SODataList = new List<SOData>();
|
|
|
|
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<SOData>();
|
|
|
|
//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<SOData> ProcessedSODataList = new List<SOData>();
|
|
|
|
//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<SODataSet>();
|
|
|
|
// //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<Guid> SODataGuidList = new List<Guid>();
|
|
// 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<Int32, Int32> MaterialHashTable = new Dictionary<Int32, Int32>();
|
|
[DataMember]
|
|
public List<Guid> SourceGUIDList = new List<Guid>();
|
|
//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);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|