Presentation helper that applies an "updated" underwear material to a character SkinnedModelRenderer. Loads a material copy asynchronously, preserves a normal map from a source material, and sets/removes material overrides on the renderer based on CharacterAppearanceData.UnderwearStyle.
using System;
using System.Threading.Tasks;
namespace CharacterEditor;
/// <summary>Updated native fabric artwork on the existing, morphable underwear meshes.</summary>
public sealed class UnderwearPresentation : IDisposable
{
public static bool CoversTop(Clothing item)
{
if (item is null) return false;
if (item.HideBody.ToString().Split(',').Any(x => x.Trim() == "Chest")) return true;
// Cloud tops often retain the visible body mesh instead of hiding Chest.
// That does not mean the built-in bra should render through their fabric.
return item.Category.ToString() is "Tops" or "TShirt" or "Sweatshirt" or "Hoodie" or "Shirt" or "Knitwear" or "Dress" or "Jumpsuit";
}
static Material updated;
SkinnedModelRenderer target;
Model model;
readonly HashSet<int> overrides = new();
public static async Task LoadAsync(CharacterAppearanceData data)
{
if (data?.BaseShape is null || data.UnderwearStyle != "updated" || updated is not null)
return;
var carrier = await Material.LoadAsync("materials/makeup/underwear/updated.vmat");
var source = await Material.LoadAsync("models/citizen_human/bodies/female/female_body_mode_light.vmat");
if (carrier is null || carrier.IsError || source is null || source.IsError)
throw new InvalidOperationException("The updated underwear material is unavailable.");
updated = carrier.CreateCopy("characterkit-updated-underwear.vmat");
updated.Set("g_tNormal", source.GetTexture("g_tNormal"));
}
public void Apply(SkinnedModelRenderer body, CharacterAppearanceData data)
{
if (!body.IsValid() || body.Model is null || data is null)
return;
if (target != body || model != body.Model)
{
Dispose();
target = body;
model = body.Model;
}
if (data.UnderwearStyle == "original")
{
foreach (var i in overrides)
body.Materials.SetOverride(i, null);
overrides.Clear();
return;
}
if (updated is null)
return;
for (int i = 0; i < body.Materials.Count; i++)
{
if (body.Materials.GetOriginal(i)?.Name != "models/citizen_human/bodies/underwear.vmat")
continue;
if (body.Materials.GetOverride(i) != updated)
body.Materials.SetOverride(i, updated);
overrides.Add(i);
}
}
public void Dispose()
{
if (target.IsValid() && target.Model == model)
foreach (var i in overrides)
target.Materials.SetOverride(i, null);
overrides.Clear();
}
}