Hat_Math Graph Previewer

#HUD

Simple HUD whose sole purpose is to preview the output of some of the game's math functions on a graph - with the ability to tweak the parameters.

This HUD may be useful for those who animate things via script often, and want to figure out what parameters to use for a specific function.

To preview a function's graph, simply open this HUD using the Open HUD kismet node, and paste the name of the function in the Command property. You may also want to enable Cinematic Mode.

List of supported functions:

  • InterpolationOvershoot
  • InterpolationSpring
  • InterpolationBounce
  • BounceAnimation
  • InterpolationAnticipate
  • InterpolationAnticipateSub
  • InterpolationDecelerate
  • InterpolationEaseInEaseOut

NOTE: This can be extended to support more functions, if needed.

Controls:

  • Parameter 1 - W/S
  • Parameter 2 - A/D
  • Parameter 3 - 1/4/scroll (if not used, these also control Parameter 1)
  • Parameter 4 - 2/3 (if not used, these also control Parameter 2)

NOTE: As of right now, none of the functions use more than 2 parameters.

Drew_HUDElement_HatMathGraphPreviewer.uc

[RAW] [Download]

/**
 * HUD whose sole purpose is to preview the output of some of the math functions on a graph.
 * To preview a function, simply enter its name in the Command property of the Open HUD node.
 *
 * Supported functions:
 *    - InterpolationOvershoot
 *    - InterpolationSpring
 *    - InterpolationBounce
 *    - BounceAnimation
 *    - InterpolationAnticipate
 *    - InterpolationAnticipateSub
 *    - InterpolationDecelerate
 *    - InterpolationEaseInEaseOut
 *
 * NOTE: This can be extended to support more functions, if needed.
 *
 * Controls:
 *    - Parameter 1 - W/S
 *    - Parameter 2 - A/D
 *    - Parameter 3 - 1/4/scroll (if not used, these also control Parameter 1)
 *    - Parameter 4 - 2/3        (if not used, these also control Parameter 2)
 *
 * NOTE: As of right now, none of the functions use more than 2 parameters.
 */
class Drew_HUDElement_HatMathGraphPreviewer extends Hat_HUDElement;
 
const GRAPH_SAMPLE_COUNT = 200;
const GRAPH_PADDING = 0.15f;
 
var string FunctionNameDisplay;
var string ParameterDisplay;
var Vector2D GraphRangeStart, GraphRangeEnd;
var int VerticalLineAmount, HorizontalLineAmount;
 
struct DebugGraphParameter
{
    var string Name;
    var float StepValue;
    var int StepCount;
    var bool IsBool;
    var transient float CurrentValue;
};
 
var DebugGraphParameter P1, P2, P3, P4;
 
delegate float MathFunctionOutput(float X)
{
    return 0.5f;
}
 
function OnOpenHUD(HUD H, optional string command)
{
    Super.OnOpenHUD(H, command);
 
    switch (Locs(command))
    {
        case "interpolationovershoot":
            FunctionNameDisplay = "InterpolationOvershoot(0, 1, X, Tension)";
            MathFunctionOutput = InterpolationOvershoot;
            GraphRangeStart.Y = 3.f;
            HorizontalLineAmount = 7;
            P1.Name = "Tension";
            P1.StepValue = 0.5f;
            P1.StepCount = 4;
            RefreshParameter(P1);
            break;
        case "interpolationspring":
            FunctionNameDisplay = "InterpolationSpring(0, 1, X, Factor, Bounce Rate)";
            MathFunctionOutput = InterpolationSpring;
            GraphRangeStart.Y = 2.f;
            HorizontalLineAmount = 5;
            P1.Name = "Factor";
            P1.StepValue = 0.05f;
            P1.StepCount = 8;
            RefreshParameter(P1);
            P2.Name = "Bounce Rate";
            P2.StepValue = 0.5f;
            P2.StepCount = 20;
            RefreshParameter(P2);
            break;
        case "interpolationbounce":
            FunctionNameDisplay = "InterpolationBounce(1, 0, X, Strength)";
            MathFunctionOutput = InterpolationBounce;
            GraphRangeEnd.X = 1.5f;
            VerticalLineAmount = 7;
            P1.Name = "Strength";
            P1.StepValue = 0.1f;
            P1.StepCount = 10;
            RefreshParameter(P1);
            break;
        case "bounceanimation":
            FunctionNameDisplay = "BounceAnimation(X, Scale, BounceCount)";
            MathFunctionOutput = BounceAnimation;
            GraphRangeStart.Y = 2.f;
            GraphRangeEnd.Y = 1.f;
            HorizontalLineAmount = 5;
            P1.Name = "Scale";
            P1.StepValue = 0.1f;
            P1.StepCount = 5;
            RefreshParameter(P1);
            P2.Name = "Bounce Count";
            P2.StepValue = 1.f;
            P2.StepCount = 4;
            RefreshParameter(P2);
            break;
        case "interpolationanticipate":
            FunctionNameDisplay = "InterpolationAnticipate(0, 1, X, Tension, Overshoot)";
            MathFunctionOutput = InterpolationAnticipate;
            GraphRangeStart.Y = 2.f;
            GraphRangeEnd.Y = -1.f;
            HorizontalLineAmount = 7;
            P1.Name = "Tension";
            P1.StepValue = 0.5f;
            P1.StepCount = 6;
            RefreshParameter(P1);
            P2.Name = "Overshoot";
            P2.StepValue = 1.f;
            P2.StepCount = 1;
            P2.IsBool = True;
            RefreshParameter(P2);
            break;
        case "interpolationanticipatesub":
            FunctionNameDisplay = "InterpolationAnticipateSub(X, S, Subtract)";
            MathFunctionOutput = InterpolationAnticipateSub;
            P1.Name = "S";
            P1.StepValue = 0.1f;
            P1.StepCount = 0;
            RefreshParameter(P1);
            P2.Name = "Subtract";
            P2.StepValue = 1.f;
            P2.StepCount = 1;
            P2.IsBool = True;
            RefreshParameter(P2);
            break;
        case "interpolationdecelerate":
            FunctionNameDisplay = "InterpolationDecelerate(0, 1, X, Factor)";
            MathFunctionOutput = InterpolationDecelerate;
            P1.Name = "Factor";
            P1.StepValue = 0.1f;
            P1.StepCount = 10;
            RefreshParameter(P1);
            break;
        case "interpolationeaseineaseout":
            FunctionNameDisplay = "InterpolationEaseInEaseOut(0, 1, X)";
            MathFunctionOutput = InterpolationEaseInEaseOut;
            break;
    }
    RefreshParameterDisplay();
}
 
function RefreshParameter(out DebugGraphParameter P)
{
    if (P.IsBool) P.StepCount = Clamp(P.StepCount, 0, 1);
    P.CurrentValue = P.StepValue * P.StepCount;
}
 
function RefreshParameterDisplay()
{
    ParameterDisplay = "";
    AppendParameterDisplayForParameter(P1);
    AppendParameterDisplayForParameter(P2);
    AppendParameterDisplayForParameter(P3);
    AppendParameterDisplayForParameter(P4);
}
 
function AppendParameterDisplayForParameter(out DebugGraphParameter P)
{
    if (P.StepValue == 0.f) return;
    if (ParameterDisplay != "") ParameterDisplay $= " , ";
    if (P.IsBool)
        ParameterDisplay $= (P.Name $ " = " $ Bool(P.CurrentValue));
    else
        ParameterDisplay $= (P.Name $ " = " $ PrettyNumberRounding(P.CurrentValue));
}
 
function bool OnPressUp(HUD H, bool menu, bool release)
{
    if (release) return false;
    if (menu && P3.StepValue != 0.f)
    {
        P3.StepCount++;
        RefreshParameter(P3);
    }
    else
    {
        P1.StepCount++;
        RefreshParameter(P1);
    }
    RefreshParameterDisplay();
    return true;
}
 
function bool OnPressDown(HUD H, bool menu, bool release)
{
    if (release) return false;
    if (menu && P3.StepValue != 0.f)
    {
        P3.StepCount--;
        RefreshParameter(P3);
    }
    else
    {
        P1.StepCount--;
        RefreshParameter(P1);
    }
    RefreshParameterDisplay();
    return true;
}
 
function bool OnPressLeft(HUD H, bool menu, bool release)
{
    if (release) return false;
    if (menu && P4.StepValue != 0.f)
    {
        P4.StepCount--;
        RefreshParameter(P4);
    }
    else
    {
        P2.StepCount--;
        RefreshParameter(P2);
    }
    RefreshParameterDisplay();
    return true;
}
 
function bool OnPressRight(HUD H, bool menu, bool release)
{
    if (release) return false;
    if (menu && P4.StepValue != 0.f)
    {
        P4.StepCount++;
        RefreshParameter(P4);
    }
    else
    {
        P2.StepCount++;
        RefreshParameter(P2);
    }
    RefreshParameterDisplay();
    return true;
}
 
function bool Render(HUD H)
{
    local Vector2D GraphStart, GraphEnd;
    local float MinClip, Length, Ratio, PosX, PosY, NextPosX, ValueX, TextSize;
    local int i;
    local TextAlign TA;
    local bool InBatched;
 
    if (!Super.Render(H)) return false;
 
    H.Canvas.SetDrawColor(0, 0, 0, 255);
    H.Canvas.SetPos(0.f, 0.f);
    H.Canvas.DrawRect(H.Canvas.ClipX, H.Canvas.ClipY);
 
    MinClip = FMin(H.Canvas.ClipX, H.Canvas.ClipY);
    TextSize = MinClip * 0.0007f;
 
    GraphStart.X = MinClip * GRAPH_PADDING;
    GraphStart.Y = GraphStart.X;
    GraphEnd.X = H.Canvas.ClipX - GraphStart.X;
    GraphEnd.Y = H.Canvas.ClipY - GraphStart.Y;
 
    H.Canvas.SetDrawColor(100, 100, 100, 255);
    H.Canvas.Font = class'Hat_FontInfo'.static.GetDefaultFont("0123456789.");
 
    // Vertical lines
    if (VerticalLineAmount >= 2)
    {
        PosY = (GraphStart.Y + GraphEnd.Y) * 0.5f;
        Length = GraphEnd.Y - GraphStart.Y;
        for (i = 0; i < VerticalLineAmount; i++)
        {
            Ratio = Float(i) / (VerticalLineAmount - 1.f);
            PosX = Lerp(GraphStart.X, GraphEnd.X, Ratio);
            DrawCenter(H, PosX, PosY, 2.f, Length, H.Canvas.DefaultTexture);
            if (i == 0)
                TA = TextAlign_Left;
            else
                TA = TextAlign_Center;
            DrawText(H.Canvas, PrettyNumberRounding(Lerp(GraphRangeStart.X, GraphRangeEnd.X, Ratio)), PosX, GraphEnd.Y + TextSize * 30.f, TextSize, TextSize, TA);
        }
    }
 
    // Horizontal lines
    if (HorizontalLineAmount >= 2)
    {
        PosX = (GraphStart.X + GraphEnd.X) * 0.5f;
        Length = GraphEnd.X - GraphStart.X;
        for (i = 0; i < HorizontalLineAmount; i++)
        {
            Ratio = Float(i) / (HorizontalLineAmount - 1.f);
            PosY = Lerp(GraphStart.Y, GraphEnd.Y, Ratio);
            DrawCenter(H, PosX, PosY, Length, 2.f, H.Canvas.DefaultTexture);
            if (i == HorizontalLineAmount - 1)
                TA = TextAlign_BottomRight;
            else
                TA = TextAlign_Right;
            DrawText(H.Canvas, PrettyNumberRounding(Lerp(GraphRangeStart.Y, GraphRangeEnd.Y, Ratio)), GraphStart.X - TextSize * 10.f, PosY, TextSize, TextSize, TA);
        }
    }
 
    H.Canvas.SetDrawColor(255, 255, 255, 255);
    NextPosX = GraphStart.X;
    InBatched = H.Canvas.StartBatchedRendering();
    for (i = 0; i < GRAPH_SAMPLE_COUNT; i++)
    {
        PosX = NextPosX;
        NextPosX = Lerp(GraphStart.X, GraphEnd.X, Float(i + 1) / GRAPH_SAMPLE_COUNT);
        ValueX = Lerp(GraphRangeStart.X, GraphRangeEnd.X, Float(i) / (GRAPH_SAMPLE_COUNT - 1.f));
        PosY = Lerp(GraphStart.Y, GraphEnd.Y, (MathFunctionOutput(ValueX) - GraphRangeStart.Y) / (GraphRangeEnd.Y - GraphRangeStart.Y));
        DrawCenterLeft(H, PosX, PosY, NextPosX - PosX, 2.f, H.Canvas.DefaultTexture);
    }
    if (InBatched) H.Canvas.EndBatchedRendering();
 
    if (FunctionNameDisplay != "")
    {
        H.Canvas.SetDrawColor(255, 255, 200, 255);
        H.Canvas.Font = class'Hat_FontInfo'.static.GetDefaultFont(FunctionNameDisplay);
        DrawText(H.Canvas, FunctionNameDisplay, H.Canvas.ClipX * 0.5f, TextSize * 1.5f * 55.f, TextSize * 1.5f, TextSize * 1.5f, TextAlign_Center);
    }
 
    if (ParameterDisplay != "")
    {
        H.Canvas.SetDrawColor(255, 255, 255, 255);
        H.Canvas.Font = class'Hat_FontInfo'.static.GetDefaultFont(ParameterDisplay);
        DrawText(H.Canvas, ParameterDisplay, H.Canvas.ClipX * 0.5f, H.Canvas.ClipY - TextSize * 55.f, TextSize, TextSize, TextAlign_Center);
    }
 
    return true;
}
 
function string PrettyNumberRounding(float InFloat)
{
    local int Number, Decimal;
    Number = Round(InFloat * 1000.f);
    Decimal = Number % 1000;
    Number /= 1000;
    while (Decimal != 0 && Decimal % 10 == 0)
        Decimal /= 10;
    if (Decimal == 0)
        return String(Number);
    else
        return (Decimal < 0 ? "-" : "") $ Int(Abs(Number)) $ "." $ Int(Abs(Decimal));
}
 
// Math functions.
 
function float InterpolationOvershoot(float X)
{
    return class'Hat_Math'.static.InterpolationOvershoot(0.f, 1.f, X, P1.CurrentValue);
}
 
function float InterpolationSpring(float X)
{
    return class'Hat_Math'.static.InterpolationSpring(0.f, 1.f, X, P1.CurrentValue, P2.CurrentValue);
}
 
function float InterpolationBounce(float X)
{
    return class'Hat_Math'.static.InterpolationBounce(1.f, 0.f, X, P1.CurrentValue);
}
 
function float BounceAnimation(float X)
{
    return class'Hat_Math'.static.BounceAnimation(X, P1.CurrentValue, Round(P2.CurrentValue));
}
 
function float InterpolationAnticipate(float X)
{
    return class'Hat_Math'.static.InterpolationAnticipate(0.f, 1.f, X, P1.CurrentValue, Bool(P2.CurrentValue));
}
 
function float InterpolationAnticipateSub(float X)
{
    return class'Hat_Math'.static.InterpolationAnticipateSub(X, P1.CurrentValue, Bool(P2.CurrentValue));
}
 
function float InterpolationDecelerate(float X)
{
    return class'Hat_Math'.static.InterpolationDecelerate(0.f, 1.f, X, P1.CurrentValue);
}
 
function float InterpolationEaseInEaseOut(float X)
{
    return class'Hat_Math'.static.InterpolationEaseInEaseOut(0.f, 1.f, X);
}
 
defaultproperties
{
    FunctionNameDisplay = "Missing command! (read script header)"
 
    GraphRangeStart = (X = 0.f, Y = 1.f)
    GraphRangeEnd   = (X = 1.f, Y = 0.f)
 
    VerticalLineAmount = 5
    HorizontalLineAmount = 5
}