Почему именно кривые Безье? В первую очередь из-за своей привычности для пользователей. Я не встречал ни одного 2d векторного редактора, за основу которого были взяты другие методы аппроксимации кривой по контрольным точкам. Безье – де-факто стандарт среди подходов к векторной графике.
Кривая Безье - это полином K степени, построенный по K + 1 точкам. Точки лежат в N-мерном пространстве. В дальнейшем для выкладок будем использовать N=2 – редактор-то у нас двумерный. Наиболее распространены для использования следующие кривые:
Квадратичная (Quadratic) – построенная по 3 точкам.
Квадратичная кривая строится рекуррентно как линейная интерполяция 2 линейных кривых.
Если упростить выражение, получим следующую формулу:
Кубическая (Cubic) – строится по 4 точкам.
Кубическая кривая выражается через линейную интерполяцию 2 квадратичных кривых:
После упрощения получим:
Можно простроить кривую для произвольных N точек, используя соотношение:
Но на практике чаще всего используют 1<=N<=3. Во-первых, описанные выше случаи легки и интуитивно понятны в управлении для пользователя, во-вторых, чем больше степень кривой, тем больше вычислений требуется для нахождения точек кривой.
Все кривые Безье независимо от степени имеют следующие важные свойства:
Кривая начинается в P0 и заканчивается в Pn
Кривая не зависит от выбора направления движения по ней, т.е.
Прямые P0P1 и Pn-1Pnявляются касательными к кривой в начальной и конечной точках соответственно
Все точки кривой находятся внутри выпуклой оболочки точек, по которым построенна кривая; оболочка на рисунке ниже обозначена красным
Любую кривую можно разбить в любой точке , получив 2 кривых той же степени, которые полностью повторяют оригинальную кривую. Иными словами, найдется набор точек , для которого:
кривые стыкуются в точке, соответствующей параметру :
кривые и совпадают с оригинальной кривой на соответствующих отрезках:
На пункте 5 остановимся подробнее. Алгоритм разбиения кривой называется разбиением de Casteljau. Рассмотрим алгоритм для кубической кривой Безье (для кривых других порядков он работает аналогично).
Пусть у нас есть кривая, построенная по точкам 1-2-3-4
Выберем некоторый параметр t* (для рисунка t* = 0.5) и найдем точки на отрезках 1-2, 2-3 и 3-4, соответствующие линейной интерполяции с параметром t. Назовем точки 12, 23, 34 соответственно. Затем повторим операцию на отрезках 12-23, 23-34, найдя точки 123 и 234. И, наконец, построив линейную интерполяцию по t для отрезка 123-234, получим точку 1234, лежащую на кривой Безье, а кривые, построенные по точкам 1-12-123-1234 и 1234-234-34-4, повторяют оригинальную кривую. Упрощенный код:
Повторюсь, что можно t* взять произвольный, чтобы разбить кривую в любой точке.
Это алгоритм очень важен для рендеринга кривых в редакторе. На его базе, как правило, строят адаптивную ломаную, приближающую форму кривой. Эту ломаную используют для отрисовки кривой.
Как связать формулы с тем, что мы видим в редакторах векторной графики
В векторных редакторах никаких формул нет. Есть кривые, построенные по контрольным точкам:
Существует 2 основных типа контрольных точек, использующихся в редакторах –
Точка с заданием кривизны (Curve) - имеет направляющие (в различных редакторах - handles, tangents, direction points), управляющие формой кривой. Обычно такая точка имеет 2 направляющих.
Угловая(Corner) - вершина без направляющих.
Фактически, контрольная точка с заданием кривизны предоставляет информацию о 2 точках, по которым в дальнейшем будет строится кривая Безье. Это позиция самой точки и позиция конца ее направляющей.
Линейная контрольная точка предоставляет лишь 1 точку для построения кривой Безье.
Соотнести изображение с формулами кривых Безье не очень сложно.
Каждая кривая Безье строится ровно по 2 контрольным точкам. Т.е. если в редакторе мы видим кривую, содержащую 4 контрольные точки (как в примере выше) – значит, она состоит из 3 кривых Безье, 5 контрольных точек - из 4 кривых Безье и т.д. Исключением являются замкнутая кривые - для них число кривых Безье совпадает с числом контрольных точек.
Степень полинома кривой Безье определяется по типам 2 контрольных точек, для которых она построена.
В зависимости от комбинации типов контрольных точке получаем следующие случаи построения кривых Безье:
Обе контрольные точки – Corner. В этом случае мы получаем прямую, проходящую через позиции котрольных точек т.е. линейную кривую Безье. В примере это прямая P1-P2:
Одна из контрольных точек – Curve, другая – Corner. Получаем квадратичную кривую Безье, построенную по позициям 2 контрольных точек и позиции конца направляющей. В примере квадратичная прямая строится для пары контрольных точек P2 (имеет направляющую с концом в точке H) и P3 (не имеет направляющей). Кривая стоится по последовательности точек P2-H-P3.
Обе контрольные точки – Curve. Получаем кубическую кривую, построенную по позициям 2 контрольных точек и позиций концов 2 handles. В примере кубическая кривая стоится для контрольных точек P3 и P4. Кривая стоится по последовательности точек P2-H1-H2-P3.
Создание 2d редактора Безье: стандартные проблемы.
Стандартных проблем две:
Как отрисовывать кривые – параметрическую кривую в чистом виде отрисовать сложно. Как правило, используется ее аппроксимация ломаной, вершины которой лежат на кривой. Понятно, что стоить ломаную можно множеством способов. Какой выбрать и почему?
Практически во всех векторных редакторах контрольные точки с заданием кривизны подразделяются на 3 типа (названия - из Corel Draw):
Cusp – точка имеет 2 полностью независимых направляющих. За счет этого можно формировать острые углы с заданной кривизной
Smooth – точка имеет 2 направляющих, которые противоположно направлены
Symmetrical – то же самое, что и Smooth, только длины обоих направляющих всегда одинаковы.
Проблема состоит в конвертировании точек из одного типа в другой.
Решение проблемы построения ломаной по кривой Безье
Решение «в лоб» – «пройтись» по кривой, изменяя параметр t с некоторым шагом, получив таким образом набор точек, лежащих на кривой. Эти точки образуют искомую ломаную.
Недостаток данного метода состоит в том, что кривая будет плохо приближаться на участках с большой кривизной. Подробнее останавливаться не буду, хорошие примеры есть в начале данной статьи
Для качественной аппроксимации кривой лучше использовать адаптивный метод. Метод основывается на рекурсивном разбиении кривой на участки, пока на каждом участке мы не достигнем требуемой нам гладкости кривой. Главная загвоздка кроется как раз в критерии для «достигнем требуемой нам гладкости». О тяжких поисках такого критерия можно почитать в статье по ссылке выше, но критерии автора мне не очень понравились из-за своей неустойчивости и множества частных случаев.
Использованный мной критерий порога кривизны, как мне кажется, прозрачнее и устойчивее описанного в статье.
Для квадратичной кривой:
Для кубической кривой:
Т.е. кривая считается «достаточно гладкой», когда углы между отрезками, соединяющих ее контрольные точки, становятся «достаточно тупыми» - в данном случае для оценки углов между ними используются cos, посчитанные через скалярное произведение нормализованных векторов отрезков. На практике хорошие результаты давали значения tolerance = 1E-4..1E-2.
Критерий не работает, если какие-либо из соседних (т.е. например, P0 и P1, P1 и P2) контрольных точек совпадают. Эти частные случаи решаются следующим образом:
Если для квадратной кривой P0 P1 P2 совпадают точки P0 P1 или P1 P2 (в программной реализации – находятся на некотором малом расстоянии), квадратичная кривая сводится к линейной, и, следовательно, достаточно добавить к аппроксимирующей ломаной получившийся отрезок линейной кривой.
Если для кубической кривой P0 P1 P2 P3 совпадают точки P0 P1 или P1 P2 или P2 P3 (в программной реализации – находятся на некотором малом расстоянии), используется критерий для кубической кривой (совпадающие точки при применении критерия будут рассматриватся как одна).