Defines PhotoFaceCorrections, a static class that lists bounded facial correction controls and computes aggregated correction amounts for a character appearance. It provides a cached array of Control records with positions, scales and flags, a VolumeCount computed from paired controls, and an Amount method that sums per-face correction contributions clamped to [-1,1].
using System;
namespace CharacterEditor;
/// <summary>Bounded identity corrections beyond the original cosmetic sliders.
/// They use native rest-space volumes, including eyes, inner mouth and merged clothing.</summary>
public static class PhotoFaceCorrections
{
public sealed record Control(string Id, string Region, Vector3 Center, Vector3 Mask, Vector3 ScaleDelta, Vector3 Translation, bool Paired = false, bool Fine = false, bool Depth = false);
const int Revision = 4;
static int loadedRevision;
static Control[] cached;
public static Control[] Controls
{
get
{
if (cached is null || loadedRevision != Revision)
{
cached = CreateControls();
loadedRevision = Revision;
}
return cached;
}
}
static Control[] CreateControls() => new Control[]
{
new("facewidth", null, new(2, 0, 64), new(7, 6, 6), new(0, .22f, 0), Vector3.Zero),
new("faceheight", null, new(3, 0, 63), new(6, 5, 5), new(0, 0, .16f), Vector3.Zero),
new("jawwidth", "jaw", new(3, 0, 60.5f), new(4, 4, 1.8f), new(0, .35f, 0), Vector3.Zero),
new("cheekwidth", "cheeks", new(3, 0, 63), new(4, 4.7f, 2), new(0, .25f, 0), Vector3.Zero),
new("eyespan", "eyes", new(4.4f, 1.65f, 64.5f), new(3, 1.9f, 1.5f), Vector3.Zero, new(0, .16f, 0), true),
new("eyelevel", "eyes", new(4.4f, 1.65f, 64.5f), new(3, 1.9f, 1.5f), Vector3.Zero, new(0, 0, .16f), true),
new("mouthlevel", "mouth", new(4.3f, 0, 61.8f), new(2.8f, 3, 1.6f), Vector3.Zero, new(0, 0, .25f)),
new("noselevel", "nose", new(5.3f, 0, 63.5f), new(2, 1.9f, 1.6f), Vector3.Zero, new(0, 0, .2f)),
// Lid caps fade out before the front of the iris. Moving a whole eye to
// change aperture would also squash the iris and couple width to height.
new("upperlid", "eyes", new(4.35f, 1.4f, 64.7f), new(.75f, 1.15f, .6f), Vector3.Zero, new(0, 0, .15f), true, true),
new("lowerlid", "eyes", new(4.35f, 1.4f, 64.15f), new(.75f, 1.15f, .5f), Vector3.Zero, new(0, 0, .10f), true, true),
new("outereyeheight", "eyes", new(4.25f, 2.0f, 64.4f), new(1.0f, .75f, .7f), Vector3.Zero, new(0, 0, .17f), true, true),
new("outereyewidth", "eyes", new(4.25f, 2.0f, 64.4f), new(1.0f, .75f, .7f), Vector3.Zero, new(0, .18f, 0), true, true),
new("innereyeheight", "eyes", new(4.6f, .75f, 64.35f), new(.8f, .55f, .55f), Vector3.Zero, new(0, 0, .10f), true, true),
new("innerbrow", "forehead", new(4.75f, .75f, 65.0f), new(1.3f, .9f, .7f), Vector3.Zero, new(0, 0, .20f), true, true),
new("outerbrow", "forehead", new(4.2f, 2.2f, 65.05f), new(1.4f, 1.05f, .8f), Vector3.Zero, new(0, 0, .20f), true, true),
new("bridgewidth", "nose", new(5.3f, 0, 64.0f), new(1.1f, 1.15f, 1.0f), new(0, .30f, 0), Vector3.Zero, false, true),
new("rootdepth", "nose", new(5.1f, 0, 64.5f), new(1.0f, .9f, .8f), Vector3.Zero, new(.20f, 0, 0), false, true, true),
new("tipwidth", "nose", new(6.0f, 0, 63.1f), new(.8f, .9f, .55f), new(0, .30f, 0), Vector3.Zero, false, true),
new("mouthcornerheight", "mouth", new(5.1f, 1.1f, 61.6f), new(1.0f, .85f, .65f), Vector3.Zero, new(0, 0, .18f), true, true),
new("mouthcornerwidth", "mouth", new(5.1f, 1.1f, 61.6f), new(1.0f, .85f, .65f), Vector3.Zero, new(0, .18f, 0), true, true),
new("cupidheight", "mouth", new(5.7f, 0, 61.95f), new(.8f, .65f, .35f), Vector3.Zero, new(0, 0, .12f), false, true),
new("lowerlipcurve", "mouth", new(5.6f, 0, 61.3f), new(.8f, .95f, .4f), Vector3.Zero, new(0, 0, .12f), false, true),
new("jawangleheight", "jaw", new(1.9f, 2.7f, 61.6f), new(1.8f, 1.25f, 1.5f), Vector3.Zero, new(0, 0, .25f), true, true),
new("jawcontourwidth", "jaw", new(3.0f, 2.2f, 61.0f), new(2.2f, 1.45f, 1.4f), Vector3.Zero, new(0, .30f, 0), true, true),
new("templewidth", "forehead", new(2.5f, 3.0f, 65.8f), new(3.0f, 1.6f, 1.7f), Vector3.Zero, new(0, .30f, 0), true, true),
new("chinpointwidth", "jaw", new(5.0f, 0, 60.2f), new(1.3f, 2.0f, .85f), new(0, .35f, 0), Vector3.Zero, false, true),
// Ridge projection is independent of eyebrow height and nasal-root depth.
// The lower falloff stays above the eyeballs and leaves the nose tip alone.
new("browdepth", "forehead", new(4.8f, 0, 65.7f), new(1.8f, 3.4f, 1.5f), Vector3.Zero, new(.20f, 0, 0), false, true, true)
};
public static int VolumeCount => Controls.Sum(c => c.Paired ? 2 : 1);
public static float Amount(CharacterAppearanceData appearance, Control control)
{
var shape = appearance.BaseShape;
return shape is null ? 0 : Math.Clamp(shape.PhotoFaces.Sum(face => FaceRegions.PhotoShare(shape, face, control.Region) * face.Strength * face.Corrections.GetValueOrDefault(control.Id)), -1, 1);
}
}