Penumbra/Penumbra/Import/Models/ModelManager.cs
2024-01-01 13:30:04 +11:00

113 lines
3.6 KiB
C#

using Dalamud.Plugin.Services;
using OtterGui.Tasks;
using Penumbra.Collections.Manager;
using Penumbra.GameData.Files;
using Penumbra.Import.Models.Export;
using SharpGLTF.Scenes;
namespace Penumbra.Import.Models;
public sealed class ModelManager : SingleTaskQueue, IDisposable
{
private readonly IFramework _framework;
private readonly IDataManager _gameData;
private readonly ActiveCollectionData _activeCollectionData;
private readonly ConcurrentDictionary<IAction, (Task, CancellationTokenSource)> _tasks = new();
private bool _disposed = false;
public ModelManager(IFramework framework, IDataManager gameData, ActiveCollectionData activeCollectionData)
{
_framework = framework;
_gameData = gameData;
_activeCollectionData = activeCollectionData;
}
public void Dispose()
{
_disposed = true;
foreach (var (_, cancel) in _tasks.Values.ToArray())
cancel.Cancel();
_tasks.Clear();
}
private Task Enqueue(IAction action)
{
if (_disposed)
return Task.FromException(new ObjectDisposedException(nameof(ModelManager)));
Task task;
lock (_tasks)
{
task = _tasks.GetOrAdd(action, action =>
{
var token = new CancellationTokenSource();
var task = Enqueue(action, token.Token);
task.ContinueWith(_ => _tasks.TryRemove(action, out var unused), CancellationToken.None);
return (task, token);
}).Item1;
}
return task;
}
public Task ExportToGltf(MdlFile mdl, SklbFile? sklb, string outputPath)
=> Enqueue(new ExportToGltfAction(this, mdl, sklb, outputPath));
private class ExportToGltfAction : IAction
{
private readonly ModelManager _manager;
private readonly MdlFile _mdl;
private readonly SklbFile? _sklb;
private readonly string _outputPath;
public ExportToGltfAction(ModelManager manager, MdlFile mdl, SklbFile? sklb, string outputPath)
{
_manager = manager;
_mdl = mdl;
_sklb = sklb;
_outputPath = outputPath;
}
public void Execute(CancellationToken cancel)
{
Penumbra.Log.Debug("Reading skeleton.");
var xivSkeleton = BuildSkeleton(cancel);
Penumbra.Log.Debug("Converting model.");
var model = ModelExporter.Export(_mdl, xivSkeleton);
Penumbra.Log.Debug("Building scene.");
var scene = new SceneBuilder();
model.AddToScene(scene);
Penumbra.Log.Debug("Saving.");
var gltfModel = scene.ToGltf2();
gltfModel.SaveGLTF(_outputPath);
}
/// <summary> Attempt to read out the pertinent information from a .sklb. </summary>
private XivSkeleton? BuildSkeleton(CancellationToken cancel)
{
if (_sklb == null)
return null;
// TODO: work out how i handle this havok deal. running it outside the framework causes an immediate ctd.
var xmlTask = _manager._framework.RunOnFrameworkThread(() => HavokConverter.HkxToXml(_sklb.Skeleton));
xmlTask.Wait(cancel);
var xml = xmlTask.Result;
return SkeletonConverter.FromXml(xml);
}
public bool Equals(IAction? other)
{
if (other is not ExportToGltfAction rhs)
return false;
// TODO: compare configuration and such
return true;
}
}
}