Зверніть увагу на те, що відбувається, коли круг повністю знаходиться
всередині багатокутника (ми виправимо це пізніше).

Прямокутник і Круг

Задачу по перевірці, чи круг має колізію з багатокутником, можна спростити до серії перевірок на зіткнення лінії з кругом, по одному для кожної сторони багатокутника. Оскільки ми вже розглядали кроки перебору вершин для перевірки зіткнення лінії з кругом, давайте просто подивимося на перевірку кожної сторони:

let isCollision = isLineWithCircleCollides(vc.x,vc.y, vn.x,vn.y, cx,cy,r);
if (isCollision) return true;

Чудово! Таким чином ми можемо спиратися на попередній код, дозволяючи гнучкому, складному коду виникати з простіших частин.

Ось повний приклад:

/ змінні для круга
let cx = 0;
let cy = 0;
let r = 30;

// масив для векторів, по одному для кожної вершини багатокутника
let vertices = [];

function setup() {
  createCanvas(window.innerWidth, window.innerHeight);
  noStroke();

  // створення координат для багатокутника (тут це трапеція)
  vertices[0] = createVector(width/2-100, height/2-100);
  vertices[1] = createVector(width/2+100, height/2-100);
  vertices[2] = createVector(width/2+50,  height/2+100);
  vertices[3] = createVector(width/2-50,  height/2+100);
}

function draw() {
  background(255);

  // оновлення координат центру круга координатами курсора
  cx = mouseX;
  cy = mouseY;

  // результат перевірки на зіткнення
  // при зіткненні змінюємо колір
  const isHit = isPolyWithCircleCollides(vertices, cx, cy, r);
  if (isHit) fill(255, 150, 0);
  else fill(0, 150, 255);

  // малювання багатокутника через перебір вершин
  noStroke();
  beginShape();
  for (const v of vertices) {
    vertex(v.x, v.y);
  }
  endShape();

  // малювання круга
  fill(0, 150);
  ellipse(cx, cy, r * 2, r * 2);
}

// перевірка на перетин між багатокутником і кругом
function isPolyWithCircleCollides(vertices, cx, cy, r) {
  // перебір кожної вершини з використанням наступної вершини в списку
  let next = 0;
  for (let current = 0; current < vertices.length; current++) {
    // отримання наступної вершини зі списку
    next = current + 1;
    // коли дійшли до останньої вершини, беремо першу під індексом 0
    if (next === vertices.length) {
      next = 0;
    }

    // отримуємо p5.Vector у нашій поточній позиції
    // це зробить наш оператор if трохи чистішим
    const vc = vertices[current];  // c для "current" (поточний)
    const vn = vertices[next];     // n для "next" (наступний)

    // перевірка колізії між кругом і лінією, утвореною між двома вершинами
    const isCollision = isLineWithCircleCollides(vc.x, vc.y, vn.x, vn.y, cx, cy, r);
    if (isCollision) return true;
  }

  // наведений вище алгоритм лише перевіряє, чи круг торкається країв багатокутника –
  // у більшості випадків цього достатньо, але ви можете розкоментувати наступний код,
  // щоб також перевірити, чи центр кола знаходиться всередині багатокутника

  // const isCenterInside = isPolygonPointCollides(vertices, cx,cy);
  // if (isCenterInside) return true;

  // у іншому випадку повертаємо false
  return false;
}

// перевірка на перетин між лінією і кругом
function isLineWithCircleCollides(x1, y1, x2, y2, cx, cy, r) {

  // якщо один з кінців всередині круга,
  // тоді одразу повертаємо true
  const inside1 = isPointWithCircleCollides(x1, y1, cx, cy, r);
  const inside2 = isPointWithCircleCollides(x2, y2, cx, cy, r);
  if (inside1 || inside2) return true;

  // отримання довжини лінії
  let distX = x1 - x2;
  let distY = y1 - y2;
  const len = sqrt((distX * distX) + (distY * distY));

  // скалярний добуток прямої та круга
  const dot = (((cx - x1) * (x2 - x1)) + ((cy - y1) * (y2 - y1))) / pow(len, 2);

  // знаходження найближчої точки на прямій
  const closestX = x1 + (dot * (x2 - x1));
  const closestY = y1 + (dot * (y2 - y1));

  // ця точка дійсно знаходиться на відрізку?
  // якщо так, продовжуємо, але повертаємо false
  const onSegment = isLineWithPointCollides(x1, y1, x2, y2, closestX, closestY);
  if (!onSegment) return false;

  // малювання кружечка, щоб підсвітити найближчу точку на лінії (опціонально)
  fill(255, 0, 0);
  noStroke();
  ellipse(closestX, closestY, 20, 20);

  // обчислення відстані до найближчої точки
  distX = closestX - cx;
  distY = closestY - cy;
  const distance = sqrt((distX * distX) + (distY * distY));

  // чи круг має перетин з лінією?
  if (distance <= r) {
    return true;
  }

  return false;
}

// перевірка на перетин між лінією і точкою
function isLineWithPointCollides(x1, y1, x2, y2, px, py) {
  // обчислення відстані від точки до двох кінців відрізка
  const d1 = dist(px, py, x1, y1);
  const d2 = dist(px, py, x2, y2);

  // обчислення довжини відрізка
  const lineLen = dist(x1, y1, x2, y2);

  // невеликий додатковий буфер для збільшення області перетину
  const buffer = 0.1;

  // якщо сума двох відстаней дорівнює довжині відрізка, тоді точка знаходиться на відрізку!
  // зауважте, що тут ми додатково використовуємо буфер для збільшення радіуса зіткнення
  if (d1 + d2 >= lineLen - buffer && d1 + d2 <= lineLen + buffer) {
    return true;
  }

  return false;
}

// перевірка на перетин між точкою і кругом
function isPointWithCircleCollides(px, py, cx, cy, r) {
  // отримання дистанції між точкою та центром круга
  // за допомоги теореми Піфагора
  const distX = px - cx;
  const distY = py - cy;
  const distance = sqrt((distX * distX) + (distY * distY));

  // якщо відстань менша за радіус кола, значить точка всередині!
  if (distance <= r) {
    return true;
  }

  return false;
}

// перевірка на перетин між багатокутником та точкою
// потрібно, лише якщо ви збираєтеся перевірити, чи круг знаходиться ВСЕРЕДИНІ багатокутника
function isPolygonPointCollides(vertices, px, py) {
  let isCollision = false;

  // перебір кожної вершини з використанням наступної вершини в списку
  let next = 0;
  for (let current = 0; current < vertices.length; current++) {

    // отримання наступної вершини зі списку
    next = current + 1;
    // коли дійшли до останньої вершини, беремо першу під індексом 0
    if (next === vertices.length) {
      next = 0;
    }

    // отримуємо p5.Vector у нашій поточній позиції
    // це зробить наш оператор if трохи чистішим
    const vc = vertices[current];// c для "current" (поточний)
    const vn = vertices[next];   // n для "next" (наступний)

    // порівняти позицію, інвертувати змінну 'isCollision'
    if (((vc.y > py && vn.y < py) || (vc.y < py && vn.y > py)) &&
      (px < (vn.x - vc.x) * (py - vc.y) / (vn.y - vc.y) + vc.x)) {
      isCollision = !isCollision;
    }
  }

  return isCollision;
}

Оскільки функція isPolyWithCircleCollides() викликає isLineWithCircleCollides() яка викликає функцію isLineWithPointCollides(), ми могли б об’єднати їх в одну єдину функцію, але ідея функцій у програмуванні полягає у їх повторному використанні. Якщо ми оновимо функцію isLineWithPointCollides(), то це вплине на усі наші проєкти, які її використовують.

Але! У нас є невелика проблема. Спробуйте перемістити круг так, щоб він був повністю всередині багатокутника. Виявлення колізії зникає! Такі ситуації називаються граничними випадками , коли для перевірки потрібен інший набір параметрів.

У більшості випадків нам не потрібно знати, чи круг всередині: уявіть, що багатокутник — це космічний корабель, а круг — астероїд. Як тільки астероїд торкається корабля, ми реєструємо зіткнення і щось робимо (наприклад, руйнуємо корабель).

Якщо потрібно дізнатися, чи знаходиться круг всередині багатокутника, ви можете додати ще два рядки коду до функції isPolyWithCircleCollides() (безпосередньо перед фіналом return false;), щоб перевірити, чи знаходиться центр круга всередині багатокутника:

const isCenterInside = isPolygonPointCollides(cx,cy, vertices);

if (isCenterInside) return true;

Ми робимо це після перевірки ребер (сторін багатокутника), оскільки вони, швидше за все, будуть перетнуті першими. Якщо ця перевірка вам не потрібна, облиште її.

Далі: Багатокутник і Прямокутник