Static utility that defines age-related surface deformation presets for character faces and bodies. It computes a child blending amount and returns arrays of named SurfaceEdit records that apply scale and translation ellipsoid edits based on appearance maturity and gender blend.
using System;
namespace CharacterEditor;
/// <summary>
/// Conservative school-age to adult surface proportions, applied after identity
/// and feature edits. These are authoring controls, not an estimate of age.
/// Limb lengths and overall stature remain the native skeleton's responsibility.
/// </summary>
public static class CharacterMaturity
{
// Same rest-space ellipsoid convention as CharacterDeformation. Translation
// is in normalized mask coordinates; the native volume applies Mask afterward.
public readonly record struct SurfaceEdit(string Name, Vector3 Center, Vector3 Mask, Vector3 Scale, Vector3 Translation);
/// <summary>0 = adult, 1 = the school-age endpoint. Midpoint retains 25% of the change.</summary>
public static float ChildAmount(float maturity)
{
if (!float.IsFinite(maturity)) return 0;
float t = Math.Clamp(maturity, 0, 1);
float remaining = 1 - t * t * (3 - 2 * t);
return remaining * remaining;
}
/// <summary>
/// Keep the list stable at the neutral endpoint so edits do not recreate the
/// volume hierarchy. Adult coefficients are exact identities and are disabled
/// by CharacterDeformation, including in all older saved appearances.
/// </summary>
public static SurfaceEdit[] FaceEdits(CharacterAppearanceData appearance)
{
float child = ChildAmount(appearance.EffectiveFaceMaturity);
return new[]
{
// Shorten the lower face toward the eye plane. The ellipsoid ends
// below the eyes and fades before the chest/head attachment boundary.
// Paired-photo evidence supports roughly 6–7% eye-to-chin shortening
// at the school-age endpoint; the field must be measured on each face.
new SurfaceEdit("Facial maturity lower face", new(3.2f, 0, 61.1f), new(5.5f, 4.4f, 3.2f),
new(1, 1, 1 - .075f * child), new(0, 0, .07734375f * child)),
// Profile changes are separate from vertical shortening. Retain the
// selected identity's nose width, curvature, nostrils and bridge.
new SurfaceEdit("Facial maturity nose", new(5.7f, 0, 63.05f), new(1.7f, 1.5f, 1.2f),
Vector3.One, new(-.12f * child, 0, 0)),
new SurfaceEdit("Facial maturity nasal root", new(5.1f, 0, 64.15f), new(1.4f, 1.05f, .85f),
Vector3.One, new(-.035f * child, 0, 0)),
new SurfaceEdit("Facial maturity chin", new(4.4f, 0, 60.1f), new(2, 2.4f, 1.55f),
Vector3.One, new(-.055f * child, 0, 0)),
// Jaw width has a smaller age effect than lower-face height. Keep
// this gentle so a broad adult jaw remains a broad child jaw.
new SurfaceEdit("Facial maturity jaw width", new(3, 0, 60.65f), new(4.5f, 4.1f, 2),
new(1, 1 - .024f * child, 1), Vector3.Zero),
// These small soft-tissue/ridge adjustments are art direction informed
// by the reference profiles, not measured universal growth rates. The
// masks end above/below the eye sockets so eyelid alignment is retained.
new SurfaceEdit("Facial maturity brow softness", new(4.8f, 0, 65.7f), new(1.8f, 3.4f, 1.4f),
Vector3.One, new(-.045f * child, 0, 0)),
new SurfaceEdit("Facial maturity left cheek", new(4, 2.1f, 62.6f), new(2.4f, 2, 1.45f),
Vector3.One, new(.045f * child, .02f * child, 0)),
new SurfaceEdit("Facial maturity right cheek", new(4, -2.1f, 62.6f), new(2.4f, 2, 1.45f),
Vector3.One, new(.045f * child, -.02f * child, 0))
};
}
/// <summary>
/// Gently reduce adult torso contours without changing the selected gender
/// blend or overwriting manual features. Broad overlapping volumes apply the
/// identical field to the body, underwear and bone-merged clothes. No volume
/// changes leg length, foot height, or animation joint positions.
/// </summary>
public static SurfaceEdit[] BodyEdits(CharacterAppearanceData appearance)
{
float child = ChildAmount(appearance.BodyMaturity);
float chestFemale = 1 - BodyRegions.MaleAmount(appearance, "chest");
float figureFemale = 1 - BodyRegions.MaleAmount(appearance, "figure");
return new[]
{
// A broad upper-body envelope avoids a band between chest and waist.
// Narrowing is supplemental to native bone proportions, not a new rig.
new SurfaceEdit("Body maturity upper torso", new(0, 0, 50.5f), new(9, 15, 10),
new(1 - .09f * child, 1 - .11f * child, 1), Vector3.Zero),
// One front envelope spans both sides of the chest and its lower
// transition. Pull the mature contour inward while preserving the
// ribcage; separate left/right caps can leave a crease between them.
new SurfaceEdit("Body maturity chest contour", new(2.5f, 0, 49.8f), new(4, 8.5f, 6),
new(1 - (.06f + .08f * chestFemale) * child, 1, 1), Vector3.Zero),
// A little less waist-to-hip contrast at the child endpoint, with
// generous falloff across the abdomen and the chest/legs mesh split.
new SurfaceEdit("Body maturity waist", new(0, 0, 43.5f), new(8, 10, 8),
new(1 - .02f * child, 1 + .035f * figureFemale * child, 1), Vector3.Zero),
new SurfaceEdit("Body maturity hips", new(0, 0, 35.5f), new(9, 11, 9),
new(1 - .03f * child, 1 - (.04f + .025f * figureFemale) * child, 1), Vector3.Zero)
};
}
}