Merge branch 'master' into mdl-import

This commit is contained in:
ackwell 2024-01-07 10:10:39 +11:00
commit 1a88cefd52
93 changed files with 2132 additions and 1744 deletions

View file

@ -1,6 +1,7 @@
using Dalamud.Plugin.Services;
using OtterGui.Tasks;
using Penumbra.Collections.Manager;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Files;
using Penumbra.Import.Models.Export;
using Penumbra.Import.Models.Import;
@ -9,21 +10,13 @@ using SharpGLTF.Schema2;
namespace Penumbra.Import.Models;
public sealed class ModelManager : SingleTaskQueue, IDisposable
public sealed class ModelManager(IFramework framework, GamePathParser _parser) : SingleTaskQueue, IDisposable
{
private readonly IFramework _framework;
private readonly IDataManager _gameData;
private readonly ActiveCollectionData _activeCollectionData;
private readonly IFramework _framework = framework;
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;
}
private bool _disposed;
public void Dispose()
{
@ -33,26 +26,6 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
_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));
@ -62,29 +35,64 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
return Enqueue(action).ContinueWith(_ => action.Out!);
}
private class ExportToGltfAction : IAction
/// <summary> Try to find the .sklb path for a .mdl file. </summary>
/// <param name="mdlPath"> .mdl file to look up the skeleton for. </param>
public string? ResolveSklbForMdl(string mdlPath)
{
private readonly ModelManager _manager;
var info = _parser.GetFileInfo(mdlPath);
if (info.FileType is not FileType.Model)
return null;
private readonly MdlFile _mdl;
private readonly SklbFile? _sklb;
private readonly string _outputPath;
public ExportToGltfAction(ModelManager manager, MdlFile mdl, SklbFile? sklb, string outputPath)
return info.ObjectType switch
{
_manager = manager;
_mdl = mdl;
_sklb = sklb;
_outputPath = outputPath;
ObjectType.Equipment => GamePaths.Skeleton.Sklb.Path(info.GenderRace, "base", 1),
ObjectType.Accessory => GamePaths.Skeleton.Sklb.Path(info.GenderRace, "base", 1),
ObjectType.Character when info.BodySlot is BodySlot.Body or BodySlot.Tail => GamePaths.Skeleton.Sklb.Path(info.GenderRace, "base",
1),
ObjectType.Character => throw new Exception($"Currently unsupported human model type \"{info.BodySlot}\"."),
ObjectType.DemiHuman => GamePaths.DemiHuman.Sklb.Path(info.PrimaryId),
ObjectType.Monster => GamePaths.Monster.Sklb.Path(info.PrimaryId),
ObjectType.Weapon => GamePaths.Weapon.Sklb.Path(info.PrimaryId),
_ => null,
};
}
private Task Enqueue(IAction action)
{
if (_disposed)
return Task.FromException(new ObjectDisposedException(nameof(ModelManager)));
Task task;
lock (_tasks)
{
task = _tasks.GetOrAdd(action, a =>
{
var token = new CancellationTokenSource();
var t = Enqueue(a, token.Token);
t.ContinueWith(_ =>
{
lock (_tasks)
{
return _tasks.TryRemove(a, out var unused);
}
}, CancellationToken.None);
return (t, token);
}).Item1;
}
return task;
}
private class ExportToGltfAction(ModelManager manager, MdlFile mdl, SklbFile? sklb, string outputPath)
: IAction
{
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);
var model = ModelExporter.Export(mdl, xivSkeleton);
Penumbra.Log.Debug("Building scene.");
var scene = new SceneBuilder();
@ -92,16 +100,16 @@ public sealed class ModelManager : SingleTaskQueue, IDisposable
Penumbra.Log.Debug("Saving.");
var gltfModel = scene.ToGltf2();
gltfModel.SaveGLTF(_outputPath);
gltfModel.SaveGLTF(outputPath);
}
/// <summary> Attempt to read out the pertinent information from a .sklb. </summary>
private XivSkeleton? BuildSkeleton(CancellationToken cancel)
{
if (_sklb == null)
if (sklb == null)
return null;
var xmlTask = _manager._framework.RunOnFrameworkThread(() => HavokConverter.HkxToXml(_sklb.Skeleton));
var xmlTask = manager._framework.RunOnFrameworkThread(() => HavokConverter.HkxToXml(sklb.Skeleton));
xmlTask.Wait(cancel);
var xml = xmlTask.Result;