Eu escrevi um artigo completo sobre o ponto no teste do triângulo. Ele mostra os métodos baseados em produtos barricêntricos, paramétricos e de pontos. Em seguida, lida com o problema de precisão que ocorre quando um ponto está exatamente em uma extremidade (com exemplos). Finalmente, expõe um novo método completo com base na distância ponto a borda. totologic.blogspot.fr/2014/01/… Aproveite!
Vale ressaltar que quaisquer métodos discutidos aqui também são válidos no espaço 3D. Eles só precisam ser precedidos por uma transformação de coordenadas (e uma projeção apropriada do ponto no plano do triângulo). Um triângulo é um objeto bidimensional.
Estou votando para encerrar esta pergunta porque é mais sobre matemática do que sobre programação e é baseada em opiniões (o que é "fácil" para você?).
TylerH
Respostas:
264
Em geral, o algoritmo mais simples (e bastante ideal) é verificar em que lado do semiplano criado pelas arestas está o ponto.
Aqui estão algumas informações de alta qualidade neste tópico no GameDev , incluindo problemas de desempenho.
É comumente usado em 2D. As coordenadas baricêntricas tendem a confundir as pessoas. Além disso, dados os cooridados do triângulo e o ponto cordado, não tenho certeza sobre a eficiência do uso de baricêntricos.
Kornel Kisielewicz
7
@Kornel A versão baricêntrica também é mais eficiente em 2D. Sua solução também tem o problema de informar um resultado diferente para pontos exatamente nas arestas do triângulo, dependendo de o triângulo estar especificado no sentido horário ou anti-horário.
Andreas Brinck
9
Para meus propósitos (a razão pela qual encontrei este site), a resposta original proposta por Kornel Kisielewicz é muito mais eficiente. Estou trabalhando com uma tela LCD com coordenadas de tamanho BYTE e um microprocessador muito típico, onde a multiplicação de números inteiros é uma instrução muito rápida e a divisão é muito, muito, mais lenta. Problemas numéricos também são muito menores, devido a nenhuma divisão! todos os cálculos são exatos. Obrigado, Rick
4
Portanto, a função sign () indica qual lado do semiplano (formado pela linha entre p2 e p3) p1 é?
David Doria
1
Observe que, se você assumir alguma ordem dos vértices (digamos no sentido anti-horário), não precisará calcular todos esses determinantes o tempo todo. De fato, na melhor das hipóteses, 1 determinante é suficiente para descobrir que o ponto não está dentro do triângulo.
Thash 9/09/16
176
Resolva o seguinte sistema de equações:
p = p0 + (p1 - p0) * s + (p2 - p0) * t
O ponto pestá dentro do triângulo se 0 <= s <= 1e 0 <= t <= 1e s + t <= 1.
Isso é mais rápido que a verificação no meio plano, mas talvez seja um pouco mais difícil de entender se você é novo nas coordenadas baricêntricas.
precisa saber é o seguinte
8
Com saídas triviais (não implementadas) no método de Kornel, as dele podem ser muito mais eficientes que as suas. Se você realmente tentar calcular se você saberá o que quero dizer.
85
Eu queria testar esta por isso fiz um jsFiddle, contando com @andreasdr solução e coproc comentário: jsfiddle.net/PerroAZUL/zdaY8/1
urraka
5
Otimização: s + t <= 1implica s <= 1e t <= 1se s >= 0e t >= 0.
Concordo com Andreas Brinck , as coordenadas baricêntricas são muito convenientes para esta tarefa. Observe que não há necessidade de resolver um sistema de equações sempre: basta avaliar a solução analítica. Usando a notação de Andreas , a solução é:
Apenas avalie s, te 1-s-t. O ponto pestá dentro do triângulo se e somente se todos forem positivos.
EDIT: Observe que a expressão acima para a área pressupõe que a numeração dos nós do triângulo esteja no sentido anti-horário. Se a numeração for no sentido horário, essa expressão retornará uma área negativa (mas com a magnitude correta). O teste em si ( s>0 && t>0 && 1-s-t>0) não depende da direção da numeração, no entanto, uma vez que as expressões acima que são multiplicadas por 1/(2*Area)também mudam de sinal se a orientação do nó do triângulo for alterada.
EDIT 2: Para uma eficiência computacional ainda melhor, consulte o comentário do coproc abaixo (que salienta que, se a orientação dos nós do triângulo (sentido horário ou anti-horário) é conhecida previamente, a divisão 2*Areanas expressões para se tpode ser evitado). Veja também o código jsfiddle de Perro Azul nos comentários na resposta de Andreas Brinck .
Sim, o que quero dizer é que qualquer crítica ao seu método com base no custo computacional da solução do sistema de equações é infundada, pois isso não precisa ser feito como parte do algoritmo.
andreasdr
13
A eficiência pode ser melhorada não se dividindo 2*Area, ou seja, calculando s´=2*|Area|*se t´=2*|Area|*t(se a orientação dos pontos - horário ou anti-horário - não for conhecida, é Areaclaro que o sinal de deve ser verificado, mas, caso contrário, talvez nem precisa ser calculado), pois para verificar s>0basta verificar s´>0. E, em vez de verificar 1-s-t>0, basta verificar s´+t´<2*|Area|.
Coproc
1
Posso acrescentar que, se p0->p1->p2for cartesiano no sentido anti-horário (que geralmente é no sentido horário nas coordenadas da tela ), o calculado por esse método será positivo. Area
Rhd
1
@ user2600366 Ao viajar ao longo do limite do triângulo na direção p0 -> p1 -> p2 -> p0 e assim por diante, você terá o interior do triângulo sempre à sua direita ou sempre à sua esquerda. No primeiro caso, a numeração é no sentido horário, no último caso, no sentido anti-horário.
precisa saber é o seguinte
47
Escrevi esse código antes de uma tentativa final com o Google e encontrei esta página, então pensei em compartilhá-lo. É basicamente uma versão otimizada da resposta Kisielewicz. Também examinei o método baricêntrico, mas, a julgar pelo artigo da Wikipedia, tenho dificuldade em ver como ele é mais eficiente (acho que há uma equivalência mais profunda). De qualquer forma, esse algoritmo tem a vantagem de não usar divisão; um problema em potencial é o comportamento da detecção de borda, dependendo da orientação.
Em palavras, a idéia é a seguinte: os pontos s à esquerda ou à direita das linhas AB e AC? Se for verdade, não pode estar lá dentro. Se falso, é pelo menos dentro dos "cones" que satisfazem a condição. Agora, como sabemos que um ponto dentro de um trigon (triângulo) deve estar do mesmo lado de AB que BC (e também CA), verificamos se eles diferem. Se o fizerem, s não pode estar dentro, senão s deve estar dentro.
Algumas palavras-chave nos cálculos são semiplanos de linha e o determinante (produto cruzado 2x2). Talvez uma maneira mais pedagógica seja provavelmente pensar nisso como um ponto dentro de si, se estiver do mesmo lado (esquerda ou direita) de cada uma das linhas AB, BC e CA. A maneira acima parecia melhor para algumas otimizações.
Este teste é cerca de 140-180% mais rápido que o primeiro fornecido (graças a vocês dois): Executei o código aqui: paste.ubuntu.com/p/k5w7ywH4p8 usando o mecanismo nodejs v8 com otimizações desativadas e obtive os seguintes 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 que você o executaria sem otimizações? Isso não é mais afastado da realidade, então?
xuiqzy
@xuiqzy Bem, meu programa contém as duas soluções diferentes. Ainda tenho que administrar o método mais rápido de fazê-lo. Talvez esse comentário deva ser removido e substituído pelos meus trabalhos concluídos sobre isso ..
surgemcgee
33
Versão em C # do método barcentric publicado por andreasdr e Perro Azul. Observe que o cálculo da área pode ser evitado se se ttiver sinais opostos. Eu verifiquei o comportamento correto com um teste de unidade bastante completo.
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);}
[ edit ] aceitou a modificação sugerida pelo @Pierre; Ver comentários
A solução com a instrução if final funciona para os pontos do triângulo no sentido horário e anti-horário.
precisa
@LukeDupin Não sei se entendi o seu comentário. Esta resposta funciona conforme publicado para qualquer pedido fornecido dos 3 pontos.
Glenn Slayden
12
Versão Java do método barricê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;}
O código acima funcionará corretamente com números inteiros, assumindo que não haja estouros. Também funcionará com triângulos no sentido horário e anti-horário. Não funcionará com triângulos colineares (mas você pode verificar isso testando det == 0).
A versão baricêntrica é mais rápida se você for testar pontos diferentes com o mesmo triângulo.
A versão baricêntrica não é simétrica nos 3 pontos do triângulo, portanto é provável que seja menos consistente que a versão de meio plano da aresta de Kornel Kisielewicz, devido a erros de arredondamento de ponto flutuante.
Crédito: Eu criei o código acima do artigo da Wikipedia sobre coordenadas baricêntricas.
Agradável ! Pode até ser aprimorado usar as tuplas Point3f / Point2f do javax.vecmath, para lidar melhor com a entrada de dados.
precisa saber é o seguinte
10
Uma maneira simples é:
encontre os vetores que conectam o ponto a cada um dos três vértices do triângulo e some os ângulos entre esses vetores. Se a soma dos ângulos é 2 * pi, então o ponto está dentro do triângulo.
Hum, esse método não é exatamente eficiente, e é muito propenso a erros numéricos ...
kornel kisielewicz
É exatamente o oposto, é muito ineficiente :-) É apenas uma maneira simples, fácil de implementar. Você pode dar um exemplo de erro numérico que isso causaria?
Simon P Stevens
Enquanto para mim isso parece ser a melhor de todas as respostas neste tópico, acho que os pontos nas bordas do triângulo são calculados para serem incluídos no triângulo e você não tem um controle sólido sobre isso.
Redu
verificar se é exatamente 2pi é numericamente impossível, dado o irracional de pi. No entanto, você só precisa verificar se os ângulos somam algo maior 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>
Isso se compara muito bem à solução Kornel Kisielewicz (25 recalls, 1 armazenamento, 15 subtrações, 6 multiplicações, 5 comparações) e pode ser ainda melhor se for necessária a detecção no sentido horário / anti-horário (que leva 6 recalls, 1 adição e 2 subtrações , 2 multiplicações e 1 comparação em si, usando o determinante da solução analítica, conforme apontado por rhgb ).
Acabei de testar o código como está e ele não funciona para mim (exemplo p -4.69317198, -6.99191951 p0 -7.05846786 0.596718192 p1 -6.8703599 -2.36565161 p2 -4.69317198, -6.99191951)
Giovanni Funchal
@GiovanniFunchal Strange, seu exemplo funciona para mim, tanto no jsfiddle (substitua as definições iniciais de "ponto" e "triângulo") quanto na minha implementação local do Python. Problemas de precisão numérica (tente extrair algumas casas decimais)?
Cédric Dufour
1
Você parece o mais rápido no meu teste: jsfiddle.net/eyal/gxw3632c/27 . A diferença entre todos os métodos é bem pequena, no entanto.
Eyal
Tente triângulo (-1, -1), (1, -1), (0,1) e ponto (0, -1). Retorna false quando deve retornar true porque s (2) + t (2)> d (2). Parece que algo está errado com a matemática nas arestas do triângulo, pois o ponto p está na fronteira entre p0 e p1 e não é uma simples questão de converter um <para um <= ou algo assim.
devnullicus 2/03
5
O que eu faço é pré-calcular as três faces normais,
em 3D por produto cruzado do vetor lateral e o vetor normal da face.
em 2D simplesmente trocando componentes e negando um,
então dentro / fora para qualquer lado é quando um produto pontual do lado normal e o vértice ao vetor pontual mudam de sinal. Repita para os outros dois (ou mais) lados.
Benefícios:
muito é pré-calculado, excelente para testes de múltiplos pontos no mesmo triângulo.
rejeição precoce de casos comuns de mais pontos externos do que internos. (também se a distribuição de pontos ponderada para um lado, pode testar esse lado primeiro.)
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))
Não consegui fazer isso funcionar, por exemplo, para o ponto no triângulo [(0,0), (3,0), (3,4)], nem os pontos (1,1) ou (0). , 0) teste positivo. Eu tentei com pontos triangulares no sentido horário e anti-horário.
ThorSummoner
3
Se você está procurando velocidade, aqui está um procedimento que pode ajudá-lo.
Classifique os vértices do triângulo em suas ordenadas. Isso leva no máximo três comparações. Seja Y0, Y1, Y2 os três valores classificados. Ao desenhar três horizontais através deles, você divide o plano em dois semiplanos e duas lajes. Seja Y a ordenada do ponto 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
Custa mais duas comparações. Como você vê, a rejeição rápida é alcançada para pontos fora da "laje delimitadora".
Opcionalmente, você pode fornecer um teste nas abscissas para rejeição rápida à esquerda e à direita ( X <= X0' or X >= X2'). Isso implementará um teste rápido de caixa delimitadora ao mesmo tempo, mas você também precisará classificar as abscissas.
Eventualmente, você precisará calcular o sinal do ponto especificado em relação aos dois lados do triângulo que delimitam a laje relevante (superior ou inferior). O teste tem a forma:
A discussão completa das i, j, kcombinações (existem seis delas, com base no resultado do tipo) está fora do escopo desta resposta e "deixada como um exercício para o leitor"; para eficiência, eles devem ser codificados.
Se você acha que essa solução é complexa, observe que ela envolve principalmente comparações simples (algumas das quais podem ser pré-computadas), mais 6 subtrações e 4 multiplicações caso o teste da caixa delimitadora falhe. É difícil superar o último custo, pois, no pior dos casos, você não pode evitar comparar o ponto de teste com dois lados (nenhum método em outras respostas tem um custo menor, alguns pioram, como 15 subtrações e 6 multiplicações, às vezes divisões).
ATUALIZAÇÃO: Mais rápido com uma transformação de cisalhamento
Como explicado acima, você pode localizar rapidamente o ponto dentro de uma das quatro bandas horizontais delimitadas pelas três ordenadas de vértices, usando duas comparações.
Opcionalmente, você pode executar um ou dois testes X extras para verificar a falta de espaço na caixa delimitadora (linhas pontilhadas).
Em seguida, considere a transformação "cisalhamento" fornecida por X'= X - m Y, Y' = Y, onde mé a inclinação DX/DYda aresta mais alta. Essa transformação tornará esse lado do triângulo vertical. E como você sabe de que lado da horizontal central você é, basta testar o sinal em relação a um único lado do triângulo.
Supondo que você precomputou a inclinação m, bem como os X'vértices dos triângulos cortados e os coeficientes das equações dos lados X = m Y + p, você precisará, no pior dos casos,
duas comparações ordenadas para classificação vertical;
opcionalmente, uma ou duas comparações de abcissa para rejeição de caixa delimitadora;
computação de X' = X - m Y;
uma ou duas comparações com as abscissas do triângulo cisalhado;
teste de um sinal X >< m' Y + p'contra o lado relevante do triângulo cisalhado.
Se você conhece as coordenadas dos três vértices e as coordenadas do ponto específico, pode obter a área do triângulo completo. Depois, calcule a área dos três segmentos do triângulo (um ponto é o ponto dado e os outros dois são quaisquer dois vértices do triângulo). Assim, você obterá a área dos três segmentos de triângulo. Se a soma dessas áreas for igual à área total (que você obteve anteriormente), o ponto deverá estar dentro do triângulo. Caso contrário, o ponto não está dentro do triângulo. Isso deve funcionar. Se houver algum problema, me avise. Obrigado.
Demora muito para plotar, mas essa grade é testada em 0,0195319652557 segundos contra 0,0844349861145 segundos do código do desenvolvedor .
Finalmente o comentário do 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>
Eu cansei esse código e ele não funciona. Sempre retorna Falso.
xApple
hmmm ... você provavelmente cometeu um erro. Aqui está um violino com essa função em execução: jsfiddle.net/jniac/rctb3gfL
Joseph Merdrignac
Eu vi sua resposta do Python, estamos usando o mesmo método, se eu usar mais uma linha ( let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)), isso é para determinar a ordem do enrolamento do triângulo, para que o método funcione com triângulos CW e CCW (consulte jsFiddle).
Joseph Merdrignac
1
hm eu cometi um erro, eu escrevi: let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)em vez de let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)modo que este é fixo, graças para relatar
Joseph Merdrignac
2
Eu só quero usar uma matemática vetorial simples para explicar a solução de coordenadas barentêntricas que Andreas havia dado, será bem mais fácil de entender.
A área A é definida como qualquer vetor dado por s * v02 + t * v01, com as condições s> = 0 et> = 0. Se algum ponto dentro do triângulo v0, v1, v2, ele deve estar dentro da área A.
Se mais restringir s, e t pertencer a [0, 1]. Obtemos a Área B, que contém todos os vetores de s * v02 + t * v01, com a condição s, t pertence a [0, 1]. Vale ressaltar que a parte baixa da área B é o espelho do triângulo v0, v1, v2. O problema ocorre se podemos dar certas condições de s, e, para excluir ainda mais a parte baixa da Área B.
Suponha que damos um valor s, e t está mudando em [0, 1]. Na foto a seguir, o ponto p está na borda da v1v2. Todos os vetores de s * v02 + t * v01 que estão ao longo da linha do traço por soma vetorial simples. No ponto cruzado v1v2 e na linha de traço p, temos:
obtemos 1 - s = tp, então 1 = s + tp. Se qualquer t> tp, que 1 <s + t está na linha de traço duplo, o vetor está fora do triângulo, qualquer t <= tp, que 1> = s + t está na linha de traço único, o vetor é dentro do triângulo.
Então, se dermos algum s em [0, 1], o t correspondente deve atender 1> = s + t, para o vetor dentro do triângulo.
Então, finalmente, obtemos v = s * v02 + t * v01, v está dentro do triângulo com a condição s, t, s + t pertence a [0, 1]. Em seguida, traduza para o ponto, temos
p - p0 = s * (p1 - p0) + t * (p2 - p0), com s, t, s + t em [0, 1]
que é o mesmo que a solução de Andreas para resolver o sistema de equações p = p0 + s * (p1 - p0) + t * (p2 - p0), com s, t, s + t pertencem a [0, 1].
Você pode apenas dizer que usa o quadro local definido pelos três vértices para que os lados se tornem s = 0, t = 0 es + t = 1. A transformação de coordenadas afins é uma operação bem conhecida da álgebra linear.
Yves Daoust
2
Aqui está uma solução em python que é eficiente, documentada e contém três unittests. É de qualidade profissional e está pronta para ser inserida em seu projeto na forma de um módulo como está.
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)
Há um teste gráfico opcional adicional para o algoritmo acima para confirmar sua validade:
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")
Existem condições de bordas traquinas em que um ponto está exatamente na borda comum de dois triângulos adjacentes. O ponto não pode estar em ambos ou em nenhum dos triângulos. Você precisa de uma maneira arbitrária, mas consistente, de atribuir o ponto. Por exemplo, desenhe uma linha horizontal através do ponto. Se a linha cruzar com o outro lado do triângulo à direita, o ponto será tratado como se estivesse dentro do triângulo. Se a interseção estiver à esquerda, o ponto estará do lado de fora.
Se a linha na qual o ponto se encontra for horizontal, use acima / abaixo.
Se o ponto estiver no vértice comum de vários triângulos, use o triângulo cujo centro o ponto forma o menor ângulo.
Mais divertido: três pontos podem estar em uma linha reta (zero graus), por exemplo (0,0) - (0,10) - (0,5). Em um algoritmo de triangulação, o "ouvido" (0,10) deve ser cortado, sendo o "triângulo" gerado o caso degenerado de uma linha reta.
Este é o conceito mais simples para determinar se um ponto está dentro ou fora do triângulo ou em um braço de um triângulo.
A determinação de um ponto está dentro de um triângulo por determinantes:
O código de trabalho mais simples:
#-*- 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)
A maneira mais fácil e funciona com todos os tipos de triângulos é simplesmente determinar os ângulos dos pontos P, pontos A, B e C. Se qualquer um dos ângulos são maiores do que 180,0 graus, então ele está fora, se 180,0 então é na circunferência e se acos batota em você e menos de 180,0 então é inside.Take um olhar para compreender http: // matemática-física -psychology.blogspot.hu/2015/01/earlish-determination-that-point-is.html
Honestamente, é tão simples quanto a resposta de Simon P Steven no entanto, com essa abordagem, você não tem um controle sólido sobre se deseja ou não incluir os pontos nas bordas do triângulo.
Minha abordagem é um pouco diferente, mas muito básica. Considere o seguinte triângulo;
Para ter o ponto no triângulo, temos que satisfazer 3 condições
O ângulo ACE (verde) deve ser menor que o ângulo ACB (vermelho)
O ângulo do BCE (azul) deve ser menor que o ângulo do ACB (vermelho)
O ponto E e o ponto C devem ter o mesmo sinal quando seus valores x e y são aplicados à equação da | AB | linha.
Neste método, você tem controle total para incluir ou excluir o ponto nas arestas individualmente. Portanto, você pode verificar se um ponto está no triângulo, incluindo apenas o | AC | borda por exemplo.
Portanto, minha solução em JavaScript seria a seguinte;
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));
Não pode ser mais eficiente que isso! Cada lado de um triângulo pode ter posição e orientação independentes, portanto, três cálculos: l1, l2 e l3 são definitivamente necessários, envolvendo 2 multiplicações cada. Uma vez que l1, l2 e l3 são conhecidos, o resultado é apenas algumas comparações básicas e operações booleanas de distância.
pointInTriangle(p, p0, p1, p2) - para triângulos no sentido anti-horário
pointInTriangle(p, p0, p1, p2) - para triângulos no sentido horário
Veja no jsFiddle (teste de desempenho incluído), também há verificação de enrolamento em uma função separada. Ou pressione "Executar snippet de código" abaixo
<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;}
coordenadas cartesianas quase perfeitas convertidas de barricêntricas são exportadas entre * v (x) e * w (y) dobra. As duas duplas de exportação devem ter um * char antes em todos os casos, provavelmente: * ve * w O código também pode ser usado para o outro triângulo de um quadrilátero. Por este meio assinado escreveu apenas triângulo abc do quadrante abcd no sentido horário.
A---B
|..\\.o|
|....\\.|
D---C
o ponto o está dentro do triângulo ABC para testar com o segundo triângulo, chame esta função de direção CDA, e os resultados devem estar corretos após *v=1-*v;e *w=1-*w;para o quadrilátero
Eu precisava apontar a verificação do triângulo no "ambiente controlável" quando você tiver certeza absoluta de que os triângulos serão no sentido horário. Então, peguei o jsfiddle do Perro Azul e o modifiquei conforme sugerido pela coproc para esses casos; também removeu multiplicações redundantes de 0,5 e 2 porque elas apenas se cancelam.
<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>
Respostas:
Em geral, o algoritmo mais simples (e bastante ideal) é verificar em que lado do semiplano criado pelas arestas está o ponto.
Aqui estão algumas informações de alta qualidade neste tópico no GameDev , incluindo problemas de desempenho.
E aqui está um código para você começar:
fonte
Resolva o seguinte sistema de equações:
O ponto
p
está dentro do triângulo se0 <= s <= 1
e0 <= t <= 1
es + t <= 1
.s
,t
e1 - s - t
são chamadas de coordenadas barricêntricas do pontop
.fonte
s + t <= 1
implicas <= 1
et <= 1
ses >= 0
et >= 0
.Concordo com Andreas Brinck , as coordenadas baricêntricas são muito convenientes para esta tarefa. Observe que não há necessidade de resolver um sistema de equações sempre: basta avaliar a solução analítica. Usando a notação de Andreas , a solução é:
onde
Area
é a área (assinada) do triângulo:Apenas avalie
s
,t
e1-s-t
. O pontop
está dentro do triângulo se e somente se todos forem positivos.EDIT: Observe que a expressão acima para a área pressupõe que a numeração dos nós do triângulo esteja no sentido anti-horário. Se a numeração for no sentido horário, essa expressão retornará uma área negativa (mas com a magnitude correta). O teste em si (
s>0 && t>0 && 1-s-t>0
) não depende da direção da numeração, no entanto, uma vez que as expressões acima que são multiplicadas por1/(2*Area)
também mudam de sinal se a orientação do nó do triângulo for alterada.EDIT 2: Para uma eficiência computacional ainda melhor, consulte o comentário do coproc abaixo (que salienta que, se a orientação dos nós do triângulo (sentido horário ou anti-horário) é conhecida previamente, a divisão
2*Area
nas expressões paras
et
pode ser evitado). Veja também o código jsfiddle de Perro Azul nos comentários na resposta de Andreas Brinck .fonte
2*Area
, ou seja, calculandos´=2*|Area|*s
et´=2*|Area|*t
(se a orientação dos pontos - horário ou anti-horário - não for conhecida, éArea
claro que o sinal de deve ser verificado, mas, caso contrário, talvez nem precisa ser calculado), pois para verificars>0
basta verificars´>0
. E, em vez de verificar1-s-t>0
, basta verificars´+t´<2*|Area|
.p0->p1->p2
for cartesiano no sentido anti-horário (que geralmente é no sentido horário nas coordenadas da tela ), o calculado por esse método será positivo.Area
Escrevi esse código antes de uma tentativa final com o Google e encontrei esta página, então pensei em compartilhá-lo. É basicamente uma versão otimizada da resposta Kisielewicz. Também examinei o método baricêntrico, mas, a julgar pelo artigo da Wikipedia, tenho dificuldade em ver como ele é mais eficiente (acho que há uma equivalência mais profunda). De qualquer forma, esse algoritmo tem a vantagem de não usar divisão; um problema em potencial é o comportamento da detecção de borda, dependendo da orientação.
Em palavras, a idéia é a seguinte: os pontos s à esquerda ou à direita das linhas AB e AC? Se for verdade, não pode estar lá dentro. Se falso, é pelo menos dentro dos "cones" que satisfazem a condição. Agora, como sabemos que um ponto dentro de um trigon (triângulo) deve estar do mesmo lado de AB que BC (e também CA), verificamos se eles diferem. Se o fizerem, s não pode estar dentro, senão s deve estar dentro.
Algumas palavras-chave nos cálculos são semiplanos de linha e o determinante (produto cruzado 2x2). Talvez uma maneira mais pedagógica seja provavelmente pensar nisso como um ponto dentro de si, se estiver do mesmo lado (esquerda ou direita) de cada uma das linhas AB, BC e CA. A maneira acima parecia melhor para algumas otimizações.
fonte
Versão em C # do método barcentric publicado por andreasdr e Perro Azul. Observe que o cálculo da área pode ser evitado se
s
et
tiver sinais opostos. Eu verifiquei o comportamento correto com um teste de unidade bastante completo.[ edit ]
aceitou a modificação sugerida pelo @Pierre; Ver comentários
fonte
Versão Java do método barricêntrico:
O código acima funcionará corretamente com números inteiros, assumindo que não haja estouros. Também funcionará com triângulos no sentido horário e anti-horário. Não funcionará com triângulos colineares (mas você pode verificar isso testando det == 0).
A versão baricêntrica é mais rápida se você for testar pontos diferentes com o mesmo triângulo.
A versão baricêntrica não é simétrica nos 3 pontos do triângulo, portanto é provável que seja menos consistente que a versão de meio plano da aresta de Kornel Kisielewicz, devido a erros de arredondamento de ponto flutuante.
Crédito: Eu criei o código acima do artigo da Wikipedia sobre coordenadas baricêntricas.
fonte
Uma maneira simples é:
Dois bons sites que explicam alternativas são:
blackpawn e wolfram
fonte
Usando a solução analítica para as coordenadas barentêntricas (apontadas por Andreas Brinck ) e:
Pode-se minimizar o número de operações "caras":
O código pode ser colado no jsfiddle do Perro Azul ou tente-o clicando em "Executar trecho de código" abaixo
Levando a:
Isso se compara muito bem à solução Kornel Kisielewicz (25 recalls, 1 armazenamento, 15 subtrações, 6 multiplicações, 5 comparações) e pode ser ainda melhor se for necessária a detecção no sentido horário / anti-horário (que leva 6 recalls, 1 adição e 2 subtrações , 2 multiplicações e 1 comparação em si, usando o determinante da solução analítica, conforme apontado por rhgb ).
fonte
O que eu faço é pré-calcular as três faces normais,
em 3D por produto cruzado do vetor lateral e o vetor normal da face.
em 2D simplesmente trocando componentes e negando um,
então dentro / fora para qualquer lado é quando um produto pontual do lado normal e o vértice ao vetor pontual mudam de sinal. Repita para os outros dois (ou mais) lados.
Benefícios:
muito é pré-calculado, excelente para testes de múltiplos pontos no mesmo triângulo.
rejeição precoce de casos comuns de mais pontos externos do que internos. (também se a distribuição de pontos ponderada para um lado, pode testar esse lado primeiro.)
fonte
Aqui está uma implementação eficiente do Python :
e um exemplo de saída:
fonte
Se você está procurando velocidade, aqui está um procedimento que pode ajudá-lo.
Classifique os vértices do triângulo em suas ordenadas. Isso leva no máximo três comparações. Seja Y0, Y1, Y2 os três valores classificados. Ao desenhar três horizontais através deles, você divide o plano em dois semiplanos e duas lajes. Seja Y a ordenada do ponto de consulta.
Custa mais duas comparações. Como você vê, a rejeição rápida é alcançada para pontos fora da "laje delimitadora".
Opcionalmente, você pode fornecer um teste nas abscissas para rejeição rápida à esquerda e à direita (
X <= X0' or X >= X2'
). Isso implementará um teste rápido de caixa delimitadora ao mesmo tempo, mas você também precisará classificar as abscissas.Eventualmente, você precisará calcular o sinal do ponto especificado em relação aos dois lados do triângulo que delimitam a laje relevante (superior ou inferior). O teste tem a forma:
A discussão completa das
i, j, k
combinações (existem seis delas, com base no resultado do tipo) está fora do escopo desta resposta e "deixada como um exercício para o leitor"; para eficiência, eles devem ser codificados.Se você acha que essa solução é complexa, observe que ela envolve principalmente comparações simples (algumas das quais podem ser pré-computadas), mais 6 subtrações e 4 multiplicações caso o teste da caixa delimitadora falhe. É difícil superar o último custo, pois, no pior dos casos, você não pode evitar comparar o ponto de teste com dois lados (nenhum método em outras respostas tem um custo menor, alguns pioram, como 15 subtrações e 6 multiplicações, às vezes divisões).
ATUALIZAÇÃO: Mais rápido com uma transformação de cisalhamento
Como explicado acima, você pode localizar rapidamente o ponto dentro de uma das quatro bandas horizontais delimitadas pelas três ordenadas de vértices, usando duas comparações.
Opcionalmente, você pode executar um ou dois testes X extras para verificar a falta de espaço na caixa delimitadora (linhas pontilhadas).
Em seguida, considere a transformação "cisalhamento" fornecida por
X'= X - m Y, Y' = Y
, ondem
é a inclinaçãoDX/DY
da aresta mais alta. Essa transformação tornará esse lado do triângulo vertical. E como você sabe de que lado da horizontal central você é, basta testar o sinal em relação a um único lado do triângulo.Supondo que você precomputou a inclinação
m
, bem como osX'
vértices dos triângulos cortados e os coeficientes das equações dos ladosX = m Y + p
, você precisará, no pior dos casos,X' = X - m Y
;X >< m' Y + p'
contra o lado relevante do triângulo cisalhado.fonte
Se você conhece as coordenadas dos três vértices e as coordenadas do ponto específico, pode obter a área do triângulo completo. Depois, calcule a área dos três segmentos do triângulo (um ponto é o ponto dado e os outros dois são quaisquer dois vértices do triângulo). Assim, você obterá a área dos três segmentos de triângulo. Se a soma dessas áreas for igual à área total (que você obteve anteriormente), o ponto deverá estar dentro do triângulo. Caso contrário, o ponto não está dentro do triângulo. Isso deve funcionar. Se houver algum problema, me avise. Obrigado.
fonte
Outra função em python , mais rápida que o método Developer (pelo menos para mim) e inspirada na solução Cédric Dufour :
Você pode testá-lo com:
Demora muito para plotar, mas essa grade é testada em 0,0195319652557 segundos contra 0,0844349861145 segundos do código do desenvolvedor .
Finalmente o comentário do código:
fonte
ptInTriang([11,45],[45, 45],[45, 45] ,[44, 45])
e ele irá retornartrue
embora seja falsaComo não há resposta JS, a solução no
sentido horário e anti-horário:
EDIT: houve um erro de digitação para a computação det (em
cy - ay
vez decx - ax
), isso é corrigido.https://jsfiddle.net/jniac/rctb3gfL/
Estou usando aqui o mesmo método descrito acima: um ponto está dentro do ABC, se estiver respectivamente no lado "igual" de cada linha AB, BC, CA.
fonte
let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)
), isso é para determinar a ordem do enrolamento do triângulo, para que o método funcione com triângulos CW e CCW (consulte jsFiddle).let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)
em vez delet det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)
modo que este é fixo, graças para relatarEu só quero usar uma matemática vetorial simples para explicar a solução de coordenadas barentêntricas que Andreas havia dado, será bem mais fácil de entender.
(1-s) | v0v2 | / | v0v2 | = tp | v0v1 | / | v0v1 |
obtemos 1 - s = tp, então 1 = s + tp. Se qualquer t> tp, que 1 <s + t está na linha de traço duplo, o vetor está fora do triângulo, qualquer t <= tp, que 1> = s + t está na linha de traço único, o vetor é dentro do triângulo.
Então, se dermos algum s em [0, 1], o t correspondente deve atender 1> = s + t, para o vetor dentro do triângulo.
Então, finalmente, obtemos v = s * v02 + t * v01, v está dentro do triângulo com a condição s, t, s + t pertence a [0, 1]. Em seguida, traduza para o ponto, temos
p - p0 = s * (p1 - p0) + t * (p2 - p0), com s, t, s + t em [0, 1]
que é o mesmo que a solução de Andreas para resolver o sistema de equações p = p0 + s * (p1 - p0) + t * (p2 - p0), com s, t, s + t pertencem a [0, 1].
fonte
Aqui está uma solução em python que é eficiente, documentada e contém três unittests. É de qualidade profissional e está pronta para ser inserida em seu projeto na forma de um módulo como está.
Há um teste gráfico opcional adicional para o algoritmo acima para confirmar sua validade:
Produzindo o seguinte gráfico:
fonte
Existem condições de bordas traquinas em que um ponto está exatamente na borda comum de dois triângulos adjacentes. O ponto não pode estar em ambos ou em nenhum dos triângulos. Você precisa de uma maneira arbitrária, mas consistente, de atribuir o ponto. Por exemplo, desenhe uma linha horizontal através do ponto. Se a linha cruzar com o outro lado do triângulo à direita, o ponto será tratado como se estivesse dentro do triângulo. Se a interseção estiver à esquerda, o ponto estará do lado de fora.
Se a linha na qual o ponto se encontra for horizontal, use acima / abaixo.
Se o ponto estiver no vértice comum de vários triângulos, use o triângulo cujo centro o ponto forma o menor ângulo.
Mais divertido: três pontos podem estar em uma linha reta (zero graus), por exemplo (0,0) - (0,10) - (0,5). Em um algoritmo de triangulação, o "ouvido" (0,10) deve ser cortado, sendo o "triângulo" gerado o caso degenerado de uma linha reta.
fonte
Este é o conceito mais simples para determinar se um ponto está dentro ou fora do triângulo ou em um braço de um triângulo.
A determinação de um ponto está dentro de um triângulo por determinantes:
O código de trabalho mais simples:
fonte
A maneira mais fácil e funciona com todos os tipos de triângulos é simplesmente determinar os ângulos dos pontos P, pontos A, B e C. Se qualquer um dos ângulos são maiores do que 180,0 graus, então ele está fora, se 180,0 então é na circunferência e se acos batota em você e menos de 180,0 então é inside.Take um olhar para compreender http: // matemática-física -psychology.blogspot.hu/2015/01/earlish-determination-that-point-is.html
fonte
Honestamente, é tão simples quanto a resposta de Simon P Steven no entanto, com essa abordagem, você não tem um controle sólido sobre se deseja ou não incluir os pontos nas bordas do triângulo.
Minha abordagem é um pouco diferente, mas muito básica. Considere o seguinte triângulo;
Para ter o ponto no triângulo, temos que satisfazer 3 condições
Neste método, você tem controle total para incluir ou excluir o ponto nas arestas individualmente. Portanto, você pode verificar se um ponto está no triângulo, incluindo apenas o | AC | borda por exemplo.
Portanto, minha solução em JavaScript seria a seguinte;
fonte
Não pode ser mais eficiente que isso! Cada lado de um triângulo pode ter posição e orientação independentes, portanto, três cálculos: l1, l2 e l3 são definitivamente necessários, envolvendo 2 multiplicações cada. Uma vez que l1, l2 e l3 são conhecidos, o resultado é apenas algumas comparações básicas e operações booleanas de distância.
fonte
Código supostamente de alto desempenho que adaptei em JavaScript (artigo abaixo):
pointInTriangle(p, p0, p1, p2)
- para triângulos no sentido anti-horáriopointInTriangle(p, p0, p1, p2)
- para triângulos no sentido horárioVeja no jsFiddle (teste de desempenho incluído), também há verificação de enrolamento em uma função separada. Ou pressione "Executar snippet de código" abaixo
Inspirado por isso: http://www.phatcode.net/articles.php?id=459
fonte
coordenadas cartesianas quase perfeitas convertidas de barricêntricas são exportadas entre * v (x) e * w (y) dobra. As duas duplas de exportação devem ter um * char antes em todos os casos, provavelmente: * ve * w O código também pode ser usado para o outro triângulo de um quadrilátero. Por este meio assinado escreveu apenas triângulo abc do quadrante abcd no sentido horário.
o ponto o está dentro do triângulo ABC para testar com o segundo triângulo, chame esta função de direção CDA, e os resultados devem estar corretos após
*v=1-*v;
e*w=1-*w;
para o quadriláterofonte
Eu precisava apontar a verificação do triângulo no "ambiente controlável" quando você tiver certeza absoluta de que os triângulos serão no sentido horário. Então, peguei o jsfiddle do Perro Azul e o modifiquei conforme sugerido pela coproc para esses casos; também removeu multiplicações redundantes de 0,5 e 2 porque elas apenas se cancelam.
http://jsfiddle.net/dog_funtom/H7D7g/
Aqui está o código C # equivalente para o Unity:
fonte
Uma das maneiras mais fáceis de verificar se a área formada pelos vértices do triângulo (x1, y1), (x2, y2), (x3, y3) é positiva ou não.
A área pode ser calculada pela fórmula:
ou código python pode ser escrito como:
fonte