Files
2025-09-29 00:52:08 +02:00

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);
}
}
}
}