Escribí un artículo completo sobre el punto en la prueba de triángulo. Muestra los métodos basados en productos baricéntricos, paramétricos y de punto. Luego trata el problema de precisión que ocurre cuando un punto se encuentra exactamente en un borde (con ejemplos). Finalmente expone un nuevo método completo basado en la distancia punto a borde. totologic.blogspot.fr/2014/01/… ¡Disfruta!
Vale la pena señalar que cualquier método discutido aquí también es válido en el espacio 3D. Solo necesitan estar precedidos por una transformación de coordenadas (y una proyección apropiada del punto en el plano del triángulo). Un triángulo es un objeto bidimensional.
Estoy votando para cerrar esta pregunta porque se trata de matemáticas en lugar de programación, y está basada en la opinión (¿qué es "fácil" para usted?).
TylerH
Respuestas:
264
En general, el algoritmo más simple (y bastante óptimo) es verificar de qué lado del semiplano creado por los bordes es el punto.
Aquí hay información de alta calidad en este tema en GameDev , incluidos problemas de rendimiento.
Se usa comúnmente en 2D. Las coordenadas barcéntricas tienden a confundir a las personas. Además, dadas las cooridaciones del triángulo y el punto cordinado, no estoy seguro acerca de la eficiencia del uso de baricentrés.
Kornel Kisielewicz
77
@Kornel La versión baricéntrica también es más eficiente en 2D. Su solución también tiene el problema de que informará un resultado diferente para los puntos exactamente en los bordes del triángulo, dependiendo de si el triángulo se especifica en el sentido horario o antihorario.
Andreas Brinck el
9
Para mis propósitos (la razón por la que encontré este sitio) la respuesta original propuesta por Kornel Kisielewicz es mucho más eficiente. Estoy trabajando con una pantalla LCD con coordenadas de tamaño BYTE y un microprocesador muy típico donde la multiplicación de enteros es una instrucción muy rápida, y la división es mucho, mucho más lenta. ¡Los problemas numéricos también son mucho más pequeños, debido a que no hay división! Todos los cálculos son exactos. Gracias, Rick
44
Entonces, la función sign () te dice de qué lado del semiplano (formado por la línea entre p2 y p3) está p1?
David Doria
1
Tenga en cuenta que si asume algún orden de los vértices (por ejemplo, en sentido contrario a las agujas del reloj), no necesita calcular todos esos determinantes todo el tiempo. De hecho, en el mejor de los casos, 1 determinante es suficiente para encontrar que el punto no está dentro del triángulo.
Thash
176
Resuelve el siguiente sistema de ecuaciones:
p = p0 + (p1 - p0) * s + (p2 - p0) * t
El punto pestá dentro del triángulo si 0 <= s <= 1y 0 <= t <= 1ys + t <= 1 .
Esto es más rápido que la verificación de medio plano, pero quizás sea un poco más difícil de comprender si eres nuevo en las coordenadas barcéntricas.
Daniel Rikowski
8
Con salidas triviales (no implementadas) en el método de Kornel, el suyo puede ser mucho más eficiente que el suyo. Si realmente intenta calcular syt, sabrá a qué me refiero.
85
Quería probar esto, así que hice un jsfiddle, confiando en la solución @andreasdr y el comentario de coproc
urraka
55
Optimización: s + t <= 1implica s <= 1y t <= 1si s >= 0y t >= 0.
Estoy de acuerdo con Andreas Brinck , las coordenadas barcéntricas son muy convenientes para esta tarea. Tenga en cuenta que no es necesario resolver un sistema de ecuaciones cada vez: solo evalúe la solución analítica. Usando la notación de Andreas , la solución es:
Solo evalúa s , ty 1-s-t. El puntop está dentro del triángulo si y solo si todos son positivos.
EDITAR: Tenga en cuenta que la expresión anterior para el área supone que la numeración del nodo triangular es en sentido antihorario. Si la numeración es en el sentido de las agujas del reloj, esta expresión devolverá un área negativa (pero con la magnitud correcta). s>0 && t>0 && 1-s-t>0Sin embargo, la prueba en sí ( ) no depende de la dirección de la numeración, ya que las expresiones anteriores se multiplican por1/(2*Area) también cambian de signo si cambia la orientación del nodo triangular.
EDITAR 2: para una eficiencia computacional aún mejor, vea el comentario de coproc a continuación (que señala que si la orientación de los nodos triangulares (en sentido horario o antihorario) se conoce de antemano, la división por 2*Areaen las expresiones para sy tpuede ser evitado). Vea también el código jsfiddle del Perro Azul en los comentarios bajo la respuesta de Andreas Brinck .
Sí, mi punto es que cualquier crítica a su método basada en el costo computacional de resolver el sistema de ecuaciones es infundada, ya que eso no tiene que hacerse como parte del algoritmo.
andreasdr
13
La eficiencia se puede mejorar al no dividir a través 2*Area, es decir, al calcular s´=2*|Area|*sy t´=2*|Area|*t(si no se conoce la orientación de los puntos, en sentido horario o antihorario), el signo de Areatiene que verificarse, por supuesto, pero de lo contrario tal vez ni siquiera debe calcularse), ya que para verificarlo s>0es suficiente verificar s´>0. Y en lugar de comprobarlo 1-s-t>0, basta con comprobarlo s´+t´<2*|Area|.
coproc
1
Puedo agregar que si p0->p1->p2es en sentido antihorario en cartesiano (que generalmente es en sentido horario en las coordenadas de la pantalla ), el Areacálculo por este método será positivo.
rhgb
1
@ user2600366 Cuando viaja a lo largo del límite del triángulo en la dirección p0 -> p1 -> p2 -> p0, y así sucesivamente, tendrá el interior del triángulo siempre a su derecha o siempre a su izquierda. En el primer caso, la numeración es en sentido horario, en el último caso, es en sentido antihorario.
andreasdr
47
Escribí este código antes de un intento final con Google y de encontrar esta página, así que pensé en compartirlo. Básicamente es una versión optimizada de la respuesta de Kisielewicz. También examiné el método barcéntrico, pero a juzgar por el artículo de Wikipedia, me resulta difícil ver cómo es más eficiente (supongo que hay una equivalencia más profunda). De todos modos, este algoritmo tiene la ventaja de no usar división; Un problema potencial es el comportamiento de la detección de bordes dependiendo de la orientación.
En palabras, la idea es esta: ¿está el punto s a la izquierda o a la derecha de ambas líneas AB y AC? Si es verdad, no puede estar adentro. Si es falso, es al menos dentro de los "conos" que satisfacen la condición. Ahora, como sabemos que un punto dentro de un trigón (triángulo) debe estar al mismo lado de AB que BC (y también CA), verificamos si difieren. Si lo hacen, s posiblemente no puede estar adentro, de lo contrario s debe estar adentro.
Algunas palabras clave en los cálculos son semiplanos de línea y el determinante (producto cruzado 2x2). Quizás una forma más pedagógica sea pensar en él como un punto dentro si está del mismo lado (izquierda o derecha) a cada una de las líneas AB, BC y CA. Sin embargo, la forma anterior parecía encajar mejor para cierta optimización.
Esta prueba es aproximadamente 140-180% más rápida que la primera proporcionada (gracias a ambos por cierto :). Ejecuté el código aquí: paste.ubuntu.com/p/k5w7ywH4p8 usando el motor nodejs v8 con optimizaciones deshabilitadas y obtuve los siguientes resultados:: w! Node -p --minimal test1: 114.852ms test2: 64.330ms test1: 115.650ms test2: 63.491ms test1: 117.671ms test2: 65.353ms test1: 119.146ms test2: 63.871ms test1: 118.271ms test1: 118.670ms test2: 63.352ms
surgemcgee
@surgemcgee ¿por qué lo ejecutarías sin optimizaciones? ¿No es eso más alejado de la realidad entonces?
xuiqzy
@xuiqzy Bueno, mi programa contiene las dos soluciones diferentes. Todavía tengo que administrar el método más rápido para hacerlo. Tal vez ese comentario debería eliminarse y reemplazarse con mis trabajos completos sobre esto ...
surgemcgee
33
Versión C # del método baricéntrico publicado por andreasdr y Perro Azul. Tenga en cuenta que el cálculo del área puede ser evitado si sy ttienen signos opuestos. Verifiqué el comportamiento correcto con una prueba unitaria bastante exhaustiva.
publicstaticboolPointInTriangle(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))returnfalse;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);}
[ editar ] acepta modificación sugerida por @Pierre; ver comentarios
La solución con la sentencia if final funciona para puntos triangulares en sentido horario y antihorario.
Luke Dupin el
@LukeDupin No estoy seguro de entender tu comentario. Esta respuesta funciona según lo publicado para cualquier pedido suministrado de los 3 puntos.
Glenn Slayden
12
Versión Java del método barcéntrico:
classTriangle{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)returnfalse;double b = y31 * dx + x13 * dy;if(b < minD || b > maxD)returnfalse;double c = det - a - b;if(c < minD || c > maxD)returnfalse;returntrue;}privatefinaldouble x3, y3;privatefinaldouble y23, x32, y31, x13;privatefinaldouble det, minD, maxD;}
El código anterior funcionará con precisión con enteros, suponiendo que no haya desbordamientos. También funcionará con triángulos en sentido horario y antihorario. No funcionará con triángulos colineales (pero puede verificarlo probando det == 0).
La versión barcéntrica es más rápida si va a probar diferentes puntos con el mismo triángulo.
La versión baricéntrica no es simétrica en los 3 puntos triangulares, por lo que es probable que sea menos consistente que la versión de medio plano del borde de Kornel Kisielewicz, debido a errores de redondeo de coma flotante.
Crédito: hice el código anterior del artículo de Wikipedia sobre coordenadas barcéntricas.
Agradable ! Incluso puede mejorar el uso de las tuplas Point3f / Point2f de javax.vecmath para manejar mejor la entrada de datos.
Alex Byrth
10
Una forma simple es:
encuentra los vectores que conectan el punto con cada uno de los tres vértices del triángulo y suma los ángulos entre esos vectores. Si la suma de los ángulos es 2 * pi, entonces el punto está dentro del triángulo.
Um, ese método no es exactamente eficiente, y es muy propenso a errores numéricos ...
Kornel Kisielewicz
Es todo lo contrario, es muy ineficiente :-) Sin embargo, es solo una forma simple, fácil de implementar. ¿Puedes dar un ejemplo de un error numérico que esto causaría?
Simon P Stevens
Si bien para mí esto simplemente parece ser la mejor de todas las respuestas bajo este tema, supongo que los puntos en los bordes del triángulo se calculan para incluirse en el triángulo y no tienes un control sólido sobre eso.
Reducido
verificar si es exactamente 2pi es numéricamente imposible dado el irracional de Pi. Sin embargo, solo necesita verificar si los ángulos se suman a algo mayor que pi.
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script><pre>Click: place the point.
Double click: random triangle.</pre><preid="result"></pre><canvaswidth="500"height="500"></canvas>
Esto se compara bastante bien con la solución Kornel Kisielewicz (25 retiros, 1 almacenamiento, 15 restas, 6 multiplicaciones, 5 comparaciones), y podría ser aún mejor si se necesita la detección en sentido horario / antihorario (que requiere 6 retiros, 1 suma, 2 restas , 2 multiplicaciones y 1 comparación en sí misma, utilizando el determinante de la solución analítica, como lo señala rhgb ).
Acabo de probar el código tal como está y no funciona para mí (ejemplo p -4.69317198, -6.99191951 p0 -7.05846786 0.596718192 p1 -6.8703599 -2.36565161 p2 -4.69317198, -6.99191951)
Giovanni Funchal
@GiovanniFunchal Strange, su ejemplo funciona para mí, tanto en jsfiddle (reemplace las definiciones iniciales de "punto" y "triángulo") como en mi implementación local de Python. ¿Problemas de precisión numérica (intente quitar algunos decimales)?
Cédric Dufour
1
Tu parece el más rápido en mi prueba: jsfiddle.net/eyal/gxw3632c/27 . Sin embargo, la diferencia entre todos los métodos es bastante pequeña.
Eyal
Pruebe el triángulo (-1, -1), (1, -1), (0,1) y el punto (0, -1). Devuelve falso cuando debería devolver verdadero porque s (2) + t (2)> d (2). Parece que hay algo mal con las matemáticas en los bordes del triángulo, ya que el punto p está justo en el borde entre p0 y p1 y no es una simple cuestión de convertir un <a un <= o algo así.
devnullicus
5
Lo que hago es precalcular las tres caras normales,
en 3D por producto cruzado del vector lateral y el vector normal de la cara.
en 2D simplemente intercambiando componentes y negando uno,
entonces adentro / afuera para cualquier lado es cuando un producto de punto del lado normal y el vector de vértice a punto, cambia de signo. Repita para los otros dos (o más) lados.
Beneficios:
mucho se calcula previamente, por lo que es excelente para pruebas de puntos múltiples en el mismo triángulo.
Rechazo temprano del caso común de más puntos externos que internos. (también si la distribución de puntos se pondera a un lado, puede probar ese lado primero).
defPointInsideTriangle2(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:returnFalseelse: 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))
No he podido hacer que esto funcione, por ejemplo, para el punto en el triángulo [(0,0), (3,0), (3,4)], ni los puntos (1,1) o (0 , 0) prueba positiva. Intenté con los puntos triangulares en sentido horario y antihorario.
ThorSummoner
3
Si está buscando velocidad, aquí hay un procedimiento que podría ayudarlo.
Ordena los vértices del triángulo en sus ordenadas. Esto lleva, en el peor de los casos, tres comparaciones. Sean Y0, Y1, Y2 los tres valores ordenados. Al dibujar tres horizontales a través de ellos, divide el plano en dos medios planos y dos losas. Deje Y ser la ordenada del punto de consulta.
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
Cuesta dos comparaciones más. Como puede ver, se logra un rechazo rápido para puntos fuera de la "losa delimitadora".
Opcionalmente, puede proporcionar una prueba en las abscisas para el rechazo rápido a la izquierda y a la derecha (X <= X0' or X >= X2' ). Esto implementará una prueba rápida de cuadro delimitador al mismo tiempo, pero también deberá ordenar las abscisas.
Eventualmente, deberá calcular el signo del punto dado con respecto a los dos lados del triángulo que delimitan la losa relevante (superior o inferior). La prueba tiene la forma:
La discusión completa de i, j, k combinaciones (hay seis de ellas, basadas en el resultado del género) está fuera del alcance de esta respuesta y "se dejó como ejercicio para el lector"; para mayor eficiencia, deben estar codificados.
Si cree que esta solución es compleja, observe que implica principalmente comparaciones simples (algunas de las cuales pueden calcularse previamente), más 6 restas y 4 multiplicaciones en caso de que falle la prueba del cuadro delimitador. El último costo es difícil de superar, ya que en el peor de los casos no puede evitar comparar el punto de prueba con dos lados (ningún método en otras respuestas tiene un costo menor, algunos lo empeoran, como 15 restas y 6 multiplicaciones, a veces divisiones).
ACTUALIZACIÓN: más rápido con una transformación de corte
Como se explicó anteriormente, puede ubicar rápidamente el punto dentro de una de las cuatro bandas horizontales delimitadas por las tres ordenadas de vértices, utilizando dos comparaciones.
Opcionalmente, puede realizar una o dos pruebas X adicionales para verificar la interioridad del cuadro delimitador (líneas punteadas).
Luego considere la transformación de "corte" dada por X'= X - m Y, Y' = Y, donde mes la pendiente DX/DYpara el borde más alto. Esta transformación hará que este lado del triángulo sea vertical. Y dado que sabe de qué lado del centro horizontal está, es suficiente probar el signo con respecto a un solo lado del triángulo.
Suponiendo que calculó previamente la pendiente m, así como los X'vértices del triángulo cizallado y los coeficientes de las ecuaciones de los lados X = m Y + p, necesitará en el peor de los casos
dos comparaciones ordenadas para clasificación vertical;
opcionalmente una o dos comparaciones de abscisas para el rechazo del cuadro delimitador;
cálculo de X' = X - m Y;
una o dos comparaciones con las abscisas del triángulo cizallado;
prueba de un signo X >< m' Y + p'contra el lado relevante del triángulo cizallado.
Si conoce las coordenadas de los tres vértices y las coordenadas del punto específico, puede obtener el área del triángulo completo. Luego, calcule el área de los tres segmentos del triángulo (un punto es el punto dado y los otros dos son dos vértices del triángulo). Por lo tanto, obtendrá el área de los tres segmentos triangulares. Si la suma de estas áreas es igual al área total (que obtuvo anteriormente), entonces, el punto debe estar dentro del triángulo. De lo contrario, el punto no está dentro del triángulo. Esto debería funcionar. Si hay algún problema, avíseme. Gracias.
Se necesita mucho para trazar, pero esa cuadrícula se prueba en 0.0195319652557 segundos contra 0.0844349861145 segundos del código del desarrollador .
Finalmente el comentario del código:
# 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
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// clockwiselet triangle1 ={
A :{ x:10, y:-10},
C :{ x:20, y:100},
B :{ x:-90, y:10},
color:'#f00',}// counter clockwiselet triangle2 ={
A :{ x:20, y:-60},
B :{ x:90, y:20},
C :{ x:20, y:60},
color:'#00f',}let scale =2let 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 =1000for(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'}elseif(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()}elseif(contains(triangle2, mouse)){
ctx.fillStyle = triangle2.color +'a'
ctx.fill()}else{
ctx.strokeStyle ='black'
ctx.stroke()}}
drawDots()
drawInteractive()// trigofunction add(...points){let x =0, y =0for(let point of points){
x += point.x
y += point.y
}return{ x, y }}function center(...points){let x =0, y =0for(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><spanclass="red">red triangle</span> is clockwise</p><p><spanclass="blue">blue triangle</span> is couter clockwise</p><br><divclass="wrapper"><canvasid="dots"></canvas><canvasid="interactive"></canvas></div>
Estoy usando el mismo método que el descrito anteriormente: un punto está dentro de ABC si está respectivamente en el "mismo" lado de cada línea AB, BC, CA.
Cansé este código y no funciona. Siempre devuelve False.
xApple el
hmmm ... probablemente cometiste un error. Aquí hay un violín con esa función ejecutándose: jsfiddle.net/jniac/rctb3gfL
Joseph Merdrignac
He visto su respuesta de Python, estamos usando el mismo método, si uso una línea más ( let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)), esto es para determinar el orden de enrollamiento del triángulo, por lo que el método funcionará con triángulos CW y CCW (ver jsFiddle).
Joseph Merdrignac
1
hm, cometí un error, escribí: en let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)lugar de let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)esto, esto está solucionado, gracias por informarnos
Joseph Merdrignac
2
Solo quiero usar algunas matemáticas vectoriales simples para explicar la solución de coordenadas barcéntricas que Andreas ha dado, será mucho más fácil de entender.
El Área A se define como cualquier vector dado por s * v02 + t * v01, con la condición s> = 0 yt> = 0. Si algún punto dentro del triángulo v0, v1, v2, debe estar dentro del Área A.
Si restringe aún más s, yt pertenece a [0, 1]. Obtenemos el Área B que contiene todos los vectores de s * v02 + t * v01, con la condición s, t pertenece a [0, 1]. Vale la pena señalar que la parte baja del Área B es el espejo del Triángulo v0, v1, v2. El problema surge si podemos dar cierta condición de s, y t, para excluir aún más la parte baja del Área B.
Supongamos que damos un valor s, y t está cambiando en [0, 1]. En la siguiente imagen, el punto p está en el borde de v1v2. Todos los vectores de s * v02 + t * v01 que están a lo largo de la línea de trazos por suma vectorial simple. En v1v2 y punto de cruce de línea de guión p, tenemos:
obtenemos 1 - s = tp, luego 1 = s + tp. Si cualquier t> tp, que 1 <s + t donde está en la línea de doble guión, el vector está fuera del triángulo, cualquier t <= tp, que 1> = s + t donde está en la línea de un solo guión, el vector es dentro del triangulo.
Entonces, si damos alguna s en [0, 1], la t correspondiente debe cumplir con 1> = s + t, para el vector dentro del triángulo.
Entonces, finalmente obtenemos v = s * v02 + t * v01, v está dentro del triángulo con la condición s, t, s + t pertenece a [0, 1]. Luego traduce al punto, tenemos
p - p0 = s * (p1 - p0) + t * (p2 - p0), con s, t, s + t en [0, 1]
que es lo mismo que la solución de Andreas para resolver el sistema de ecuaciones p = p0 + s * (p1 - p0) + t * (p2 - p0), con s, t, s + t pertenecen a [0, 1].
Simplemente puede decir que usa el marco local definido por los tres vértices para que los lados se conviertan en s = 0, t = 0 y s + t = 1. La transformación de coordenadas afines es una operación bien conocida de álgebra lineal.
Yves Daoust
2
Aquí hay una solución en Python que es eficiente, documentada y contiene tres pruebas unitarias. Es de calidad profesional y está listo para ser incluido en su proyecto en forma de módulo tal cual.
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's 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 negativereturn(side_1 <0.0)==(side_2 <0.0)==(side_3 <0.0)###############################################################################classTestPointInTriangle(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's 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)
Hay una prueba gráfica opcional adicional para el algoritmo anterior para confirmar su validez:
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 importPolygon
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")
Hay condiciones de borde molestas donde un punto está exactamente en el borde común de dos triángulos adyacentes. El punto no puede estar en ambos, ni en ninguno de los triángulos. Necesita una forma arbitraria pero consistente de asignar el punto. Por ejemplo, dibuje una línea horizontal a través del punto. Si la línea se cruza con el otro lado del triángulo a la derecha, el punto se trata como si estuviera dentro del triángulo. Si la intersección está a la izquierda, el punto está afuera.
Si la línea en la que se encuentra el punto es horizontal, use arriba / abajo.
Si el punto está en el vértice común de varios triángulos, use el triángulo con cuyo centro el punto forma el ángulo más pequeño.
Más divertido: tres puntos pueden estar en línea recta (cero grados), por ejemplo (0,0) - (0,10) - (0,5). En un algoritmo de triangulación, el "oído" (0,10) debe ser cortado, el "triángulo" generado es el caso degenerado de una línea recta.
Este es el concepto más simple para determinar si un punto está dentro o fuera del triángulo o en un brazo de un triángulo.
La determinación de un punto está dentro de un triángulo por determinantes:
El código de trabajo más simple:
#-*- 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 ==0or M2 ==0or M3 ==0):print("Point: ",point," lies on the arms of Triangle")elif((M1 >0and M2 >0and M3 >0)or(M1 <0and M2 <0and M3 <0)):#if products is non 0 check if all of their sign is sameprint("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)
La forma más fácil y funciona con todo tipo de triángulos es simplemente determinar los ángulos de los ángulos de los puntos P, A, B y C. Si alguno de los ángulos es mayor que 180.0 grados, entonces está afuera, si 180.0 está en la circunferencia y si te está engañando y menos de 180.0, entonces está adentro. http: // math-physics -psychology.blogspot.hu/2015/01/earlish-determination-that-point-is.html
Sinceramente, es tan simple como la respuesta de Simon P Steven sin embargo, con ese enfoque no tienes un control sólido sobre si quieres que se incluyan o no los puntos en los bordes del triángulo.
Mi enfoque es un poco diferente pero muy básico. Considere el siguiente triángulo;
Para tener el punto en el triángulo tenemos que cumplir 3 condiciones
El ángulo ACE (verde) debe ser menor que el ángulo ACB (rojo)
El ángulo BCE (azul) debe ser menor que el ángulo ACB (rojo)
El punto E y el punto C deben tener el mismo signo cuando sus valores x e y se aplican a la ecuación de | AB | línea.
En este método, tiene control total para incluir o excluir el punto en los bordes individualmente. Por lo tanto, puede verificar si un punto está en el triángulo incluyendo solo el | AC | borde por ejemplo.
Entonces mi solución en JavaScript sería la siguiente;
function isInTriangle(t,p){function isInBorder(a,b,c,p){var m =(a.y - b.y)/(a.x - b.x);// calculate the slopereturnMath.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 lengthreturnMath.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));
¡No puede ser más eficiente que esto! Cada lado de un triángulo puede tener una posición y orientación independientes, por lo tanto, tres cálculos: l1, l2 y l3 son definitivamente necesarios e involucran 2 multiplicaciones cada uno. Una vez que se conocen l1, l2 y l3, el resultado es solo unas pocas comparaciones básicas y operaciones booleanas de distancia.
pointInTriangle(p, p0, p1, p2) - para triángulos en sentido antihorario
pointInTriangle(p, p0, p1, p2) - para triángulos en sentido horario
Mire en jsFiddle (prueba de rendimiento incluida), también hay comprobación de bobinado en una función separada. O presione "Ejecutar fragmento de código" a continuación
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script><buttonid="performance">Run performance test (open console)</button><pre>Click: place the point.
Double click: random triangle.</pre><preid="result"></pre><canvaswidth="500"height="500"></canvas>
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)returntrue;//is insideelsereturnfalse;//is outsidereturn0;}
Las coordenadas cartesianas casi perfectas convertidas de barcéntricas se exportan dentro de * v (x) y * w (y) dobles. Ambos dobles de exportación deben tener un * char antes en todos los casos, probablemente: * v y * w Code también se pueden usar para el otro triángulo de un cuadrángulo. Por la presente, se escribió solo el triángulo abc del cuadrante abcd en sentido horario.
A---B
|..\\.o|
|....\\.|
D---C
el punto o está dentro del triángulo ABC para probar con el segundo triángulo, llame a esta función dirección CDA, y los resultados deben ser correctos después *v=1-*v;y *w=1-*w;para el cuadrángulo
Necesitaba un punto de verificación de triángulos en "entorno controlable" cuando estás absolutamente seguro de que los triángulos estarán en el sentido de las agujas del reloj. Entonces, tomé el jsfiddle de Perro Azul y lo modifiqué según lo sugerido por coproc para tales casos; También eliminó las multiplicaciones redundantes de 0.5 y 2 porque simplemente se cancelan entre sí.
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script><pre>Click: place the point.
Double click: random triangle.</pre><preid="result"></pre><canvaswidth="500"height="500"></canvas>
Respuestas:
En general, el algoritmo más simple (y bastante óptimo) es verificar de qué lado del semiplano creado por los bordes es el punto.
Aquí hay información de alta calidad en este tema en GameDev , incluidos problemas de rendimiento.
Y aquí hay un código para comenzar:
fuente
Resuelve el siguiente sistema de ecuaciones:
El punto
p
está dentro del triángulo si0 <= s <= 1
y0 <= t <= 1
ys + t <= 1
.s
,t
y1 - s - t
se denominan coordenadas barcéntricas del puntop
.fuente
s + t <= 1
implicas <= 1
yt <= 1
sis >= 0
yt >= 0
.Estoy de acuerdo con Andreas Brinck , las coordenadas barcéntricas son muy convenientes para esta tarea. Tenga en cuenta que no es necesario resolver un sistema de ecuaciones cada vez: solo evalúe la solución analítica. Usando la notación de Andreas , la solución es:
donde
Area
está el área (firmada) del triángulo:Solo evalúa
s
,t
y1-s-t
. El puntop
está dentro del triángulo si y solo si todos son positivos.EDITAR: Tenga en cuenta que la expresión anterior para el área supone que la numeración del nodo triangular es en sentido antihorario. Si la numeración es en el sentido de las agujas del reloj, esta expresión devolverá un área negativa (pero con la magnitud correcta).
s>0 && t>0 && 1-s-t>0
Sin embargo, la prueba en sí ( ) no depende de la dirección de la numeración, ya que las expresiones anteriores se multiplican por1/(2*Area)
también cambian de signo si cambia la orientación del nodo triangular.EDITAR 2: para una eficiencia computacional aún mejor, vea el comentario de coproc a continuación (que señala que si la orientación de los nodos triangulares (en sentido horario o antihorario) se conoce de antemano, la división por
2*Area
en las expresiones paras
yt
puede ser evitado). Vea también el código jsfiddle del Perro Azul en los comentarios bajo la respuesta de Andreas Brinck .fuente
2*Area
, es decir, al calculars´=2*|Area|*s
yt´=2*|Area|*t
(si no se conoce la orientación de los puntos, en sentido horario o antihorario), el signo deArea
tiene que verificarse, por supuesto, pero de lo contrario tal vez ni siquiera debe calcularse), ya que para verificarlos>0
es suficiente verificars´>0
. Y en lugar de comprobarlo1-s-t>0
, basta con comprobarlos´+t´<2*|Area|
.p0->p1->p2
es en sentido antihorario en cartesiano (que generalmente es en sentido horario en las coordenadas de la pantalla ), elArea
cálculo por este método será positivo.Escribí este código antes de un intento final con Google y de encontrar esta página, así que pensé en compartirlo. Básicamente es una versión optimizada de la respuesta de Kisielewicz. También examiné el método barcéntrico, pero a juzgar por el artículo de Wikipedia, me resulta difícil ver cómo es más eficiente (supongo que hay una equivalencia más profunda). De todos modos, este algoritmo tiene la ventaja de no usar división; Un problema potencial es el comportamiento de la detección de bordes dependiendo de la orientación.
En palabras, la idea es esta: ¿está el punto s a la izquierda o a la derecha de ambas líneas AB y AC? Si es verdad, no puede estar adentro. Si es falso, es al menos dentro de los "conos" que satisfacen la condición. Ahora, como sabemos que un punto dentro de un trigón (triángulo) debe estar al mismo lado de AB que BC (y también CA), verificamos si difieren. Si lo hacen, s posiblemente no puede estar adentro, de lo contrario s debe estar adentro.
Algunas palabras clave en los cálculos son semiplanos de línea y el determinante (producto cruzado 2x2). Quizás una forma más pedagógica sea pensar en él como un punto dentro si está del mismo lado (izquierda o derecha) a cada una de las líneas AB, BC y CA. Sin embargo, la forma anterior parecía encajar mejor para cierta optimización.
fuente
Versión C # del método baricéntrico publicado por andreasdr y Perro Azul. Tenga en cuenta que el cálculo del área puede ser evitado si
s
yt
tienen signos opuestos. Verifiqué el comportamiento correcto con una prueba unitaria bastante exhaustiva.[ editar ]
acepta modificación sugerida por @Pierre; ver comentarios
fuente
Versión Java del método barcéntrico:
El código anterior funcionará con precisión con enteros, suponiendo que no haya desbordamientos. También funcionará con triángulos en sentido horario y antihorario. No funcionará con triángulos colineales (pero puede verificarlo probando det == 0).
La versión barcéntrica es más rápida si va a probar diferentes puntos con el mismo triángulo.
La versión baricéntrica no es simétrica en los 3 puntos triangulares, por lo que es probable que sea menos consistente que la versión de medio plano del borde de Kornel Kisielewicz, debido a errores de redondeo de coma flotante.
Crédito: hice el código anterior del artículo de Wikipedia sobre coordenadas barcéntricas.
fuente
Una forma simple es:
Dos buenos sitios que explican alternativas son:
blackpawn y wolfram
fuente
Al usar la solución analítica a las coordenadas barcéntricas (señalado por Andreas Brinck ) y
Se puede minimizar el número de operaciones "costosas":
El código puede pegarse en Perro Azul jsfiddle o probarlo haciendo clic en "Ejecutar fragmento de código" a continuación
Llevando a:
Esto se compara bastante bien con la solución Kornel Kisielewicz (25 retiros, 1 almacenamiento, 15 restas, 6 multiplicaciones, 5 comparaciones), y podría ser aún mejor si se necesita la detección en sentido horario / antihorario (que requiere 6 retiros, 1 suma, 2 restas , 2 multiplicaciones y 1 comparación en sí misma, utilizando el determinante de la solución analítica, como lo señala rhgb ).
fuente
Lo que hago es precalcular las tres caras normales,
en 3D por producto cruzado del vector lateral y el vector normal de la cara.
en 2D simplemente intercambiando componentes y negando uno,
entonces adentro / afuera para cualquier lado es cuando un producto de punto del lado normal y el vector de vértice a punto, cambia de signo. Repita para los otros dos (o más) lados.
Beneficios:
mucho se calcula previamente, por lo que es excelente para pruebas de puntos múltiples en el mismo triángulo.
Rechazo temprano del caso común de más puntos externos que internos. (también si la distribución de puntos se pondera a un lado, puede probar ese lado primero).
fuente
Aquí hay una implementación eficiente de Python :
y un ejemplo de salida:
fuente
Si está buscando velocidad, aquí hay un procedimiento que podría ayudarlo.
Ordena los vértices del triángulo en sus ordenadas. Esto lleva, en el peor de los casos, tres comparaciones. Sean Y0, Y1, Y2 los tres valores ordenados. Al dibujar tres horizontales a través de ellos, divide el plano en dos medios planos y dos losas. Deje Y ser la ordenada del punto de consulta.
Cuesta dos comparaciones más. Como puede ver, se logra un rechazo rápido para puntos fuera de la "losa delimitadora".
Opcionalmente, puede proporcionar una prueba en las abscisas para el rechazo rápido a la izquierda y a la derecha (
X <= X0' or X >= X2'
). Esto implementará una prueba rápida de cuadro delimitador al mismo tiempo, pero también deberá ordenar las abscisas.Eventualmente, deberá calcular el signo del punto dado con respecto a los dos lados del triángulo que delimitan la losa relevante (superior o inferior). La prueba tiene la forma:
La discusión completa de
i, j, k
combinaciones (hay seis de ellas, basadas en el resultado del género) está fuera del alcance de esta respuesta y "se dejó como ejercicio para el lector"; para mayor eficiencia, deben estar codificados.Si cree que esta solución es compleja, observe que implica principalmente comparaciones simples (algunas de las cuales pueden calcularse previamente), más 6 restas y 4 multiplicaciones en caso de que falle la prueba del cuadro delimitador. El último costo es difícil de superar, ya que en el peor de los casos no puede evitar comparar el punto de prueba con dos lados (ningún método en otras respuestas tiene un costo menor, algunos lo empeoran, como 15 restas y 6 multiplicaciones, a veces divisiones).
ACTUALIZACIÓN: más rápido con una transformación de corte
Como se explicó anteriormente, puede ubicar rápidamente el punto dentro de una de las cuatro bandas horizontales delimitadas por las tres ordenadas de vértices, utilizando dos comparaciones.
Opcionalmente, puede realizar una o dos pruebas X adicionales para verificar la interioridad del cuadro delimitador (líneas punteadas).
Luego considere la transformación de "corte" dada por
X'= X - m Y, Y' = Y
, dondem
es la pendienteDX/DY
para el borde más alto. Esta transformación hará que este lado del triángulo sea vertical. Y dado que sabe de qué lado del centro horizontal está, es suficiente probar el signo con respecto a un solo lado del triángulo.Suponiendo que calculó previamente la pendiente
m
, así como losX'
vértices del triángulo cizallado y los coeficientes de las ecuaciones de los ladosX = m Y + p
, necesitará en el peor de los casosX' = X - m Y
;X >< m' Y + p'
contra el lado relevante del triángulo cizallado.fuente
Si conoce las coordenadas de los tres vértices y las coordenadas del punto específico, puede obtener el área del triángulo completo. Luego, calcule el área de los tres segmentos del triángulo (un punto es el punto dado y los otros dos son dos vértices del triángulo). Por lo tanto, obtendrá el área de los tres segmentos triangulares. Si la suma de estas áreas es igual al área total (que obtuvo anteriormente), entonces, el punto debe estar dentro del triángulo. De lo contrario, el punto no está dentro del triángulo. Esto debería funcionar. Si hay algún problema, avíseme. Gracias.
fuente
Otra función en python , más rápida que el método del desarrollador (al menos para mí) e inspirada en la solución de Cédric Dufour :
Puedes probarlo con:
Se necesita mucho para trazar, pero esa cuadrícula se prueba en 0.0195319652557 segundos contra 0.0844349861145 segundos del código del desarrollador .
Finalmente el comentario del código:
fuente
ptInTriang([11,45],[45, 45],[45, 45] ,[44, 45])
y volverátrue
aunque sea falsoComo no hay una respuesta JS, la
solución en sentido horario y antihorario:
EDITAR: hubo un error tipográfico para el cómputo det (en
cy - ay
lugar decx - ax
), esto se corrigió.https://jsfiddle.net/jniac/rctb3gfL/
Estoy usando el mismo método que el descrito anteriormente: un punto está dentro de ABC si está respectivamente en el "mismo" lado de cada línea AB, BC, CA.
fuente
let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)
), esto es para determinar el orden de enrollamiento del triángulo, por lo que el método funcionará con triángulos CW y CCW (ver jsFiddle).let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)
lugar delet det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)
esto, esto está solucionado, gracias por informarnosSolo quiero usar algunas matemáticas vectoriales simples para explicar la solución de coordenadas barcéntricas que Andreas ha dado, será mucho más fácil de entender.
(1-s) | v0v2 | / | v0v2 | = tp | v0v1 | / | v0v1 |
obtenemos 1 - s = tp, luego 1 = s + tp. Si cualquier t> tp, que 1 <s + t donde está en la línea de doble guión, el vector está fuera del triángulo, cualquier t <= tp, que 1> = s + t donde está en la línea de un solo guión, el vector es dentro del triangulo.
Entonces, si damos alguna s en [0, 1], la t correspondiente debe cumplir con 1> = s + t, para el vector dentro del triángulo.
Entonces, finalmente obtenemos v = s * v02 + t * v01, v está dentro del triángulo con la condición s, t, s + t pertenece a [0, 1]. Luego traduce al punto, tenemos
p - p0 = s * (p1 - p0) + t * (p2 - p0), con s, t, s + t en [0, 1]
que es lo mismo que la solución de Andreas para resolver el sistema de ecuaciones p = p0 + s * (p1 - p0) + t * (p2 - p0), con s, t, s + t pertenecen a [0, 1].
fuente
Aquí hay una solución en Python que es eficiente, documentada y contiene tres pruebas unitarias. Es de calidad profesional y está listo para ser incluido en su proyecto en forma de módulo tal cual.
Hay una prueba gráfica opcional adicional para el algoritmo anterior para confirmar su validez:
Produciendo el siguiente gráfico:
fuente
Hay condiciones de borde molestas donde un punto está exactamente en el borde común de dos triángulos adyacentes. El punto no puede estar en ambos, ni en ninguno de los triángulos. Necesita una forma arbitraria pero consistente de asignar el punto. Por ejemplo, dibuje una línea horizontal a través del punto. Si la línea se cruza con el otro lado del triángulo a la derecha, el punto se trata como si estuviera dentro del triángulo. Si la intersección está a la izquierda, el punto está afuera.
Si la línea en la que se encuentra el punto es horizontal, use arriba / abajo.
Si el punto está en el vértice común de varios triángulos, use el triángulo con cuyo centro el punto forma el ángulo más pequeño.
Más divertido: tres puntos pueden estar en línea recta (cero grados), por ejemplo (0,0) - (0,10) - (0,5). En un algoritmo de triangulación, el "oído" (0,10) debe ser cortado, el "triángulo" generado es el caso degenerado de una línea recta.
fuente
Este es el concepto más simple para determinar si un punto está dentro o fuera del triángulo o en un brazo de un triángulo.
La determinación de un punto está dentro de un triángulo por determinantes:
El código de trabajo más simple:
fuente
La forma más fácil y funciona con todo tipo de triángulos es simplemente determinar los ángulos de los ángulos de los puntos P, A, B y C. Si alguno de los ángulos es mayor que 180.0 grados, entonces está afuera, si 180.0 está en la circunferencia y si te está engañando y menos de 180.0, entonces está adentro. http: // math-physics -psychology.blogspot.hu/2015/01/earlish-determination-that-point-is.html
fuente
Sinceramente, es tan simple como la respuesta de Simon P Steven sin embargo, con ese enfoque no tienes un control sólido sobre si quieres que se incluyan o no los puntos en los bordes del triángulo.
Mi enfoque es un poco diferente pero muy básico. Considere el siguiente triángulo;
Para tener el punto en el triángulo tenemos que cumplir 3 condiciones
En este método, tiene control total para incluir o excluir el punto en los bordes individualmente. Por lo tanto, puede verificar si un punto está en el triángulo incluyendo solo el | AC | borde por ejemplo.
Entonces mi solución en JavaScript sería la siguiente;
fuente
¡No puede ser más eficiente que esto! Cada lado de un triángulo puede tener una posición y orientación independientes, por lo tanto, tres cálculos: l1, l2 y l3 son definitivamente necesarios e involucran 2 multiplicaciones cada uno. Una vez que se conocen l1, l2 y l3, el resultado es solo unas pocas comparaciones básicas y operaciones booleanas de distancia.
fuente
Supuestamente código de alto rendimiento que adapté en JavaScript (artículo a continuación):
pointInTriangle(p, p0, p1, p2)
- para triángulos en sentido antihorariopointInTriangle(p, p0, p1, p2)
- para triángulos en sentido horarioMire en jsFiddle (prueba de rendimiento incluida), también hay comprobación de bobinado en una función separada. O presione "Ejecutar fragmento de código" a continuación
Inspirado por esto: http://www.phatcode.net/articles.php?id=459
fuente
Las coordenadas cartesianas casi perfectas convertidas de barcéntricas se exportan dentro de * v (x) y * w (y) dobles. Ambos dobles de exportación deben tener un * char antes en todos los casos, probablemente: * v y * w Code también se pueden usar para el otro triángulo de un cuadrángulo. Por la presente, se escribió solo el triángulo abc del cuadrante abcd en sentido horario.
el punto o está dentro del triángulo ABC para probar con el segundo triángulo, llame a esta función dirección CDA, y los resultados deben ser correctos después
*v=1-*v;
y*w=1-*w;
para el cuadrángulofuente
Necesitaba un punto de verificación de triángulos en "entorno controlable" cuando estás absolutamente seguro de que los triángulos estarán en el sentido de las agujas del reloj. Entonces, tomé el jsfiddle de Perro Azul y lo modifiqué según lo sugerido por coproc para tales casos; También eliminó las multiplicaciones redundantes de 0.5 y 2 porque simplemente se cancelan entre sí.
http://jsfiddle.net/dog_funtom/H7D7g/
Aquí hay un código C # equivalente para Unity:
fuente
Una de las formas más fáciles de verificar si el área formada por los vértices del triángulo (x1, y1), (x2, y2), (x3, y3) es positiva o no.
El área puede calcularse mediante la fórmula:
o el código de Python se puede escribir como:
fuente