Static helper that loads and caches skin-related materials and normal textures for character appearance. It loads different material paths and texture channels based on skin id, body type, and style, and exposes lookup helpers for head/body color and normal maps.
using System;
using System.Threading.Tasks;
namespace CharacterEditor;
/// <summary>Preserved and refreshed skin maps with matched collar color and surface detail.</summary>
public static class SkinStyleAssets
{
static readonly Dictionary<string, Material> carriers = new();
static readonly Dictionary<string, Texture> normals = new();
static string Id(string skin) => System.IO.Path.GetFileNameWithoutExtension(skin);
static string BodyId(string skin) => Id(skin) is "human01" or "human02" or "human06" ? "dark" : "light";
public static async Task LoadAsync(CharacterAppearanceData data)
{
if (data.BaseShape is not null && !HasFemaleBody(data.SkinPath))
{
await Load($"materials/makeup/body/{(data.SkinStyle == "original" ? "original_" : "")}{BodyId(data.SkinPath)}.vmat");
foreach (var channel in new[] { "Normal", "AgeNormal", "CombinedMasks" })
{
var key = "body_" + BodyId(data.SkinPath) + channel;
if (normals.ContainsKey(key)) continue;
var texture = await Texture.LoadAsync($"textures/makeup/body/{BodyId(data.SkinPath)}_{channel.ToLowerInvariant()}.vtex");
if (texture is not { IsError: false }) throw new InvalidOperationException("The body map is unavailable: " + channel);
texture.MarkUsed(Math.Max(texture.Width, texture.Height));
normals[key] = texture;
}
}
if (data.BaseShape is not null && HasFemaleBody(data.SkinPath))
{
await Load($"materials/makeup/female/{BodyId(data.SkinPath)}.vmat");
foreach (var channel in new[] { "Normal", "AgeNormal", "CombinedMasks" })
{
var key = "female_" + BodyId(data.SkinPath) + channel;
if (normals.ContainsKey(key)) continue;
var texture = await Texture.LoadAsync($"textures/makeup/female/{BodyId(data.SkinPath)}_{channel.ToLowerInvariant()}.vtex");
if (texture is not { IsError: false }) throw new InvalidOperationException("The female body map is unavailable: " + channel);
texture.MarkUsed(Math.Max(texture.Width, texture.Height));
normals[key] = texture;
}
}
if (data.SkinStyle == "stylized")
{
await Load($"materials/makeup/neck/stylized/{Id(data.SkinPath)}.vmat");
if (data.BaseShape is not null)
{
var key = Id(data.SkinPath) + "_stylized";
if (!normals.ContainsKey(key))
{
var texture = await Texture.LoadAsync($"textures/makeup/neck/stylized/{Id(data.SkinPath)}_normal.vtex");
if (texture is not { IsError: false }) throw new InvalidOperationException("The matched neck normal map is unavailable: " + key);
texture.MarkUsed(Math.Max(texture.Width, texture.Height));
normals[key] = texture;
}
}
return;
}
await UnifiedNeckAssets.LoadHeadAsync(data.SkinPath);
await LoadNormal(Id(data.SkinPath));
await Load($"materials/makeup/original/{BodyId(data.SkinPath)}.vmat");
await LoadNormal(BodyId(data.SkinPath));
}
static async Task LoadNormal(string id)
{
if (normals.ContainsKey(id))
return;
var texture = await Texture.LoadAsync($"textures/makeup/original/{id}_normal.vtex");
if (texture is null || texture.IsError)
throw new InvalidOperationException("The original skin normal map is unavailable: " + id);
texture.MarkUsed(Math.Max(texture.Width, texture.Height));
normals[id] = texture;
}
static async Task Load(string path)
{
if (!carriers.TryGetValue(path, out var material))
material = await Material.LoadAsync(path);
var texture = material?.GetTexture("g_tColor");
if (texture is null || texture.IsError)
throw new InvalidOperationException("The selected skin style is unavailable: " + path);
texture.MarkUsed(Math.Max(texture.Width, texture.Height));
carriers[path] = material;
}
static Texture Map(string name) => carriers[$"materials/makeup/{name}.vmat"].GetTexture("g_tColor");
public static Texture HeadColor(string skin, string style) => style == "stylized" ? Map($"neck/stylized/{Id(skin)}") : UnifiedNeckAssets.ColorTexture(skin);
public static Texture OriginalNormal(string skin, bool head) => normals[head ? Id(skin) : BodyId(skin)];
public static Texture HeadNormal(string skin, string style) => normals[Id(skin) + (style == "stylized" ? "_stylized" : "")];
public static Texture OriginalBodyColor(string skin) => Map($"original/{BodyId(skin)}");
public static bool HasFemaleBody(string skin) => Id(skin) is "human02" or "human04";
public static Texture FemaleBodyColor(string skin) => Map($"female/{BodyId(skin)}");
public static Texture FemaleBodyNormal(string skin) => FemaleBodyMap(skin, "Normal");
public static Texture FemaleBodyMap(string skin, string channel) => normals["female_" + BodyId(skin) + channel];
public static Texture UnifiedBodyColor(string skin, string style) => HasFemaleBody(skin) ? FemaleBodyColor(skin) : Map($"body/{(style == "original" ? "original_" : "")}{BodyId(skin)}");
public static Texture BodyMap(string skin, string channel) => HasFemaleBody(skin) ? FemaleBodyMap(skin, channel) : normals["body_" + BodyId(skin) + channel];
}