Как определить, когда повернутые прямоугольники сталкиваются друг с другом - PullRequest
0 голосов
/ 26 мая 2020

После того, как я много раз увидел этот вопрос и ответил старым (непригодным для использования) кодом, я решил все переделать и опубликовать об этом.

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

  • center: x и y для его позиции (помните, что 0; 0 - TOP Left, поэтому Y go вниз)
  • size: x и y для его размера
  • angle за его вращение (в градусах, 0 градусов следует за осью OX и поворачивается по часовой стрелке)

Цель должен знать, сталкиваются ли 2 прямоугольника или нет.

1 Ответ

2 голосов
/ 26 мая 2020

Будет использовать Javascript для демонстрации этого (а также предоставления кода), но я могу сделать это на любом языке после процесса.

Ссылки

Концепция

В Для этого мы будем использовать проекции углов на оси другого прямоугольника 2 (X и Y). Два прямоугольника сталкиваются только тогда, когда 4 проекции на одном прямоугольнике соприкасаются с другими:

  • Прямоугольные синие углы на прямоугольной оранжевой оси X
  • Прямоугольные синие углы на прямоугольной оранжевой оси Y
  • Прямоугольники оранжевого цвета на оси X Rect Blue
  • Прямоугольники оранжевого цвета на оси Y Rect Blue

3 concept previews

Процесс

1- Найдите ось прямоугольника

Начните с создания 2 векторов для оси 0; от 0 (центр прямоугольника) до X (OX ) и Y (OY), затем поверните их оба, чтобы выровнять их по оси прямоугольников.

Википедия о повороте 2D-вектора

const getAxis = (rect) => {
  const OX = new Vector({x:1, y:0});
  const OY = new Vector({x:0, y:1});
  // Do not forget to transform degree to radian
  const RX = OX.Rotate(rect.angle * Math.PI / 180);
  const RY = OY.Rotate(rect.angle * Math.PI / 180);

  return [
     new Line({...rect.center, dx: RX.x, dy: RX.y}),
     new Line({...rect.center, dx: RY.x, dy: RY.y}),
  ];
}

Где вектор простой объект x,y

class Vector {
  constructor({x=0,y=0}={}) {
    this.x = x;
    this.y = y;
  }
  Rotate(theta) {
    return new Vector({
      x: this.x * Math.cos(theta) - this.y * Math.sin(theta),
      y: this.x * Math.sin(theta) + this.y * Math.cos(theta),
    });
  }
}

И линия представляет собой уклон с использованием 2 векторов:

  • origin: вектор для начальной позиции
  • direction: вектор для единицы Направление
class Line {
  constructor({x=0,y=0, dx=0, dy=0}) {
    this.origin = new Vector({x,y});
    this.direction = new Vector({x:dx,y:dy});
  }
}

Результат шага

enter image description here

2- Используйте прямую ось для получения углов

* 10 76 * Сначала нужно расширить нашу ось (у нас размер единицы 1 пиксель), чтобы получить половину ширины (для X) и высоты (для Y), чтобы можно было, добавив, когда (и обратное), получить все углы.
const getCorners = (rect) => {
  const axis = getAxis(rect);
  const RX = axis[0].direction.Multiply(rect.w/2);
  const RY = axis[1].direction.Multiply(rect.h/2);
  return [
    rect.center.Add(RX).Add(RY),
    rect.center.Add(RX).Add(RY.Multiply(-1)),
    rect.center.Add(RX.Multiply(-1)).Add(RY.Multiply(-1)),
    rect.center.Add(RX.Multiply(-1)).Add(RY),
  ]
}

Использование этих 2 методов новостей для Vector:

  // Add(5)
  // Add(Vector)
  // Add({x, y})
  Add(factor) {
    const f = typeof factor === 'object'
      ? { x:0, y:0, ...factor}
      : {x:factor, y:factor}
    return new Vector({
      x: this.x + f.x,
      y: this.y + f.y,
    })
  }
  // Multiply(5)
  // Multiply(Vector)
  // Multiply({x, y})
  Multiply(factor) {
    const f = typeof factor === 'object'
      ? { x:0, y:0, ...factor}
      : {x:factor, y:factor}
    return new Vector({
      x: this.x * f.x,
      y: this.y * f.y,
    })
  }

Результат шага

enter image description here

3- Получить проекции углов

Для каждого угла прямоугольника получить координаты проекции по обеим осям другого прямоугольника.

Просто добавив эту функцию в класс Vector:

  Project(line) {
    let dotvalue = line.direction.x * (this.x - line.origin.x)
      + line.direction.y * (this.y - line.origin.y);
    return new Vector({
      x: line.origin.x + line.direction.x * dotvalue,
      y: line.origin.y + line.direction.y * dotvalue,
    })
  }

(Особая благодарность Mbo за решение для получения проекции.)

Результат шага

enter image description here

4- Выберите внешние углы на проекциях

Чтобы отсортировать (по оси прямоугольника) все проецируемые точки и взять минимальные и Максимальное количество спроецированных точек, которые мы можем:

  • Создать вектор для представления: Прямоугольник от центра до проецируемого угла
  • Получить расстояние с помощью Vector Ma Функция gnitude.
  get magnitude() {
    return Math.sqrt(this.x * this.x + this.y * this.y);
  }
  • Используйте скалярное произведение , чтобы узнать, обращен ли вектор в том же направлении оси обратного (где расстояние со знаком "отрицательное")
getSignedDistance = (rect, line, corner) => {
  const projected = corner.Project(line);
  const CP = projected.Minus(rect.center);
  // Sign: Same directon of axis : true.
  const sign = (CP.x * line.direction.x) + (CP.y * line.direction.y) > 0;
  const signedDistance = CP.magnitude * (sign ? 1 : -1);
}

Затем, используя простой l oop и тест min / max, мы можем найти 2 внешних угла. Сегмент между ними - это проекция прямоугольника на другую ось.

Результат шага

enter image description here

5- Финал: все ли проекции попадают в прямоугольник?

Используя простой одномерный тест вдоль оси, мы можем узнать, попадают ли они или нет:

const isProjectionHit = (minSignedDistance < 0 && maxSignedDistance > 0
        || Math.abs(minSignedDistance) < rectHalfSize
        || Math.abs(maxSignedDistance) < rectHalfSize);

enter image description here

Готово

Проверка всех 4 прогнозов даст вам окончательный результат. =] !!

3 concept previews

Надеюсь, этот ответ поможет как можно большему количеству людей. Любые комментарии приветствуются.

...