Дуальные числа и касательная к кривой Безье
Дуальные числа, поддержка которых не так давно появилась в 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;
Таким образом, нехитрая алгебра дуальных чисел позволяет эффективно вычислять производные и векторы касательных, что, несомненно, может найти широкое применение в игровых движках – например, когда необходимо получить вектор скорости объекта, движущегося по некой математически описанной траектории.