Code/CharacterKit/CharacterTailAttachment.cs

Static helper that computes the rest-space attachment point for a character tail on a human model. It applies gender morphs, body-region masks, size/fitness/maturity spherical deformation transforms to return a vertex position in model inches.

Native Interop
using System;

namespace CharacterEditor;

/// <summary>Appearance-dependent tail root in the human model's rest space, in Source inches.</summary>
public static class CharacterTailAttachment
{
	/// <summary>
	/// Follows the sacrum surface through gender morphs and the native ordered
	/// deformation stack. The origin is embedded 1.1 inches into source vertex 235
	/// at (-4.666983, -.006638, 37.608448), so the tail's root cannot open a gap.
	/// Animation-space attachment is separate: the nearby skin is approximately
	/// 75% pelvis / 25% spine_0, rather than rigidly bound to either bone alone.
	/// Use the compiled model's bind transforms for that conversion; its pelvis
	/// rest position is approximately (+.19685, 0, 36.61417).
	/// </summary>
	public static Vector3 RestPoint(CharacterAppearanceData data)
	{
		// The flared root is wider than a point attachment. Bury the entire root
		// plane so its tilted rim blends through the skin rather than showing a cap.
		var point = new Vector3(-3.567f, 0, 37.6084f);
		// The local body-morph partition crosses Figure/Thighs here. Preserve
		// Overall for the remainder, exactly as BodyRegions.Apply composes it.
		float female = 1 - UnifiedHuman.MaleAmount(data);
		if (data.BaseShape is not null)
		{
			female += BodyRegions.Mask("figure", point) * (1 - BodyRegions.MaleAmount(data, "figure") - female)
				+ BodyRegions.Mask("thighs", point) * (1 - BodyRegions.MaleAmount(data, "thighs") - female);
		}
		// Measured female-minus-male displacement of that same source vertex.
		point += new Vector3(.0039458275f, .029701619f, .016834259f) * female;

		// Same order and coefficients as CharacterDeformation.Edits. The other
		// body masks lie outside this anchor: ButtSize may affect the collar's
		// lower rim, but not this sacrum center; belly/bulge are on the front.
		float waist = 2 * data.WaistSize - 1;
		point = SphereTransform(point, new(0, 0, 37), 17, new(1, 1, .8f),
			new(1 + .15f * waist, 1 + .15f * waist, 1));
		float build = MathF.Abs(2 * data.Fitness - 1);
		var buildDelta = data.Fitness < .5f ? new Vector3(.16f, .18f, 0) : new Vector3(-.06f, -.05f, 0);
		point = SphereTransform(point, new(0, 0, 36), 15, new(1, 1, .75f), Vector3.One + buildDelta * build);
		// The same final surface fields also shape the sacrum under the tail.
		// Skeletal growth is inherited separately from the evaluated pelvis/spine.
		foreach (var edit in CharacterMaturity.BodyEdits(data))
			point = SphereTransform(point, edit.Center, 1, edit.Mask, edit.Scale);
		return point;
	}

	// Position-only counterpart of DeformationVolume.hlsl Evaluate/ConstrainToShape
	// for the transform/sphere operations used above. Both Falloff and
	// TransformBlend are .65 on these native volumes. Evaluation occurs in each
	// ellipsoid's unscaled space and the next volume receives the moved point.
	static Vector3 SphereTransform(Vector3 point, Vector3 center, float radius, Vector3 mask, Vector3 scale)
	{
		var relative = point - center;
		var local = new Vector3(relative.x / mask.x, relative.y / mask.y, relative.z / mask.z);
		float distance = local.Length - radius;
		if (distance >= 0) return point;
		float t = Math.Clamp(-distance / (radius * .65f), 0, 1);
		float influence = t * t * (3 - 2 * t);
		var moved = local + local * (scale - Vector3.One) * influence;
		if (moved.LengthSquared > radius * radius)
			moved *= radius / MathF.Sqrt(moved.LengthSquared);
		return point + (moved - local) * mask;
	}
}