isInside и определение внутри/снаружи

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

Метод применяется к объектам типа Item (в частности, Path, Shape, CompoundPath) и возвращает логическое значение:

  • true — точка находится внутри фигуры
  • false — точка находится снаружи

Базовое использование

var path = new Path.Circle({
    center: [100, 100],
    radius: 50,
    fillColor: 'blue'
});

var point = new Point(120, 120);

console.log(path.isInside(point)); // true или false

Метод принимает объект Point или массив координат [x, y].


Геометрическая интерпретация

Определение “внутри” зависит от формы объекта:

  • Для замкнутых путей (closed = true) используется алгоритм определения точки внутри полигона
  • Для незамкнутых путей метод всегда возвращает false, так как у линии нет площади
  • Для сложных фигур учитываются правила заливки (fill-rule)

Алгоритмы определения принадлежности

Paper.js использует стандартные подходы вычислительной геометрии:

1. Even-Odd Rule (чётно-нечётное правило)

Суть:

  • Из точки проводится луч в бесконечность
  • Подсчитывается количество пересечений с границами
  • Чётное число — точка снаружи
  • Нечётное число — внутри

2. Non-Zero Winding Rule (правило обхода)

Учитывает направление обхода сегментов:

  • Если суммарное направление ≠ 0 → точка внутри
  • Используется для сложных фигур с отверстиями

В Paper.js правило выбирается автоматически в зависимости от структуры пути.


Влияние свойства closed

var path = new Path({
    segments: [[0, 0], [100, 0], [100, 100]],
    closed: false
});

console.log(path.isInside([50, 50])); // false

Даже если точки визуально образуют область, без closed = true метод не будет считать её замкнутой.


Работа с составными путями (CompoundPath)

var compound = new CompoundPath({
    children: [
        new Path.Circle([100, 100], 50),
        new Path.Circle([100, 100], 20)
    ],
    fillColor: 'black'
});

console.log(compound.isInside([100, 100]));

Особенности:

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

Границы и пограничные случаи

Точка на границе

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

path.isInside(point);

Результат может зависеть от:

  • точности вычислений
  • особенностей реализации

Чаще всего такие точки считаются внутренними, но это не гарантировано.


Преобразования и координатные системы

Метод isInside учитывает все трансформации:

path.scale(2);
path.rotate(45);

path.isInside(point); // корректно учитывает изменения

Все вычисления происходят в глобальной системе координат проекта.


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

Типичный сценарий — обработка кликов:

function onMouseDown(event) {
    if (path.isInside(event.point)) {
        path.fillColor = 'red';
    }
}

Метод используется для:

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

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

isInside — относительно дорогая операция:

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

Рекомендации:

  • избегать вызова в циклах для большого числа объектов
  • использовать предварительную фильтрацию через bounds
if (path.bounds.contains(point) && path.isInside(point)) {
    // более эффективная проверка
}

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

bounds.contains(point) работает значительно быстрее:

if (path.bounds.contains(point)) {
    if (path.isInside(point)) {
        // точное попадание
    }
}

Это уменьшает количество дорогостоящих проверок.


Отличие от hitTest

Метод hitTest:

project.hitTest(point);

Отличается:

  • возвращает объект с подробной информацией
  • учитывает обводку, сегменты, контрольные точки
  • работает медленнее

isInside:

  • только проверка области
  • быстрее
  • проще

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

var items = project.activeLayer.children;

items.forEach(function(item) {
    if (item.isInside(point)) {
        item.fillColor = 'green';
    }
});

Важно учитывать:

  • порядок объектов
  • перекрытия
  • прозрачность

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

Для путей с кривыми:

var path = new Path({
    segments: [[0, 0], [100, 50], [50, 100]],
    closed: true
});

path.smooth();

Метод корректно учитывает:

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

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

  • Не работает для незамкнутых путей
  • Не учитывает прозрачность (opacity)
  • Не различает визуально “пустые” области без заливки
  • Может давать погрешности на границах

Практические сценарии применения

1. Игровая логика

if (enemyZone.isInside(player.position)) {
    triggerAlarm();
}

2. Графические редакторы

  • выделение фигур
  • определение попадания курсора

3. Визуализация данных

  • проверка принадлежности точки области графика

Расширенные техники

Кэширование сложных форм

Для сложных путей:

var isPointInside = path.isInside.bind(path);

Позволяет избежать повторного поиска метода.


Пространственное индексирование

При большом количестве объектов:

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

Комбинирование с булевыми операциями

var result = path1.unite(path2);

result.isInside(point);

После объединения:

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

Диагностика и отладка

Для визуализации проверяемой точки:

new Path.Circle({
    center: point,
    radius: 3,
    fillColor: 'red'
});

Помогает:

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

Итоговая модель поведения

Метод isInside:

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

Правильное применение обеспечивает точное и эффективное определение принадлежности точки области в сложных графических сценах.