Дуальные числа и касательная к кривой Безье

Дуальные числа, поддержка которых не так давно появилась в dlib (dlib.math.dual), обладают замечательным свойством: с их помощью можно реализовать автоматическое дифференцирование функций. Если производить вычисления не над вещественными, а над дуальными числами, то в вещественной части результата получается значение самой функции в заданной точке, а в дуальной – значение ее производной.
При этом если оформить функцию в виде шаблона, она без лишних телодвижений расширяется до множества дуальных чисел. Следующий пример показывает дифференцирование простейшей квадратичной функции:

import std.stdio;
import dlib.math.dual;

T parabola(T)(T x)
{
    return x*x;
}

void main()
{
    float x = 1.0f;

    Dualf eval = parabola(Dualf(x, 1.0f));
    float value = eval.re;
    float deriv = eval.du;
    
    writeln(deriv);
}

При запуске программа выдаст значение производной – 2 для точки 1. Правильность результата нетрудно проверить, зная формулу производной степенной функции: если f(x) = xn, то f ‘(x) = nxn-1. Следовательно, если f(x) = x2, то f ‘(x) = 2x.

Теперь начинается самое интересное. Попробуем вместо скалярных величин взять векторные и дифференцировать функцию кривой Безье (dlib.geometry.bezier) для двумерного случая:

import dlib.math.dual;
import dlib.math.vector;
import dlib.geometry.bezier;

alias DualVector2f = Vector!(Dualf, 2);

void main()
{
    float t = 0.5f;
    
    DualVector2f eval = bezierCurveFunc2D(
        DualVector2f(Dualf(0.0f), Dualf(0.0f)),
        DualVector2f(Dualf(1.0f), Dualf(1.0f)),
        DualVector2f(Dualf(2.0f), Dualf(1.0f)),
        DualVector2f(Dualf(3.0f), Dualf(0.0f)),
        Dualf(t, 1.0f));
}

Результирующий вектор eval будет содержать в вещественной части точку на кривой, а в дуальной – вектор касательной к кривой в этой точке, который нам остается только нормировать:

Vector2f point = Vector2f(eval.x.re, eval.y.re);
Vector2f tangent = Vector2f(eval.x.du, eval.y.du).normalized;

Таким образом, нехитрая алгебра дуальных чисел позволяет эффективно вычислять производные и векторы касательных, что, несомненно, может найти широкое применение в игровых движках – например, когда необходимо получить вектор скорости объекта, движущегося по некой математически описанной траектории.