From beae632721b06ec5cba76efcd88fb1ec012afc21 Mon Sep 17 00:00:00 2001
From: Passive <20432486+PassiveModding@users.noreply.github.com>
Date: Fri, 27 Sep 2024 21:02:29 +1000
Subject: [PATCH] Model space support
- package updates
- cleanup unused parts
- move export type to common options instead of just layout
- use export type for character exports
- add option to toggle pose export
- add option to include model scaling (used by c+ in meddle exports)
---
Meddle/Meddle.Plugin/Meddle.Plugin.csproj | 2 +-
.../Models/Composer/CharacterComposer.cs | 10 +-
Meddle/Meddle.Plugin/Models/Enums.cs | 18 +
Meddle/Meddle.Plugin/Models/Groups.cs | 40 -
Meddle/Meddle.Plugin/Models/MenuType.cs | 8 -
.../Models/Skeletons/ParsedHkaPose.cs | 25 +-
Meddle/Meddle.Plugin/Plugin.cs | 32 +-
.../Services/AnimationExportService.cs | 106 +++
.../Meddle.Plugin/Services/ComposerFactory.cs | 4 +-
.../Meddle.Plugin/Services/ExportService.cs | 473 ----------
Meddle/Meddle.Plugin/Services/UI/CommonUI.cs | 8 +-
Meddle/Meddle.Plugin/UI/AnimationTab.cs | 8 +-
.../Meddle.Plugin/UI/Archive/CharacterTab.cs | 884 ------------------
.../Meddle.Plugin/UI/Archive/WorldOverlay.cs | 325 -------
.../Meddle.Plugin/UI/Archive/WorldService.cs | 101 --
Meddle/Meddle.Plugin/UI/Archive/WorldTab.cs | 430 ---------
Meddle/Meddle.Plugin/UI/DebugTab.cs | 54 +-
Meddle/Meddle.Plugin/UI/Layout/Config.cs | 54 +-
Meddle/Meddle.Plugin/UI/Layout/Instance.cs | 1 -
.../Meddle.Plugin/UI/Layout/LayoutWindow.cs | 2 +-
Meddle/Meddle.Plugin/UI/LiveCharacterTab.cs | 93 +-
Meddle/Meddle.Plugin/UI/OptionsTab.cs | 100 +-
Meddle/Meddle.Plugin/Utils/ExportUtil.cs | 25 +
Meddle/Meddle.Plugin/Utils/SkeletonUtils.cs | 133 ++-
Meddle/Meddle.Plugin/Utils/UIUtil.cs | 10 +-
Meddle/Meddle.Plugin/packages.lock.json | 6 +-
Meddle/Meddle.Utils/BoneNodeBuilder.cs | 6 +-
27 files changed, 482 insertions(+), 2476 deletions(-)
create mode 100644 Meddle/Meddle.Plugin/Models/Enums.cs
delete mode 100644 Meddle/Meddle.Plugin/Models/Groups.cs
delete mode 100644 Meddle/Meddle.Plugin/Models/MenuType.cs
create mode 100644 Meddle/Meddle.Plugin/Services/AnimationExportService.cs
delete mode 100644 Meddle/Meddle.Plugin/Services/ExportService.cs
delete mode 100644 Meddle/Meddle.Plugin/UI/Archive/CharacterTab.cs
delete mode 100644 Meddle/Meddle.Plugin/UI/Archive/WorldOverlay.cs
delete mode 100644 Meddle/Meddle.Plugin/UI/Archive/WorldService.cs
delete mode 100644 Meddle/Meddle.Plugin/UI/Archive/WorldTab.cs
create mode 100644 Meddle/Meddle.Plugin/Utils/ExportUtil.cs
diff --git a/Meddle/Meddle.Plugin/Meddle.Plugin.csproj b/Meddle/Meddle.Plugin/Meddle.Plugin.csproj
index ef77e47..d3b5f9a 100644
--- a/Meddle/Meddle.Plugin/Meddle.Plugin.csproj
+++ b/Meddle/Meddle.Plugin/Meddle.Plugin.csproj
@@ -27,7 +27,7 @@
-
+
diff --git a/Meddle/Meddle.Plugin/Models/Composer/CharacterComposer.cs b/Meddle/Meddle.Plugin/Models/Composer/CharacterComposer.cs
index ba41538..196e49b 100644
--- a/Meddle/Meddle.Plugin/Models/Composer/CharacterComposer.cs
+++ b/Meddle/Meddle.Plugin/Models/Composer/CharacterComposer.cs
@@ -20,11 +20,15 @@ public class CharacterComposer
private static TexFile? CubeMapTex;
private static PbdFile? PbdFile;
private static readonly object StaticFileLock = new();
+ private readonly SkeletonUtils.PoseMode poseMode;
+ private readonly bool includePose;
- public CharacterComposer(DataProvider dataProvider, Action? progress = null)
+ public CharacterComposer(DataProvider dataProvider, Configuration config, Action? progress = null)
{
this.dataProvider = dataProvider;
this.progress = progress;
+ includePose = config.IncludePose;
+ poseMode = config.PoseMode;
lock (StaticFileLock)
{
@@ -306,7 +310,7 @@ private bool HandleRootAttach(ParsedCharacterInfo characterInfo, SceneBuilder sc
BoneNodeBuilder? rootBone;
try
{
- bones = SkeletonUtils.GetBoneMap(characterInfo.Skeleton, true, out rootBone);
+ bones = SkeletonUtils.GetBoneMap(characterInfo.Skeleton, includePose ? poseMode : null, out rootBone);
if (rootBone == null)
{
Plugin.Logger?.LogWarning("Root bone not found");
@@ -402,7 +406,7 @@ public void ComposeModels(ParsedModelInfo[] models, GenderRace genderRace, Custo
BoneNodeBuilder? rootBone;
try
{
- bones = SkeletonUtils.GetBoneMap(skeleton, true, out rootBone);
+ bones = SkeletonUtils.GetBoneMap(skeleton, includePose ? poseMode : null, out rootBone);
if (rootBone == null)
{
Plugin.Logger?.LogWarning("Root bone not found");
diff --git a/Meddle/Meddle.Plugin/Models/Enums.cs b/Meddle/Meddle.Plugin/Models/Enums.cs
new file mode 100644
index 0000000..b3c323b
--- /dev/null
+++ b/Meddle/Meddle.Plugin/Models/Enums.cs
@@ -0,0 +1,18 @@
+namespace Meddle.Plugin.Models;
+
+public enum MenuType
+{
+ Default = 0,
+ Debug = 1,
+ Testing = 2,
+}
+
+[Flags]
+public enum ExportType
+{
+ // ReSharper disable InconsistentNaming
+ GLTF = 1,
+ GLB = 2,
+ OBJ = 4
+ // ReSharper restore InconsistentNaming
+}
diff --git a/Meddle/Meddle.Plugin/Models/Groups.cs b/Meddle/Meddle.Plugin/Models/Groups.cs
deleted file mode 100644
index ed80f08..0000000
--- a/Meddle/Meddle.Plugin/Models/Groups.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System.Numerics;
-using Meddle.Plugin.Models.Skeletons;
-using Meddle.Utils.Export;
-using Meddle.Utils.Files;
-
-namespace Meddle.Plugin.Models;
-
-public record CharacterGroup(
- CustomizeParameter CustomizeParams,
- CustomizeData CustomizeData,
- GenderRace GenderRace,
- MdlFileGroup[] MdlGroups,
- ParsedSkeleton Skeleton,
- AttachedModelGroup[] AttachedModelGroups);
-
-public record AttachedModelGroup(ParsedAttach Attach, MdlFileGroup[] MdlGroups, ParsedSkeleton Skeleton);
-
-public record MdlFileGroup(
- string CharacterPath,
- string Path,
- DeformerGroup? DeformerGroup,
- MdlFile MdlFile,
- IMtrlFileGroup[] MtrlFiles,
- Model.ShapeAttributeGroup? ShapeAttributeGroup);
-
-public record MtrlFileStubGroup(string Path) : IMtrlFileGroup;
-
-public interface IMtrlFileGroup;
-
-public record MtrlFileGroup(
- string MdlPath,
- string Path,
- MtrlFile MtrlFile,
- string ShpkPath,
- ShpkFile ShpkFile,
- TexResourceGroup[] TexFiles) : IMtrlFileGroup;
-
-public record TexResourceGroup(string MtrlPath, string Path, TextureResource Resource);
-
-public record DeformerGroup(string Path, ushort RaceSexId, ushort DeformerId);
diff --git a/Meddle/Meddle.Plugin/Models/MenuType.cs b/Meddle/Meddle.Plugin/Models/MenuType.cs
deleted file mode 100644
index 0d658fb..0000000
--- a/Meddle/Meddle.Plugin/Models/MenuType.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Meddle.Plugin.Models;
-
-public enum MenuType
-{
- Default = 0,
- Debug = 1,
- Testing = 2,
-}
diff --git a/Meddle/Meddle.Plugin/Models/Skeletons/ParsedHkaPose.cs b/Meddle/Meddle.Plugin/Models/Skeletons/ParsedHkaPose.cs
index 15bcf2d..a43ded2 100644
--- a/Meddle/Meddle.Plugin/Models/Skeletons/ParsedHkaPose.cs
+++ b/Meddle/Meddle.Plugin/Models/Skeletons/ParsedHkaPose.cs
@@ -10,23 +10,32 @@ public unsafe ParsedHkaPose(Pointer pose) : this(pose.Value) { }
public unsafe ParsedHkaPose(hkaPose* pose)
{
+ var boneCount = pose->Skeleton->Bones.Length;
+
var transforms = new List();
-
- var boneCount = pose->LocalPose.Length;
+ var syncedLocalPose = pose->GetSyncedPoseLocalSpace()->Data;
for (var i = 0; i < boneCount; ++i)
{
- var localSpace = pose->AccessBoneLocalSpace(i);
- if (localSpace == null)
- {
- throw new Exception("Failed to access bone local space");
- }
+ var localSpace = syncedLocalPose[i];
+ transforms.Add(new Transform(localSpace));
+ }
- transforms.Add(new Transform(*localSpace));
+ var modelTransforms = new List();
+ var modelSpace = pose->GetSyncedPoseModelSpace()->Data;
+ for (var i = 0; i < boneCount; ++i)
+ {
+ var model = modelSpace[i];
+ modelTransforms.Add(new Transform(model));
}
+
Pose = transforms;
+ ModelPose = modelTransforms;
}
[JsonIgnore]
public IReadOnlyList Pose { get; }
+
+ [JsonIgnore]
+ public IReadOnlyList ModelPose { get; }
}
diff --git a/Meddle/Meddle.Plugin/Plugin.cs b/Meddle/Meddle.Plugin/Plugin.cs
index 92add16..11bd9b8 100644
--- a/Meddle/Meddle.Plugin/Plugin.cs
+++ b/Meddle/Meddle.Plugin/Plugin.cs
@@ -4,8 +4,10 @@
using Dalamud.Configuration;
using Dalamud.IoC;
using Dalamud.Plugin;
+using Meddle.Plugin.Models;
using Meddle.Plugin.Services;
using Meddle.Plugin.UI.Layout;
+using Meddle.Plugin.Utils;
using Meddle.Utils.Files.SqPack;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -96,6 +98,9 @@ public void Dispose()
public class Configuration : IPluginConfiguration
{
+ public const ExportType DefaultExportType = ExportType.GLTF;
+ public const SkeletonUtils.PoseMode DefaultPoseMode = SkeletonUtils.PoseMode.Model;
+
[PluginService]
[JsonIgnore]
private IDalamudPluginInterface PluginInterface { get; set; } = null!;
@@ -106,12 +111,33 @@ public class Configuration : IPluginConfiguration
public bool OpenOnLoad { get; set; }
public bool DisableUserUiHide { get; set; }
public bool DisableAutomaticUiHide { get; set; }
- public bool DisableCutsceneUiHide { get; set; }
- public bool DisableGposeUiHide { get; set; }
+ public bool DisableCutsceneUiHide { get; set; } = true;
+ public bool DisableGposeUiHide { get; set; } = true;
public float WorldCutoffDistance { get; set; } = 100;
public Vector4 WorldDotColor { get; set; } = new(1f, 1f, 1f, 0.5f);
+
+ ///
+ /// Used to hide names in the UI
+ ///
public string PlayerNameOverride { get; set; } = string.Empty;
-
+
+ ///
+ /// If enabled, pose will be included at 0 on the timeline under the 'pose' track.
+ ///
+ public bool IncludePose { get; set; } = true;
+
+ ///
+ /// Indicates whether scaling should be taken from the model pose rather than the local pose.
+ ///
+ public SkeletonUtils.PoseMode PoseMode { get; set; } = DefaultPoseMode;
+
+ ///
+ /// GLTF = GLTF JSON
+ /// GLB = GLTF Binary
+ /// OBJ = Wavefront OBJ
+ ///
+ public ExportType ExportType { get; set; } = DefaultExportType;
+
public int Version { get; set; } = 1;
public LayoutWindow.LayoutConfig LayoutConfig { get; set; } = new();
diff --git a/Meddle/Meddle.Plugin/Services/AnimationExportService.cs b/Meddle/Meddle.Plugin/Services/AnimationExportService.cs
new file mode 100644
index 0000000..b5b2700
--- /dev/null
+++ b/Meddle/Meddle.Plugin/Services/AnimationExportService.cs
@@ -0,0 +1,106 @@
+using System.Diagnostics;
+using System.Numerics;
+using Meddle.Plugin.Models;
+using Meddle.Plugin.Utils;
+using Microsoft.Extensions.Logging;
+using SharpGLTF.Geometry;
+using SharpGLTF.Geometry.VertexTypes;
+using SharpGLTF.Materials;
+using SharpGLTF.Scenes;
+
+namespace Meddle.Plugin.Services;
+
+public class AnimationExportService : IDisposable, IService
+{
+ private static readonly ActivitySource ActivitySource = new("Meddle.Plugin.Utils.ExportUtil");
+ private readonly Configuration configuration;
+ private readonly ILogger logger;
+
+ public AnimationExportService(Configuration configuration, ILogger logger)
+ {
+ this.configuration = configuration;
+ this.logger = logger;
+ }
+
+ public void Dispose()
+ {
+ logger.LogDebug("Disposing ExportUtil");
+ }
+
+ public void ExportAnimation(List<(DateTime, AttachSet[])> frames, bool includePositionalData, string path, CancellationToken token = default)
+ {
+ try
+ {
+ using var activity = ActivitySource.StartActivity();
+ var boneSets = SkeletonUtils.GetAnimatedBoneMap(frames.ToArray(), configuration.PoseMode);
+ var startTime = frames.Min(x => x.Item1);
+ //var folder = GetPathForOutput();
+ var folder = path;
+ Directory.CreateDirectory(folder);
+ foreach (var (id, (bones, root, timeline)) in boneSets)
+ {
+ var scene = new SceneBuilder();
+ if (root == null) throw new InvalidOperationException("Root bone not found");
+ logger.LogInformation("Adding bone set {Id}", id);
+
+ if (includePositionalData)
+ {
+ var startPos = timeline.First().Attach.Transform.Translation;
+ foreach (var frameTime in timeline)
+ {
+ if (token.IsCancellationRequested) return;
+ var pos = frameTime.Attach.Transform.Translation;
+ var rot = frameTime.Attach.Transform.Rotation;
+ var scale = frameTime.Attach.Transform.Scale;
+ var time = SkeletonUtils.TotalSeconds(frameTime.Time, startTime);
+ root.UseTranslation().UseTrackBuilder("pose").WithPoint(time, pos - startPos);
+ root.UseRotation().UseTrackBuilder("pose").WithPoint(time, rot);
+ root.UseScale().UseTrackBuilder("pose").WithPoint(time, scale);
+ }
+ }
+
+ scene.AddNode(root);
+ scene.AddSkinnedMesh(GetDummyMesh(id), Matrix4x4.Identity, bones.Cast().ToArray());
+ var sceneGraph = scene.ToGltf2();
+ var outputPath = Path.Combine(folder, $"motion_{id}.gltf");
+ sceneGraph.SaveGLTF(outputPath);
+ }
+
+ Process.Start("explorer.exe", folder);
+ logger.LogInformation("Export complete");
+ }
+ catch (Exception e)
+ {
+ logger.LogError(e, "Failed to export animation");
+ throw;
+ }
+ }
+
+ // https://github.com/0ceal0t/Dalamud-VFXEditor/blob/be00131b93b3c6dd4014a4f27c2661093daf3a85/VFXEditor/Utils/Gltf/GltfSkeleton.cs#L132
+ public static MeshBuilder GetDummyMesh(string name = "DUMMY_MESH")
+ {
+ var dummyMesh = new MeshBuilder(name);
+ var material = new MaterialBuilder("material");
+
+ var p1 = new VertexPosition
+ {
+ Position = new Vector3(0.000001f, 0, 0)
+ };
+ var p2 = new VertexPosition
+ {
+ Position = new Vector3(0, 0.000001f, 0)
+ };
+ var p3 = new VertexPosition
+ {
+ Position = new Vector3(0, 0, 0.000001f)
+ };
+
+ dummyMesh.UsePrimitive(material).AddTriangle(
+ (p1, new VertexEmpty(), new VertexJoints4(0)),
+ (p2, new VertexEmpty(), new VertexJoints4(0)),
+ (p3, new VertexEmpty(), new VertexJoints4(0))
+ );
+
+ return dummyMesh;
+ }
+}
diff --git a/Meddle/Meddle.Plugin/Services/ComposerFactory.cs b/Meddle/Meddle.Plugin/Services/ComposerFactory.cs
index bec167b..2270571 100644
--- a/Meddle/Meddle.Plugin/Services/ComposerFactory.cs
+++ b/Meddle/Meddle.Plugin/Services/ComposerFactory.cs
@@ -45,11 +45,11 @@ public CharacterComposer CreateCharacterComposer(string? cacheDir = null, Action
Directory.CreateDirectory(cacheDir);
var dataProvider = CreateDataProvider(cacheDir, cancellationToken);
- return new CharacterComposer(dataProvider, progress);
+ return new CharacterComposer(dataProvider, configuration, progress);
}
public CharacterComposer CreateCharacterComposer(DataProvider dataProvider)
{
- return new CharacterComposer(dataProvider);
+ return new CharacterComposer(dataProvider, configuration);
}
}
diff --git a/Meddle/Meddle.Plugin/Services/ExportService.cs b/Meddle/Meddle.Plugin/Services/ExportService.cs
deleted file mode 100644
index 89ac5d1..0000000
--- a/Meddle/Meddle.Plugin/Services/ExportService.cs
+++ /dev/null
@@ -1,473 +0,0 @@
-using System.Collections.Concurrent;
-using System.Diagnostics;
-using System.Numerics;
-using Meddle.Plugin.Models;
-using Meddle.Plugin.Models.Layout;
-using Meddle.Plugin.Models.Skeletons;
-using Meddle.Plugin.Utils;
-using Meddle.Utils;
-using Meddle.Utils.Export;
-using Meddle.Utils.Files;
-using Meddle.Utils.Files.SqPack;
-using Meddle.Utils.Materials;
-using Microsoft.Extensions.Logging;
-using SharpGLTF.Geometry;
-using SharpGLTF.Geometry.VertexTypes;
-using SharpGLTF.Materials;
-using SharpGLTF.Scenes;
-using SharpGLTF.Transforms;
-using SkiaSharp;
-using Material = Meddle.Utils.Export.Material;
-using Model = Meddle.Utils.Export.Model;
-
-namespace Meddle.Plugin.Services;
-
-public class ExportService : IDisposable, IService
-{
- private static readonly ActivitySource ActivitySource = new("Meddle.Plugin.Utils.ExportUtil");
- private readonly TexFile catchlightTex;
- private readonly SqPack pack;
- private readonly ILogger logger;
- private readonly ParseService parseService;
- private readonly PbdFile pbdFile;
- private readonly TexFile tileNormTex;
- private readonly TexFile tileOrbTex;
-
- public ExportService(SqPack pack, ILogger logger, ParseService parseService)
- {
- this.pack = pack;
- this.logger = logger;
- this.parseService = parseService;
-
- // chara/xls/boneDeformer/human.pbd
- var pbdData = pack.GetFile("chara/xls/boneDeformer/human.pbd");
- if (pbdData == null) throw new Exception("Failed to load human.pbd");
- pbdFile = new PbdFile(pbdData.Value.file.RawData);
-
- var catchlight = pack.GetFile("chara/common/texture/sphere_d_array.tex");
- if (catchlight == null) throw new InvalidOperationException("Failed to get catchlight texture");
-
- var tileNorm = pack.GetFile("chara/common/texture/tile_norm_array.tex");
- if (tileNorm == null) throw new InvalidOperationException("Failed to get tile norm texture");
-
- var tileOrb = pack.GetFile("chara/common/texture/tile_orb_array.tex");
- if (tileOrb == null) throw new InvalidOperationException("Failed to get tile orb texture");
- tileNormTex = new TexFile(tileNorm.Value.file.RawData);
- tileOrbTex = new TexFile(tileOrb.Value.file.RawData);
- catchlightTex = new TexFile(catchlight.Value.file.RawData);
- }
-
- public void Dispose()
- {
- logger.LogDebug("Disposing ExportUtil");
- }
-
- private static string GetPathForOutput()
- {
- var now = DateTime.Now;
- var folder = Path.Combine(Plugin.TempDirectory, "output", now.ToString("yyyy-MM-dd-HH-mm-ss"));
- if (!Directory.Exists(folder))
- {
- Directory.CreateDirectory(folder);
- }
-
- return folder;
- }
-
- public static void ExportTexture(SKBitmap bitmap, string path)
- {
- using var activity = ActivitySource.StartActivity();
- activity?.SetTag("path", path);
- var folder = GetPathForOutput();
- var outputPath = Path.Combine(folder, $"{Path.GetFileNameWithoutExtension(path)}.png");
-
- using var str = new SKDynamicMemoryWStream();
- bitmap.Encode(str, SKEncodedImageFormat.Png, 100);
-
- var data = str.DetachAsData().AsSpan();
- File.WriteAllBytes(outputPath, data.ToArray());
- Process.Start("explorer.exe", folder);
- }
-
- public void ExportAnimation(List<(DateTime, AttachSet[])> frames, bool includePositionalData, string path, CancellationToken token = default)
- {
- try
- {
- using var activity = ActivitySource.StartActivity();
- var boneSets = SkeletonUtils.GetAnimatedBoneMap(frames.ToArray());
- var startTime = frames.Min(x => x.Item1);
- //var folder = GetPathForOutput();
- var folder = path;
- Directory.CreateDirectory(folder);
- foreach (var (id, (bones, root, timeline)) in boneSets)
- {
- var scene = new SceneBuilder();
- if (root == null) throw new InvalidOperationException("Root bone not found");
- logger.LogInformation("Adding bone set {Id}", id);
-
- if (includePositionalData)
- {
- var startPos = timeline.First().Attach.Transform.Translation;
- foreach (var frameTime in timeline)
- {
- if (token.IsCancellationRequested) return;
- var pos = frameTime.Attach.Transform.Translation;
- var rot = frameTime.Attach.Transform.Rotation;
- var scale = frameTime.Attach.Transform.Scale;
- var time = SkeletonUtils.TotalSeconds(frameTime.Time, startTime);
- root.UseTranslation().UseTrackBuilder("pose").WithPoint(time, pos - startPos);
- root.UseRotation().UseTrackBuilder("pose").WithPoint(time, rot);
- root.UseScale().UseTrackBuilder("pose").WithPoint(time, scale);
- }
- }
-
- scene.AddNode(root);
- scene.AddSkinnedMesh(GetDummyMesh(id), Matrix4x4.Identity, bones.Cast().ToArray());
- var sceneGraph = scene.ToGltf2();
- var outputPath = Path.Combine(folder, $"motion_{id}.gltf");
- sceneGraph.SaveGLTF(outputPath);
- }
-
- Process.Start("explorer.exe", folder);
- logger.LogInformation("Export complete");
- }
- catch (Exception e)
- {
- logger.LogError(e, "Failed to export animation");
- throw;
- }
- }
-
- // https://github.com/0ceal0t/Dalamud-VFXEditor/blob/be00131b93b3c6dd4014a4f27c2661093daf3a85/VFXEditor/Utils/Gltf/GltfSkeleton.cs#L132
- public static MeshBuilder GetDummyMesh(string name = "DUMMY_MESH")
- {
- var dummyMesh = new MeshBuilder(name);
- var material = new MaterialBuilder("material");
-
- var p1 = new VertexPosition
- {
- Position = new Vector3(0.000001f, 0, 0)
- };
- var p2 = new VertexPosition
- {
- Position = new Vector3(0, 0.000001f, 0)
- };
- var p3 = new VertexPosition
- {
- Position = new Vector3(0, 0, 0.000001f)
- };
-
- dummyMesh.UsePrimitive(material).AddTriangle(
- (p1, new VertexEmpty(), new VertexJoints4(0)),
- (p2, new VertexEmpty(), new VertexJoints4(0)),
- (p3, new VertexEmpty(), new VertexJoints4(0))
- );
-
- return dummyMesh;
- }
-
- public void Export(CharacterGroup characterGroup, string? outputFolder = null, CancellationToken token = default)
- {
- try
- {
- using var activity = ActivitySource.StartActivity();
- var scene = new SceneBuilder();
-
- var bones = SkeletonUtils.GetBoneMap(characterGroup.Skeleton, true, out var root);
- //var bones = XmlUtils.GetBoneMap(characterGroup.Skeletons, out var root);
- if (root != null)
- {
- scene.AddNode(root);
- }
-
- var meshOutput = new List<(Model model, ModelBuilder.MeshExport mesh)>();
- foreach (var mdlGroup in characterGroup.MdlGroups)
- {
- if (token.IsCancellationRequested) return;
- if (mdlGroup.Path.Contains("b0003_top")) continue;
- try
- {
- var meshes = HandleModel(characterGroup.CustomizeData, characterGroup.CustomizeParams,
- characterGroup.GenderRace, mdlGroup, ref bones, root, token);
- foreach (var mesh in meshes)
- {
- meshOutput.Add((mesh.model, mesh.mesh));
- }
- }
- catch (Exception e)
- {
- logger.LogError(e, "Failed to export model {Path}", mdlGroup.Path);
- throw;
- }
- }
-
- if (token.IsCancellationRequested) return;
-
- var meshOutputAttach =
- new List<(Matrix4x4 Position, Model Model, ModelBuilder.MeshExport Mesh, BoneNodeBuilder[] Bones)>();
- for (var i = 0; i < characterGroup.AttachedModelGroups.Length; i++)
- {
- var attachedModelGroup = characterGroup.AttachedModelGroups[i];
- var attachName = characterGroup.Skeleton.PartialSkeletons[attachedModelGroup.Attach.PartialSkeletonIdx]
- .HkSkeleton!.BoneNames[(int)attachedModelGroup.Attach.BoneIdx];
- var attachBones = SkeletonUtils.GetBoneMap(attachedModelGroup.Skeleton, true, out var attachRoot);
- logger.LogDebug("Adding attach {AttachName} to {ParentBone}", attachName, attachRoot?.BoneName);
- if (attachRoot == null)
- {
- throw new InvalidOperationException("Failed to get attach root");
- }
-
- attachRoot.SetSuffixRecursively(i);
-
- if (attachedModelGroup.Attach.OffsetTransform is { } ct)
- {
- attachRoot.WithLocalScale(ct.Scale);
- attachRoot.WithLocalRotation(ct.Rotation);
- attachRoot.WithLocalTranslation(ct.Translation);
- if (attachRoot.AnimationTracksNames.Contains("pose"))
- {
- attachRoot.UseScale().UseTrackBuilder("pose").WithPoint(0, ct.Scale);
- attachRoot.UseRotation().UseTrackBuilder("pose").WithPoint(0, ct.Rotation);
- attachRoot.UseTranslation().UseTrackBuilder("pose").WithPoint(0, ct.Translation);
- }
- }
-
- var attachPointBone =
- bones.FirstOrDefault(b => b.BoneName.Equals(attachName, StringComparison.Ordinal));
- if (attachPointBone == null)
- {
- scene.AddNode(attachRoot);
- }
- else
- {
- attachPointBone.AddNode(attachRoot);
- }
-
-
- var transform = Matrix4x4.Identity;
- NodeBuilder c = attachRoot;
- while (c != null)
- {
- transform *= c.LocalMatrix;
- c = c.Parent;
- }
-
- foreach (var mdlGroup in attachedModelGroup.MdlGroups)
- {
- var meshes = HandleModel(characterGroup.CustomizeData,
- characterGroup.CustomizeParams, characterGroup.GenderRace,
- mdlGroup, ref attachBones, attachPointBone, token);
- foreach (var mesh in meshes)
- {
- meshOutputAttach.Add((transform, mesh.model, mesh.mesh, attachBones.ToArray()));
- }
- }
- }
-
- foreach (var (model, mesh) in meshOutput)
- {
- if (token.IsCancellationRequested) return;
- AddMesh(scene, Matrix4x4.Identity, model, mesh, bones.ToArray());
- }
-
- foreach (var (position, model, mesh, attachBones) in meshOutputAttach)
- {
- if (token.IsCancellationRequested) return;
- AddMesh(scene, position, model, mesh, attachBones);
- }
-
- var sceneGraph = scene.ToGltf2();
- if (outputFolder != null)
- {
- Directory.CreateDirectory(outputFolder);
- }
-
- var folder = outputFolder ?? GetPathForOutput();
- var outputPath = Path.Combine(folder, "character.gltf");
- sceneGraph.SaveGLTF(outputPath);
- Process.Start("explorer.exe", folder);
- logger.LogInformation("Export complete");
- }
- catch (Exception e)
- {
- logger.LogError(e, "Failed to export character");
- throw;
- }
- }
-
- public static void AddMesh(
- SceneBuilder scene, Matrix4x4 position, Model model, ModelBuilder.MeshExport mesh, BoneNodeBuilder[] bones)
- {
- InstanceBuilder instance;
- if (bones.Length > 0)
- {
- instance = scene.AddSkinnedMesh(mesh.Mesh, position, bones.Cast().ToArray());
- }
- else
- {
- instance = scene.AddRigidMesh(mesh.Mesh, position);
- }
-
- ApplyMeshShapes(instance, model, mesh.Shapes);
-
- // Remove subMeshes that are not enabled
- if (mesh.Submesh != null)
- {
- var enabledAttributes = Model.GetEnabledValues(model.EnabledAttributeMask,
- model.AttributeMasks);
-
- if (!mesh.Submesh.Attributes.All(enabledAttributes.Contains))
- {
- instance.Remove();
- }
- }
- }
-
- private MaterialBuilder HandleMaterial(CustomizeData customizeData, CustomizeParameter customizeParams, MtrlFileGroup mtrlGroup, string? suffix = null)
- {
- var material = new Material(mtrlGroup.MdlPath, mtrlGroup.MtrlFile, mtrlGroup.TexFiles.ToDictionary(x => x.MtrlPath, x => x.Resource), mtrlGroup.ShpkFile);
- using var activityMtrl = ActivitySource.StartActivity();
- activityMtrl?.SetTag("mtrlPath", material.HandlePath);
-
- logger.LogInformation("Exporting {HandlePath} => {Path}", material.HandlePath, mtrlGroup.Path);
- activityMtrl?.SetTag("shaderPackageName", material.ShaderPackageName);
- var name =
- $"{Path.GetFileNameWithoutExtension(material.HandlePath)}_{Path.GetFileNameWithoutExtension(material.ShaderPackageName)}";
- if (suffix != null)
- {
- name += $"_{suffix}";
- }
- var builder = material.ShaderPackageName switch
- {
- "bg.shpk" => MaterialUtility.BuildBg(material, name),
- "bgprop.shpk" => MaterialUtility.BuildBgProp(material, name),
- "character.shpk" => MaterialUtility.BuildCharacter(material, name),
- "characterocclusion.shpk" => MaterialUtility.BuildCharacterOcclusion(material, name),
- "characterlegacy.shpk" => MaterialUtility.BuildCharacterLegacy(material, name),
- "charactertattoo.shpk" => MaterialUtility.BuildCharacterTattoo(
- material, name, customizeParams, customizeData),
- "hair.shpk" => MaterialUtility.BuildHair(material, name, customizeParams,
- customizeData),
- "skin.shpk" => MaterialUtility.BuildSkin(material, name, customizeParams,
- customizeData, (tileNormTex, tileOrbTex)),
- "iris.shpk" => MaterialUtility.BuildIris(material, name, catchlightTex, customizeParams,
- customizeData),
- "water.shpk" => MaterialUtility.BuildWater(material, name),
- "lightshaft.shpk" => MaterialUtility.BuildLightShaft(material, name),
- _ => BuildAndLogFallbackMaterial(material, name)
- };
-
- return builder;
- }
-
- private MaterialBuilder BuildAndLogFallbackMaterial(Material material, string name)
- {
- logger.LogWarning("[{Shpk}] Using fallback material for {Path}", material.ShaderPackageName, material.HandlePath);
- return MaterialUtility.BuildFallback(material, name);
- }
-
- private List<(Model model, ModelBuilder.MeshExport mesh)> HandleModel(
- CustomizeData customizeData,
- CustomizeParameter customizeParams,
- GenderRace genderRace,
- MdlFileGroup mdlGroup,
- ref List bones,
- BoneNodeBuilder? root,
- CancellationToken token,
- string? suffix = null)
- {
- using var activity = ActivitySource.StartActivity();
- activity?.SetTag("characterPath", mdlGroup.CharacterPath);
- activity?.SetTag("path", mdlGroup.Path);
- logger.LogInformation("Exporting {CharacterPath} => {Path}", mdlGroup.CharacterPath, mdlGroup.Path);
- var model = new Model(mdlGroup.CharacterPath, mdlGroup.MdlFile, mdlGroup.ShapeAttributeGroup);
-
- foreach (var mesh in model.Meshes)
- {
- if (mesh.BoneTable == null) continue;
-
- foreach (var boneName in mesh.BoneTable)
- {
- if (bones.All(b => !b.BoneName.Equals(boneName, StringComparison.Ordinal)))
- {
- logger.LogInformation("Adding bone {BoneName} from mesh {MeshPath}", boneName,
- mdlGroup.Path);
- var bone = new BoneNodeBuilder(boneName);
- if (root == null) throw new InvalidOperationException("Root bone not found");
- root.AddNode(bone);
- logger.LogInformation("Added bone {BoneName} to {ParentBone}", boneName, root.BoneName);
-
- bones.Add(bone);
- }
- }
- }
-
- var materials = new List();
- var meshOutput = new List<(Model, ModelBuilder.MeshExport)>();
- foreach (var mtrlFile in mdlGroup.MtrlFiles)
- {
- if (mtrlFile is MtrlFileGroup mtrlGroup)
- {
- if (token.IsCancellationRequested) return meshOutput;
- var builder = HandleMaterial(customizeData, customizeParams, mtrlGroup, suffix);
- materials.Add(builder);
- }
- else if (mtrlFile is MtrlFileStubGroup stub)
- {
- var stubName = Path.GetFileNameWithoutExtension(stub.Path);
- var builder = new MaterialBuilder($"{stubName}_stub");
- materials.Add(builder);
- }
- else
- {
- throw new InvalidOperationException("Unknown mtrl file type");
- }
- }
-
- if (token.IsCancellationRequested) return meshOutput;
-
- (GenderRace from, GenderRace to, RaceDeformer deformer)? deform;
- if (mdlGroup.DeformerGroup != null)
- {
- // custom pbd may exist
- var pbdFileData = pack.GetFileOrReadFromDisk(mdlGroup.DeformerGroup.Path);
- if (pbdFileData == null)
- throw new InvalidOperationException($"Failed to get deformer pbd {mdlGroup.DeformerGroup.Path}");
- deform = ((GenderRace)mdlGroup.DeformerGroup.DeformerId, (GenderRace)mdlGroup.DeformerGroup.RaceSexId, new RaceDeformer(new PbdFile(pbdFileData), bones));
- logger.LogDebug("Using deformer pbd {Path}", mdlGroup.DeformerGroup.Path);
- }
- else
- {
- var parsed = RaceDeformer.ParseRaceCode(mdlGroup.CharacterPath);
- if (Enum.IsDefined(parsed))
- {
- deform = (parsed, genderRace, new RaceDeformer(pbdFile, bones));
- }
- else
- {
- deform = null;
- }
- }
-
- var meshes = ModelBuilder.BuildMeshes(model, materials, bones, deform);
- foreach (var mesh in meshes)
- {
- meshOutput.Add((model, mesh));
- }
-
- return meshOutput;
- }
-
- private static void ApplyMeshShapes(InstanceBuilder builder, Model model, IReadOnlyList? appliedShapes)
- {
- if (model.Shapes.Count == 0 || appliedShapes == null) return;
-
- // This will set the morphing value to 1 if the shape is enabled, 0 if not
- var enabledShapes = Model.GetEnabledValues(model.EnabledShapeMask, model.ShapeMasks)
- .ToArray();
- var shapes = model.Shapes
- .Where(x => appliedShapes.Contains(x.Name))
- .Select(x => (x, enabledShapes.Contains(x.Name)));
- builder.Content.UseMorphing().SetValue(shapes.Select(x => x.Item2 ? 1f : 0).ToArray());
- }
-}
diff --git a/Meddle/Meddle.Plugin/Services/UI/CommonUI.cs b/Meddle/Meddle.Plugin/Services/UI/CommonUI.cs
index b18a140..399c224 100644
--- a/Meddle/Meddle.Plugin/Services/UI/CommonUI.cs
+++ b/Meddle/Meddle.Plugin/Services/UI/CommonUI.cs
@@ -1,5 +1,6 @@
using System.Numerics;
using Dalamud.Game.ClientState.Objects.Types;
+using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
@@ -77,8 +78,11 @@ public unsafe void DrawCharacterSelect(ref ICharacter? selectedCharacter)
if (clientState.IsGPosing)
{
- ImGui.SameLine();
- ImGui.TextDisabled("?");
+ ImGui.SameLine();
+ using (ImRaii.PushFont(UiBuilder.IconFont))
+ {
+ ImGui.Text(FontAwesomeIcon.QuestionCircle.ToIconString());
+ }
if (ImGui.IsItemHovered())
{
ImGui.BeginTooltip();
diff --git a/Meddle/Meddle.Plugin/UI/AnimationTab.cs b/Meddle/Meddle.Plugin/UI/AnimationTab.cs
index 1140494..d866633 100644
--- a/Meddle/Meddle.Plugin/UI/AnimationTab.cs
+++ b/Meddle/Meddle.Plugin/UI/AnimationTab.cs
@@ -16,7 +16,7 @@ namespace Meddle.Plugin.UI;
public class AnimationTab : ITab
{
- private readonly ExportService exportService;
+ private readonly AnimationExportService animationExportService;
private readonly CommonUi commonUi;
private readonly List<(DateTime Time, AttachSet[])> frames = [];
private readonly IFramework framework;
@@ -32,12 +32,12 @@ public class AnimationTab : ITab
public AnimationTab(
IFramework framework, ILogger logger,
- ExportService exportService,
+ AnimationExportService animationExportService,
CommonUi commonUi)
{
this.framework = framework;
this.logger = logger;
- this.exportService = exportService;
+ this.animationExportService = animationExportService;
this.commonUi = commonUi;
this.framework.Update += OnFrameworkUpdate;
}
@@ -83,7 +83,7 @@ public void Draw()
fileDialog.SaveFolderDialog("Save Animation", folderName, (result, path) =>
{
if (!result) return;
- exportService.ExportAnimation(frames, includePositionalData, path);
+ animationExportService.ExportAnimation(frames, includePositionalData, path);
}, Plugin.TempDirectory);
diff --git a/Meddle/Meddle.Plugin/UI/Archive/CharacterTab.cs b/Meddle/Meddle.Plugin/UI/Archive/CharacterTab.cs
deleted file mode 100644
index 9b9b482..0000000
--- a/Meddle/Meddle.Plugin/UI/Archive/CharacterTab.cs
+++ /dev/null
@@ -1,884 +0,0 @@
-/*using System.Numerics;
-using System.Runtime.InteropServices;
-using Dalamud.Game.ClientState.Objects.Types;
-using Dalamud.Interface.Textures;
-using Dalamud.Interface.Textures.TextureWraps;
-using Dalamud.Interface.Utility.Raii;
-using Dalamud.Plugin.Services;
-using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
-using FFXIVClientStructs.Interop;
-using ImGuiNET;
-using Meddle.Plugin.Models;
-using Meddle.Plugin.Services;
-using Meddle.Plugin.Utils;
-using Meddle.Utils;
-using Meddle.Utils.Export;
-using Meddle.Utils.Files.Structs.Material;
-using Meddle.Utils.Materials;
-using Meddle.Utils.Models;
-using Microsoft.Extensions.Logging;
-using CSCharacter = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
-using CustomizeParameter = Meddle.Utils.Export.CustomizeParameter;
-using Model = Meddle.Utils.Export.Model;
-
-namespace Meddle.Plugin.UI;
-
-public unsafe class CharacterTab : ITab
-{
- private readonly Dictionary channelCache = new();
-
- private readonly IClientState clientState;
- private readonly Configuration config;
- private readonly ExportService exportService;
- private readonly ILogger log;
- private readonly IObjectTable objectTable;
- private readonly ParseService parseService;
-
- private readonly Dictionary textureCache = new();
- private readonly ITextureProvider textureProvider;
-
- private CharacterGroup? characterGroup;
- private Task? exportTask;
- private CharacterGroup? selectedSetGroup;
-
- public CharacterTab(
- IObjectTable objectTable,
- IClientState clientState,
- ILogger log,
- ExportService exportService,
- ITextureProvider textureProvider,
- ParseService parseService,
- Configuration config)
- {
- this.log = log;
- this.exportService = exportService;
- this.parseService = parseService;
- this.config = config;
- this.objectTable = objectTable;
- this.clientState = clientState;
- this.textureProvider = textureProvider;
- }
-
- private ICharacter? SelectedCharacter { get; set; }
-
- private bool ExportTaskIncomplete => exportTask?.IsCompleted == false;
-
-
- private bool IsDisposed { get; set; }
- public string Name => "(Old)Character";
- public int Order => 999;
- public bool DisplayTab => config.ShowTesting;
-
- public void Draw()
- {
- DrawObjectPicker();
- }
-
- public void Dispose()
- {
- if (!IsDisposed)
- {
- log.LogDebug("Disposing CharacterTab");
- foreach (var (_, textureImage) in textureCache)
- {
- textureImage.Wrap.Dispose();
- }
-
- textureCache.Clear();
- IsDisposed = true;
- }
- }
-
- private void DrawObjectPicker()
- {
- // Warning text:
- ImGui.TextWrapped("NOTE: Exported models use a rudimentary approximation of the games pixel shaders, " +
- "they will likely not match 1:1 to the in-game appearance.");
-
- ICharacter[] objects;
- if (clientState.LocalPlayer != null)
- {
- objects = objectTable.OfType()
- .Where(obj => obj.IsValid() && obj.IsValidCharacterBase())
- .OrderBy(c => clientState.GetDistanceToLocalPlayer(c).LengthSquared())
- .ToArray();
- }
- else
- {
- // login/char creator produces "invalid" characters but are still usable I guess
- objects = objectTable.OfType()
- .Where(obj => obj.IsValidHuman())
- .OrderBy(c => clientState.GetDistanceToLocalPlayer(c).LengthSquared())
- .ToArray();
- }
-
- SelectedCharacter ??= objects.FirstOrDefault() ?? clientState.LocalPlayer;
-
- ImGui.Text("Select Character");
- var preview = SelectedCharacter != null
- ? clientState.GetCharacterDisplayText(SelectedCharacter, config.PlayerNameOverride)
- : "None";
- using (var combo = ImRaii.Combo("##Character", preview))
- {
- if (combo)
- {
- foreach (var character in objects)
- {
- if (ImGui.Selectable(clientState.GetCharacterDisplayText(character, config.PlayerNameOverride)))
- {
- SelectedCharacter = character;
- }
- }
- }
- }
-
- if (exportTask is {IsFaulted: true})
- {
- ImGui.Text("An Error Occured: " + exportTask.Exception?.Message);
- }
-
- if (SelectedCharacter != null)
- {
- ImGui.BeginDisabled(ExportTaskIncomplete);
- if (ImGui.Button("Parse"))
- {
- exportTask = ParseCharacter(SelectedCharacter);
- }
-
- ImGui.EndDisabled();
- }
- else
- {
- ImGui.TextWrapped("No character selected");
- }
-
-
- if (characterGroup == null)
- {
- ImGui.Text("Parse a character to view data");
- return;
- }
-
- DrawCharacterGroup();
- }
-
- private void DrawCharacterGroup()
- {
- ImGui.PushID(characterGroup!.GetHashCode());
- var availWidth = ImGui.GetContentRegionAvail().X;
- ImGui.Columns(2, "Customize", true);
- try
- {
- ImGui.SetColumnWidth(0, availWidth * 0.8f);
- ImGui.Text("Customize Parameters");
- ImGui.NextColumn();
- ImGui.Text("Customize Data");
- ImGui.NextColumn();
- ImGui.Separator();
- // draw customizeparams
- var customizeParams = characterGroup.CustomizeParams;
- UiUtil.DrawCustomizeParams(ref customizeParams);
- ImGui.NextColumn();
- // draw customize data
-
- var customizeData = characterGroup.CustomizeData;
- UiUtil.DrawCustomizeData(customizeData);
- ImGui.Text(characterGroup.GenderRace.ToString());
-
- ImGui.NextColumn();
- } finally
- {
- ImGui.Columns(1);
- }
-
- ImGui.Separator();
-
- DrawExportOptions();
-
- if (ImGui.BeginTable("CharacterTable", 2, ImGuiTableFlags.Borders | ImGuiTableFlags.Resizable))
- {
- ImGui.TableSetupColumn("Options", ImGuiTableColumnFlags.WidthFixed, 100);
- // Export button, export toggle checkbox
- ImGui.TableSetupColumn("Character Data", ImGuiTableColumnFlags.WidthStretch);
- // Character data
- ImGui.TableHeadersRow();
-
- foreach (var mdlGroup in characterGroup.MdlGroups)
- {
- ImGui.PushID(mdlGroup.GetHashCode());
- ImGui.TableNextRow();
- ImGui.TableSetColumnIndex(0);
- if (ImGui.Button("Export"))
- {
- exportTask = Task.Run(() =>
- {
- try
- {
- exportService.Export(characterGroup with
- {
- MdlGroups = [mdlGroup], AttachedModelGroups = []
- });
- }
- catch (Exception e)
- {
- log.LogError(e, "Failed to export mdl group");
- throw;
- }
- });
- }
-
- if (selectedSetGroup != null)
- {
- ImGui.SameLine();
- // checkbox for selecting
- var selected = selectedSetGroup.MdlGroups.Contains(mdlGroup);
- if (ImGui.Checkbox($"##{mdlGroup.GetHashCode()}", ref selected))
- {
- // if selected, make sure mdlgroup is in selectedSetGroup
- if (selected)
- {
- selectedSetGroup = selectedSetGroup with
- {
- MdlGroups = selectedSetGroup.MdlGroups.Append(mdlGroup).ToArray()
- };
- }
- else
- {
- selectedSetGroup = selectedSetGroup with
- {
- MdlGroups = selectedSetGroup.MdlGroups.Where(m => m != mdlGroup).ToArray()
- };
- }
- }
- }
-
- ImGui.TableSetColumnIndex(1);
- if (ImGui.CollapsingHeader(mdlGroup.CharacterPath))
- {
- ImGui.Indent();
- DrawMdlGroup(mdlGroup);
- ImGui.Unindent();
- }
-
- ImGui.PopID();
- }
-
- ImGui.EndTable();
- }
-
-
- if (characterGroup.AttachedModelGroups.Length > 0)
- {
- if (ImGui.BeginTable("AttachedCharacterTable", 2, ImGuiTableFlags.Borders | ImGuiTableFlags.Resizable))
- {
- ImGui.TableSetupColumn("Options", ImGuiTableColumnFlags.WidthFixed, 100);
- // Export button, export toggle checkbox
- ImGui.TableSetupColumn("Attached Character Data", ImGuiTableColumnFlags.WidthStretch);
- // Character data
- ImGui.TableHeadersRow();
-
- foreach (var attachedModelGroup in characterGroup.AttachedModelGroups)
- {
- ImGui.PushID(attachedModelGroup.GetHashCode());
- ImGui.TableNextRow();
- ImGui.TableSetColumnIndex(0);
- if (ImGui.Button("Export"))
- {
- exportTask = Task.Run(() =>
- {
- try
- {
- exportService.Export(characterGroup with
- {
- AttachedModelGroups = [attachedModelGroup], MdlGroups = []
- });
- }
- catch (Exception e)
- {
- log.LogError(e, "Failed to export attached model group");
- throw;
- }
- });
- }
-
- if (selectedSetGroup != null)
- {
- ImGui.SameLine();
- // checkbox for selecting
- var selected = selectedSetGroup.AttachedModelGroups.Contains(attachedModelGroup);
- if (ImGui.Checkbox($"##{attachedModelGroup.GetHashCode()}", ref selected))
- {
- // if selected, make sure mdlgroup is in selectedSetGroup
- if (selected)
- {
- selectedSetGroup = selectedSetGroup with
- {
- AttachedModelGroups = selectedSetGroup.AttachedModelGroups
- .Append(attachedModelGroup).ToArray()
- };
- }
- else
- {
- selectedSetGroup = selectedSetGroup with
- {
- AttachedModelGroups = selectedSetGroup.AttachedModelGroups
- .Where(m => m != attachedModelGroup).ToArray()
- };
- }
- }
- }
-
- ImGui.TableSetColumnIndex(1);
- foreach (var mdlGroup in attachedModelGroup.MdlGroups)
- {
- ImGui.PushID(mdlGroup.GetHashCode());
- if (ImGui.CollapsingHeader(mdlGroup.CharacterPath))
- {
- ImGui.Indent();
- DrawMdlGroup(mdlGroup);
- ImGui.Unindent();
- }
-
- ImGui.PopID();
- }
-
- ImGui.PopID();
- }
-
- ImGui.EndTable();
- }
- }
-
-
- ImGui.PopID();
- }
-
- private void DrawExportOptions()
- {
- if (characterGroup == null) return;
- var availWidth = ImGui.GetContentRegionAvail().X;
-
- ImGui.BeginDisabled(ExportTaskIncomplete);
- if (ImGui.Button("Export All"))
- {
- exportTask = Task.Run(() =>
- {
- try
- {
- exportService.Export(characterGroup);
- }
- catch (Exception e)
- {
- log.LogError(e, "Failed to export character");
- throw;
- }
- });
- }
-
- ImGui.SameLine();
- var selectedCount = (selectedSetGroup?.MdlGroups.Length ?? 0) +
- (selectedSetGroup?.AttachedModelGroups.Length ?? 0);
- if (ImGui.Button($"Export {selectedCount} Selected##ExportSelected"))
- {
- if (selectedSetGroup == null)
- {
- log.LogWarning("No selected set group");
- return;
- }
-
- exportTask = Task.Run(() =>
- {
- try
- {
- exportService.Export(selectedSetGroup);
- }
- catch (Exception e)
- {
- log.LogError(e, "Failed to export selected set group");
- throw;
- }
- });
- }
-
- ImGui.SameLine();
- if (ImGui.Button("Export Raw Textures"))
- {
- exportTask = Task.Run(() =>
- {
- try
- {
- exportService.ExportRawTextures(characterGroup);
- }
- catch (Exception e)
- {
- log.LogError(e, "Failed to export raw textures");
- throw;
- }
- });
- }
-
- ImGui.EndDisabled();
- }
-
- private Task ParseCharacter(ICharacter character)
- {
- if (!exportTask?.IsCompleted ?? false)
- {
- return exportTask;
- }
-
- var charPtr = (CSCharacter*)character.Address;
- var drawObject = charPtr->GameObject.DrawObject;
- if (drawObject == null)
- {
- return Task.CompletedTask;
- }
-
- var objectType = drawObject->Object.GetObjectType();
- if (objectType != ObjectType.CharacterBase)
- throw new InvalidOperationException($"Object type is not CharacterBase: {objectType}");
-
- var modelType = ((CharacterBase*)drawObject)->GetModelType();
- var characterBase = (CharacterBase*)drawObject;
- CustomizeParameter customizeParams;
- CustomizeData customizeData;
- GenderRace genderRace;
- if (modelType == CharacterBase.ModelType.Human)
- {
- var human = (Human*)drawObject;
- var customizeCBuf = human->CustomizeParameterCBuffer->TryGetBuffer()[0];
- customizeParams = new CustomizeParameter
- {
- SkinColor = customizeCBuf.SkinColor,
- MuscleTone = customizeCBuf.MuscleTone,
- SkinFresnelValue0 = customizeCBuf.SkinFresnelValue0,
- LipColor = customizeCBuf.LipColor,
- MainColor = customizeCBuf.MainColor,
- FacePaintUVMultiplier = customizeCBuf.FacePaintUVMultiplier,
- HairFresnelValue0 = customizeCBuf.HairFresnelValue0,
- MeshColor = customizeCBuf.MeshColor,
- FacePaintUVOffset = customizeCBuf.FacePaintUVOffset,
- LeftColor = customizeCBuf.LeftColor,
- RightColor = customizeCBuf.RightColor,
- OptionColor = customizeCBuf.OptionColor
- };
- customizeData = new CustomizeData
- {
- LipStick = human->Customize.Lipstick,
- Highlights = human->Customize.Highlights
- };
- genderRace = (GenderRace)human->RaceSexId;
- }
- else
- {
- customizeParams = new CustomizeParameter();
- customizeData = new CustomizeData();
- genderRace = GenderRace.Unknown;
- }
-
- var colorTableTextures = parseService.ParseColorTableTextures(characterBase);
-
- var attachDict = new Dictionary, Dictionary>();
- if (charPtr->Mount.MountObject != null)
- {
- var mountDrawObject = charPtr->Mount.MountObject->GameObject.DrawObject;
- if (mountDrawObject != null && mountDrawObject->Object.GetObjectType() == ObjectType.CharacterBase)
- {
- var mountBase = (CharacterBase*)mountDrawObject;
- var mountColorTableTextures = parseService.ParseColorTableTextures(mountBase);
- attachDict[mountBase] = mountColorTableTextures;
- }
- }
-
- if (charPtr->OrnamentData.OrnamentObject != null)
- {
- var ornamentDrawObject = charPtr->OrnamentData.OrnamentObject->DrawObject;
- if (ornamentDrawObject != null && ornamentDrawObject->Object.GetObjectType() == ObjectType.CharacterBase)
- {
- var ornamentBase = (CharacterBase*)ornamentDrawObject;
- var ornamentColorTableTextures = parseService.ParseColorTableTextures(ornamentBase);
- attachDict[ornamentBase] = ornamentColorTableTextures;
- }
- }
-
- if (charPtr->DrawData.IsWeaponHidden == false)
- {
- var weaponDataSpan = charPtr->DrawData.WeaponData;
- foreach (var weaponData in weaponDataSpan)
- {
- var draw = weaponData.DrawObject;
- if (draw == null)
- {
- continue;
- }
-
- if (draw->Object.GetObjectType() != ObjectType.CharacterBase)
- {
- continue;
- }
-
- var weaponBase = (CharacterBase*)draw;
- var weaponColorTableTextures = parseService.ParseColorTableTextures(weaponBase);
- attachDict[weaponBase] = weaponColorTableTextures;
- }
- }
-
- // begin background work
- try
- {
- characterGroup = parseService.HandleCharacterGroup(characterBase, colorTableTextures, attachDict,
- customizeParams, customizeData, genderRace);
- selectedSetGroup = characterGroup;
- }
- catch (Exception e)
- {
- log.LogError(e, "Failed to parse character");
- throw;
- }
-
- return Task.CompletedTask;
- }
-
- private void DrawMdlGroup(MdlFileGroup mdlGroup)
- {
- ImGui.Text($"Character Path: {mdlGroup.CharacterPath}");
- ImGui.Text($"Path: {mdlGroup.Path}");
- ImGui.Text($"Mtrl Files: {mdlGroup.MtrlFiles.Length}");
-
- if (mdlGroup.DeformerGroup != null)
- {
- ImGui.Text($"Deformer Path: {mdlGroup.DeformerGroup.Path}");
- ImGui.Text($"RaceSexId: {mdlGroup.DeformerGroup.RaceSexId}");
- ImGui.Text($"DeformerId: {mdlGroup.DeformerGroup.DeformerId}");
- }
- else
- {
- ImGui.Text("No Deformer Group");
- }
-
- var shouldShowShapeAttributeMenu =
- mdlGroup.ShapeAttributeGroup is {ShapeMasks.Length: > 0} or {AttributeMasks.Length: > 0};
-
- if (shouldShowShapeAttributeMenu && ImGui.CollapsingHeader("Shape/Attribute Masks"))
- {
- var enabledShapes = Model.GetEnabledValues(mdlGroup.ShapeAttributeGroup!.EnabledShapeMask,
- mdlGroup.ShapeAttributeGroup.ShapeMasks).ToArray();
- var enabledAttributes = Model.GetEnabledValues(mdlGroup.ShapeAttributeGroup.EnabledAttributeMask,
- mdlGroup.ShapeAttributeGroup.AttributeMasks).ToArray();
-
- if (ImGui.BeginTable("ShapeAttributeTable", 2, ImGuiTableFlags.Borders | ImGuiTableFlags.Resizable))
- {
- ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, 100);
- ImGui.TableSetupColumn("Enabled", ImGuiTableColumnFlags.WidthStretch);
- ImGui.TableHeadersRow();
-
- foreach (var shape in mdlGroup.ShapeAttributeGroup.ShapeMasks)
- {
- ImGui.TableNextRow();
- ImGui.TableSetColumnIndex(0);
- ImGui.Text($"[{shape.id}] {shape.name}");
- ImGui.TableSetColumnIndex(1);
- ImGui.Text(enabledShapes.Contains(shape.name) ? "Yes" : "No");
- }
-
- foreach (var attribute in mdlGroup.ShapeAttributeGroup.AttributeMasks)
- {
- ImGui.TableNextRow();
- ImGui.TableSetColumnIndex(0);
- ImGui.Text($"[{attribute.id}] {attribute.name}");
- ImGui.TableSetColumnIndex(1);
- ImGui.Text(enabledAttributes.Contains(attribute.name) ? "Yes" : "No");
- }
-
- ImGui.EndTable();
- }
- }
-
- foreach (var mtrlGroup in mdlGroup.MtrlFiles)
- {
- ImGui.PushID(mtrlGroup.GetHashCode());
- if (ImGui.CollapsingHeader($"{mtrlGroup.MdlPath}"))
- {
- try
- {
- ImGui.Indent();
- DrawMtrlGroup(mtrlGroup);
- } finally
- {
- ImGui.Unindent();
- }
- }
-
- ImGui.PopID();
- }
- }
-
- private void DrawMtrlGroup(MtrlFileGroup mtrlGroup)
- {
- ImGui.Text($"Mdl Path: {mtrlGroup.MdlPath}");
- ImGui.Text($"Path: {mtrlGroup.Path}");
- ImGui.Text($"Shpk Path: {mtrlGroup.ShpkPath}");
- ImGui.Text($"Tex Files: {mtrlGroup.TexFiles.Length}");
-
- if (ImGui.CollapsingHeader($"Constants##{mtrlGroup.GetHashCode()}"))
- {
- if (ImGui.BeginTable("ConstantsTable", 4, ImGuiTableFlags.Borders | ImGuiTableFlags.Resizable))
- {
- ImGui.TableSetupColumn("ID", ImGuiTableColumnFlags.WidthFixed, 100);
- ImGui.TableSetupColumn("Offset", ImGuiTableColumnFlags.WidthFixed, 100);
- ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, 100);
- ImGui.TableSetupColumn("Values", ImGuiTableColumnFlags.WidthStretch);
- ImGui.TableHeadersRow();
-
- foreach (var constant in mtrlGroup.MtrlFile.Constants)
- {
- var index = constant.ValueOffset / 4;
- var count = constant.ValueSize / 4;
- var buf = new List(128);
- for (var j = 0; j < count; j++)
- {
- var value = mtrlGroup.MtrlFile.ShaderValues[index + j];
- var bytes = BitConverter.GetBytes(value);
- buf.AddRange(bytes);
- }
-
- // Display as floats
- var floats = MemoryMarshal.Cast(buf.ToArray());
-
- ImGui.TableNextRow();
- ImGui.TableSetColumnIndex(0);
- ImGui.Text($"0x{constant.ConstantId:X4}");
- // If has named value in MaterialConstant enum, display
- if (Enum.IsDefined(typeof(MaterialConstant), constant.ConstantId))
- {
- ImGui.SameLine();
- ImGui.Text($"({(MaterialConstant)constant.ConstantId})");
- }
-
- ImGui.TableSetColumnIndex(1);
- ImGui.Text($"{constant.ValueOffset:X4}");
-
- ImGui.TableSetColumnIndex(2);
- ImGui.Text($"{count}");
-
- ImGui.TableSetColumnIndex(3);
- ImGui.Text(string.Join(", ", floats.ToArray()));
- }
-
- ImGui.EndTable();
- }
- }
-
- if (ImGui.CollapsingHeader($"Shader Keys##{mtrlGroup.GetHashCode()}"))
- {
- var keys = mtrlGroup.MtrlFile.ShaderKeys;
- if (ImGui.BeginTable("ShaderKeysTable", 2, ImGuiTableFlags.Borders | ImGuiTableFlags.Resizable))
- {
- ImGui.TableSetupColumn("Category", ImGuiTableColumnFlags.WidthFixed, 150);
- ImGui.TableSetupColumn("Value", ImGuiTableColumnFlags.WidthStretch);
- ImGui.TableHeadersRow();
-
- foreach (var key in keys)
- {
- ImGui.TableNextRow();
- ImGui.TableSetColumnIndex(0);
- ImGui.Text($"0x{key.Category:X8}");
- if (Enum.IsDefined(typeof(ShaderCategory), key.Category))
- {
- ImGui.SameLine();
- ImGui.Text($"({(ShaderCategory)key.Category})");
- }
-
- ImGui.TableSetColumnIndex(1);
- var catText = $"0x{key.Value:X8}";
- var catSuffix = (ShaderCategory)key.Category switch
- {
- ShaderCategory.CategoryHairType => $" ({(HairType)key.Value})",
- ShaderCategory.CategorySkinType => $" ({(SkinType)key.Value})",
- ShaderCategory.CategoryFlowMapType => $" ({(FlowType)key.Value})",
- ShaderCategory.CategoryTextureType => $" ({(TextureMode)key.Value})",
- ShaderCategory.CategorySpecularType => $" ({(SpecularMode)key.Value})",
- _ => ""
- };
-
- ImGui.Text($"{catText}{catSuffix}");
- }
-
- ImGui.EndTable();
- }
- }
-
-
- if (ImGui.CollapsingHeader($"Color Table##{mtrlGroup.GetHashCode()}"))
- {
- UiUtil.DrawColorTable(mtrlGroup.MtrlFile);
- }
-
- foreach (var texGroup in mtrlGroup.TexFiles)
- {
- if (ImGui.CollapsingHeader($"{texGroup.MtrlPath}##{texGroup.GetHashCode()}"))
- {
- DrawTexGroup(texGroup);
- }
- }
- }
-
- private void DrawTexGroup(TexResourceGroup texGroup)
- {
- ImGui.Text($"Mtrl Path: {texGroup.MtrlPath}");
- ImGui.Text($"Path: {texGroup.Path}");
-
- ImGui.PushID(texGroup.GetHashCode());
- try
- {
- DrawTexFile(texGroup.MtrlPath, texGroup.Resource);
- } finally
- {
- ImGui.PopID();
- }
- }
-
- private void DrawTexFile(string path, TextureResource file)
- {
- ImGui.Text($"Width: {file.Width}");
- ImGui.Text($"Height: {file.Height}");
- //ImGui.Text($"Depth: {file}");
- ImGui.Text($"Mipmaps: {file.MipLevels}");
-
- // select channels
- if (!channelCache.TryGetValue(path, out var channels))
- {
- channels = Channel.Rgb;
- channelCache[path] = channels;
- }
-
- var channelsInt = (int)channels;
- var changed = false;
- ImGui.Text("Channels");
- if (ImGui.CheckboxFlags($"Red##{path}", ref channelsInt, (int)Channel.Red))
- {
- changed = true;
- }
-
- ImGui.SameLine();
- if (ImGui.CheckboxFlags($"Green##{path}", ref channelsInt, (int)Channel.Green))
- {
- changed = true;
- }
-
- ImGui.SameLine();
- if (ImGui.CheckboxFlags($"Blue##{path}", ref channelsInt, (int)Channel.Blue))
- {
- changed = true;
- }
-
- ImGui.SameLine();
- if (ImGui.CheckboxFlags($"Alpha##{path}", ref channelsInt, (int)Channel.Alpha))
- {
- changed = true;
- }
-
- channels = (Channel)channelsInt;
- channelCache[path] = channels;
-
- if (changed)
- {
- textureCache.Remove(path);
- }
-
- if (!textureCache.TryGetValue(path, out var textureImage))
- {
- var texture = new Texture(file, path, null, null, null);
- var bitmap = texture.ToTexture();
-
- // remove channels
- if (channels != Channel.All)
- {
- for (var x = 0; x < bitmap.Width; x++)
- for (var y = 0; y < bitmap.Height; y++)
- {
- var pixel = bitmap[x, y];
- var newPixel = new Vector4();
- if (channels.HasFlag(Channel.Red))
- {
- newPixel.X = pixel.Red / 255f;
- }
-
- if (channels.HasFlag(Channel.Green))
- {
- newPixel.Y = pixel.Green / 255f;
- }
-
- if (channels.HasFlag(Channel.Blue))
- {
- newPixel.Z = pixel.Blue / 255f;
- }
-
- if (channels.HasFlag(Channel.Alpha))
- {
- newPixel.W = pixel.Alpha / 255f;
- }
- else
- {
- newPixel.W = 1f;
- }
-
- // if only alpha, set rgb to alpha and alpha to 1
- if (channels == Channel.Alpha)
- {
- newPixel.X = newPixel.W;
- newPixel.Y = newPixel.W;
- newPixel.Z = newPixel.W;
- newPixel.W = 1f;
- }
-
- bitmap[x, y] = newPixel.ToSkColor();
- }
- }
-
- var pixelSpan = bitmap.Bitmap.GetPixelSpan();
- var pixelsCopy = new byte[pixelSpan.Length];
- pixelSpan.CopyTo(pixelsCopy);
- var wrap = textureProvider.CreateFromRaw(
- RawImageSpecification.Rgba32(file.Width, file.Height), pixelsCopy,
- "Meddle.Texture");
-
- textureImage = new TextureImage(bitmap, wrap);
- textureCache[path] = textureImage;
- }
-
- var availableWidth = ImGui.GetContentRegionAvail().X;
- float displayWidth;
- float displayHeight;
- if (file.Width > availableWidth)
- {
- displayWidth = availableWidth;
- displayHeight = file.Height * (displayWidth / file.Width);
- }
- else
- {
- displayWidth = file.Width;
- displayHeight = file.Height;
- }
-
- ImGui.Image(textureImage.Wrap.ImGuiHandle, new Vector2(displayWidth, displayHeight));
-
- if (ImGui.Button("Export as .png"))
- {
- ExportService.ExportTexture(textureImage.Bitmap.Bitmap, path);
- }
- }
-
- private enum Channel
- {
- Red = 1,
- Green = 2,
- Blue = 4,
- Alpha = 8,
- Rgb = Red | Green | Blue,
- All = Red | Green | Blue | Alpha
- }
-
- private record TextureImage(SKTexture Bitmap, IDalamudTextureWrap Wrap);
-}
-*/
diff --git a/Meddle/Meddle.Plugin/UI/Archive/WorldOverlay.cs b/Meddle/Meddle.Plugin/UI/Archive/WorldOverlay.cs
deleted file mode 100644
index e46f657..0000000
--- a/Meddle/Meddle.Plugin/UI/Archive/WorldOverlay.cs
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
-using System.Numerics;
-using Dalamud.Interface.Utility;
-using Dalamud.Interface.Utility.Raii;
-using Dalamud.Plugin.Services;
-using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
-using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
-using FFXIVClientStructs.Interop;
-using ImGuiNET;
-using Meddle.Plugin.Models.Structs;
-using Meddle.Plugin.Services;
-using Microsoft.Extensions.Logging;
-using Object = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object;
-
-namespace Meddle.Plugin.UI;
-
-public class WorldOverlay : IOverlay
-{
- private readonly ILogger log;
- private readonly WorldService worldService;
- private readonly IClientState clientState;
- private readonly IGameGui gui;
- private readonly SigUtil sigUtil;
- private readonly HousingService housingService;
-
- public WorldOverlay(
- ILogger log,
- WorldService worldService,
- IClientState clientState,
- IGameGui gui,
- SigUtil sigUtil,
- HousingService housingService)
- {
- this.log = log;
- this.worldService = worldService;
- this.clientState = clientState;
- this.gui = gui;
- this.sigUtil = sigUtil;
- this.housingService = housingService;
- }
-
- private unsafe List> RecurseWorldObjects(
- Object.SiblingEnumerator siblingEnumerator, HashSet> visited)
- {
- var worldObjects = new List>();
- foreach (var childObject in siblingEnumerator)
- {
- if (childObject == null)
- continue;
- if (!visited.Add(childObject))
- continue;
- worldObjects.Add(childObject);
- worldObjects.AddRange(RecurseWorldObjects(childObject->ChildObjects, visited));
- }
-
- return worldObjects;
- }
-
- public unsafe bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos) {
- var device = sigUtil.GetDevice();
- var camera = sigUtil.GetCamera();
- float width = device->Width;
- float height = device->Height;
- var pCoords = Vector4.Transform(new Vector4(worldPos, 1f), camera->ViewMatrix * camera->RenderCamera->ProjectionMatrix);
- if (Math.Abs(pCoords.W) < float.Epsilon) {
- screenPos = Vector2.Zero;
- return false;
- }
-
- pCoords *= MathF.Abs(1.0f / pCoords.W);
- screenPos = new Vector2 {
- X = (pCoords.X + 1.0f) * width * 0.5f,
- Y = (1.0f - pCoords.Y) * height * 0.5f
- };
-
- return IsOnScreen(new Vector3(pCoords.X, pCoords.Y, pCoords.Z));
-
- static bool IsOnScreen(Vector3 pos) {
- return -1.0 <= pos.X && pos.X <= 1.0 && -1.0 <= pos.Y && pos.Y <= 1.0 && pos.Z <= 1.0 && 0.0 <= pos.Z;
- }
- }
-
- public unsafe void DrawWorldOverlay()
- {
- var world = sigUtil.GetWorld();
- if (world == null)
- {
- log.LogError("World instance is null");
- return;
- }
-
- var camera = sigUtil.GetCamera();
- var localPos = clientState.LocalPlayer?.Position ?? Vector3.Zero;
- var worldObjects = RecurseWorldObjects(world->ChildObjects, []);
-
- var hoveredInFrame = new List>();
- foreach (var wo in worldObjects)
- {
- if (wo == null || wo.Value == null)
- continue;
-
- var childObject = wo.Value;
- var drawObject = (DrawObject*)childObject;
- if (!drawObject->IsVisible) continue;
- if (Vector3.Abs(childObject->Position - camera->Position).Length() > worldService.CutoffDistance) continue;
- Vector2 screenPos;
- if (worldService.ResolveUsingGameGui)
- {
- if (!gui.WorldToScreen(childObject->Position, out screenPos)) continue;
- }
- else
- {
- if (!camera->WorldToScreen(childObject->Position, out var csScreenPos)) continue;
- screenPos = csScreenPos;
- }
-
- var bg = ImGui.GetBackgroundDrawList();
- bg.AddCircleFilled(screenPos, 5, ImGui.GetColorU32(worldService.DotColor));
-
- if (ImGui.IsMouseHoveringRect(screenPos - new Vector2(5, 5),
- screenPos + new Vector2(5, 5)) &&
- !ImGui.IsWindowHovered(ImGuiHoveredFlags.AnyWindow))
- {
- hoveredInFrame.Add(childObject);
- }
- }
-
- if (worldService.ShouldAddAllInRange)
- {
- worldService.ShouldAddAllInRange = false;
- foreach (var wo in worldObjects)
- {
- if (wo == null || wo.Value == null)
- continue;
-
- var childObject = wo.Value;
- if (Vector3.Abs((Vector3)childObject->Position - localPos).Length() > worldService.CutoffDistance)
- continue;
- worldService.SelectedObjects[(nint)childObject] = ParseObject(wo);
- }
- }
-
- if (hoveredInFrame.Count != 0)
- {
- var housingItems = housingService.GetHousingItems();
- var housingMap = new Dictionary();
- foreach (var housingItem in housingItems)
- {
- foreach (var bgPart in housingItem.Value.BgParts)
- {
- if (bgPart == null || bgPart.Value == null)
- continue;
- housingMap[(nint)bgPart.Value] = housingItem.Value;
- }
- }
-
- using var tt = ImRaii.Tooltip();
- for (var i = 0; i < hoveredInFrame.Count; i++)
- {
- var worldObj = hoveredInFrame[i];
- var childObject = worldObj.Value;
- var type = childObject->GetObjectType();
- ImGui.Separator();
- ImGui.Text($"Address: {(nint)childObject:X8}");
- ImGui.Text($"Type: {type}");
- if (WorldService.IsSupportedObject(type))
- {
- var path = WorldService.GetPath(childObject);
- ImGui.Text($"Path: {path}");
- }
-
- ImGui.Text($"Position: {childObject->Position}");
- ImGui.Text($"Rotation: {childObject->Rotation}");
- ImGui.Text($"Scale: {childObject->Scale}");
-
- if (housingMap.TryGetValue((nint)childObject, out var item))
- {
- ImGui.Text($"Furniture: {item.Object->NameString}");
- ImGui.Text($"Index: {item.Furniture.Index}");
- if (item.Stain != null)
- {
- ImGui.Text($"Stain: {item.Furniture.Stain}");
- ImGui.SameLine();
- var stainColor = ImGui.ColorConvertU32ToFloat4(item.Stain.Color);
- ImGui.ColorButton("Stain", stainColor, ImGuiColorEditFlags.NoPicker | ImGuiColorEditFlags.NoTooltip);
- }
- }
-
- ImGui.SetNextFrameWantCaptureMouse(true);
- if (ImGui.IsMouseClicked(ImGuiMouseButton.Left))
- {
- worldService.SelectedObjects[(nint)childObject] = ParseObject(worldObj);
- }
- }
- }
- }
-
- public unsafe void DrawHousingOverlay()
- {
- var housingItems = housingService.GetHousingItems();
- if (housingItems.Count == 0) return;
-
- var camera = sigUtil.GetCamera();
- var localPos = clientState.LocalPlayer?.Position ?? Vector3.Zero;
-
- foreach (var item in housingItems.Values)
- {
- var childObject = item.Object;
- if (Vector3.Abs((Vector3)childObject->Position - localPos).Length() > worldService.CutoffDistance)
- continue;
- Vector2 screenPos;
- if (worldService.ResolveUsingGameGui)
- {
- if (!gui.WorldToScreen(childObject->Position, out screenPos)) continue;
- }
- else
- {
- if (!camera->WorldToScreen(childObject->Position, out var csScreenPos)) continue;
- screenPos = csScreenPos;
- }
-
- var bg = ImGui.GetBackgroundDrawList();
- bg.AddCircleFilled(screenPos, 5, ImGui.GetColorU32(worldService.DotColor));
-
- if (ImGui.IsMouseHoveringRect(screenPos - new Vector2(5, 5),
- screenPos + new Vector2(5, 5)) &&
- !ImGui.IsWindowHovered(ImGuiHoveredFlags.AnyWindow))
- {
- using var tt = ImRaii.Tooltip();
- ImGui.Separator();
- ImGui.Text($"Address: {(nint)childObject:X8}");
- ImGui.Text($"Kind: {item.Object->GetObjectKind()}");
- ImGui.Text($"Furniture: {item.Object->NameString}");
- ImGui.Text($"Index: {item.Furniture.Index}");
- if (item.Stain != null)
- {
- ImGui.Text($"Stain: {item.Furniture.Stain}");
- ImGui.SameLine();
- var stainColor = ImGui.ColorConvertU32ToFloat4(item.Stain.Color);
- ImGui.ColorButton("Stain", stainColor, ImGuiColorEditFlags.NoPicker | ImGuiColorEditFlags.NoTooltip);
- }
-
- foreach (var bgObject in item.BgParts)
- {
- if (bgObject == null || bgObject.Value == null)
- continue;
-
- var draw = bgObject.Value;
-
- ImGui.Text($"File: {draw->ModelResourceHandle->ResourceHandle.FileName}");
- ImGui.Text($"Position: {draw->DrawObject.Position}");
- ImGui.Text($"Rotation: {draw->DrawObject.Rotation}");
- ImGui.Text($"Scale: {draw->DrawObject.Scale}");
- }
-
- ImGui.SetNextFrameWantCaptureMouse(true);
- if (ImGui.IsMouseClicked(ImGuiMouseButton.Left))
- {
- foreach (var bgObject in item.BgParts)
- {
- if (bgObject == null || bgObject.Value == null)
- continue;
- worldService.SelectedObjects[(nint)bgObject.Value] = ParseHousingBgObject((Object*)bgObject.Value, item);
- }
- }
- }
- }
- }
-
- public void DrawOverlay()
- {
- if (!worldService.ShouldDrawOverlay)
- return;
- worldService.ShouldDrawOverlay = false;
-
- switch (worldService.Overlay)
- {
- case WorldService.OverlayType.World:
- DrawWorldOverlay();
- break;
- case WorldService.OverlayType.Housing:
- DrawHousingOverlay();
- break;
- }
- }
-
- private unsafe WorldService.ObjectSnapshot ParseObject(Pointer