Utility class that converts character appearance parameters into runtime stature-related scales and animation speed compensation. It computes uniform height scale, stride scale from growth pose or actual measurements, and converts world-ground speed into native animation speed.
using System;
namespace CharacterEditor;
/// <summary>
/// Appearance-to-stature conversion shared by previews and world avatars.
/// Overall size and the native human graph's body proportions remain separate.
/// These calculations do not own animation clips, playback clocks or bone poses.
/// </summary>
public static class CharacterStature
{
/// <summary>
/// Uniform body-object scale. The saved midpoint is exactly the existing size;
/// the same transform carries the body, merged clothing and attachments.
/// Multiply the caller's original local scale by this value, never its last
/// already-scaled value. Invalid input falls back to the unchanged adult size.
/// </summary>
public static float Height(CharacterAppearanceData appearance)
{
float value = appearance?.Height ?? .5f;
if (!float.IsFinite(value)) value = .5f;
return .8f + .4f * Math.Clamp(value, 0, 1);
}
/// <summary>Fallback leg ratio before a renderer's measured growth pose exists.</summary>
public static float GrowthStrideScale(CharacterAppearanceData appearance)
=> CharacterGrowthPose.EstimateStrideFactor(appearance?.BodyMaturity ?? 1);
/// <summary>Combined appearance scale of a ground-plane walking stride.</summary>
public static float StrideScale(CharacterAppearanceData appearance)
=> Height(appearance) * GrowthStrideScale(appearance);
/// <summary>
/// Combines the actual applied uniform world scale and the configured growth
/// pose's measured StrideFactor. Pass the controller's coordinate-space scale
/// explicitly when it supplies local-space rather than world-space speeds.
/// Invalid optional values use the appearance-only estimates.
/// </summary>
public static float StrideScale(CharacterAppearanceData appearance, float actualUniformScale, float actualGrowthStride)
{
float uniform = float.IsFinite(actualUniformScale) && actualUniformScale > .001f ? actualUniformScale : Height(appearance);
float growth = float.IsFinite(actualGrowthStride) && actualGrowthStride > .001f ? actualGrowthStride : GrowthStrideScale(appearance);
float combined = uniform * growth;
return float.IsFinite(combined) && combined > .001f ? combined : 1;
}
/// <summary>
/// Converts an unmodified world-space ground speed to native animation speed.
/// Call once on the controller's input, or restore the original input before
/// each controller update and apply after it; repeatedly scaling a renderer's
/// existing parameter would compound. Keep gait selection, directional blend
/// samples and the global animation playback rate under the controller's control.
/// </summary>
public static float CompensateGroundSpeed(float worldSpeed, CharacterAppearanceData appearance)
=> CompensateGroundSpeed(worldSpeed, StrideScale(appearance));
/// <summary>Converts raw speed using an already measured world stride scale.</summary>
public static float CompensateGroundSpeed(float worldSpeed, float strideScale)
{
if (!float.IsFinite(worldSpeed)) return 0;
if (!float.IsFinite(strideScale) || strideScale <= .001f || strideScale == 1) return worldSpeed;
float adjusted = worldSpeed / strideScale;
return float.IsFinite(adjusted) ? adjusted : worldSpeed;
}
}