In continuation matts answer. we need to use
https://math.stackexchange.com/questions/190111/how-to-check-if-a-point-is-inside-a-rectangle/190373#190373
solution to make it work
Below does not work
0 <= dot(AB,AM) <= dot(AB,AB) && 0 <= dot(BC,BM) <= dot(BC,BC)
Below works
0 <= dot(AB,AM) <= dot(AB,AB) && 0 <= dot(AM,AC) <= dot(AC,AC)
you check by pasting below in javascript console
//Javascript solution for same
var screenWidth = 320;
var screenHeight = 568;
var appHeaderWidth = 320;
var AppHeaderHeight = 65;
var regionWidth = 200;
var regionHeight = 200;
this.topLeftBoundary = {
A: {x: 0, y: AppHeaderHeight},
B: {x: regionWidth, y: AppHeaderHeight},
C: {x: 0, y: regionHeight + AppHeaderHeight},
D: {x: regionWidth, y: regionHeight + AppHeaderHeight}
}
this.topRightBoundary = {
A: {x: screenWidth, y: AppHeaderHeight},
B: {x: screenWidth - regionWidth, y: AppHeaderHeight},
C: {x: screenWidth, y: regionHeight + AppHeaderHeight},
D: {x: screenWidth - regionWidth, y: regionHeight + AppHeaderHeight}
}
this.bottomRightBoundary = {
A: {x: screenWidth, y: screenHeight},
B: {x: screenWidth - regionWidth, y: screenHeight},
C: {x: screenWidth, y: screenHeight - regionHeight},
D: {x: screenWidth - regionWidth, y: screenHeight - regionHeight}
}
this.bottomLeftBoundary = {
A: {x: 0, y: screenHeight},
B: {x: regionWidth, y: screenHeight},
C: {x: 0, y: screenHeight - regionHeight},
D: {x: regionWidth, y: screenHeight - regionHeight}
}
console.log(this.topLeftBoundary);
console.log(this.topRightBoundary);
console.log(this.bottomRightBoundary);
console.log(this.bottomLeftBoundary);
checkIfTapFallsInBoundary = function (region, point) {
console.log("region " + JSON.stringify(region));
console.log("point" + JSON.stringify(point));
var r = region;
var m = point;
function vector(p1, p2) {
return {
x: (p2.x - p1.x),
y: (p2.y - p1.y)
};
}
function dot(u, v) {
console.log("DOT " + (u.x * v.x + u.y * v.y));
return u.x * v.x + u.y * v.y;
}
function pointInRectangle(m, r) {
var AB = vector(r.A, r.B);
var AM = vector(r.A, m);
var AC = vector(r.A, r.C);
var BC = vector(r.B, r.C);
var BM = vector(r.B, m);
console.log("AB " + JSON.stringify(AB));
console.log("AM " + JSON.stringify(AM));
console.log("AM " + JSON.stringify(AC));
console.log("BC " + JSON.stringify(BC));
console.log("BM " + JSON.stringify(BM));
var dotABAM = dot(AB, AM);
var dotABAB = dot(AB, AB);
var dotBCBM = dot(BC, BM);
var dotBCBC = dot(BC, BC);
var dotAMAC = dot(AM, AC);
var dotACAC = dot(AC, AC);
console.log("ABAM " + JSON.stringify(dotABAM));
console.log("ABAB " + JSON.stringify(dotABAB));
console.log("BCBM " + JSON.stringify(dotBCBM));
console.log("BCBC " + JSON.stringify(dotBCBC));
console.log("AMAC " + JSON.stringify(dotAMAC));
console.log("ACAC" + JSON.stringify(dotACAC));
var check = ((0 <= dotABAM && dotABAM <= dotABAB) && (0 <= dotBCBM && dotBCBM <= dotBCBC));
console.log(" first check" + check);
var check = ((0 <= dotABAM && dotABAM <= dotABAB) && (0 <= dotAMAC && dotAMAC <= dotACAC));
console.log("second check" + check);
return check;
}
return pointInRectangle(m, r);
}
//var point = {x: 136, y: 342};
checkIfTapFallsInBoundary(topLeftBoundary, {x: 136, y: 342});
checkIfTapFallsInBoundary(topRightBoundary, {x: 136, y: 274});
checkIfTapFallsInBoundary(bottomRightBoundary, {x: 141, y: 475});
checkIfTapFallsInBoundary(bottomRightBoundary, {x: 131, y: 272});
checkIfTapFallsInBoundary(bottomLeftBoundary, {x: 131, y: 272});
Как решить без матриц.
Пусть в системе координат сторон прямоугольника координаты некоторой точки (a,b)
— то есть по оси Ox
координата точки a*AB
, а по оси Oy
— b*AD
. Тогда у точки, в которую переходит данна, координаты внутри прямоугольника будут тоже (a,b)
: x' = A'_x + a*A'B'
, y' = A'_y + b*A'D'
Для поиска неподвижной точки достаточно приравнять x==x' && y==y'
. Система уравнений относительно a
и b
на рисунке выше.
Прошу простить, в C#
плаваю, писал последний раз лет 10 назад, поэтому я вам дам решение на Пайтоне, хорошо?
Для начала класс Матрица-два-на-два
, чтобы решать по формуле Крамера
class Matrix2x2:
def __init__(self, rows) -> None:
self.rows = rows
def det(self):
"Детерминант"
return self[0,0]*self[1,1]-self[0,1]*self[1,0]
def __getitem__(self, idx):
"Функция индексации: позволяет писать `m[0,1]` для коэффициента в первой строке и втором столбце."
if isinstance(idx, tuple):
return self.rows[idx[0]][idx[1]]
raise ValueError("Not a tuple: ", idx)
def replace_col(self, col, a1, a2):
"Меняет колонку с номером col на столбец [a1, a2] (транспонированный)"
if col == 0:
return Matrix2x2([[a1, self[0,1]],[a2, self[1,1]]])
elif col == 1:
return Matrix2x2([[self[0,0], a1],[self[1,0], a2]])
else:
raise ValueError("Out of bounds: ", col)
def solve_eq(self, x, y):
"Решает систему линейных уравнений с правой частью [x, y]"
d0 = self.det()
m1 = self.replace_col(0, x,y)
m2 = self.replace_col(1, x,y)
return m1.det()/d0, m2.det()/d0
def __repr__(self):
"ToString"
return f"{self.rows}"
Теперь решатель и ваши тестовые случаи:
tests = [
[(10, 5), ( 4.5, 2.0, 4.5, 4.0, 5.5, 4.0, 5.5, 2.0 ), (5.104166, 3.020833)],
[(10, 5), ( 4.5, 4.0, 4.5, 2.0, 5.5, 2.0, 5.5, 4.0 ), (5.09615, 2.98076)],
[(10, 5), ( 5.5, 4.0, 5.5, 2.0, 4.5, 2.0, 4.5, 4.0 ), (4.89583, 3.020833)],
[(10, 5), ( 5.5, 2.0, 5.5, 4.0, 4.5, 4.0, 4.5, 2.0 ), (4.90384, 2.98076)],
[(10, 5), ( 1.0, 1.5, 3.0, 1.5, 3.0, 2.5, 1.0, 2.5 ), (1.25, 1.875)],
[(10, 5), ( 3.0, 1.5, 1.0, 1.5, 1.0, 2.5, 3.0, 2.5 ), (2.5, 1.875)],
[(10, 5), ( 3.0, 2.5, 1.0, 2.5, 1.0, 1.5, 3.0, 1.5 ), (2.5, 12.5 / 6)],
[(10, 5), ( 1.0, 2.5, 3.0, 2.5, 3.0, 1.5, 1.0, 1.5 ), (1.25, 12.5 / 6)]
]
def solve(test_case):
(A,D),(Ax,Ay,Bx,By,_,_,Dx,Dy),_ = test_case
m = Matrix2x2([
[A-Bx+Ax, -Dx+Ax],
[-By+Ay, D-Dy+Ay]
])
x,y = m.solve_eq(Ax,Ay)
return x*A, y*D
def test(test_case):
print(solve(test_case),test_case[-1])
for tc in tests:
test(tc)
Результат. В каждой строке первая пара — найденное решение, вторая пара ожидаемое значение.
(5.104166666666666, 3.020833333333333) (5.104166, 3.020833)
(5.096153846153846, 2.980769230769231) (5.09615, 2.98076)
(4.895833333333333, 3.020833333333333) (4.89583, 3.020833)
(4.903846153846153, 2.980769230769231) (4.90384, 2.98076)
(1.25, 1.875) (1.25, 1.875)
(2.5, 1.875) (2.5, 1.875)
(2.5, 2.0833333333333335) (2.5, 2.0833333333333335)
(1.25, 2.0833333333333335) (1.25, 2.0833333333333335)
Я хочу найти, находится ли точка внутри прямоугольника или нет. Прямоугольник может быть ориентирован любым образом и не должен быть ориентирован по оси.
Один из способов, который я мог подумать, — повернуть прямоугольник и координаты точек, чтобы выравнивать ось прямоугольника, а затем просто проверить координаты точки, лежат ли они внутри прямоугольника или нет.
Вышеуказанный метод требует вращения и, следовательно, операций с плавающей запятой. Есть ли другой эффективный способ сделать это?
Ответ 1
Как представлен прямоугольник? Три очка? Четыре очка? Точка, стороны и угол? Две точки и сторона? Что-то другое? Не зная этого, любые попытки ответить на ваш вопрос будут иметь чисто академическое значение.
В любом случае, для любого выпуклого многоугольника (включая прямоугольник) тест очень прост: проверьте каждый край многоугольника, предполагая, что каждый край ориентирован против часовой стрелки, и проверьте, находится ли точка слева от края (слева полуплан самолета). Если все ребра проходят тест — точка находится внутри. Если хотя бы один сбой — точка находится снаружи.
Чтобы проверить, лежит ли точка (xp, yp)
с левой стороны ребра (x1, y1) - (x2, y2)
, вам просто нужно вычислить
D = (x2 - x1) * (yp - y1) - (xp - x1) * (y2 - y1)
Если D > 0
, точка находится на левой стороне. Если D < 0
, точка находится на правой стороне. Если D = 0
, точка находится на прямой.
Предыдущая версия этого ответа описывала, казалось бы, другую версию левого теста (см. Ниже). Но легко показать, что он вычисляет одно и то же значение.
… Чтобы проверить, лежит ли точка (xp, yp)
с левой стороны ребра (x1, y1) - (x2, y2)
, вам нужно построить уравнение для линии, содержащей ребро, Уравнение выглядит следующим образом
A * x + B * y + C = 0
где
A = -(y2 - y1)
B = x2 - x1
C = -(A * x1 + B * y1)
Теперь все, что вам нужно сделать, это рассчитать
D = A * xp + B * yp + C
Если D > 0
, точка находится на левой стороне. Если D < 0
, точка находится на правой стороне. Если D = 0
, точка находится на прямой.
Однако этот тест, опять же, работает для любого выпуклого многоугольника, а это значит, что он может быть слишком общим для прямоугольника. Прямоугольник может позволить более простой тест… Например, в прямоугольнике (или в любом другом параллелограмме) значения A
и B
имеют одинаковую величину, но разные знаки для противоположных (то есть параллельных) ребер, которые можно использовать для упрощения тест.
Ответ 2
Предполагая, что прямоугольник представлен тремя точками A, B, C, с AB и BC перпендикулярно, вам нужно только проверить проекции точки запроса M на AB и BC:
0 <= dot(AB,AM) <= dot(AB,AB) &&
0 <= dot(BC,BM) <= dot(BC,BC)
AB
— вектор AB с координатами (Bx-Ax, By-Ay), а dot(U,V)
— точечное произведение векторов U и V: Ux*Vx+Uy*Vy
.
Обновление. Приведем пример, иллюстрирующий это: A (5,0) B (0,2) C (1,5) и D (6,3).
Из координаты точки получаем AB = (- 5,2), BC = (1,3), dot (AB, AB) = 29, точка (BC, BC) = 10.
Для точки запроса M (4,2) имеем AM = (- 1,2), BM = (4,0), dot (AB, AM) = 9, dot (BC, BM) = 4. M находится внутри прямоугольника.
Для точки запроса P (6,1) имеем AP = (1,1), BP = (6, -1), точка (AB, AP) = — 3, точка (BC, BP) = 3, P не находится внутри прямоугольника, так как его проекция на сторону AB не находится внутри сегмента AB.
Ответ 3
# Pseudo code
# Corners in ax,ay,bx,by,dx,dy
# Point in x, y
bax = bx - ax
bay = by - ay
dax = dx - ax
day = dy - ay
if ((x - ax) * bax + (y - ay) * bay < 0.0) return false
if ((x - bx) * bax + (y - by) * bay > 0.0) return false
if ((x - ax) * dax + (y - ay) * day < 0.0) return false
if ((x - dx) * dax + (y - dy) * day > 0.0) return false
return true
Ответ 4
Я заимствовал у Эрика Бэйнвиля ответ:
0 <= dot(AB,AM) <= dot(AB,AB) && 0 <= dot(BC,BM) <= dot(BC,BC)
Что в javascript выглядит так:
function pointInRectangle(m, r) {
var AB = vector(r.A, r.B);
var AM = vector(r.A, m);
var BC = vector(r.B, r.C);
var BM = vector(r.B, m);
var dotABAM = dot(AB, AM);
var dotABAB = dot(AB, AB);
var dotBCBM = dot(BC, BM);
var dotBCBC = dot(BC, BC);
return 0 <= dotABAM && dotABAM <= dotABAB && 0 <= dotBCBM && dotBCBM <= dotBCBC;
}
function vector(p1, p2) {
return {
x: (p2.x - p1.x),
y: (p2.y - p1.y)
};
}
function dot(u, v) {
return u.x * v.x + u.y * v.y;
}
например:
var r = {
A: {x: 50, y: 0},
B: {x: 0, y: 20},
C: {x: 10, y: 50},
D: {x: 60, y: 30}
};
var m = {x: 40, y: 20};
то
pointInRectangle(m, r); // returns true.
Вот код, чтобы сделать вывод как визуальный тест:)
http://codepen.io/mattburns/pen/jrrprN
Ответ 5
Я понимаю, что это старая нить, но для тех, кто заинтересован в том, чтобы смотреть на это с чисто математической точки зрения, есть отличный поток на обмене математического стека, здесь:
https://math.stackexchange.com/questions/190111/how-to-check-if-a-point-is-inside-a-rectangle
Изменить: Вдохновленный этой нитью, я собрал простой векторный метод для быстрого определения, где находится ваша точка.
Предположим, что у вас есть прямоугольник с точками в p1 = (x1, y1), p2 = (x2, y2), p3 = (x3, y3) и p4 = (x4, y4), идущий по часовой стрелке. Если точка p = (x, y) лежит внутри прямоугольника, то точечное произведение (p — p1). (P2 — p1) будет лежать между 0 и | p2 — p1 | ^ 2 и (p — p1). (p4 — p1) будет находиться между 0 и | p4 — p1 | ^ 2. Это эквивалентно тому, чтобы проекция вектора p-p1 вдоль длины и ширины прямоугольника, причем p1 как начало.
Это может иметь смысл, если я покажу эквивалентный код:
p21 = (x2 - x1, y2 - y1)
p41 = (x4 - x1, y4 - y1)
p21magnitude_squared = p21[0]^2 + p21[1]^2
p41magnitude_squared = p41[0]^2 + p41[1]^2
for x, y in list_of_points_to_test:
p = (x - x1, y - y1)
if 0 <= p[0] * p21[0] + p[1] * p21[1] <= p21magnitude_squared:
if 0 <= p[0] * p41[0] + p[1] * p41[1]) <= p41magnitude_squared:
return "Inside"
else:
return "Outside"
else:
return "Outside"
И что это. Он также будет работать для параллелограммов.
Ответ 6
Если вы не можете решить проблему для прямоугольника, попробуйте делить проблему на более простые задачи.
Разделите прямоугольник на 2 треугольника, проверьте, находится ли точка внутри любого из них, как они объясняют в здесь
По существу, вы циклически просматриваете ребра на каждой из двух пар линий из точки. Затем используйте кросс-продукт, чтобы проверить, находится ли точка между двумя линиями, используя кросс-продукт. Если он проверен для всех трех точек, то точка находится внутри треугольника. Хорошая вещь об этом методе заключается в том, что он не создает ошибок с плавающей точкой, которые возникают, если вы проверяете углы.
Ответ 7
bool pointInRectangle(Point A, Point B, Point C, Point D, Point m ) {
Point AB = vect2d(A, B); float C1 = -1 * (AB.y*A.x + AB.x*A.y); float D1 = (AB.y*m.x + AB.x*m.y) + C1;
Point AD = vect2d(A, D); float C2 = -1 * (AD.y*A.x + AD.x*A.y); float D2 = (AD.y*m.x + AD.x*m.y) + C2;
Point BC = vect2d(B, C); float C3 = -1 * (BC.y*B.x + BC.x*B.y); float D3 = (BC.y*m.x + BC.x*m.y) + C3;
Point CD = vect2d(C, D); float C4 = -1 * (CD.y*C.x + CD.x*C.y); float D4 = (CD.y*m.x + CD.x*m.y) + C4;
return 0 >= D1 && 0 >= D4 && 0 <= D2 && 0 >= D3;}
Point vect2d(Point p1, Point p2) {
Point temp;
temp.x = (p2.x - p1.x);
temp.y = -1 * (p2.y - p1.y);
return temp;}
Я только что реализовал AnT Answer, используя c++. Я использовал этот код, чтобы проверить, находится ли координация пикселей (X, Y) внутри фигуры или нет.
Ответ 8
Если точка находится внутри прямоугольника. На плоскости. Для координат математики или геодезии (GPS)
- Пусть прямоугольник задан вершинами A, B, C, D. Точка P. Координаты прямоугольные: x, y.
- Продлим стороны прямоугольника. Таким образом, мы имеем 4 прямые линии l AB, l BC, l CD, l DA или, для краткости, l 1, l 2, l 3, l 4.
-
Составьте уравнение для каждого l i. Уравнение вроде:
f i (P) = 0.
P это точка. Для точек, принадлежащих l i, уравнение верно.
- Нам нужны функции в левой части уравнений. Это f 1, f 2, f 3, f 4.
- Обратите внимание, что для каждой точки с одной стороны от l i функция f i больше 0, а для точек с другой стороны f i меньше 0.
- Таким образом, если мы проверяем, что P находится в прямоугольнике, нам нужно только, чтобы p было на правильных сторонах всех четырех линий. Итак, мы должны проверить четыре функции на их признаки.
- Но какая сторона линии является правильной, к которой принадлежит прямоугольник? Это сторона, где лежат вершины прямоугольника, которые не принадлежат линии. Для проверки мы можем выбрать любую из двух не принадлежащих вершин.
-
Итак, мы должны проверить это:
f AB (P) f AB (C)> = 0
f BC (P) f BC (D)> = 0
f CD (P) f CD (A)> = 0
f DA (P) f DA (B)> = 0
Уклонения не являются строгими, поскольку, если точка находится на границе, она также принадлежит прямоугольнику. Если вам не нужны точки на границе, вы можете поменять неравенства на строгие. Но пока вы работаете в операциях с плавающей запятой, выбор не имеет значения.
- Для точки, которая находится в прямоугольнике, все четыре неравенства верны. Обратите внимание, что это работает также для каждого выпуклого многоугольника, только количество линий/уравнений будет отличаться.
-
Осталось только получить уравнение для линии, проходящей через две точки. Это хорошо известное линейное уравнение. Давайте напишем это для линии AB и точки P:
f AB (P) ≡ (x A -x B) (y P -y B) — (y A -y B) (x P -x B)
Проверка может быть упрощена — отпустите прямоугольник по часовой стрелке — A, B, C, D, A. Тогда все правильные стороны будут справа от линий. Таким образом, нам не нужно сравнивать со стороной, где находится другая вершина. И нам нужно проверить набор более коротких неравенств:
f AB (P)> = 0
f BC (P)> = 0
f CD (P)> = 0
f DA (P)> = 0
Но это верно для нормального, математического (из школьной математики) набора координат, где X — справа, а Y — вверху. А для координат геодезии, которые используются в GPS, где X вверху, а Y вправо, мы должны повернуть неравенства:
f AB (P) <= 0
f BC (P) <= 0
f CD (P) <= 0
f DA (P) <= 0
Если вы не уверены в направлениях осей, будьте осторожны с этой упрощенной проверкой — проверьте одну точку с известным расположением, если вы выбрали правильные уравнения.
Ответ 9
Самый простой способ, который я думал, — просто проецировать точку на ось прямоугольника. Позвольте мне объяснить:
Если вы можете получить вектор от центра прямоугольника до верхнего или нижнего края и левого или правого края. И у вас также есть вектор от центра прямоугольника до вашей точки, вы можете проецировать эту точку на ваши векторы ширины и высоты.
P = точечный вектор, H = вектор высоты, W = вектор ширины
Получить единичный вектор W ‘, H’ путем деления векторов на их величину
proj_P, H = P — (P.H ‘) H’
proj_P, W = P — (P.W ‘) W’
Если я не ошибаюсь, что я не думаю, что я… (Исправьте меня, если я ошибаюсь), но если величина проекции вашей точки на вектор высоты меньше, то величина вектора высоты (что составляет половину высоты прямоугольника), а величина проекции вашей точки на вектор ширины равна, то у вас есть точка внутри вашего прямоугольника.
Если у вас есть универсальная система координат, вам, возможно, придется вычислять векторы высоты/ширины/точки, используя векторное вычитание. Векторные прогнозы потрясающие! помните это.
Методы определения принадлежности точки многоугольнику
Недавно на хабре была статья, в которой описывалось как можно определить, где находится точка по отношению к многоугольнику: внутри или снаружи. Подобная проблема встречается в геометрическом моделировании и в компьютерной графике достаточно часто. А так как метод, описанный в статье, был несколько не оптимален, а в комментариях был небольшой хаос, возникла мысль написать эту статью. Итак, какие алгоритмы существуют в современной компьютерной графике, чтобы определить, принадлежит ли заданная точка многоугольнику или нет.
Прежде, чем начать, хочу сразу описать проблему. Хотя сама проблема проста: у нас задан набор точек и задан порядок, в котором эти точки соединяются. И задана точка, которую мы тестируем на принадлежность. Подразумевается, что у нас многоугольник замкнутый, и в общем случае ребра многоугольника не пересекаются друг с другом, то есть он избавлен от самопересечений. Ограничений на количество вершин нет, то есть легко может быть задан многоугольник с миллионом вершин. Мы надеемся, что пользователь не задаст нам непонятно что, но и гарантировать это тоже не можем. И еще один нюанс: так как мы работаем с компьютерной графикой, что означает, что мы используем арифметику с плавающей точкой, которая хотя и позволяет оперировать с числами достаточно точно, все равно не избавлена от ошибок.
Ну вроде определились с проблемой, давайте теперь посмотрим, какие методы решения существуют.
Метод 1. Трассировка лучей
Начну я с того, который считается наиболее популярным в мире графики и игр: трассировка лучей. Вкратце, алгоритм можно описать следующим образом:
- Из тестируемой точки выпускаем луч либо в заранее заданном, либо в произвольном направлении.
- Считаем количество пересечений с многоугольником.
- Если количество пересечений четное, мы находимся снаружи. Если количество пересечений нечетное, мы – внутри.
Звучит просто, не правда ли? И правда, это самый простой метод. Он в итоге сводится к некоторому количеству пересечений отрезка (грани многоугольника) и луча, то есть по сути пересечения двух прямых и тестирования полученной точки на принадлежность лучу и отрезку. В самом простом случае придется пересечь луч со всеми отрезками, при использовании деревьев (квадратичных, бинарных или BVH), можно сократить это количество. В целом говорят, что алгоритмическая сложность этого алгоритма O(log n), где n – количество ребер в многоугольнике.
Метод простой, но, к сожалению, в общем случае его лучше не применять. Причиной этого является случай, когда мы пересекаем лучом вершину многоугольника или ребро, которое частично совпадает с лучом. Иллюстрирую это на примере.
Допустим, у нас есть многоугольник, и есть точка. В самом начале мы договорились, что направление будет вдоль оси х. Выпускаем из точки луч в положительном направлении оси x и луч благополучно пересек многоугольник в вершине. Тут возникает вопрос, как именно мы проверяем такую ситуацию? Не забываем, что мы работаем с числами с плавающей точкой, и небольшие погрешности возможны. Перейдем в мир аналитической геометрии, чтобы можно было оперировать не просто геометрическими понятиями, а числами.
Уравнение каждого отрезка (грани многоугольника): a + t(b — a), где а – координаты одного конца отрезка, b – координаты другого конца отрезка. Очевидно, что если луч пересекает отрезок, t должно быть в интервале [0,1]. Если мы лучом пересекаем вершину, то t = 0 или t = 1. Это в идеальном мире математики. В реальном мире компьютерной алгебры у вас может получиться что-то вроде t = 1e-10. Или t = -1e-10. Или 0.99999999999998. Или 1.000000000000001. Поскольку пересечение двух прямых включает в себя процедуру деления, такое может запросто получиться. И что же в таком случае делать? Довериться компьютеру и считать, что если мы строго больше или равны нулю или строго меньше или равны единицы, то считаем это за пересечение, а иначе не считаем? Доверились и получили ситуацию, когда с одним ребром параметр t = -1e-20, с другим ребром t = 1.000000000000000001. В результате пересечениями это не считаем, и у нас луч благополучно проскочил мимо и результат оказывается неправильным.
Посмотрим в другом направлении. Отправили луч в отрицательном направлении. Там тоже не очень хорошо – луч пересекает вершину внутри многоугольника. Тоже может оказаться что угодно. Вместо горизонтального направления взять вертикальное? Никто не гарантирует, что вы опять не пересечете вершину. В конкретно выбранном мной примере наверху точка подобрана таким образом, что пересечение ее с лучом, параллельным оси y и идущий сверху вниз тоже пересекает многоугольник в вершине.
Причем если вы думаете, что пересечение с вершиной – это плохо, смотрите что еще может произойти:
Здесь мы пересекаем луч с отрезком, который с этим лучом совпадает. Как быть в таком случае? А если не совпадает, а почти совпадает? А представьте себе, что в многоугольнике множество почти вырожденных ребер, как с таким пересекать?
Самое печальное во всей этой ситуации то, что нам вот кажется: «мне надо что-то очень простое для моих простых целей, меня такая ситуация не коснется». По закону Мерфи, к сожалению, именно такая ситуация возникает всякий раз когда ее совсем не ждешь. И поэтому я плавно перехожу ко второму методу.
Метод 2. Ближняя точка и ее нормаль
Вообще у этого метода есть страшное название angle weighted pseudo normals и связан он в понятием так называемых полей расстояний со знаком (signed distance fields). Но пугать лишний раз я никого не хочу, так что пусть будет просто ближняя точка и ее нормаль (то есть перпендикулярный вектор).
Алгоритм в данном случае такой:
- Для тестируемой точки ищем ближайшую точку на многоугольнике. При этом помним, что ближайшая точка может быть не только на отрезке, но и в вершине.
- Ищем нормаль ближайшей точки. Если ближняя точка лежит на ребре, то нормалью является вектор, перпендикулярный ребру и смотрящий наружу многоугольника. Если ближняя точка – одна из вершин, то нормалью является усредненный вектор ребер, прилежащих к вершине.
- Вычисляем угол между нормалью ближайшей точки и вектором от тестируемой точки до ближайшей. Если угол меньше 90 градусов, то мы – внутри, иначе – снаружи.
Причем угол как таковой считать не обязательно, достаточно проверить знак косинуса этого угла. Если положительный – внутри, если отрицательный – снаружи. А поскольку нас интересует только знак косинуса, то по сути мы вычисляем знак скалярного произведения между двумя векторами.
Рассмотрим пример. Точка A1, ближайшая точка для нее находится на ребре. Если все делаем правильно, нормаль к ребру параллельна вектору от тестируемой точки до ближайшей. В случае точки A1, угол между векторами = 0. Или почти нуль, так как из-за операций с плавающей точкой все возможно. Меньше 90 градусов, тестируемая точка A1 – внутри. Протестируем точку A2. У нее ближайшая точка – вершина, нормаль к которой – усредненная нормаль ребер прилегающих к этой вершине. Считаем скалярное произведение двух векторов, должно быть отрицательным. Мы – снаружи.
Так, вроде бы с сутью метода разобрались. Что там с производительностью и проблемами, связанной с плавающей точкой?
Как и в случае трассировки точек, производительность – O(log n), если использовать деревья для хранения информации о ребрах. С вычислительной точки зрения метод, хотя и имеет подобную сложность, будет несколько помедленнее, чем трассировка. Прежде всего оттого, что расстояние между точкой и ребром чуть более дорогостоящая операция, чем пересечение двух линий. Неприятности, связанные с плавающей точкой, возникают в этом методе, как правило недалеко от ребер многоугольника. Причем чем мы ближе к ребру, тем больше вероятность неправильного определения знака. К счастью, чем мы ближе к ребру, тем меньше расстояние. То есть если мы, например, говорим, что если полученное расстояние меньше заранее заданного минимального (это может быть константа вроде DBL_EPSILON или FLT_EPSILON), то точка принадлежит ребру. А если она принадлежит ребру, то мы уже сами решаем, часть ли многоугольника его ребро или нет (как правило – часть).
Описывая предыдущий метод, достаточно много было сказано о недостатках. Пришло время назвать несколько недостатков и этого способа. Прежде всего, этот метод требует, чтобы все нормали к ребрам были направлены в правильную сторону. То есть до того, как определять, снаружи мы или внутри, надо провести некую работу по вычислению этих нормалей и правильное их ориентирование. Очень часто, особенно когда на входе большая свалка из вершин и ребер, этот процесс не всегда прост. Если надо определить только для одной точки, процесс ориентации нормалей может занять большую часть времени, которую можно было бы потратить на что-то еще. Также, этот метод очень не любит, когда на вход подается многоугольник с самопересечениями. В начале я сказал, что в нашей задаче такой случай не рассматривается, но если бы он рассматривался, то этот метод мог выдать совершенно неочевидные результаты.
Но в целом метод неплох, особенно если у нас на входе многоугольник с большим количеством вершин и ребер, а точек на принадлежность надо протестировать много. Если же точек мало, трассировка лучей нестабильна, а хочется чего-то более-менее надежного, то есть и третий способ.
Метод 3. Индекс точки относительно многоугольника
Этот метод известен довольно давно, но в основном остается теоретическим, по большей части потому, что он не так эффективен, как предыдущие два. Вот его суть «на пальцах». Возьмем единичную окружность с центром в тестируемой точке. Потом каждую вершину многоугольника спроецируем на эту окружность лучами, которые проходят через вершину и тестируемую точку. Как-то примерно так:
На рисунке точки P1, P2 и так далее – вершины многоугольника, а точки P1’, P2’ и так далее – их проекции на окружность. Теперь когда мы рассматриваем ребро многоугольника, по проекциям можно определить, происходит ли вращение против часовой стрелки или по часовой стрелке при переходе от одной вершины к другой. Вращение против часовой стрелки будем считать положительным поворотом, а вращение по часовой стрелке – отрицательным. Угол, который соответствует каждому ребру – это угол между сегментами окружности через проекции вершин этого ребра. Так как поворот у нас может быть положительный или отрицательный, то и угол может быть положительный или отрицательный.
Если после этого сложить все эти углы, то сумма должна быть +360 или -360 градусов для точки находящейся внутри и 0 для точки снаружи. Плюс-минус тут неспроста. Если при задании порядка обхода многоугольник обходится против часовой стрелки, должно получиться +360. Если же порядок обхода ребер в многоугольнике против часовой стрелки, то получается -360. Во избежание числовых ошибок как правило делают так: делят получившуюся сумму на 360 и приводят к ближайшему целому. Получившееся число кстати и является тем самым индексом точки. Результат может быть один из трех: -1 означает что мы внутри и многоугольник обходим по часовой стрелке, 0 означает что мы снаружи, +1 означает что мы снаружи. Все остальное означает что мы где-то ошиблись, или что входные данные некорректны.
Алгоритм в этом случае следующий:
- Устанавливаем сумму углов в 0.
- Для всех ребер многоугольника:
- С помощью скалярного произведения вычисляем угол, образованный векторами начинающимся в тестируемой точке и заканчивающимися в концах ребра.
- Вычисляем векторное произведение этих же векторов. Если его z-компонента положительна – прибавляем угол к сумме углов, а иначе – вычитаем из той же суммы.
Делим сумму на 2 если считаем в радианах или на 360 если считаем в градусах. Последнее маловероятно, но вдруг.
Округляем результат до ближайшего целого. +1 или -1 значит, что мы внутри. 0 значит мы снаружи.
Рассмотрим пример. Есть многоугольник, порядок которого установлен против часовой стрелки. Есть точка А, которую мы тестируем. Для тестирования сначала вычисляем угол между векторами AP1 и AP2. Векторное произведение этих же векторов смотрит на нас, значит прибавляем к сумме. Переходим дальше и считаем угол между AP2 и AP3. Векторное произведение смотрит на нас, полученный угол вычитаем. И так далее.
Для конкретно этого рисунка я все посчитал и вот что получилось:
(AP1, AP2)=74.13, (AP2, AP3)=51.58, (AP3, AP4)=89.99, (AP4, AP5)=126.47, (AP5, AP1)=120.99.
sum=74.13-51.58+89.99+126.47+120.99=360. 360/360=1 Точка – внутри.
(BP1, BP2)=44.78, (BP2, BP3)=89.11, (BP3, BP4)=130.93, (BP4, BP5)=52.97, (BP5, BP1)=33.63.
sum=-44.78+89.11-130.93+52.97+33.63=0. Точка – снаружи.
И традиционно опишем плюсы и минусы данного подхода. Начнем с минусов. Метод прост математически, но не так-то эффективен с точки зрения производительности. Во-первых, его алгоритмическая сложность O(n) и, как ни крути, а все ребра многоугольника придется перебрать. Во-вторых, для вычисления угла придётся воспользоваться операцией арккосинуса и двумя операциями взятия корня (формула скалярного произведения и связь его с углом тем в помощь, кто не понимает, почему). Эти операции очень недешевы с точки зрения скорости, и, к тому же, погрешности связанные с ними могут быть существенны. И в третьих, алгоритм напрямую не определяет точку, лежащую на ребре. Либо – снаружи, либо – внутри. Третьего не дано. Впрочем, последний недостаток легко определяется: если хотя бы один из углов равен (или почти равен) 180 градусам, это автоматически означает ребро.
Недостатки метода в чем-то компенсируются его достоинствами. Во-первых, это самый стабильный метод. Если многоугольник на вход подан корректный, то результат получается корректный для всех точек, за исключением разве что точек на ребрах, но о них смотри выше. Более того, метод позволяет частично бороться с некорректными входными данными. Многоугольник самопересекается? Не беда, метод скорее всего определит большинство точек правильно. Многоугольник не замкнут или вообще не многоугольник а малоосмысленный набор сегментов? Метод определит точки верно в большом количестве случаев. В общем, всем метод хорош, но медленный и требует вычислений арккосинусов.
Чем бы хотелось закончить этот обзор? А тем, что методов для решения проблемы определения принадлежности точки многоугольнику существует не один и даже не два. Они служат для разных целей и некоторые более подходят в случаях, когда важна скорость, другие – когда важно качество. Ну и не забываем о том, что у нас непредсказуемые входные данные и мы работаем с компьютером, у которого арифметика с плавающей точкой подвержена погрешностям. Если нужна скорость и качество совершенно неважно – трассировка лучей в помощь. В большинстве реальных приложений скорее всего поможет метод ближней точки и нормали. Если же на первом месте – точность определения при непонятных входных данных, метод индекса точки должен помочь.
Если будут какие-то вопросы, задавайте. Как человек, занимающийся геометрией и подобными проблемами связанными с графикой, буду рад помочь чем смогу.
Определение того, находится ли точка внутри прямоугольника или нет
Я хочу узнать, лежит ли точка внутри прямоугольника или нет. Прямоугольник можно ориентировать любым способом, и его не требуется выравнивать по оси.
Один из методов, который я мог придумать, состоял в том, чтобы повернуть прямоугольник и координаты точки, чтобы выровнять ось прямоугольника, а затем просто проверить координаты точки, лежат ли они в пределах прямоугольника или нет.
Вышеупомянутый метод требует вращения и, следовательно, операций с плавающей запятой. Есть ли другой эффективный способ сделать это?
Как изображен прямоугольник? Три очка? Четыре очка? Точка, стороны и угол? Два очка и сторона? Что-то другое? Не зная этого, любые попытки ответить на ваш вопрос будут иметь чисто академическое значение.
В любом случае для любого выпуклого многоугольника (включая прямоугольник) проверка очень проста: проверьте каждое ребро многоугольника, предполагая, что каждое ребро ориентировано против часовой стрелки, и проверьте, лежит ли точка слева. от края (слева). -ручная полуплоскость). Если все ребра проходят проверку — точка внутри. Если хоть один выйдет из строя — дело во внешнем.
Чтобы проверить, (xp, yp) лежит ли точка слева от края (x1, y1) — (x2, y2) , вам просто нужно вычислить
Если D > 0 , точка находится слева. Если D , точка находится справа. Если D = 0 , то точка находится на линии.
Предыдущая версия этого ответа описывала, казалось бы, другую версию левого теста (см. Ниже). Но легко показать, что он вычисляет то же значение.
. Чтобы проверить, (xp, yp) лежит ли точка на левой стороне кромки (x1, y1) — (x2, y2) , необходимо построить линейное уравнение для линии, содержащей кромку. Уравнение выглядит следующим образом
Теперь все, что вам нужно сделать, это вычислить
Если D > 0 , точка находится слева. Если D , точка находится справа. Если D = 0 , то точка находится на линии.
Однако этот тест, опять же, работает для любого выпуклого многоугольника, а это означает, что он может быть слишком общим для прямоугольника. Прямоугольник может позволить более простой тест . Например, в прямоугольнике (или в любом другом параллелограмме) значения A и B имеют одинаковую величину, но разные знаки для противоположных (т. Е. Параллельных) краев, что может быть использовано для упрощения теста .
Прямоугольник и точка М внутри
Моя любовь к математике безгранична. Сколько прекрасных уравнений, тождеств, соотношений и общих решений можно находить! Даже в обычном прямоугольнике проявляются удивительные открытия. Об одном из них, найденных совсем недавно, я расскажу.
От точки М внутри прямоугольника идут линии ко всем четырем вершинам. Длины линий заданы и равны буквенным выражениям, что на рисунке. Например a равно 25 метров, b равно 39 метров, с равно 52 метра и d — аж 60 метров. Все линии (допустим это металлические стержни) в точке М соединены общим шарниром и могут перемещаться, как радиусы окружностей. Но обязательно концы стержней должны быть вершинами именно прямоугольника. С этим, наверное, ясно. Ясно и то, что можно образовать бесконечно много прямоугольников в пределах некоторых границ. Пунктирными линиями как раз и показаны предельные границы. Оказывается, между красным и зеленым прямоугольниками находится некий черный, площадь которого самая большая. Вот габариты такой наибольшей фигуры и следует найти.
Задача относится к классу оптимизационных. Если удается найти математическую функцию, применяют дифференциальное исчисление. Просто берут производную этой функции, приравнивают ее нулю и получают значение аргумента, при котором исходная функция может иметь экстремум. Говорю «может», потому что не всегда график зависимости имеет форму холма или ямки. Иногда это точка перегиба или что-то совсем фантастическое. Во всяком случае данный вопрос требует дополнительного исследования. Я такое исследование произвел и убедился, что всегда экстремум существует и он — суть максимум. Все формулы, которые я получил и привел выше, не требуют знаний выше школьных. Теперь вроде старшеклассники проходят даже элементы «вышки». Не то, что полвека назад, когда учился ваш покорный слуга.
Проблема решена полностью, изящно и верно. Наибольшая площадь прямоугольника вычисляется по формуле:
Чтобы ее запомнить, с моим внуком Андреем (учится в седьмом классе) мы придумали простое мнемоническое правило:
Понятно, что тут «Ад» — это площадь Ad, «Всем» — по первым двум буквам площадь Bc. Андрею правило понравилось и запомнилось (по его утверждению) на всю оставшуюся жизнь.
В качестве вишенки на торте я привел таблицу, в которой показаны несколько целочисленных решений. Их, естественно, бесконечное количество, но показал лишь варианты, когда целые числа — самые малые по величине.
И последнее. Все сказанное получил благодаря обязательной самоизоляции из-за опаснейшего коронавируса. Из всего надо извлекать пользу. Но при этом не попадать в беду.
http://qastack.ru/programming/2752725/finding-whether-a-point-lies-inside-a-rectangle-or-not
http://proza.ru/2020/04/24/175
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
class Program { static void Main(string[] args) { Point[] poly = new Point[] { new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10) }; Console.WriteLine(InPoly(poly, new Point(5, 5))); Console.ReadKey(true); } private static float Rotate(Point a, Point b, Point c) { return (b.X - a.X) * (c.Y - b.Y) - (b.Y - a.Y) * (c.X - b.X); } private static bool Intersect(Point a, Point b, Point c, Point d) { return Rotate(a, b, c) * Rotate(a, b, d) <= 0 & Rotate(c, d, a) * Rotate(c, d, b) < 0; } private static bool InPoly(Point[] poly, Point pt) { int n = poly.Length; float x, y; if ((x = Rotate(poly[0], poly[1], pt)) < 0 || (y = Rotate(poly[0], poly[n - 1], pt)) > 0) return false; int p = 1, r = n - 1; while (r - p > 1) { int q = (p + r) / 2; if (Rotate(poly[0], poly[q], pt) < 0) r = q; else p = q; } return !Intersect(poly[0], pt, poly[p], poly[r]); } } class Point { public int X, Y; public Point() { X = Y = 0; } public Point(int x, int y) { X = x; Y = y; } } |