Code/CharacterKit/PhotoContourWarp.cs

Math utilities for photo contour warping in the character editor. Applies detector bias to 2D projection offsets based on face correction IDs and yaw, and applies 3D kernel-based contour deformation to vertex positions using packed kernel rows.

Native Interop
using System;

namespace CharacterEditor;
/// <summary>Exact ordered native Transform kernels for fine contour fitting.
/// Templates are packed by the installed engine; this pure-math evaluation is
/// safe on the fitting worker and does not create scene objects.</summary>
public static class PhotoContourWarp
{
	public static void DetectorBias(Vector2[] projection, PhotoFace face, float yaw)
	{
		var ids = PhotoContourDetectorBasis.Ids;
		var offsets = PhotoContourDetectorBasis.Offsets;
		var yaws = PhotoContourDetectorBasis.Yaws;
		int lo = yaw < 0 ? 0 : 1, hi = lo + 1;
		float t = Math.Clamp((yaw - yaws[lo]) / (yaws[hi] - yaws[lo]), 0, 1);
		for (int c = 0; c < ids.Length; c++)
		{
			float amount = face.Corrections.GetValueOrDefault(ids[c]);
			if (MathF.Abs(amount) < .000001f)
				continue;
			var control = PhotoFaceCorrections.Controls.First(x => x.Id == ids[c]);
			var weights = face.WeightsFor(control.Region);
			float female = weights[1] + weights[3];
			for (int family = 0; family < 2; family++)
			{
				float strength = (family == 0 ? 1 - female : female) * MathF.Abs(amount);
				if (strength < .000001f)
					continue;
				var row = offsets[family * ids.Length * 2 + c * 2 + (amount >= 0 ? 1 : 0)];
				for (int p = 0; p < projection.Length; p++)
					projection[p] += (row[lo][p] * (1 - t) + row[hi][p] * t) * strength;
			}
		}
	}

	static float Dot(Vector4 a, Vector3 b) => a.x * b.x + a.y * b.y + a.z * b.z + a.w;
	public static void Apply(Vector3[] points, PhotoFace face)
	{
		var ids = PhotoContourKernelBasis.Ids;
		var kernels = PhotoContourKernelBasis.Rows;
		for (int i = 0; i < ids.Length; i++)
		{
			float amount = face.Corrections.GetValueOrDefault(ids[i]);
			if (MathF.Abs(amount) < .000001f)
				continue;
			foreach (var rows in kernels[i])
				for (int p = 0; p < points.Length; p++)
					points[p] = Apply(points[p], rows, amount);
		}
	}

	static Vector3 Apply(Vector3 point, Vector4[] r, float amount)
	{
		var p = new Vector3(Dot(r[0], point), Dot(r[1], point), Dot(r[2], point)) - new Vector3(r[9].x, r[9].y, r[9].z);
		float radius = r[6].w, distance = p.Length - radius;
		if (distance >= 0)
			return point;
		float width = radius * r[7].w;
		float t = width > 0 ? Math.Clamp(-distance / MathF.Max(width, .000001f), 0, 1) : 1;
		float influence = r[8].x * t * t * (3 - 2 * t);
		var transformed = new Vector3(Dot(r[10], p), Dot(r[11], p), Dot(r[12], p));
		var candidate = p + (transformed - p) * (influence * amount);
		if (candidate.Length > radius)
			candidate = candidate.Normal * radius;
		var delta = candidate - p;
		return point + new Vector3(r[3].x * delta.x + r[3].y * delta.y + r[3].z * delta.z, r[4].x * delta.x + r[4].y * delta.y + r[4].z * delta.z, r[5].x * delta.x + r[5].y * delta.y + r[5].z * delta.z);
	}
}