Я столкнулся с той же проблемой: кажется, что каждый пакет анимации использует кривые Безье для управления значениями во времени, но нет никакой информации о том, как реализовать кривую Безье как функцию y(x). Итак, вот что я придумал.
Стандартная кубическая кривая Безье в двумерном пространстве может быть определена четырьмя точками P0=(x0, y0) . , P3=(x3, y3).
P0 и P3 — конечные точки кривой, а P1 и P2 — маркеры, влияющие на ее форму. Используя параметр t ϵ [0, 1], координаты x и y для любой заданной точки на кривой можно определить с помощью уравнений
A) x = (1-t)3< /sup>x0 + 3t(1-t)2x1 + 3t2(1-t )x2 + t3x3 и
B) y = (1-t)< sup>3y0 + 3t(1-t)2y1 + 3t2 (1-t)y2 + t3y3.
Нам нужна функция y(x), которая по координате x возвращает соответствующую координату y кривой. Чтобы это работало, кривая должна монотонно двигаться слева направо, чтобы она не занимала одну и ту же координату x более одного раза в разных позициях y. Самый простой способ обеспечить это — ограничить входные точки таким образом, чтобы x0 ‹ x3 и x1< /sub>, x2 ϵ [x0, x3]. Другими словами, P0 должен быть слева от P3 с двумя маркерами между ними.
Чтобы вычислить y для данного x, мы должны сначала определить t из x. Получение y из t является простым применением t к уравнению B.
Я вижу два способа определения t для заданного y.
Во-первых, вы можете попробовать бинарный поиск t. Начните с нижней границы 0 и верхней границы 1 и вычислите x для этих значений для t с помощью уравнения A. Продолжайте делить интервал пополам, пока не получите достаточно близкое приближение. Хотя это должно работать нормально, оно не будет ни особенно быстрым, ни очень точным (по крайней мере, не то и другое одновременно).
Второй подход состоит в том, чтобы фактически решить уравнение A для t. Это немного сложно реализовать, потому что уравнение кубическое. С другой стороны, расчет становится очень быстрым и дает точные результаты.
Уравнение A можно переписать как
(-x0+3x1-3x2+x3< /sub>)t3 + (3x0-6x1+3x2)t2 + (-3x0+3x1)t + (x0-x) = 0.< br> Подставляя ваши фактические значения для x0..x3, мы получаем кубическое уравнение вида at3 sup> + bt2 + c*t + d = 0, для которого, как мы знаем, существует только одно решение в пределах [0, 1]. Теперь мы можем решить это уравнение с помощью алгоритма, подобного тому, который опубликован в этом ответе на переполнение стека.
Ниже приведен небольшой класс C#, демонстрирующий этот подход. Это должно быть достаточно просто, чтобы преобразовать его на язык по вашему выбору.
using System;
public class Point {
public Point(double x, double y) {
X = x;
Y = y;
}
public double X { get; private set; }
public double Y { get; private set; }
}
public class BezierCurve {
public BezierCurve(Point p0, Point p1, Point p2, Point p3) {
P0 = p0;
P1 = p1;
P2 = p2;
P3 = p3;
}
public Point P0 { get; private set; }
public Point P1 { get; private set; }
public Point P2 { get; private set; }
public Point P3 { get; private set; }
public double? GetY(double x) {
// Determine t
double t;
if (x == P0.X) {
// Handle corner cases explicitly to prevent rounding errors
t = 0;
} else if (x == P3.X) {
t = 1;
} else {
// Calculate t
double a = -P0.X + 3 * P1.X - 3 * P2.X + P3.X;
double b = 3 * P0.X - 6 * P1.X + 3 * P2.X;
double c = -3 * P0.X + 3 * P1.X;
double d = P0.X - x;
double? tTemp = SolveCubic(a, b, c, d);
if (tTemp == null) return null;
t = tTemp.Value;
}
// Calculate y from t
return Cubed(1 - t) * P0.Y
+ 3 * t * Squared(1 - t) * P1.Y
+ 3 * Squared(t) * (1 - t) * P2.Y
+ Cubed(t) * P3.Y;
}
// Solves the equation ax³+bx²+cx+d = 0 for x ϵ ℝ
// and returns the first result in [0, 1] or null.
private static double? SolveCubic(double a, double b, double c, double d) {
if (a == 0) return SolveQuadratic(b, c, d);
if (d == 0) return 0;
b /= a;
c /= a;
d /= a;
double q = (3.0 * c - Squared(b)) / 9.0;
double r = (-27.0 * d + b * (9.0 * c - 2.0 * Squared(b))) / 54.0;
double disc = Cubed(q) + Squared(r);
double term1 = b / 3.0;
if (disc > 0) {
double s = r + Math.Sqrt(disc);
s = (s < 0) ? -CubicRoot(-s) : CubicRoot(s);
double t = r - Math.Sqrt(disc);
t = (t < 0) ? -CubicRoot(-t) : CubicRoot(t);
double result = -term1 + s + t;
if (result >= 0 && result <= 1) return result;
} else if (disc == 0) {
double r13 = (r < 0) ? -CubicRoot(-r) : CubicRoot(r);
double result = -term1 + 2.0 * r13;
if (result >= 0 && result <= 1) return result;
result = -(r13 + term1);
if (result >= 0 && result <= 1) return result;
} else {
q = -q;
double dum1 = q * q * q;
dum1 = Math.Acos(r / Math.Sqrt(dum1));
double r13 = 2.0 * Math.Sqrt(q);
double result = -term1 + r13 * Math.Cos(dum1 / 3.0);
if (result >= 0 && result <= 1) return result;
result = -term1 + r13 * Math.Cos((dum1 + 2.0 * Math.PI) / 3.0);
if (result >= 0 && result <= 1) return result;
result = -term1 + r13 * Math.Cos((dum1 + 4.0 * Math.PI) / 3.0);
if (result >= 0 && result <= 1) return result;
}
return null;
}
// Solves the equation ax² + bx + c = 0 for x ϵ ℝ
// and returns the first result in [0, 1] or null.
private static double? SolveQuadratic(double a, double b, double c) {
double result = (-b + Math.Sqrt(Squared(b) - 4 * a * c)) / (2 * a);
if (result >= 0 && result <= 1) return result;
result = (-b - Math.Sqrt(Squared(b) - 4 * a * c)) / (2 * a);
if (result >= 0 && result <= 1) return result;
return null;
}
private static double Squared(double f) { return f * f; }
private static double Cubed(double f) { return f * f * f; }
private static double CubicRoot(double f) { return Math.Pow(f, 1.0 / 3.0); }
}
person
Daniel Wolf
schedule
10.04.2013