Path morphing

В Paper.js морфинг путей (path morphing) представляет собой плавное преобразование одной кривой или фигуры в другую, сохраняя при этом непрерывность и форму сегментов. Этот процесс требует понимания структуры Path и работы с его сегментами, кривыми Безье и узлами.

Структура Path

Path состоит из последовательности сегментов (Segment), каждый из которых включает:

  • point — координату узла.
  • handleIn — вектор, определяющий входящую кривую Безье.
  • handleOut — вектор, определяющий исходящую кривую Безье.

Прямые линии можно рассматривать как кривые с нулевыми ручками. Плавный морфинг достигается согласованием количества сегментов между исходным и целевым Path.

let path1 = new Path.Circle(new Point(100, 100), 50);
let path2 = new Path.Rectangle(new Point(50, 50), new Size(100, 100));

Для успешного морфинга количество сегментов должно совпадать. Если сегментов меньше, добавляют дополнительные точки с помощью path.add() или path.insert().

Интерполяция сегментов

Ключ к плавному морфингу — линейная интерполяция между точками и ручками сегментов. Для сегментов A и B интерполяция выглядит так:

function interpolateSegments(segA, segB, t) {
    return new Segment({
        point: segA.point.multiply(1 - t).add(segB.point.multiply(t)),
        handleIn: segA.handleIn.multiply(1 - t).add(segB.handleIn.multiply(t)),
        handleOut: segA.handleOut.multiply(1 - t).add(segB.handleOut.multiply(t))
    });
}

Здесь t — параметр от 0 до 1, где 0 соответствует исходной фигуре, а 1 — целевой.

Анимация морфинга

Для анимации используется функция onFrame, которая вызывается каждый кадр:

let t = 0;
let path1Segments = path1.segments;
let path2Segments = path2.segments;

view.onFr ame = (event) => {
    t += 0.01;
    if (t > 1) t = 1;

    for (let i = 0; i < path1Segments.length; i++) {
        let newSeg = interpolateSegments(path1Segments[i], path2Segments[i], t);
        path1Segments[i].point = newSeg.point;
        path1Segments[i].handleIn = newSeg.handleIn;
        path1Segments[i].handleOut = newSeg.handleOut;
    }
};

Это создаёт плавный переход между формами без разрывов.

Согласование сегментов

Если исходный и целевой пути имеют разное количество сегментов, применяются методы:

  • Равномерная вставка сегментов: распределение дополнительных точек по длине кривой с помощью path.length и path.getPointAt().
  • Копирование узлов с делением кривых: более сложный вариант для сохранения формы кривой, когда сегменты добавляются в местах максимального отклонения.
function equalizeSegments(pathA, pathB) {
    while (pathA.segments.length < pathB.segments.length) {
        let segment = pathA.getPointAt(pathA.length / (pathB.segments.length));
        pathA.add(segment);
    }
}

Морфинг сложных фигур

Для сложных путей с внутренними контурами (например, буквы, логотипы) морфинг выполняется отдельно для каждого контура. Контуры должны быть согласованы по количеству сегментов и порядку обхода (clockwise или counter-clockwise), иначе появятся визуальные артефакты.

Использование кривых Безье для сглаживания

Ручки кривых (handleIn и handleOut) играют ключевую роль в плавности морфинга. Для сглаживания перехода применяют:

  • Нормализацию длины ручек по длине сегмента.
  • Плавную интерполяцию угла и длины ручек с использованием функции Point.interpolate().
let newHandleIn = segA.handleIn.multiply(1 - t).add(segB.handleIn.multiply(t));
let newHandleOut = segA.handleOut.multiply(1 - t).add(segB.handleOut.multiply(t));

Контроль скорости морфинга

Параметр t можно изменять нелинейно для создания эффектов ускорения и замедления, используя кривые Безье или функции easing:

function easeInOut(t) {
    return t < 0.5 ? 2*t*t : -1 + (4 - 2*t)*t;
}

Это позволяет плавно разгонять морфинг в начале и замедлять к концу.

Практические советы

  • Всегда предварительно согласовывать количество сегментов для избежания разрывов.
  • Использовать копирование сегментов вместо прямого перемещения исходного пути, если нужно сохранить начальную фигуру.
  • Применять path.smooth() после интерполяции, чтобы убрать мелкие неровности.
  • Для сложных анимаций использовать отдельные слои (Layer) для каждого морфящегося пути.

Итоговые механизмы Paper.js для морфинга

  • Segment — ключевой элемент для морфинга.
  • Path — контейнер сегментов, позволяющий анимировать форму.
  • Point.multiply и Point.add — инструменты для линейной интерполяции.
  • view.onFrame — базовый механизм анимации.
  • path.length и path.getPointAt() — методы для равномерного распределения сегментов.
  • path.smooth() — метод для сглаживания кривых после морфинга.

Эти техники обеспечивают создание плавных, визуально точных преобразований фигур, позволяя Paper.js использоваться для сложной анимации в веб-графике.