Defines BodyRegionBlend for per-region gender blend weights and BodyRegions static utilities for body-region IDs, labels, masking by vertex position, morph naming, and applying gender/maturity morphs to a SkinnedModelRenderer based on CharacterAppearanceData.
using System;
namespace CharacterEditor;
/// <summary>Relative regional weights. An absent or empty override follows the overall body.</summary>
public sealed class BodyRegionBlend
{
public float OverallInfluence { get; set; } = 1;
public float FemaleInfluence { get; set; }
public float MaleInfluence { get; set; }
[System.Text.Json.Serialization.JsonIgnore]
public float Total => OverallInfluence + FemaleInfluence + MaleInfluence;
[System.Text.Json.Serialization.JsonIgnore]
public bool Valid => Unit(OverallInfluence) && Unit(FemaleInfluence) && Unit(MaleInfluence);
static bool Unit(float value) => float.IsFinite(value) && value >= 0 && value <= 1;
public float Resolve(float overall) => Total > 0 ? (OverallInfluence * overall + MaleInfluence) / Total : overall;
public BodyRegionBlend DeepCopy() => new() { OverallInfluence = OverallInfluence, FemaleInfluence = FemaleInfluence, MaleInfluence = MaleInfluence };
}
/// <summary>Shared rest-space masks for native body endpoints and fitted garments.</summary>
public static class BodyRegions
{
public static readonly string[] Ids = { "figure", "chest", "shoulders", "upperarms", "forearms", "hands", "thighs", "calves", "feet" };
public static string Label(string id) => id switch
{
"overall" => "Overall", "figure" => "Figure & hips", "chest" => "Chest",
"shoulders" => "Shoulders", "upperarms" => "Upper arms", "forearms" => "Forearms",
"hands" => "Hands", "thighs" => "Thighs", "calves" => "Calves", "feet" => "Feet", _ => id
};
public static float MaleAmount(CharacterAppearanceData appearance, string region)
{
float overall = UnifiedHuman.MaleAmount(appearance);
return appearance.BaseShape?.BodyOverrides?.TryGetValue(region, out var blend) == true ? blend.Resolve(overall) : overall;
}
public static string Morph(string region, bool subtract = false) => $"ce_gender_{region}_{(subtract ? "subtract" : "add")}";
static float Smooth(float low, float high, float value)
{
float t = Math.Clamp((value - low) / (high - low), 0, 1);
return t * t * (3 - 2 * t);
}
// A smooth partition (sum <= 1). The head/neck retain Overall. The authoring
// exporter uses these same equations for the compiled body morphs.
public static float Mask(string region, Vector3 point)
{
float z = point.z, y = MathF.Abs(point.y);
float legs = 1 - Smooth(32, 38, z), upper = 1 - legs;
float armShare = Smooth(7.5f, 11.5f, y) * (1 - Smooth(53, 57, z));
float arm = upper * armShare, torso = upper * (1 - armShare);
float shoulder = Smooth(4.5f, 7.5f, y) * Smooth(49, 53, z) * (1 - Smooth(58, 60, z));
// Keep the waist with Figure; share only the upper abdomen/lower ribcage.
// Positive morph composition below handles the shading at this join.
float chestShare = Smooth(42, 49, z);
return region switch
{
"feet" => legs * (1 - Smooth(4, 7, z)),
"calves" => legs * Smooth(4, 7, z) * (1 - Smooth(20, 24, z)),
"thighs" => legs * Smooth(20, 24, z),
"hands" => arm * (1 - Smooth(35, 39, z)),
"forearms" => arm * Smooth(35, 39, z) * (1 - Smooth(43, 47, z)),
"upperarms" => arm * Smooth(43, 47, z) * (1 - Smooth(49, 53, z)),
"shoulders" => arm * Smooth(49, 53, z) + torso * shoulder,
"chest" => torso * (1 - shoulder) * chestShare * (1 - Smooth(56, 60, z)),
"figure" => torso * (1 - shoulder) * (1 - chestShare),
_ => 0
};
}
public static void Apply(SkinnedModelRenderer renderer, CharacterAppearanceData appearance)
{
if (appearance.BaseShape is null) return;
float overallFemale = 1 - appearance.BaseShape.MaleBody;
float child = CharacterMaturity.ChildAmount(appearance.BodyMaturity);
// The school-age torso has not acquired the adult chest/waist contours.
// Resolve this in the shared positive morph partition so garments and
// exposed skin use the same smooth chest-to-figure transition. Saved
// gender weights remain intact and return continuously with maturity.
float Female(string region)
{
float adult = 1 - MaleAmount(appearance, region);
return adult * (1 - (region == "chest" ? .94f : region == "figure" ? .70f : 0) * child);
}
// Some imported morph normal frames cannot be undone by a mirrored target.
// Start from the least-female requested region and only add the remaining
// positive differences. This is the same positional partition, with no
// female/subtract cancellation. Unmodified bodies keep their native morph.
float baseline = MathF.Min(overallFemale, Ids.Min(Female));
renderer.Morphs.Set("ce_body_female", baseline, 0);
foreach (var region in Ids)
{
renderer.Morphs.Set(Morph(region), MathF.Max(0, Female(region) - baseline), 0);
renderer.Morphs.Set(Morph(region, true), 0, 0);
}
renderer.Morphs.Set(Morph("remainder"), overallFemale - baseline, 0);
}
}