Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion Meddle/Meddle.Plugin/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Meddle.Plugin.UI.Layout;
using Meddle.Plugin.UI.Windows;
using Meddle.Plugin.Utils;
using Meddle.Utils;
using Microsoft.Extensions.Logging;

namespace Meddle.Plugin;
Expand Down Expand Up @@ -58,6 +59,7 @@ public class ExportConfiguration

public bool LimitTerrainExportRange { get; set; }
public float TerrainExportDistance { get; set; } = 500f;
public bool EnableWindingFlip { get; set; }

// public enum ExportRootAttachHandling
// {
Expand All @@ -81,7 +83,8 @@ public ExportConfiguration Clone()
// RootAttachHandling = RootAttachHandling
UseDeformer = UseDeformer,
LimitTerrainExportRange = LimitTerrainExportRange,
TerrainExportDistance = TerrainExportDistance
TerrainExportDistance = TerrainExportDistance,
EnableWindingFlip = EnableWindingFlip
};
}

Expand All @@ -105,6 +108,15 @@ public void Apply(ExportConfiguration other)
UseDeformer = other.UseDeformer;
LimitTerrainExportRange = other.LimitTerrainExportRange;
TerrainExportDistance = other.TerrainExportDistance;
EnableWindingFlip = other.EnableWindingFlip;
}

public MeshBuilderOptions CreateMeshBuilderOptions()
{
return new MeshBuilderOptions
{
EnableWindingFlip = EnableWindingFlip
};
}
}

Expand Down
2 changes: 1 addition & 1 deletion Meddle/Meddle.Plugin/Models/Composer/CharacterComposer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ private void HandleModel(ParsedCharacterInfo characterInfo, ParsedModelInfo m, S
}

var enabledAttributes = Model.GetEnabledValues(model.EnabledAttributeMask, model.AttributeMasks).ToArray();
var meshes = ModelBuilder.BuildMeshes(model, materialBuilders, skinningContext.Bones, deform);
var meshes = ModelBuilder.BuildMeshes(model, materialBuilders, skinningContext.Bones, deform, exportConfig.CreateMeshBuilderOptions());
foreach (var mesh in meshes)
{
var extrasDict = new Dictionary<string, string>
Expand Down
101 changes: 51 additions & 50 deletions Meddle/Meddle.Plugin/Models/Composer/ComposerCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,6 @@ public string CacheTexture(string fullPath)
File.WriteAllBytes(pngCachePath, textureBytes);
return pngCachePath;
}

private static readonly IReadOnlyList<string> SkinSlotShaders =
[
"characterstockings.shpk"
];

public MaterialBuilder ComposeMaterial(string mtrlPath,
ParsedMaterialInfo? materialInfo = null,
Expand Down Expand Up @@ -259,63 +254,67 @@ public MaterialBuilder ComposeMaterial(string mtrlPath,
if (materialInfo != null)
{
// kinda janky, but we set skin data first so that the keys are still available to be overridden by the main material info.
if (materialInfo.SkinSlotMaterial != null && SkinSlotShaders.Contains(materialInfo.Shpk))
if (materialInfo.RenderMaterialOutput != null)
{
var skinMtrlFile = GetMtrlFile(materialInfo.SkinSlotMaterial.Path.FullPath, out var skinMtrlCachePath);
if (skinMtrlCachePath != null)
var render = materialInfo.RenderMaterialOutput;
if (render.DecalTexturePath != null)
{
material.SetProperty("SkinMtrlCachePath", Path.GetRelativePath(cacheDir, skinMtrlCachePath));
var decalCachePath = CacheTexture(render.DecalTexturePath);
material.SetProperty("Decal_PngCachePath", Path.GetRelativePath(cacheDir, decalCachePath));
material.SetProperty("DecalPath", render.DecalTexturePath);
}
var skinShaderPackage = GetShaderPackage(skinMtrlFile.GetShaderPackageName());
var skinMaterial = new MaterialComposer(skinMtrlFile, materialInfo.SkinSlotMaterial.Path.FullPath, skinShaderPackage);
var constants = Names.GetConstants();
foreach (var (key, value) in skinMaterial.ShaderKeyDict)
else if (render.DecalTexture != null)
{
var category = (uint)key;
var keyMatch = constants.GetValueOrDefault(category);
var valMatch = constants.GetValueOrDefault(value);
material.SetProperty(keyMatch != null ? keyMatch.Value : $"0x{category:X8}", valMatch != null ? valMatch.Value : $"0x{value:X8}");
var tex = render.DecalTexture;
var decalCachePath = SaveInMemoryTex(tex, "decals");
material.SetProperty("Decal_PngCachePath", Path.GetRelativePath(cacheDir, decalCachePath));
material.SetProperty("DecalPath", $"InMemoryTexture_{tex.GetHashCode()}");
}
foreach (var texture in skinMaterial.TextureUsageDict)

if (render.SkinMaterialTextures.Count > 0)
{
var fullPath = texture.Value.FullPath;
var match = materialInfo.Textures.FirstOrDefault(x => x.Path.GamePath == texture.Value.GamePath);
if (match != null)
foreach (var texture in render.SkinMaterialTextures)
{
fullPath = match.Path.FullPath;
}
var fullPath = texture.TexturePath;
var match = materialInfo.Textures.FirstOrDefault(x => x.Path.GamePath == texture.TexturePathFromMaterial);
if (match != null)
{
fullPath = match.Path.FullPath;
}

var cachePath = CacheTexture(fullPath);
var keyUsage = $"{texture.Key}".Replace("g_Sampler", "g_SamplerSkin");
material.SetProperty($"{keyUsage}", texture.Value.GamePath);
material.SetProperty($"{keyUsage}_PngCachePath", Path.GetRelativePath(cacheDir, cachePath));
var cachePath = CacheTexture(fullPath);
// var keyUsage = $"{key}".Replace("g_Sampler", "g_SamplerSkin");
if (shaderPackage.Textures.TryGetValue(texture.TargetSamplerCrc, out var samplerName))
{
material.SetProperty($"{samplerName}", texture.TexturePath);
material.SetProperty($"{samplerName}_PngCachePath", Path.GetRelativePath(cacheDir, cachePath));
}
}
}
}

material.SetPropertiesFromMaterialInfo(materialInfo);
if (materialInfo.ColorTable != null)
{
material.SetPropertiesFromColorTable(materialInfo.ColorTable);
// since colortables are purely in-memory, they dont have a path.
// going to store them in the 'ColorTables' directory in the cache with a unique name based on the hash.
if (materialInfo.ColorTable is ColorTableSet colorTableSet)
{
var tex = colorTableSet.ColorTable.ToTexture();
var colorTablePath = SaveColorTableTex(tex);
var colorTablePath = SaveInMemoryTex(tex, "color_tables");
material.SetProperty("ColorTable_PngCachePath", Path.GetRelativePath(cacheDir, colorTablePath));
}
else if (materialInfo.ColorTable is LegacyColorTableSet legacyColorTableSet)
{
var tex = legacyColorTableSet.ColorTable.ToTexture();
var colorTablePath = SaveColorTableTex(tex);
var colorTablePath = SaveInMemoryTex(tex, "color_tables");
material.SetProperty("LegacyColorTable_PngCachePath", Path.GetRelativePath(cacheDir, colorTablePath));
}
}

string SaveColorTableTex(SkTexture tex)
string SaveInMemoryTex(SkTexture tex, string type)
{
var colorTableCacheDir = Path.Combine(cacheDir, "color_tables");
Directory.CreateDirectory(colorTableCacheDir);
var texCacheDir = Path.Combine(cacheDir, type);
Directory.CreateDirectory(texCacheDir);
var buf = tex.Bitmap.Bytes;
var hash = System.Security.Cryptography.SHA256.HashData(buf);
var hashStr = Convert.ToHexStringLower(hash);
Expand All @@ -325,7 +324,7 @@ string SaveColorTableTex(SkTexture tex)
hashStr = hashStr[..8];
}
var mtrlPathWithoutExtension = Path.GetFileNameWithoutExtension(mtrlPath);
var colorTablePath = Path.Combine(colorTableCacheDir, $"{mtrlPathWithoutExtension}_{materialInfo.Shpk}_{hashStr}.png");
var colorTablePath = Path.Combine(texCacheDir, $"{mtrlPathWithoutExtension}_{materialInfo.Shpk}_{hashStr}.png");
if (!File.Exists(colorTablePath))
{
using var fileStream = new FileStream(colorTablePath, FileMode.Create, FileAccess.Write);
Expand Down Expand Up @@ -362,21 +361,23 @@ string SaveColorTableTex(SkTexture tex)
// remove full path prefix, get only dir below cache dir.
material.SetProperty($"{texture.Key}_PngCachePath", Path.GetRelativePath(cacheDir, cachePath));
}

if (characterInfo != null)
{
if (characterInfo.CustomizeData?.DecalPath != null)
{
var decalCachePath = CacheTexture(characterInfo.CustomizeData.DecalPath);
material.SetProperty("Decal_PngCachePath", Path.GetRelativePath(cacheDir, decalCachePath));
}

if (characterInfo.CustomizeData?.LegacyBodyDecalPath != null)
{
var legacyDecalCachePath = CacheTexture(characterInfo.CustomizeData.LegacyBodyDecalPath);
material.SetProperty("LegacyBodyDecal_PngCachePath", Path.GetRelativePath(cacheDir, legacyDecalCachePath));
}
}
//
// if (characterInfo != null)
// {
// if (characterInfo.CustomizeData?.DecalPath != null && materialInfo?.ApplyDecal == true)
// {
// var decalCachePath = CacheTexture(characterInfo.CustomizeData.DecalPath);
// material.SetProperty("Decal_PngCachePath", Path.GetRelativePath(cacheDir, decalCachePath));
// material.SetProperty("DecalPath", characterInfo.CustomizeData.DecalPath ?? "");
// }
//
// if (characterInfo.CustomizeData?.LegacyBodyDecalPath != null && materialInfo?.ApplyLegacyDecal == true)
// {
// var legacyDecalCachePath = CacheTexture(characterInfo.CustomizeData.LegacyBodyDecalPath);
// material.SetProperty("LegacyBodyDecal_PngCachePath", Path.GetRelativePath(cacheDir, legacyDecalCachePath));;
// material.SetProperty("LegacyBodyDecalPath", characterInfo.CustomizeData.LegacyBodyDecalPath ?? "");
// }
// }

materialBuilder.Extras = material.ExtrasNode;

Expand Down
4 changes: 2 additions & 2 deletions Meddle/Meddle.Plugin/Models/Composer/InstanceComposer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ private NodeBuilder ComposeCameraInstance(ParsedCameraInstance parsedCameraInsta
}

var model = new Model(bgPartsInstance.Path.GamePath, mdlFile, null);
var meshExports = ModelBuilder.BuildMeshes(model, materialBuilders, [], null);
var meshExports = ModelBuilder.BuildMeshes(model, materialBuilders, [], null, exportConfig.CreateMeshBuilderOptions());
meshes = meshExports.Select(x => x.Mesh).ToArray();
}

Expand Down Expand Up @@ -652,7 +652,7 @@ public NodeBuilder ComposeTerrain(ParsedTerrainInstance terrainInstance, SceneBu
}

var model = new Model(mdlPath, mdlFile, null);
var meshes = ModelBuilder.BuildMeshes(model, materialBuilders, [], null);
var meshes = ModelBuilder.BuildMeshes(model, materialBuilders, [], null, exportConfig.CreateMeshBuilderOptions());

var plateRoot = new NodeBuilder(mdlPath);
for (var meshIdx = 0; meshIdx < meshes.Count; meshIdx++)
Expand Down
2 changes: 0 additions & 2 deletions Meddle/Meddle.Plugin/Models/Composer/MaterialComposer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ public void SetPropertiesFromCharacterInfo(ParsedCharacterInfo characterInfo)
SetProperty("Highlights", customizeData.Highlights);
SetProperty("LipStick", customizeData.LipStick);
SetProperty("FacePaintReversed", customizeData.FacePaintReversed);
SetProperty("LegacyBodyDecalPath", customizeData.LegacyBodyDecalPath ?? "");
SetProperty("DecalPath", customizeData.DecalPath ?? "");
SetProperty("CustomizeData", JsonNode.Parse(JsonSerializer.Serialize(customizeData, JsonOptions))!);
}

Expand Down
30 changes: 15 additions & 15 deletions Meddle/Meddle.Plugin/Models/Enums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,21 @@ public enum CacheFileType

public enum HumanModelSlotIndex
{
Head = 0,
Top = 1,
Arms = 2,
Legs = 3,
Feet = 4,
Ear = 5,
Neck = 6,
Wrist = 7,
RFinger = 8,
LFinger = 9,
Hair = 10,
Face = 11,
TailEars = 12,
Glasses = 16,
Extra = 17,
Head = 0, // 0x0
Top = 1, // 0x1
Arms = 2, // 0x2
Legs = 3, // 0x3
Feet = 4, // 0x4 all slots <= 0x4 *can* have skin material assigned if shpk = skin.shpk
Ear = 5, // 0x5
Neck = 6, // 0x6
Wrist = 7, // 0x7
RFinger = 8, // 0x8
LFinger = 9, // 0x9 all slots <= 0x9 *can* have Human->LegacyBodyDecal if shpk = skin.shpk
Hair = 10, // 0xA
Face = 11, // 0xB enables Human->Decal
TailEars = 12, // 0xC
Glasses = 16, // 0x10
Extra = 17, // 0x11
}

public enum HumanEquipmentSlotIndex
Expand Down
20 changes: 10 additions & 10 deletions Meddle/Meddle.Plugin/Models/Layout/ParsedInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -446,13 +446,13 @@ public class ParsedTextureInfo(string path, string pathFromMaterial, TextureReso
public TextureResource Resource { get; } = resource;
}

public class ParsedMaterialInfo(string path, string pathFromModel, string shpk, IColorTableSet? colorTable, ParsedTextureInfo[] textures)
public class ParsedMaterialInfo(string path, string pathFromModel, string shpk, OnRenderMaterialOutput? renderMaterialOutput, IColorTableSet? colorTable, ParsedTextureInfo[] textures)
{
public HandleString Path { get; } = new() { FullPath = path, GamePath = pathFromModel };
public string Shpk { get; } = shpk;
public OnRenderMaterialOutput? RenderMaterialOutput { get; } = renderMaterialOutput;
public ParsedStain? Stain0 { get; init; }
public ParsedStain? Stain1 { get; init; }
public ParsedMaterialInfo? SkinSlotMaterial { get; init; }

[JsonIgnore]
public IColorTableSet? ColorTable { get; } = colorTable;
Expand Down Expand Up @@ -517,24 +517,24 @@ public record ParsedCharacterInfo
public IReadOnlyList<ParsedModelInfo> Models;
public readonly ParsedSkeleton Skeleton;
public readonly ParsedAttach Attach;
private readonly ResolverService.ParsedHumanInfo humanInfo;
public readonly ParsedHumanInfo HumanInfo;
public IReadOnlyList<ParsedCharacterInfo> Attaches = [];
public CustomizeData? CustomizeData => humanInfo.CustomizeData;
public CustomizeParameter? CustomizeParameter => humanInfo.CustomizeParameter;
public IReadOnlyList<ParsedMaterialInfo?> SkinSlotMaterials => humanInfo.SkinSlotMaterials;
public IReadOnlyList<EquipmentModelId> EquipmentModelIds => humanInfo.EquipmentModelIds;
public GenderRace GenderRace => humanInfo.GenderRace;
public readonly DateTime ParsedAt = DateTime.UtcNow;
public CustomizeData? CustomizeData => HumanInfo.CustomizeData;
public CustomizeParameter? CustomizeParameter => HumanInfo.CustomizeParameter;
public IReadOnlyList<EquipmentModelId> EquipmentModelIds => HumanInfo.EquipmentModelIds;
public GenderRace GenderRace => HumanInfo.GenderRace;

public ParsedCharacterInfo(
ParsedModelInfo[] models,
ParsedSkeleton skeleton,
ParsedAttach attach,
ResolverService.ParsedHumanInfo humanInfo)
ParsedHumanInfo humanInfo)
{
Models = models;
Skeleton = skeleton;
Attach = attach;
this.humanInfo = humanInfo;
this.HumanInfo = humanInfo;
}
}

Expand Down
Loading
Loading