Static helper that computes the rest-space attachment point for a character tail on the human model. It applies gender morph blending, body-region masks, size/fitness/maturity sphere-based deformations and returns a final Vector3 in model rest units.
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;
}
}