Contains и проверка вложенности

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


Метод contains: базовый принцип

Метод contains(point) используется для проверки, лежит ли заданная точка внутри геометрического объекта.

Поддерживается для:

  • Path
  • CompoundPath
  • Shape

Пример:

var circle = new Path.Circle({
    center: [100, 100],
    radius: 50
});

var point = new Point(120, 120);

console.log(circle.contains(point)); // true или false

Как работает проверка

Paper.js использует алгоритм заливки (winding rule), чтобы определить принадлежность точки:

  • Non-zero rule (по умолчанию) — учитывает направление обхода сегментов
  • Even-odd rule — учитывает количество пересечений луча

Тип правила влияет на результат при сложных самопересекающихся фигурах.


Проверка точки относительно кривых

Для кривых (Path) результат зависит от того:

  • замкнут ли путь (closed = true)
  • имеет ли он заливку (fillColor)

Если путь:

  • не замкнут, contains() всегда возвращает false
  • замкнут, но без заливки, поведение остаётся корректным — Paper.js анализирует геометрию, а не визуальные стили

Особенности работы с CompoundPath

CompoundPath — это составной путь, включающий несколько вложенных путей.

Пример:

var outer = new Path.Circle([100, 100], 80);
var inner = new Path.Circle([100, 100], 40);

var compound = new CompoundPath({
    children: [outer, inner]
});

В таком случае:

  • внутренняя область может рассматриваться как «дырка»
  • результат contains() зависит от направления сегментов

Важный момент

Если внутренний путь направлен противоположно внешнему:

  • область внутри него исключается

Проверка вложенности объектов

Для определения, находится ли один объект внутри другого, применяется комбинация методов.

Проверка через точки

Наиболее распространённый способ — проверить ключевые точки объекта:

function isInside(pathA, pathB) {
    return pathA.segments.every(function(segment) {
        return pathB.contains(segment.point);
    });
}

Недостатки:

  • не учитываются кривые между сегментами
  • возможны ложные результаты при сложной геометрии

Использование bounds для предварительной проверки

Перед точной проверкой полезно использовать ограничивающие прямоугольники:

if (!pathB.bounds.contains(pathA.bounds)) {
    return false;
}

Это:

  • ускоряет вычисления
  • отсеивает очевидные случаи

Геометрические пересечения как критерий вложенности

Метод getIntersections() позволяет определить, пересекаются ли два пути:

var intersections = pathA.getIntersections(pathB);

if (intersections.length === 0) {
    // возможна вложенность
}

Комбинированный подход

Полная проверка вложенности:

function isFullyInside(inner, outer) {
    if (!outer.bounds.contains(inner.bounds)) return false;

    if (inner.getIntersections(outer).length > 0) return false;

    return outer.contains(inner.position);
}

Проверка центра объекта

Упрощённый вариант:

outer.contains(inner.position);

Подходит:

  • для простых фигур
  • когда объект не выходит за пределы центра

Не подходит:

  • для вытянутых или сложных форм

Проверка вложенности с учётом трансформаций

Paper.js учитывает:

  • поворот (rotation)
  • масштаб (scale)
  • сдвиг (position)

Метод contains() автоматически работает в мировой системе координат, поэтому дополнительные преобразования не требуются.


Работа с кривыми Безье

Пути в Paper.js состоят из сегментов с кривыми Безье. Метод contains():

  • аппроксимирует кривые
  • выполняет проверку с высокой точностью

Однако при очень сложных формах:

  • возможны погрешности
  • рекомендуется увеличивать количество сегментов

Проверка на границе

Если точка лежит точно на границе:

path.contains(point);

Результат может быть:

  • true или false в зависимости от численной точности

Для явной проверки границы используется:

path.getNearestPoint(point).getDistance(point) < epsilon

где epsilon — малая величина (например, 0.1)


Влияние направления сегментов

Направление пути (clockwise или counterclockwise) влияет на:

  • внутренние области
  • работу CompoundPath

Проверка направления:

path.clockwise

Изменение:

path.reverse();

Производительность

Операции contains():

  • достаточно быстрые для большинства задач
  • могут замедляться при большом количестве сегментов

Оптимизация:

  • использовать bounds перед точной проверкой
  • избегать лишних вызовов в циклах
  • кешировать результаты

Практические сценарии

Определение клика внутри фигуры

function onMouseDown(event) {
    if (path.contains(event.point)) {
        console.log('Клик внутри');
    }
}

Проверка попадания объекта в область

if (zone.contains(object.position)) {
    object.fillColor = 'green';
}

Выделение вложенных объектов

items.forEach(function(item) {
    if (container.contains(item.position)) {
        item.selected = true;
    }
});

Работа с отверстиями

var shape = new CompoundPath({
    children: [outerPath, innerPath]
});

shape.fillRule = 'evenodd';

Позволяет явно управлять логикой вложенности.


Ограничения метода contains

  • Не работает с открытыми путями
  • Не учитывает толщину обводки (strokeWidth)
  • Может давать погрешности на границах
  • Не определяет частичную вложенность

Альтернативные методы анализа

Для более сложных задач используются:

  • intersects() — проверка пересечения
  • subtract() — геометрическая разность
  • unite() — объединение областей

Пример:

var result = pathA.subtract(pathB);

Если результат совпадает с pathA, значит pathB не пересекает его.


Итоговые принципы

  • contains() — основной инструмент проверки принадлежности точки

  • вложенность объектов определяется комбинацией:

    • проверки точек
    • пересечений
    • ограничивающих областей
  • сложные формы требуют комбинированного анализа

  • направление сегментов критично для составных путей

  • оптимизация достигается через предварительные проверки и минимизацию вычислений