Есть ли простой способ определить, находится ли точка внутри треугольника? Это 2D, а не 3D.
Как определить, находится ли точка в двумерном треугольнике?
Ответ 1
В общем, самый простой (и довольно оптимальный) алгоритм проверяет, на какой стороне полуплоскости, созданной ребрами, находится точка.
Вот некоторая качественная информация в этой теме о GameDev, включая проблемы с производительностью.
А вот код, который поможет вам начать:
float sign (fPoint p1, fPoint p2, fPoint p3)
{
return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
}
bool PointInTriangle (fPoint pt, fPoint v1, fPoint v2, fPoint v3)
{
float d1, d2, d3;
bool has_neg, has_pos;
d1 = sign(pt, v1, v2);
d2 = sign(pt, v2, v3);
d3 = sign(pt, v3, v1);
has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);
return !(has_neg && has_pos);
}
Ответ 2
Решите следующую систему уравнений:
p = p0 + (p1 - p0) * s + (p2 - p0) * t
Точка p
находится внутри треугольника, если 0 <= s <= 1
и 0 <= t <= 1
и s + t <= 1
.
s
, t
и 1 - s - t
называются барицентрические координаты точки p
.
Ответ 3
Я согласен с Andreas Brinck, для этой задачи очень удобны барицентрические координаты. Обратите внимание, что нет необходимости решать систему уравнений каждый раз: просто оцените аналитическое решение. Используя нотацию Andreas, решение:
s = 1/(2*Area)*(p0y*p2x - p0x*p2y + (p2y - p0y)*px + (p0x - p2x)*py);
t = 1/(2*Area)*(p0x*p1y - p0y*p1x + (p0y - p1y)*px + (p1x - p0x)*py);
где Area
- (подписанная) область треугольника:
Area = 0.5 *(-p1y*p2x + p0y*(-p1x + p2x) + p0x*(p1y - p2y) + p1x*p2y);
Просто оцените s
, t
и 1-s-t
. Точка p
находится внутри треугольника тогда и только тогда, когда все они положительны.
EDIT: Обратите внимание, что указанное выше выражение для области предполагает, что нумерация треугольника node направлена против часовой стрелки. Если нумерация по часовой стрелке, это выражение вернет отрицательную область (но с правильной величиной). Однако сам тест (s>0 && t>0 && 1-s-t>0
) не зависит от направления нумерации, так как выражения, которые умножаются на 1/(2*Area)
, также меняют знак, если меняется ориентация треугольника node.
РЕДАКТИРОВАНИЕ 2: для получения еще лучшей вычислительной эффективности см. комментарий coproc ниже (что указывает на то, что если ориентация узлов треугольника (по часовой стрелке или против часовой стрелки) известна заранее, разделение 2*Area
в выражениях для s
и t
можно избежать). См. Также Perro Azul jsfiddle-code в комментариях в ответе Andreas Brinck.
Ответ 4
Я написал этот код до последней попытки с Google и поиска этой страницы, поэтому я решил поделиться им. Это в основном оптимизированная версия ответа Киселевича. Я также изучил барицентрический метод, но, судя по статье в Википедии, мне трудно понять, как он более эффективен (полагаю, что существует более глубокая эквивалентность). В любом случае, этот алгоритм имеет то преимущество, что не использует деление; потенциальная проблема заключается в поведении обнаружения края в зависимости от ориентации.
bool intpoint_inside_trigon(intPoint s, intPoint a, intPoint b, intPoint c)
{
int as_x = s.x-a.x;
int as_y = s.y-a.y;
bool s_ab = (b.x-a.x)*as_y-(b.y-a.y)*as_x > 0;
if((c.x-a.x)*as_y-(c.y-a.y)*as_x > 0 == s_ab) return false;
if((c.x-b.x)*(s.y-b.y)-(c.y-b.y)*(s.x-b.x) > 0 != s_ab) return false;
return true;
}
На словах идея такова: точка слева или справа от обеих линий AB и AC? Если это правда, это не может быть внутри. Если ложно, это по крайней мере внутри "конусов", которые удовлетворяют условию. Теперь, когда мы знаем, что точка внутри треangularьника (треangularьника) должна находиться на той же стороне AB, что и BC (и также CA), мы проверяем, отличаются ли они. Если они есть, s не может быть внутри, иначе s должен быть внутри.
Некоторые ключевые слова в расчетах - это линейные полуплоскости и определитель (перекрестное произведение 2x2). Возможно, более педагогический способ - это думать о ней как о точке, находящейся внутри, если она находится с одной и той же стороны (слева или справа) от каждой из линий AB, BC и CA. Однако вышеприведенный способ лучше подходит для некоторой оптимизации.
Ответ 5
С# версия барицентрического метода, опубликованная andreasdr и Perro Azul. Обратите внимание, что расчета площади можно избежать, если s
и t
имеют противоположные знаки. Я проверил правильное поведение с помощью довольно тщательного юнит-теста.
public static bool PointInTriangle(Point p, Point p0, Point p1, Point p2)
{
var s = p0.Y * p2.X - p0.X * p2.Y + (p2.Y - p0.Y) * p.X + (p0.X - p2.X) * p.Y;
var t = p0.X * p1.Y - p0.Y * p1.X + (p0.Y - p1.Y) * p.X + (p1.X - p0.X) * p.Y;
if ((s < 0) != (t < 0))
return false;
var A = -p1.Y * p2.X + p0.Y * (p2.X - p1.X) + p0.X * (p1.Y - p2.Y) + p1.X * p2.Y;
return A < 0 ?
(s <= 0 && s + t >= A) :
(s >= 0 && s + t <= A);
}
[редактировать]
accepted suggested modification by @Pierre; see comments
Ответ 6
Java-версия барицентрического метода:
class Triangle {
Triangle(double x1, double y1, double x2, double y2, double x3,
double y3) {
this.x3 = x3;
this.y3 = y3;
y23 = y2 - y3;
x32 = x3 - x2;
y31 = y3 - y1;
x13 = x1 - x3;
det = y23 * x13 - x32 * y31;
minD = Math.min(det, 0);
maxD = Math.max(det, 0);
}
boolean contains(double x, double y) {
double dx = x - x3;
double dy = y - y3;
double a = y23 * dx + x32 * dy;
if (a < minD || a > maxD)
return false;
double b = y31 * dx + x13 * dy;
if (b < minD || b > maxD)
return false;
double c = det - a - b;
if (c < minD || c > maxD)
return false;
return true;
}
private final double x3, y3;
private final double y23, x32, y31, x13;
private final double det, minD, maxD;
}
Приведенный выше код будет точно работать с целыми числами, при условии отсутствия переполнений. Он также будет работать с треangularьниками по часовой стрелке и против часовой стрелки. Он не будет работать с коллинеарными треangularьниками (но вы можете проверить это, протестировав det == 0).
Барицентрическая версия является самой быстрой, если вы собираетесь тестировать разные точки с одним и тем же треangularьником.
Барицентрическая версия не является симметричной в 3 точках треangularьника, поэтому она, вероятно, будет менее последовательной, чем версия полуплоскости Kornel Kisielewicz, из-за ошибок округления с плавающей точкой.
Предоставлено: Я сделал приведенный выше код из статьи Википедии о барицентрических координатах.
Ответ 7
Простым способом является:
найдите векторы, соединяющие укажите на каждый из треугольников три вершины и суммируют углы между эти векторы. Если сумма углы 2 * pi, то точка внутри треугольника.
Два хороших сайта, объясняющих альтернативы:
Ответ 8
Используя аналитическое решение для барицентрических координат (на это указывает Андреас Бринк) и:
- не распределяя умножение по скобкам
- избегая вычисления нескольких раз одних и тех же терминов, сохраняя их
- сокращение количества сравнений (как указали coproc и Томас Эдинг)
Можно минимизировать количество "дорогостоящих" операций:
function ptInTriangle(p, p0, p1, p2) {
var dX = p.x-p2.x;
var dY = p.y-p2.y;
var dX21 = p2.x-p1.x;
var dY12 = p1.y-p2.y;
var D = dY12*(p0.x-p2.x) + dX21*(p0.y-p2.y);
var s = dY12*dX + dX21*dY;
var t = (p2.y-p0.y)*dX + (p0.x-p2.x)*dY;
if (D<0) return s<=0 && t<=0 && s+t>=D;
return s>=0 && t>=0 && s+t<=D;
}
Код можно вставить в Perro Azul jsfiddle или попробовать, нажав "Выполнить фрагмент кода" ниже
.
var ctx = $("canvas")[0].getContext("2d");
var W = 500;
var H = 500;
var point = { x: W / 2, y: H / 2 };
var triangle = randomTriangle();
$("canvas").click(function(evt) {
point.x = evt.pageX - $(this).offset().left;
point.y = evt.pageY - $(this).offset().top;
test();
});
$("canvas").dblclick(function(evt) {
triangle = randomTriangle();
test();
});
test();
function test() {
var result = ptInTriangle(point, triangle.a, triangle.b, triangle.c);
var info = "point = (" + point.x + "," + point.y + ")\n";
info += "triangle.a = (" + triangle.a.x + "," + triangle.a.y + ")\n";
info += "triangle.b = (" + triangle.b.x + "," + triangle.b.y + ")\n";
info += "triangle.c = (" + triangle.c.x + "," + triangle.c.y + ")\n";
info += "result = " + (result ? "true" : "false");
$("#result").text(info);
render();
}
function ptInTriangle(p, p0, p1, p2) {
var A = 1/2 * (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
var sign = A < 0 ? -1 : 1;
var s = (p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y) * sign;
var t = (p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y) * sign;
return s > 0 && t > 0 && (s + t) < 2 * A * sign;
}
function render() {
ctx.fillStyle = "#CCC";
ctx.fillRect(0, 0, 500, 500);
drawTriangle(triangle.a, triangle.b, triangle.c);
drawPoint(point);
}
function drawTriangle(p0, p1, p2) {
ctx.fillStyle = "#999";
ctx.beginPath();
ctx.moveTo(p0.x, p0.y);
ctx.lineTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.closePath();
ctx.fill();
ctx.fillStyle = "#000";
ctx.font = "12px monospace";
ctx.fillText("1", p0.x, p0.y);
ctx.fillText("2", p1.x, p1.y);
ctx.fillText("3", p2.x, p2.y);
}
function drawPoint(p) {
ctx.fillStyle = "#F00";
ctx.beginPath();
ctx.arc(p.x, p.y, 5, 0, 2 * Math.PI);
ctx.fill();
}
function rand(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function randomTriangle() {
return {
a: { x: rand(0, W), y: rand(0, H) },
b: { x: rand(0, W), y: rand(0, H) },
c: { x: rand(0, W), y: rand(0, H) }
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<pre>Click: place the point.
Double click: random triangle.</pre>
<pre id="result"></pre>
<canvas width="500" height="500"></canvas>
Ответ 9
То, что я делаю, является предварительным вычислением трех нормалей лица,
-
в 3D поперечным произведением бокового вектора и вектора нормали лица.
-
в 2D путем простой замены компонентов и отрицания,
то внутри/снаружи для любой одной стороны есть точечное произведение боковой нормали и вершины к точечному вектору, смените знак. Повторите для других двух (или более) сторон.
Преимущества:
-
многое предсказуемо настолько велико для многоточечного тестирования на том же треугольнике.
-
раннее отклонение общего случая более внешнего, чем внутри точек. (также, если распределение точек, взвешенное с одной стороны, может сначала проверить эту сторону.)
Ответ 10
Вот эффективная реализация Python :
def PointInsideTriangle2(pt,tri):
'''checks if point pt(2) is inside triangle tri(3x2). @Developer'''
a = 1/(-tri[1,1]*tri[2,0]+tri[0,1]*(-tri[1,0]+tri[2,0])+ \
tri[0,0]*(tri[1,1]-tri[2,1])+tri[1,0]*tri[2,1])
s = a*(tri[2,0]*tri[0,1]-tri[0,0]*tri[2,1]+(tri[2,1]-tri[0,1])*pt[0]+ \
(tri[0,0]-tri[2,0])*pt[1])
if s<0: return False
else: t = a*(tri[0,0]*tri[1,1]-tri[1,0]*tri[0,1]+(tri[0,1]-tri[1,1])*pt[0]+ \
(tri[1,0]-tri[0,0])*pt[1])
return ((t>0) and (1-s-t>0))
и пример вывода:
Ответ 11
Если вы ищете скорость, вот процедура, которая может вам помочь.
Отсоедините треугольные вершины по их ординатам. Это занимает в худшем случае три сравнения. Пусть Y0, Y1, Y2 - три отсортированных значения. Рисуя три горизонтали через них, вы разбиваете плоскость на две полуплоскости и две плиты. Пусть Y - ордината точки запроса.
if Y < Y1
if Y <= Y0 -> the point lies in the upper half plane, outside the triangle; you are done
else Y > Y0 -> the point lies in the upper slab
else
if Y >= Y2 -> the point lies in the lower half plane, outside the triangle; you are done
else Y < Y2 -> the point lies in the lower slab
Затраты на два сравнения. Как вы видите, быстрое отклонение достигается за точки, находящиеся за пределами "ограничивающей плиты".
По желанию вы можете поставить тест по абсциссам для быстрого отклонения слева и справа (X <= X0' or X >= X2'
). Одновременно будет выполняться быстрый тест с ограничивающей рамкой, но вам также придется сортировать по абсциссам.
В конце концов вам нужно будет вычислить знак данной точки относительно двух сторон треугольника, которые ограничивают соответствующую плиту (верхнюю или нижнюю). Тест имеет форму:
((X - Xi) * (Y - Yj) > (X - Xi) * (Y - Yj)) == ((X - Xi) * (Y - Yk) > (X - Xi) * (Y - Yk))
Полное обсуждение комбинаций i, j, k
(их шесть из них основаны на результатах сортировки) выходит за рамки этого ответа и "остается как упражнение для читателя"; для эффективности они должны быть жестко закодированы.
Если вы считаете, что это решение является сложным, обратите внимание на то, что он в основном включает в себя простые сравнения (некоторые из которых могут быть предварительно вычислены), плюс 6 вычитаний и 4 умножения в случае сбоя теста ограничительной рамки. Последнюю стоимость трудно превзойти, так как в худшем случае вы не можете избежать сравнения контрольной точки с двумя сторонами (ни один из методов в других ответах не имеет более низкой стоимости, некоторые из них хуже, например, 15 вычитаний и 6 умножений, иногда делений).
UPDATE: Быстрее с поперечным преобразованием
Как объяснялось выше, вы можете быстро найти точку внутри одной из четырех горизонтальных полос, разделенных тремя ординатами вершин, используя два сравнения.
Вы можете выполнить один или два дополнительных X-теста, чтобы проверить настойчивость к ограничивающей рамке (пунктирные линии).
Затем рассмотрим преобразование "сдвига", данное X'= X - m Y, Y' = Y
, где m
- наклон DX/DY
для самого высокого ребра. Это преобразование сделает эту сторону треугольника вертикальной. И так как вы знаете, на какой стороне средней горизонтали вы находитесь, достаточно проверить знак по отношению к одной стороне треугольника.
Предполагая, что вы предварительно вычислили наклон m
, а также X'
для срезанных треугольных вершин и коэффициентов уравнений сторон как X = m Y + p
, вам понадобится в худшем случае
- два сравнения ординат для вертикальной классификации;
- необязательно одно или два сравнения абсцисс для отклонения прямоугольной рамки;
- вычисление
X' = X - m Y
; - одно или два сравнения с абсциссами срезанного треугольника;
- одно знаковое испытание
X >< m' Y + p'
против соответствующей стороны срезанного треугольника.
Ответ 12
Если вам известны координаты трех вершин и координаты конкретной точки, вы можете получить область полного треугольника. Затем вычислите площадь трех треугольных сегментов (одна точка - заданная точка, а две другие - любые две вершины треугольника). Таким образом, вы получите площадь трех треугольных сегментов. Если сумма этих площадей равна общей площади (которую вы получили ранее), то точка должна находиться внутри треугольника. В противном случае точка не находится внутри треугольника. Это должно сработать. Если есть какие-либо проблемы, сообщите мне. Спасибо.
Ответ 13
Другая функция в python, более быстрая, чем метод разработчика (по крайней мере для меня) и вдохновленная решением Седрика Дюфура :
def ptInTriang(p_test, p0, p1, p2):
dX = p_test[0] - p0[0]
dY = p_test[1] - p0[1]
dX20 = p2[0] - p0[0]
dY20 = p2[1] - p0[1]
dX10 = p1[0] - p0[0]
dY10 = p1[1] - p0[1]
s_p = (dY20*dX) - (dX20*dY)
t_p = (dX10*dY) - (dY10*dX)
D = (dX10*dY20) - (dY10*dX20)
if D > 0:
return ( (s_p >= 0) and (t_p >= 0) and (s_p + t_p) <= D )
else:
return ( (s_p <= 0) and (t_p <= 0) and (s_p + t_p) >= D )
Вы можете проверить это с помощью:
X_size = 64
Y_size = 64
ax_x = np.arange(X_size).astype(np.float32)
ax_y = np.arange(Y_size).astype(np.float32)
coords=np.meshgrid(ax_x,ax_y)
points_unif = (coords[0].reshape(X_size*Y_size,),coords[1].reshape(X_size*Y_size,))
p_test = np.array([0 , 0])
p0 = np.array([22 , 8])
p1 = np.array([12 , 55])
p2 = np.array([7 , 19])
fig = plt.figure(dpi=300)
for i in range(0,X_size*Y_size):
p_test[0] = points_unif[0][i]
p_test[1] = points_unif[1][i]
if ptInTriang(p_test, p0, p1, p2):
plt.plot(p_test[0], p_test[1], '.g')
else:
plt.plot(p_test[0], p_test[1], '.r')
Построение занимает много времени, но эта сетка проверяется за 0,0195319652557 секунд против 0,0844349861145 секунд кода разработчика.
Наконец код комментария:
# Using barycentric coordintes, any point inside can be described as:
# X = p0.x * r + p1.x * s + p2.x * t
# Y = p0.y * r + p1.y * s + p2.y * t
# with:
# r + s + t = 1 and 0 < r,s,t < 1
# then: r = 1 - s - t
# and then:
# X = p0.x * (1 - s - t) + p1.x * s + p2.x * t
# Y = p0.y * (1 - s - t) + p1.y * s + p2.y * t
#
# X = p0.x + (p1.x-p0.x) * s + (p2.x-p0.x) * t
# Y = p0.y + (p1.y-p0.y) * s + (p2.y-p0.y) * t
#
# X - p0.x = (p1.x-p0.x) * s + (p2.x-p0.x) * t
# Y - p0.y = (p1.y-p0.y) * s + (p2.y-p0.y) * t
#
# we have to solve:
#
# [ X - p0.x ] = [(p1.x-p0.x) (p2.x-p0.x)] * [ s ]
# [ Y - p0.Y ] [(p1.y-p0.y) (p2.y-p0.y)] [ t ]
#
# ---> b = A*x ; ---> x = A^-1 * b
#
# [ s ] = A^-1 * [ X - p0.x ]
# [ t ] [ Y - p0.Y ]
#
# A^-1 = 1/D * adj(A)
#
# The adjugate of A:
#
# adj(A) = [(p2.y-p0.y) -(p2.x-p0.x)]
# [-(p1.y-p0.y) (p1.x-p0.x)]
#
# The determinant of A:
#
# D = (p1.x-p0.x)*(p2.y-p0.y) - (p1.y-p0.y)*(p2.x-p0.x)
#
# Then:
#
# s_p = { (p2.y-p0.y)*(X - p0.x) - (p2.x-p0.x)*(Y - p0.Y) }
# t_p = { (p1.x-p0.x)*(Y - p0.Y) - (p1.y-p0.y)*(X - p0.x) }
#
# s = s_p / D
# t = t_p / D
#
# Recovering r:
#
# r = 1 - (s_p + t_p)/D
#
# Since we only want to know if it is insidem not the barycentric coordinate:
#
# 0 < 1 - (s_p + t_p)/D < 1
# 0 < (s_p + t_p)/D < 1
# 0 < (s_p + t_p) < D
#
# The condition is:
# if D > 0:
# s_p > 0 and t_p > 0 and (s_p + t_p) < D
# else:
# s_p < 0 and t_p < 0 and (s_p + t_p) > D
#
# s_p = { dY20*dX - dX20*dY }
# t_p = { dX10*dY - dY10*dX }
# D = dX10*dY20 - dY10*dX20
Ответ 14
Я просто хочу использовать простую векторную математику, чтобы объяснить решение о барицентрических координатах, которое дал Андреас, это будет легче понять.
- Область A определяется как любой вектор, заданный s * v02 + t * v01, с условием s >= 0 и t >= 0. Если любая точка внутри треугольника v0, v1, v2, она должна быть внутри области A.
- Если дальнейшее ограничение s и t принадлежит [0, 1]. Мы получаем область B, которая содержит все векторы s * v02 + t * v01, причем условие s, t принадлежит [0, 1]. Стоит отметить, что нижняя часть Района B является зеркалом треугольника v0, v1, v2. Проблема возникает, если мы можем дать определенное условие s и t для дальнейшего исключения нижней части области B.
- Предположим, что мы даем значение s, а t меняется в [0, 1]. В следующем рисунке точка p находится на краю v1v2. Все векторы s * v02 + t * v01, которые расположены вдоль штриховой линии простой векторной суммой. В точке v1v2 и пунктирной линии p мы имеем:
(1-s) | v0v2 |/| v0v2 | = tp | v0v1 |/| v0v1 |
получаем 1 - s = tp, то 1 = s + tp. Если любое t > tp, которое 1 < s + t, где находится линия двойной линии, вектор находится вне треугольника, любой t <= tp, который 1 >= s + t, где находится на одной линии штриховки, вектор находится внутри треугольника.
Тогда, если мы задали любые s из [0, 1], то для вектора внутри треугольника соответствующая t должна соответствовать 1 >= s + t.
Итак, наконец, получим v = s * v02 + t * v01, v внутри треугольника с условием s, t, s + t принадлежит [0, 1]. Тогда переведите в точку, мы имеем
p - p0 = s * (p1 - p0) + t * (p2 - p0), с s, t, s + t в [0, 1]
что совпадает с решением Андреаса для решения системы уравнений p = p0 + s * (p1 - p0) + t * (p2 - p0), причем s, t, s + t принадлежат [0, 1].
Ответ 15
Есть досадные краевые условия, когда точка находится на общем краю двух смежных треугольников. Точка не может быть ни в обоих, ни в любом из треугольников. Вам нужен произвольный, но последовательный способ назначения точки. Например, нарисуйте горизонтальную линию через точку. Если линия пересекается с другой стороной треугольника справа, точка обрабатывается так, как если бы она находилась внутри треугольника. Если пересечение находится слева, точка находится вне.
Если линия, на которой находится точка, является горизонтальной, используйте выше/ниже.
Если точка находится в общей вершине множества треугольников, используйте треугольник, центр которого точка образует наименьший угол.
Больше удовольствия: три точки могут быть на прямой (нулевые градусы), например (0,0) - (0,10) - (0,5). В триангулирующем алгоритме "ухо" (0,10) нужно отловить, "треугольник", порожденный как вырожденный случай прямой линии.
Ответ 16
bool isInside( float x, float y, float x1, float y1, float x2, float y2, float x3, float y3 ) {
float l1 = (x-x1)*(y3-y1) - (x3-x1)*(y-y1),
l2 = (x-x2)*(y1-y2) - (x1-x2)*(y-y2),
l3 = (x-x3)*(y2-y3) - (x2-x3)*(y-y3);
return (l1>0 && l2>0 && l3>0) || (l1<0 && l2<0 && l3<0);
}
Это не может быть более эффективным, чем это! Каждая сторона треangularьника может иметь независимую позицию и ориентацию, следовательно, три вычисления: l1, l2 и l3, безусловно, необходимы, включая 2 умножения каждый. Как только l1, l2 и l3 станут известны, результатом будет лишь несколько базовых сравнений и логических операций.
Ответ 17
Вот решение в Python, которое является эффективным, документированным и содержит три юнит-теста. Это профессиональное качество и готовность к внедрению в ваш проект в виде модуля как есть.
import unittest
###############################################################################
def point_in_triangle(point, triangle):
"""Returns True if the point is inside the triangle
and returns False if it falls outside.
- The argument *point* is a tuple with two elements
containing the X,Y coordinates respectively.
- The argument *triangle* is a tuple with three elements each
element consisting of a tuple of X,Y coordinates.
It works like this:
Walk clockwise or counterclockwise around the triangle
and project the point onto the segment we are crossing
by using the dot product.
Finally, check that the vector created is on the same side
for each of the triangle segments.
"""
# Unpack arguments
x, y = point
ax, ay = triangle[0]
bx, by = triangle[1]
cx, cy = triangle[2]
# Segment A to B
side_1 = (x - bx) * (ay - by) - (ax - bx) * (y - by)
# Segment B to C
side_2 = (x - cx) * (by - cy) - (bx - cx) * (y - cy)
# Segment C to A
side_3 = (x - ax) * (cy - ay) - (cx - ax) * (y - ay)
# All the signs must be positive or all negative
return (side_1 < 0.0) == (side_2 < 0.0) == (side_3 < 0.0)
###############################################################################
class TestPointInTriangle(unittest.TestCase):
triangle = ((22 , 8),
(12 , 55),
(7 , 19))
def test_inside(self):
point = (15, 20)
self.assertTrue(point_in_triangle(point, self.triangle))
def test_outside(self):
point = (1, 7)
self.assertFalse(point_in_triangle(point, self.triangle))
def test_border_case(self):
"""If the point is exactly on one of the triangle edges,
we consider it is inside."""
point = (7, 19)
self.assertTrue(point_in_triangle(point, self.triangle))
###############################################################################
if __name__ == "__main__":
suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestPointInTriangle)
unittest.TextTestRunner().run(suite)
Существует дополнительный необязательный графический тест для алгоритма, описанного выше, для подтверждения его правильности:
import random
from matplotlib import pyplot
from triangle_test import point_in_triangle
###############################################################################
# The area #
size_x = 64
size_y = 64
# The triangle #
triangle = ((22 , 8),
(12 , 55),
(7 , 19))
# Number of random points #
count_points = 10000
# Prepare the figure #
figure = pyplot.figure()
axes = figure.add_subplot(111, aspect='equal')
axes.set_title("Test the 'point_in_triangle' function")
axes.set_xlim(0, size_x)
axes.set_ylim(0, size_y)
# Plot the triangle #
from matplotlib.patches import Polygon
axes.add_patch(Polygon(triangle, linewidth=1, edgecolor='k', facecolor='none'))
# Plot the points #
for i in range(count_points):
x = random.uniform(0, size_x)
y = random.uniform(0, size_y)
if point_in_triangle((x,y), triangle): pyplot.plot(x, y, '.g')
else: pyplot.plot(x, y, '.b')
# Save it #
figure.savefig("point_in_triangle.pdf")
Создание следующего рисунка:
Ответ 18
Предположительно высокопроизводительный код, который я адаптировал в JavaScript (статья ниже):
function pointInTriangle (p, p0, p1, p2) {
return (((p1.y - p0.y) * (p.x - p0.x) - (p1.x - p0.x) * (p.y - p0.y)) | ((p2.y - p1.y) * (p.x - p1.x) - (p2.x - p1.x) * (p.y - p1.y)) | ((p0.y - p2.y) * (p.x - p2.x) - (p0.x - p2.x) * (p.y - p2.y))) >= 0;
}
pointInTriangle(p, p0, p1, p2)
- для треangularьников против часовой стрелкиpointInTriangle(p, p0, p1, p2)
- для треangularьников по часовой стрелке
Посмотрите в jsFiddle (тест производительности включен), там также проверка обмотки в отдельной функции. Или нажмите "Выполнить фрагмент кода" ниже
var ctx = $("canvas")[0].getContext("2d");
var W = 500;
var H = 500;
var point = { x: W / 2, y: H / 2 };
var triangle = randomTriangle();
$("canvas").click(function(evt) {
point.x = evt.pageX - $(this).offset().left;
point.y = evt.pageY - $(this).offset().top;
test();
});
$("canvas").dblclick(function(evt) {
triangle = randomTriangle();
test();
});
document.querySelector('#performance').addEventListener('click', _testPerformance);
test();
function test() {
var result = checkClockwise(triangle.a, triangle.b, triangle.c) ? pointInTriangle(point, triangle.a, triangle.c, triangle.b) : pointInTriangle(point, triangle.a, triangle.b, triangle.c);
var info = "point = (" + point.x + "," + point.y + ")\n";
info += "triangle.a = (" + triangle.a.x + "," + triangle.a.y + ")\n";
info += "triangle.b = (" + triangle.b.x + "," + triangle.b.y + ")\n";
info += "triangle.c = (" + triangle.c.x + "," + triangle.c.y + ")\n";
info += "result = " + (result ? "true" : "false");
$("#result").text(info);
render();
}
function _testPerformance () {
var px = [], py = [], p0x = [], p0y = [], p1x = [], p1y = [], p2x = [], p2y = [], p = [], p0 = [], p1 = [], p2 = [];
for(var i = 0; i < 1000000; i++) {
p[i] = {x: Math.random() * 100, y: Math.random() * 100};
p0[i] = {x: Math.random() * 100, y: Math.random() * 100};
p1[i] = {x: Math.random() * 100, y: Math.random() * 100};
p2[i] = {x: Math.random() * 100, y: Math.random() * 100};
}
console.time('optimal: pointInTriangle');
for(var i = 0; i < 1000000; i++) {
pointInTriangle(p[i], p0[i], p1[i], p2[i]);
}
console.timeEnd('optimal: pointInTriangle');
console.time('original: ptInTriangle');
for(var i = 0; i < 1000000; i++) {
ptInTriangle(p[i], p0[i], p1[i], p2[i]);
}
console.timeEnd('original: ptInTriangle');
}
function pointInTriangle (p, p0, p1, p2) {
return (((p1.y - p0.y) * (p.x - p0.x) - (p1.x - p0.x) * (p.y - p0.y)) | ((p2.y - p1.y) * (p.x - p1.x) - (p2.x - p1.x) * (p.y - p1.y)) | ((p0.y - p2.y) * (p.x - p2.x) - (p0.x - p2.x) * (p.y - p2.y))) >= 0;
}
function ptInTriangle(p, p0, p1, p2) {
var s = (p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y);
var t = (p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y);
if (s <= 0 || t <= 0) return false;
var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
return (s + t) < A;
}
function render() {
ctx.fillStyle = "#CCC";
ctx.fillRect(0, 0, 500, 500);
drawTriangle(triangle.a, triangle.b, triangle.c);
drawPoint(point);
}
function checkClockwise(p0, p1, p2) {
var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
return A > 0;
}
function drawTriangle(p0, p1, p2) {
ctx.fillStyle = "#999";
ctx.beginPath();
ctx.moveTo(p0.x, p0.y);
ctx.lineTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.closePath();
ctx.fill();
ctx.fillStyle = "#000";
ctx.font = "12px monospace";
ctx.fillText("1", p0.x, p0.y);
ctx.fillText("2", p1.x, p1.y);
ctx.fillText("3", p2.x, p2.y);
}
function drawPoint(p) {
ctx.fillStyle = "#F00";
ctx.beginPath();
ctx.arc(p.x, p.y, 5, 0, 2 * Math.PI);
ctx.fill();
}
function rand(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function randomTriangle() {
return {
a: { x: rand(0, W), y: rand(0, H) },
b: { x: rand(0, W), y: rand(0, H) },
c: { x: rand(0, W), y: rand(0, H) }
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id="performance">Run performance test (open console)</button>
<pre>Click: place the point.
Double click: random triangle.</pre>
<pre id="result"></pre>
<canvas width="500" height="500"></canvas>
Ответ 19
Самый простой способ и он работает со всеми типами треугольников - это просто определить углы точек точки P, A, B, C. Если какой-либо из углов больше 180 градусов, то он снаружи, если он равен 180.0, то он находится по окружности, и если acos обманывает вас и меньше 180.0, то он внутри. Взгляните на понимание http://math-physics-psychology.blogspot.hu/2015/01/earlish-determination-that-point-is.html
Ответ 20
Честно говоря, это просто, как Simon P Steven ответить, однако при таком подходе у вас нет твердого контроля над тем, хотите ли вы точек на краях треугольник, который будет включен или нет.
Мой подход немного другой, но очень простой. Рассмотрим следующий треугольник:
Чтобы иметь точку в треугольнике, мы должны удовлетворять 3 условиям
- Угол ACE (зеленый) должен быть меньше угла ACB (красный)
- Угол ECB (синий) должен быть меньше угла ACB (красный)
- Точка E и точка C shoud имеют один и тот же знак, когда их значения x и y применяются к уравнению | AB | линия.
В этом методе вы имеете полный контроль над включением или исключением точки на краях по отдельности. Таким образом, вы можете проверить, находится ли точка в треугольнике, включая только | AC | Например, край.
Итак, мое решение в JavaScript было бы следующим:
function isInTriangle(t,p){
function isInBorder(a,b,c,p){
var m = (a.y - b.y) / (a.x - b.x); // calculate the slope
return Math.sign(p.y - m*p.x + m*a.x - a.y) === Math.sign(c.y - m*c.x + m*a.x - a.y);
}
function findAngle(a,b,c){ // calculate the C angle from 3 points.
var ca = Math.hypot(c.x-a.x, c.y-a.y), // ca edge length
cb = Math.hypot(c.x-b.x, c.y-b.y), // cb edge length
ab = Math.hypot(a.x-b.x, a.y-b.y); // ab edge length
return Math.acos((ca*ca + cb*cb - ab*ab) / (2*ca*cb)); // return the C angle
}
var pas = t.slice(1)
.map(tp => findAngle(p,tp,t[0])), // find the angle between (p,t[0]) with (t[1],t[0]) & (t[2],t[0])
ta = findAngle(t[1],t[2],t[0]);
return pas[0] < ta && pas[1] < ta && isInBorder(t[1],t[2],t[0],p);
}
var triangle = [{x:3, y:4},{x:10, y:8},{x:6, y:10}],
point1 = {x:3, y:9},
point2 = {x:7, y:9};
console.log(isInTriangle(triangle,point1));
console.log(isInTriangle(triangle,point2));
Ответ 21
Так как нет ответа JS,
По часовой стрелке Решение против часовой стрелки:
function triangleContains(ax, ay, bx, by, cx, cy, x, y) {
let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)
return det * ((bx - ax) * (y - ay) - (by - ay) * (x - ax)) > 0 &&
det * ((cx - bx) * (y - by) - (cy - by) * (x - bx)) > 0 &&
det * ((ax - cx) * (y - cy) - (ay - cy) * (x - cx)) > 0
}
ОБНОВЛЕНИЕ: была опечатка для вычисления det (cy - ay
вместо cx - ax
), это исправлено.
https://jsfiddle.net/jniac/rctb3gfL/
function triangleContains(ax, ay, bx, by, cx, cy, x, y) {
let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)
return det * ((bx - ax) * (y - ay) - (by - ay) * (x - ax)) > 0 &&
det * ((cx - bx) * (y - by) - (cy - by) * (x - bx)) > 0 &&
det * ((ax - cx) * (y - cy) - (ay - cy) * (x - cx)) > 0
}
let width = 500, height = 500
// clockwise
let triangle1 = {
A : { x: 10, y: -10 },
C : { x: 20, y: 100 },
B : { x: -90, y: 10 },
color: '#f00',
}
// counter clockwise
let triangle2 = {
A : { x: 20, y: -60 },
B : { x: 90, y: 20 },
C : { x: 20, y: 60 },
color: '#00f',
}
let scale = 2
let mouse = { x: 0, y: 0 }
// DRAW >
let wrapper = document.querySelector('div.wrapper')
wrapper.onmousemove = ({ layerX:x, layerY:y }) => {
x -= width / 2
y -= height / 2
x /= scale
y /= scale
mouse.x = x
mouse.y = y
drawInteractive()
}
function drawArrow(ctx, A, B) {
let v = normalize(sub(B, A), 3)
let I = center(A, B)
let p
p = add(I, rotate(v, 90), v)
ctx.moveTo(p.x, p.y)
ctx.lineTo(I.x, I .y)
p = add(I, rotate(v, -90), v)
ctx.lineTo(p.x, p.y)
}
function drawTriangle(ctx, { A, B, C, color }) {
ctx.beginPath()
ctx.moveTo(A.x, A.y)
ctx.lineTo(B.x, B.y)
ctx.lineTo(C.x, C.y)
ctx.closePath()
ctx.fillStyle = color + '6'
ctx.strokeStyle = color
ctx.fill()
drawArrow(ctx, A, B)
drawArrow(ctx, B, C)
drawArrow(ctx, C, A)
ctx.stroke()
}
function contains({ A, B, C }, P) {
return triangleContains(A.x, A.y, B.x, B.y, C.x, C.y, P.x, P.y)
}
function resetCanvas(canvas) {
canvas.width = width
canvas.height = height
let ctx = canvas.getContext('2d')
ctx.resetTransform()
ctx.clearRect(0, 0, width, height)
ctx.setTransform(scale, 0, 0, scale, width/2, height/2)
}
function drawDots() {
let canvas = document.querySelector('canvas#dots')
let ctx = canvas.getContext('2d')
resetCanvas(canvas)
let count = 1000
for (let i = 0; i < count; i++) {
let x = width * (Math.random() - .5)
let y = width * (Math.random() - .5)
ctx.beginPath()
ctx.ellipse(x, y, 1, 1, 0, 0, 2 * Math.PI)
if (contains(triangle1, { x, y })) {
ctx.fillStyle = '#f00'
} else if (contains(triangle2, { x, y })) {
ctx.fillStyle = '#00f'
} else {
ctx.fillStyle = '#0003'
}
ctx.fill()
}
}
function drawInteractive() {
let canvas = document.querySelector('canvas#interactive')
let ctx = canvas.getContext('2d')
resetCanvas(canvas)
ctx.beginPath()
ctx.moveTo(0, -height/2)
ctx.lineTo(0, height/2)
ctx.moveTo(-width/2, 0)
ctx.lineTo(width/2, 0)
ctx.strokeStyle = '#0003'
ctx.stroke()
drawTriangle(ctx, triangle1)
drawTriangle(ctx, triangle2)
ctx.beginPath()
ctx.ellipse(mouse.x, mouse.y, 4, 4, 0, 0, 2 * Math.PI)
if (contains(triangle1, mouse)) {
ctx.fillStyle = triangle1.color + 'a'
ctx.fill()
} else if (contains(triangle2, mouse)) {
ctx.fillStyle = triangle2.color + 'a'
ctx.fill()
} else {
ctx.strokeStyle = 'black'
ctx.stroke()
}
}
drawDots()
drawInteractive()
// trigo
function add(...points) {
let x = 0, y = 0
for (let point of points) {
x += point.x
y += point.y
}
return { x, y }
}
function center(...points) {
let x = 0, y = 0
for (let point of points) {
x += point.x
y += point.y
}
x /= points.length
y /= points.length
return { x, y }
}
function sub(A, B) {
let x = A.x - B.x
let y = A.y - B.y
return { x, y }
}
function normalize({ x, y }, length = 10) {
let r = length / Math.sqrt(x * x + y * y)
x *= r
y *= r
return { x, y }
}
function rotate({ x, y }, angle = 90) {
let length = Math.sqrt(x * x + y * y)
angle *= Math.PI / 180
angle += Math.atan2(y, x)
x = length * Math.cos(angle)
y = length * Math.sin(angle)
return { x, y }
}
* {
margin: 0;
}
html {
font-family: monospace;
}
body {
padding: 32px;
}
span.red {
color: #f00;
}
span.blue {
color: #00f;
}
canvas {
position: absolute;
border: solid 1px #ddd;
}
<p><span class="red">red triangle</span> is clockwise</p>
<p><span class="blue">blue triangle</span> is couter clockwise</p>
<br>
<div class="wrapper">
<canvas id="dots"></canvas>
<canvas id="interactive"></canvas>
</div>
Ответ 22
Это простейшая концепция, чтобы определить, находится ли точка внутри или снаружи треangularьника или на плече треangularьника.
Определение точки внутри треangularьника по определителям:
Самый простой рабочий код:
#-*- coding: utf-8 -*-
import numpy as np
tri_points = [(1,1),(2,3),(3,1)]
def pisinTri(point,tri_points):
Dx , Dy = point
A,B,C = tri_points
Ax, Ay = A
Bx, By = B
Cx, Cy = C
M1 = np.array([ [Dx - Bx, Dy - By, 0],
[Ax - Bx, Ay - By, 0],
[1 , 1 , 1]
])
M2 = np.array([ [Dx - Ax, Dy - Ay, 0],
[Cx - Ax, Cy - Ay, 0],
[1 , 1 , 1]
])
M3 = np.array([ [Dx - Cx, Dy - Cy, 0],
[Bx - Cx, By - Cy, 0],
[1 , 1 , 1]
])
M1 = np.linalg.det(M1)
M2 = np.linalg.det(M2)
M3 = np.linalg.det(M3)
print(M1,M2,M3)
if(M1 == 0 or M2 == 0 or M3 ==0):
print("Point: ",point," lies on the arms of Triangle")
elif((M1 > 0 and M2 > 0 and M3 > 0)or(M1 < 0 and M2 < 0 and M3 < 0)):
#if products is non 0 check if all of their sign is same
print("Point: ",point," lies inside the Triangle")
else:
print("Point: ",point," lies outside the Triangle")
print("Vertices of Triangle: ",tri_points)
points = [(0,0),(1,1),(2,3),(3,1),(2,2),(4,4),(1,0),(0,4)]
for c in points:
pisinTri(c,tri_points)
Ответ 23
Недостаточно представителей, чтобы прокомментировать решение John Bananas, таким образом, разместив сообщение здесь.
Его решение не рассматривает точку, которая совпадает с одной из вершин треугольника, чтобы быть в треугольнике.
Следующее имеет неверный результат:
intPoint s, a, b, c;
s.x = 0; s.y = 0;
a.x = 0; a.y = 0;
b.x = 10; b.y = 0;
c.x = 5; c.y = 10;
bool result = intpoint_inside_trigon(s, a, b, c);
Ответ 24
Мне нужно было проверить треangularьник в "контролируемой среде", когда вы абсолютно уверены, что треangularьники будут по часовой стрелке. Итак, я взял Perro Azul jsfiddle и изменил его, как предложено coproc для таких случаев; также удалены избыточные 0,5 и 2 умножения, потому что они просто отменяют друг друга.
http://jsfiddle.net/dog_funtom/H7D7g/
var ctx = $("canvas")[0].getContext("2d");
var W = 500;
var H = 500;
var point = {
x: W / 2,
y: H / 2
};
var triangle = randomTriangle();
$("canvas").click(function (evt) {
point.x = evt.pageX - $(this).offset().left;
point.y = evt.pageY - $(this).offset().top;
test();
});
$("canvas").dblclick(function (evt) {
triangle = randomTriangle();
test();
});
test();
function test() {
var result = ptInTriangle(point, triangle.a, triangle.b, triangle.c);
var info = "point = (" + point.x + "," + point.y + ")\n";
info += "triangle.a = (" + triangle.a.x + "," + triangle.a.y + ")\n";
info += "triangle.b = (" + triangle.b.x + "," + triangle.b.y + ")\n";
info += "triangle.c = (" + triangle.c.x + "," + triangle.c.y + ")\n";
info += "result = " + (result ? "true" : "false");
$("#result").text(info);
render();
}
function ptInTriangle(p, p0, p1, p2) {
var s = (p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y);
var t = (p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y);
if (s <= 0 || t <= 0) return false;
var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
return (s + t) < A;
}
function checkClockwise(p0, p1, p2) {
var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
return A > 0;
}
function render() {
ctx.fillStyle = "#CCC";
ctx.fillRect(0, 0, 500, 500);
drawTriangle(triangle.a, triangle.b, triangle.c);
drawPoint(point);
}
function drawTriangle(p0, p1, p2) {
ctx.fillStyle = "#999";
ctx.beginPath();
ctx.moveTo(p0.x, p0.y);
ctx.lineTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.closePath();
ctx.fill();
ctx.fillStyle = "#000";
ctx.font = "12px monospace";
ctx.fillText("1", p0.x, p0.y);
ctx.fillText("2", p1.x, p1.y);
ctx.fillText("3", p2.x, p2.y);
}
function drawPoint(p) {
ctx.fillStyle = "#F00";
ctx.beginPath();
ctx.arc(p.x, p.y, 5, 0, 2 * Math.PI);
ctx.fill();
}
function rand(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function randomTriangle() {
while (true) {
var result = {
a: {
x: rand(0, W),
y: rand(0, H)
},
b: {
x: rand(0, W),
y: rand(0, H)
},
c: {
x: rand(0, W),
y: rand(0, H)
}
};
if (checkClockwise(result.a, result.b, result.c)) return result;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<pre>Click: place the point.
Double click: random triangle.</pre>
<pre id="result"></pre>
<canvas width="500" height="500"></canvas>
Ответ 25
bool point2Dtriangle(double e,double f, double a,double b,double c, double g,double h,double i, double v, double w){
/* inputs: e=point.x, f=point.y
a=triangle.Ax, b=triangle.Bx, c=triangle.Cx
g=triangle.Ay, h=triangle.By, i=triangle.Cy */
v = 1 - (f * (b - c) + h * (c - e) + i * (e - b)) / (g * (b - c) + h * (c - a) + i * (a - b));
w = (f * (a - b) + g * (b - e) + h * (e - a)) / (g * (b - c) + h * (c - a) + i * (a - b));
if (*v > -0.0 && *v < 1.0000001 && *w > -0.0 && *w < *v) return true;//is inside
else return false;//is outside
return 0;
}
почти идеальные декартовы координаты, преобразованные из барицентрических экспортируются в двойных значениях * v (x) и * w (y). Оба экспортных двойника должны иметь символ * в каждом случае, вероятно: * v и * w Код можно использовать и для другого треangularьника четырехangularьника. Настоящим со знаком написано только треangularьник abc из четырехangularьника abcd по часовой стрелке.
A---B
|..\\.o|
|....\\.|
D---C
точка o находится внутри треangularьника ABC
для тестирования со вторым треangularьником вызовите эту функцию CDA direction, и результаты должны быть правильными после *v=1-*v;
и *w=1-*w;
для четырехangularьника
Ответ 26
Один из самых простых способов проверить, образована ли область вершинами треangularьника (x1, y1), (x2, y2), (x3, y3) положительны или нет.
Площадь можно рассчитать по формуле:
1/2 [x1(y2–y3) + x2(y3–y1) + x3(y1–y2)]
или код Python может быть записан как:
def triangleornot(p1,p2,p3):
return (1/ 2) [p1[0](p2[1]–p3[1]) + p2[0] (p3[1]–p1[1]) + p3[0] (p1[0]–p2[0])]