Useful functions

This is a collection of functions used in game development that often come in handy.

Linear interpolation

float Lerp(float a, float b, float t)
{
    return a * (1 - t) + b * t;
}

Exponential interpolation

Linear interpolation for multiplicative quantities like pitch, scale, zoom, etc.

// Source: Freya Holmér
float Eerp(float a, float b, float t)
{
    return a * float.Exp(t * float.Log(b/a));
}

Exponential decay

Exponential decay interpolation over given time interval. Good values for decay sit between 1 and 25, from slow to fast.

// Source: Freya Holmér
float ExpDecay(float a, float b, float decay, float dt)
{
    return b + (a-b) * float.Exp(-decay * dt);
}

Map

Maps a value s in range a1-a2 to b1-b2.

float Map(float a1, float a2, float b1, float b2, float s)
{
    return b1 + (s - a1) * (b2 - b1) / (a2 - a1);
}

Smooth approach

Smoothly transitions from pastPosition to targetPosition given a speed and deltaTime. Good values for speed hover around 20.

// Source: luispedrofonseca
float SmoothApproach(float pastPosition, float pastTargetPosition, float targetPosition, float speed, float deltaTime)
{
    var t = deltaTime * speed;
    var v = (targetPosition - pastTargetPosition) / t;
    var f = pastPosition - pastTargetPosition + v;
    return targetPosition - v + f * float.Exp(-t);
}

float SmoothApproach(float pastPosition, float targetPosition, float speed, float deltaTime)
{
    // if past target position isn't known, this is fine too
    return SmoothApproach(pastPosition, targetPosition, targetPosition, speed, deltaTime);
}

Snap

Snap a value to increments of snapSize.

float Snap(float x, float snapSize)
{
    return float.Round(x / snapSize) * snapSize;
}

Smoothstep

Maps a value x from range edge0-edge1 to 0-1 with smooth easing. Sometimes referred to as the “most useful function”.

float Smoothstep(float edge0, float edge1, float x)
{
    var t = float.Clamp((x - edge0) / (edge1 - edge0), 0, 1);
    return t * t * (3f - 2f * t);
}

HSV - RGB conversion

Functions to convert HSV to RBG and back. All values range from 0 to 1, even hue.

void GetHsv(Vector3 rgb, out float h, out float s, out float v)
{
    float R = rgb.X;
    float G = rgb.Y;
    float B = rgb.Z;

    float max = float.Max(R, float.Max(G, B));
    float min = float.Min(R, float.Min(G, B));
    float delta = max - min;

    if (delta == 0)
        h = 0;
    else if (max == R)
        h = ((G - B) / delta) % 6;
    else if (max == G)
        h = (B - R) / delta + 2;
    else // max == B
        h = (R - G) / delta + 4;

    h /= 6;
    if (h < 0)
        h += 1;

    s = max == 0 ? 0 : delta / max;
    v = max;
}

Vector3 FromHsv(float h, float s, float v)
{
    float r = 0, g = 0, b = 0;

    float i = (int)(h * 6);
    float f = h * 6 - i;
    float p = v * (1 - s);
    float q = v * (1 - f * s);
    float t = v * (1 - (1 - f) * s);

    switch (((int)i) % 6)
    {
        case 0:
            r = v; g = t; b = p;
            break;
        case 1:
            r = q; g = v; b = p;
            break;
        case 2:
            r = p; g = v; b = t;
            break;
        case 3:
            r = p; g = q; b = v;
            break;
        case 4:
            r = t; g = p; b = v;
            break;
        case 5:
            r = v; g = p; b = q;
            break;
    }

    return new Vector3(r, g, b);
}

Blackbody temperature to RGB

Assuming an ideal blackbody radiator, this function returns the RGB approximation (ranged 0 to 1) of the given temperature in Kelvin.

Vector3 BlackbodyToColor(float kelvin)
{
    var temp = kelvin / 100;
    var color = new Vector3();
    if (temp <= 66)
    {
        color.X = 255;
        color.Y = temp;
        color.Y = 99.4708025861f * float.Log(color.Y) - 161.1195681661f;
        if (temp <= 19)
            color.Z = 0;
        else
        {
            color.Z = temp - 10;
            color.Z = 138.5177312231f * float.Log(color.Z) - 305.0447927307f;
        }
    }
    else
    {
        color.X = temp - 60;
        color.X = 329.698727446f * float.Pow(color.X, -0.1332047592f);
        color.Y = temp - 60;
        color.Y = 288.1221695283f * float.Pow(color.Y, -0.0755148492f);
        color.Z = 255f;
    }

    return new Vector3(
        color.X / 255f,
        color.Y / 255f,
        color.Z / 255f
    );
}

Open explorer

Opens explorer on most systems.

void OpenExplorer(string path)
{
    string executable;

    if (OperatingSystem.IsWindows())
        executable = "explorer";
    else if (OperatingSystem.IsMacOS())
        executable = "open";
    else if (OperatingSystem.IsLinux())
        executable = "xdg-open";
    else return;

    System.Diagnostics.Process.Start(executable, path);
}

Open browser

Opens browser on most systems.

void OpenBrowser(string url)
{
    try
    {
        System.Diagnostics.Process.Start(url);
    }
    catch (Exception)
    {
        if (OperatingSystem.IsWindows())
        {
            url = url.Replace("&", "^&");
            System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });
        }
        else if (OperatingSystem.IsMacOS())
            System.Diagnostics.Process.Start("open", url);
        else if (OperatingSystem.IsLinux())
            System.Diagnostics.Process.Start("xdg-open", url);
        else
            throw;
    }
}