Como você pode determinar que um ponto está entre dois outros pontos em um segmento de linha?

93

Digamos que você tenha um plano bidimensional com 2 pontos (chamados de aeb) representados por um inteiro x e um inteiro y para cada ponto.

Como você pode determinar se outro ponto c está no segmento de linha definido por a e b?

Eu uso python mais, mas exemplos em qualquer linguagem seriam úteis.

Paul D. Eden
fonte
4
Vejo MUITAS coisas length = sqrt (x) acontecendo nessas respostas; eles podem funcionar, mas não são rápidos. Considere o uso de comprimento ao quadrado; se você está apenas comparando valores de comprimento ao quadrado entre si, não há perda de precisão e você salva chamadas lentas para sqrt ().
ojrac
1
O ponto c também é representado por 2 inteiros? Se sim, você quer saber se c está exatamente ao longo de uma linha reta real entre a e b ou se encontra na aproximação raster da linha reta entre a e b? Este é um esclarecimento importante.
RobS
Uma pergunta semelhante foi feita aqui: stackoverflow.com/q/31346862/1914034 com uma solução quando uma distância de buffer da linha é necessária
Abaixo do radar
1
Aviso aos futuros leitores: Um bom número de respostas está incorreto ou incompleto. Alguns casos extremos que frequentemente não funcionam são as linhas horizontais e verticais.
Stefnotch

Respostas:

127

Verifique se o produto vetorial de (ba) e (ca) é 0, como diz Darius Bacon, informa se os pontos a, bec estão alinhados.

Mas, como você quer saber se c está entre aeb, você também deve verificar se o produto escalar de (ba) e (ca) é positivo e é menor que o quadrado da distância entre a e b.

Em pseudocódigo não otimizado:

def isBetween(a, b, c):
    crossproduct = (c.y - a.y) * (b.x - a.x) - (c.x - a.x) * (b.y - a.y)

    # compare versus epsilon for floating point values, or != 0 if using integers
    if abs(crossproduct) > epsilon:
        return False

    dotproduct = (c.x - a.x) * (b.x - a.x) + (c.y - a.y)*(b.y - a.y)
    if dotproduct < 0:
        return False

    squaredlengthba = (b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y)
    if dotproduct > squaredlengthba:
        return False

    return True
Cyrille Ka
fonte
5
-epsilon < crossproduct < epsilon and min(a.x, b.x) <= c.x <= max(a.x, b.x) and min(a.y, b.y) <= c.y <= max(a.y, b.y)é suficiente, não é?
jfs
9
O valor absoluto do produto cruzado é o dobro da área do triângulo formado pelos três pontos (com o sinal indicando o lado do terceiro ponto), então IMHO você deve usar um épsilon que seja proporcional à distância entre os dois pontos finais.
Bart
2
Você pode nos dizer por que não funcionaria com inteiros? Não vejo o problema, desde que a verificação de epsilon seja substituída por "! = 0".
Cyrille Ka
2
Sim, o parêntese extra é apenas um erro de digitação. 4 anos se passaram antes que alguém dissesse algo. :)
Cyrille Ka
4
Você deve renomear a, b, c para deixar mais claro quais são os pontos finais do segmento e qual é o ponto de consulta.
Craig Gidney
48

É assim que eu faria:

def distance(a,b):
    return sqrt((a.x - b.x)**2 + (a.y - b.y)**2)

def is_between(a,c,b):
    return distance(a,c) + distance(c,b) == distance(a,b)
Jules
fonte
7
Esta é uma solução elegante.
Paul D. Eden
6
O único problema com isso é a estabilidade numérica - tirar diferenças de números e assim por diante pode perder a precisão.
Jonathan Leffler
26
-epsilon < (distance(a, c) + distance(c, b) - distance(a, b)) < epsilon
jfs
1
@jfs o que você quer dizer com isso? Para que serve o cheque com o épsilon?
Neon Warge
3
@NeonWarge: sqrt () retorna um float. ==é uma coisa errada para flutuadores na maioria dos casos . math.isclose()poderia ser usado em seu lugar. Não houve math.isclose()em 2008 e, portanto, forneci a desigualdade explícita com epsilon( abs_tolna math.isclose()fala).
jfs
35

Verifique se o produto vetorial de b-ae c-aé 0: isso significa que todos os pontos são colineares. Se estiverem, verifique se cas coordenadas de 's estão entre a' se b's. Use as coordenadas x ou y, desde que ae bsejam separados nesse eixo (ou sejam iguais em ambos).

def is_on(a, b, c):
    "Return true iff point c intersects the line segment from a to b."
    # (or the degenerate case that all 3 points are coincident)
    return (collinear(a, b, c)
            and (within(a.x, c.x, b.x) if a.x != b.x else 
                 within(a.y, c.y, b.y)))

def collinear(a, b, c):
    "Return true iff a, b, and c all lie on the same line."
    return (b.x - a.x) * (c.y - a.y) == (c.x - a.x) * (b.y - a.y)

def within(p, q, r):
    "Return true iff q is between p and r (inclusive)."
    return p <= q <= r or r <= q <= p

Essa resposta costumava ser uma confusão de três atualizações. As informações valiosas deles: o capítulo de Brian Hayes em Beautiful Code cobre o espaço de design para uma função de teste de colinearidade - um histórico útil. A resposta de Vincent ajudou a melhorar este. E foi Hayes quem sugeriu testar apenas uma das coordenadas x ou y; originalmente o código tinha andno lugar de if a.x != b.x else.

Darius Bacon
fonte
Uma vez que a verificação do intervalo é mais rápida, seria melhor verificar primeiro o intervalo e depois verificar se há colinear se estiver na caixa delimitadora.
Grant M
1
A função is_on (a, b, c) é errada para o caso em que a == b! = C. Nesse caso, ele retornará verdadeiro, embora c não intercepte um segmento de linha de a para b.
Mikko Virkkilä de
@SuperFlux, acabei de tentar executar e obtive False.
Darius Bacon,
2
Acho que essa resposta é claramente superior à resposta aceita atualmente.
Rick apoia Monica de
1
collinear (a, b, c) está testando números de ponto flutuante por igualdade. Não deveria usar um epsilon / tolerância?
jwezorek
7

Aqui está outra abordagem:

  • Vamos supor que os dois pontos sejam A (x1, y1) e B (x2, y2)
  • A equação da linha que passa por esses pontos é (x-x1) / (y-y1) = (x2-x1) / (y2-y1) .. (apenas equacionando as inclinações)

O ponto C (x3, y3) ficará entre A e B se:

  • x3, y3 satisfaz a equação acima.
  • x3 fica entre x1 e x2 e y3 fica entre y1 e y2 (verificação trivial)
Sridhar Iyer
fonte
Isso não leva em conta os erros de arredondamento (inexatidão das coordenadas).
Bart
Essa é a ideia certa, eu acho, mas com poucos detalhes (como verificamos essa equação na prática?) E um pouco complicada: o último y3 deve ser y2.
Darius Bacon
@Darius: corrigiu aquele erro de digitação
Harley Holcombe
7

O comprimento do segmento não é importante, portanto, o uso de uma raiz quadrada não é obrigatório e deve ser evitado, pois podemos perder alguma precisão.

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class Segment:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def is_between(self, c):
        # Check if slope of a to c is the same as a to b ;
        # that is, when moving from a.x to c.x, c.y must be proportionally
        # increased than it takes to get from a.x to b.x .

        # Then, c.x must be between a.x and b.x, and c.y must be between a.y and b.y.
        # => c is after a and before b, or the opposite
        # that is, the absolute value of cmp(a, b) + cmp(b, c) is either 0 ( 1 + -1 )
        #    or 1 ( c == a or c == b)

        a, b = self.a, self.b             

        return ((b.x - a.x) * (c.y - a.y) == (c.x - a.x) * (b.y - a.y) and 
                abs(cmp(a.x, c.x) + cmp(b.x, c.x)) <= 1 and
                abs(cmp(a.y, c.y) + cmp(b.y, c.y)) <= 1)

Algum exemplo aleatório de uso:

a = Point(0,0)
b = Point(50,100)
c = Point(25,50)
d = Point(0,8)

print Segment(a,b).is_between(c)
print Segment(a,b).is_between(d)
vincente
fonte
1
Se cx ou cy são flutuador, em seguida, o primeiro ==em is_between()poderia falhar (btw é uma CrossProduct disfarçado).
jfs
adicionar a is_between():a, b = self.a, self.b
jfs
Parece que retornará verdadeiro se todos os três pontos forem iguais (o que está certo, imho), mas falso se exatamente dois dos pontos forem iguais - uma maneira bastante inconsistente de definir a diferença. Eu postei uma alternativa em minha resposta.
Darius Bacon
consertou isso por outro truque cmp, mas este código começa a cheirar mal ;-)
vincent
5

Aqui está uma maneira diferente de fazer isso, com código fornecido em C ++. Dados dois pontos, l1 e l2, é trivial expressar o segmento de linha entre eles como

l1 + A(l2 - l1)

onde 0 <= A <= 1. Isso é conhecido como a representação vetorial de uma linha se você estiver mais interessado além de apenas usá-la para este problema. Podemos dividir os componentes xey disso, dando:

x = l1.x + A(l2.x - l1.x)
y = l1.y + A(l2.y - l1.y)

Pegue um ponto (x, y) e substitua seus componentes xey nessas duas expressões para resolver A. O ponto está na linha se as soluções para A em ambas as expressões forem iguais e 0 <= A <= 1. Porque resolver para A requer divisão, há casos especiais que precisam de tratamento para interromper a divisão por zero quando o segmento de linha é horizontal ou vertical. A solução final é a seguinte:

// Vec2 is a simple x/y struct - it could very well be named Point for this use

bool isBetween(double a, double b, double c) {
    // return if c is between a and b
    double larger = (a >= b) ? a : b;
    double smaller = (a != larger) ? a : b;

    return c <= larger && c >= smaller;
}

bool pointOnLine(Vec2<double> p, Vec2<double> l1, Vec2<double> l2) {
    if(l2.x - l1.x == 0) return isBetween(l1.y, l2.y, p.y); // vertical line
    if(l2.y - l1.y == 0) return isBetween(l1.x, l2.x, p.x); // horizontal line

    double Ax = (p.x - l1.x) / (l2.x - l1.x);
    double Ay = (p.y - l1.y) / (l2.y - l1.y);

    // We want Ax == Ay, so check if the difference is very small (floating
    // point comparison is fun!)

    return fabs(Ax - Ay) < 0.000001 && Ax >= 0.0 && Ax <= 1.0;
}
Matthew Henry
fonte
4

Usando uma abordagem mais geométrica, calcule as seguintes distâncias:

ab = sqrt((a.x-b.x)**2 + (a.y-b.y)**2)
ac = sqrt((a.x-c.x)**2 + (a.y-c.y)**2)
bc = sqrt((b.x-c.x)**2 + (b.y-c.y)**2)

e testar se ac + bc é igual a ab :

is_on_segment = abs(ac + bc - ab) < EPSILON

Isso porque existem três possibilidades:

  • Os 3 pontos formam um triângulo => ac + bc> ab
  • Eles são colineares e c está fora do segmento ab => ac + bc> ab
  • Eles são colineares e c está dentro do segmento ab => ac + bc = ab
efotinis
fonte
Como Jonathan Leffler menciona em outro comentário, isso tem problemas numéricos que outras abordagens, como o produto cruzado, evitam. O capítulo para o qual estou vinculado em minha resposta explica.
Darius Bacon
3

Ok, muitas menções de álgebra linear (produto vetorial de vetores) e isso funciona em um espaço real (ou seja, contínuo ou ponto flutuante), mas a questão afirmava especificamente que os dois pontos foram expressos como inteiros e, portanto, um produto vetorial não é o correto embora possa fornecer uma solução aproximada.

A solução correta é usar o Algoritmo de Linha de Bresenham entre os dois pontos e ver se o terceiro ponto é um dos pontos da linha. Se os pontos estiverem suficientemente distantes para que o cálculo do algoritmo não tenha um desempenho (e teria que ser muito grande para que fosse o caso), tenho certeza de que você poderia pesquisar e encontrar otimizações.

cletus
fonte
Ele resolve como desenhar uma linha através de um espaço inteiro bidimensional entre dois pontos arbitrários e está matematicamente correto. Se o terceiro ponto for um dos pontos dessa linha, ele está, por definição, entre esses dois pontos.
cletus
1
Não, o Algoritmo de Linha de Bresenham resolve como criar uma aproximação de um segmento de linha em um espaço inteiro bidimensional. Não vejo na mensagem do autor da postagem original que fosse uma pergunta sobre rasterização.
Cyrille Ka
"Digamos que você tenha um plano bidimensional com 2 pontos (chamados de aeb) representados por um x INTEGER e ay INTEGER para cada ponto." (ênfase adicionada por mim).
cletus
1
Acho que o algoritmo de linha de Bresenham dá pontos inteiros próximos a uma linha, que podem então ser usados ​​para traçar a linha. Eles podem não estar na linha. Por exemplo, se para (0,0) a (11,13), o algoritmo fornecerá um número de pixels para desenhar, mas não há pontos inteiros exceto os pontos finais, porque 11 e 13 são coprimos.
Grant M de
Como uma solução correta para o espaço real (ℝ × ℝ) pode não ser correta para o espaço inteiro (ℕ × ℕ), como as. Ou você quer dizer: "não é ideal para ..." em vez de 'não é correto?
Ideograma de
2

O produto escalar entre (ca) e (ba) deve ser igual ao produto de seus comprimentos (isso significa que os vetores (ca) e (ba) estão alinhados e com a mesma direção). Além disso, o comprimento de (ca) deve ser menor ou igual ao de (ba). Pseudo-código:

# epsilon = small constant

def isBetween(a, b, c):
    lengthca2  = (c.x - a.x)*(c.x - a.x) + (c.y - a.y)*(c.y - a.y)
    lengthba2  = (b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y)
    if lengthca2 > lengthba2: return False
    dotproduct = (c.x - a.x)*(b.x - a.x) + (c.y - a.y)*(b.y - a.y)
    if dotproduct < 0.0: return False
    if abs(dotproduct*dotproduct - lengthca2*lengthba2) > epsilon: return False 
    return True
Federico A. Ramponi
fonte
A última condição não deveria ser mais parecida com: ABS (produto - lengthca * lengthba) <epsilon?
Jonathan Leffler
Em vez disso, você não deveria comparar comprimentos quadrados? As raízes quadradas devem ser evitadas. Além disso, se isso for inevitável devido ao estouro, você pode usar math.hypot em vez de math.sqrt (com a mudança apropriada de argumentos).
Darius Bacon
Também me pergunto sobre aquele épsilon. Você pode explicar isso? Claro, se devemos lidar com flutuadores, devemos ter cuidado com as comparações, mas não está claro para mim por que um ípsilon torna esta comparação particular mais precisa.
Darius Bacon
Eu concordo. Existem várias respostas boas para essa pergunta, e esta é boa. Mas esse código precisa ser alterado para não usar sqrt e a última comparação corrigida.
Cyrille Ka
@ Jonathan: de fato, o código é mais familiar e elegante usando abs. Obrigado.
Federico A. Ramponi
2

Eu precisava disso para javascript para uso em uma tela html5 para detectar se o cursor do usuário estava sobre ou perto de uma determinada linha. Então eu modifiquei a resposta dada por Darius Bacon em coffeescript:

is_on = (a,b,c) ->
    # "Return true if point c intersects the line segment from a to b."
    # (or the degenerate case that all 3 points are coincident)
    return (collinear(a,b,c) and withincheck(a,b,c))

withincheck = (a,b,c) ->
    if a[0] != b[0]
        within(a[0],c[0],b[0]) 
    else 
        within(a[1],c[1],b[1])

collinear = (a,b,c) ->
    # "Return true if a, b, and c all lie on the same line."
    ((b[0]-a[0])*(c[1]-a[1]) < (c[0]-a[0])*(b[1]-a[1]) + 1000) and ((b[0]-a[0])*(c[1]-a[1]) > (c[0]-a[0])*(b[1]-a[1]) - 1000)

within = (p,q,r) ->
    # "Return true if q is between p and r (inclusive)."
    p <= q <= r or r <= q <= p
bfcoder
fonte
2

Você pode usar o produto cunha e escalar:

def dot(v,w): return v.x*w.x + v.y*w.y
def wedge(v,w): return v.x*w.y - v.y*w.x

def is_between(a,b,c):
   v = a - b
   w = b - c
   return wedge(v,w) == 0 and dot(v,w) > 0
Jules
fonte
1

Veja como eu fiz na escola. Esqueci porque não é uma boa ideia.

EDITAR:

@Darius Bacon: cita um livro "Beautiful Code" que contém uma explicação de porque o código abaixo não é uma boa ideia.

#!/usr/bin/env python
from __future__ import division

epsilon = 1e-6

class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y

class LineSegment:
    """
    >>> ls = LineSegment(Point(0,0), Point(2,4))
    >>> Point(1, 2) in ls
    True
    >>> Point(.5, 1) in ls
    True
    >>> Point(.5, 1.1) in ls
    False
    >>> Point(-1, -2) in ls
    False
    >>> Point(.1, 0.20000001) in ls
    True
    >>> Point(.1, 0.2001) in ls
    False
    >>> ls = LineSegment(Point(1, 1), Point(3, 5))
    >>> Point(2, 3) in ls
    True
    >>> Point(1.5, 2) in ls
    True
    >>> Point(0, -1) in ls
    False
    >>> ls = LineSegment(Point(1, 2), Point(1, 10))
    >>> Point(1, 6) in ls
    True
    >>> Point(1, 1) in ls
    False
    >>> Point(2, 6) in ls 
    False
    >>> ls = LineSegment(Point(-1, 10), Point(5, 10))
    >>> Point(3, 10) in ls
    True
    >>> Point(6, 10) in ls
    False
    >>> Point(5, 10) in ls
    True
    >>> Point(3, 11) in ls
    False
    """
    def __init__(self, a, b):
        if a.x > b.x:
            a, b = b, a
        (self.x0, self.y0, self.x1, self.y1) = (a.x, a.y, b.x, b.y)
        self.slope = (self.y1 - self.y0) / (self.x1 - self.x0) if self.x1 != self.x0 else None

    def __contains__(self, c):
        return (self.x0 <= c.x <= self.x1 and
                min(self.y0, self.y1) <= c.y <= max(self.y0, self.y1) and
                (not self.slope or -epsilon < (c.y - self.y(c.x)) < epsilon))

    def y(self, x):        
        return self.slope * (x - self.x0) + self.y0

if __name__ == '__main__':
    import  doctest
    doctest.testmod()
jfs
fonte
1

Qualquer ponto no segmento de linha ( a , b ) (onde a e b são vetores) pode ser expresso como uma combinação linear dos dois vetores a e b :

Em outras palavras, se c estiver no segmento de linha ( a , b ):

c = ma + (1 - m)b, where 0 <= m <= 1

Resolvendo para m , obtemos:

m = (c.x - b.x)/(a.x - b.x) = (c.y - b.y)/(a.y - b.y)

Então, nosso teste se torna (em Python):

def is_on(a, b, c):
    """Is c on the line segment ab?"""

    def _is_zero( val ):
        return -epsilon < val < epsilon

    x1 = a.x - b.x
    x2 = c.x - b.x
    y1 = a.y - b.y
    y2 = c.y - b.y

    if _is_zero(x1) and _is_zero(y1):
        # a and b are the same point:
        # so check that c is the same as a and b
        return _is_zero(x2) and _is_zero(y2)

    if _is_zero(x1):
        # a and b are on same vertical line
        m2 = y2 * 1.0 / y1
        return _is_zero(x2) and 0 <= m2 <= 1
    elif _is_zero(y1):
        # a and b are on same horizontal line
        m1 = x2 * 1.0 / x1
        return _is_zero(y2) and 0 <= m1 <= 1
    else:
        m1 = x2 * 1.0 / x1
        if m1 < 0 or m1 > 1:
            return False
        m2 = y2 * 1.0 / y1
        return _is_zero(m2 - m1)
Shankster
fonte
1

c # De http://www.faqs.org/faqs/graphics/algorithms-faq/ -> Assunto 1.02: Como faço para encontrar a distância de um ponto a uma linha?

Boolean Contains(PointF from, PointF to, PointF pt, double epsilon)
        {

            double segmentLengthSqr = (to.X - from.X) * (to.X - from.X) + (to.Y - from.Y) * (to.Y - from.Y);
            double r = ((pt.X - from.X) * (to.X - from.X) + (pt.Y - from.Y) * (to.Y - from.Y)) / segmentLengthSqr;
            if(r<0 || r>1) return false;
            double sl = ((from.Y - pt.Y) * (to.X - from.X) - (from.X - pt.X) * (to.Y - from.Y)) / System.Math.Sqrt(segmentLengthSqr);
            return -epsilon <= sl && sl <= epsilon;
        }
edid
fonte
A maneira correta de evitar problemas de precisão na maioria das outras abordagens. Também é significativamente mais eficiente que a maioria das outras abordagens.
Robin Davies,
1

Aqui está um código Java que funcionou para mim:

boolean liesOnSegment(Coordinate a, Coordinate b, Coordinate  c) {

    double dotProduct = (c.x - a.x) * (c.x - b.x) + (c.y - a.y) * (c.y - b.y);
    if (dotProduct < 0) return true;
    return false;
}
mihahh
fonte
1
O dotProduct só pode falar sobre alinhamento. Seu código está incompleto !!! Com a (0,0), b (4,0), c (1,1) você tem produto escalar = (1-0) * (1-4) + (1-0) * (1-0) = - 3 + 1 = -3
user43968
0

que tal garantir que a inclinação seja a mesma e o ponto entre as outras?

pontos dados (x1, y1) e (x2, y2) (com x2> x1) e ponto candidato (a, b)

if (b-y1) / (a-x1) = (y2-y2) / (x2-x1) E x1 <a <x2

Então (a, b) deve estar na linha entre (x1, y1) e (x2, y2)

Charles Bretana
fonte
Que tal problemas malucos de precisão de ponto flutuante quando algumas das coordenadas são próximas ou idênticas?
Robin Davies,
Os computadores não funcionam bem com pontos flutuantes. Em um computador, não existem valores continuamente ajustáveis. Portanto, se você estiver usando pontos flutuantes, deverá definir, definir e usar algum valor pequeno épsilon como um determinante, e quaisquer dois pontos mais próximos do que épsilon devem ser considerados o mesmo ponto. Determine o ponto que está na mesma linha e à mesma distância dos pontos finais. Se seu ponto candidato está dentro de seu épsilon daquele ponto calculado, chame-o de idêntico.
Charles Bretana,
Meu ponto é que essa resposta é inutilizável devido a problemas de precisão quando você realmente a implementa no código. Portanto, ninguém deve usá-lo. Uma resposta adorável em um teste de matemática. Mas um fracasso competitivo em um curso de comp-sci. Vim aqui procurando o método de produto escalar (que é correto); por isso, pensei em reservar alguns minutos para sinalizar as muitas respostas incorretas neste tópico para que outras pessoas que estão familiarizadas com a solução correta não fiquem tentadas a usá-las.
Robin Davies
Você está correto sobre os problemas que surgem devido à incapacidade dos computadores de representar todos os números reais possíveis em uma linha. Você está incorreto ao afirmar que qualquer solução (incluindo o método de produto escalar) pode ser imune a esses problemas. Qualquer solução pode sofrer com esses problemas. A menos que você faça algumas concessões para épsilon aceitável, um ponto que está exatamente na linha (mas cujas coordenadas não são representáveis ​​na representação binária de ponto flutuante ieee), também falhará no teste de produto escalar, uma vez que o computador representará as coordenadas do ponto de maneira imprecisa por alguma quantia.
Charles Bretana
0

Uma resposta em C # usando uma classe Vector2D

public static bool IsOnSegment(this Segment2D @this, Point2D c, double tolerance)
{
     var distanceSquared = tolerance*tolerance;
     // Start of segment to test point vector
     var v = new Vector2D( @this.P0, c ).To3D();
     // Segment vector
     var s = new Vector2D( @this.P0, @this.P1 ).To3D();
     // Dot product of s
     var ss = s*s;
     // k is the scalar we multiply s by to get the projection of c onto s
     // where we assume s is an infinte line
     var k = v*s/ss;
     // Convert our tolerance to the units of the scalar quanity k
     var kd = tolerance / Math.Sqrt( ss );
     // Check that the projection is within the bounds
     if (k <= -kd || k >= (1+kd))
     {
        return false;
     }
     // Find the projection point
     var p = k*s;
     // Find the vector between test point and it's projection
     var vp = (v - p);
     // Check the distance is within tolerance.
     return vp * vp < distanceSquared;
}

Observe que

s * s

é o produto escalar do vetor de segmento via sobrecarga de operador em C #

A chave é aproveitar a projeção do ponto na reta infinita e observar que a quantidade escalar da projeção nos diz trivialmente se a projeção está no segmento ou não. Podemos ajustar os limites da quantidade escalar para usar uma tolerância difusa.

Se a projeção estiver dentro dos limites, apenas testamos se a distância do ponto até a projeção está dentro dos limites.

O benefício sobre a abordagem de produto cruzado é que a tolerância tem um valor significativo.

bradgonesurfing
fonte
0

Aqui está minha solução com C # no Unity.

private bool _isPointOnLine( Vector2 ptLineStart, Vector2 ptLineEnd, Vector2 ptPoint )
{
    bool bRes = false;
    if((Mathf.Approximately(ptPoint.x, ptLineStart.x) || Mathf.Approximately(ptPoint.x, ptLineEnd.x)))
    {
        if(ptPoint.y > ptLineStart.y && ptPoint.y < ptLineEnd.y)
        {
            bRes = true;
        }
    }
    else if((Mathf.Approximately(ptPoint.y, ptLineStart.y) || Mathf.Approximately(ptPoint.y, ptLineEnd.y)))
    {
        if(ptPoint.x > ptLineStart.x && ptPoint.x < ptLineEnd.x)
        {
            bRes = true;
        }
    }
    return bRes;
}
caleidos
fonte
Parece que este código funcionaria apenas com segmentos de linha vertical e horizontal. E se ptLineStart for (0,0), ptLineEnd for (2,2) e ptPoint for (1, 1)?
vac
0

Versão C # da resposta de Jules:

public static double CalcDistanceBetween2Points(double x1, double y1, double x2, double y2)
{
    return Math.Sqrt(Math.Pow (x1-x2, 2) + Math.Pow (y1-y2, 2));
}

public static bool PointLinesOnLine (double x, double y, double x1, double y1, double x2, double y2, double allowedDistanceDifference)
{
    double dist1 = CalcDistanceBetween2Points(x, y, x1, y1);
    double dist2 = CalcDistanceBetween2Points(x, y, x2, y2);
    double dist3 = CalcDistanceBetween2Points(x1, y1, x2, y2);
    return Math.Abs(dist3 - (dist1 + dist2)) <= allowedDistanceDifference;
}
Tom Škoda
fonte
0

Você pode fazer isso resolvendo a equação da linha para aquele segmento de linha com as coordenadas do ponto, você saberá se aquele ponto está na linha e, em seguida, verificar os limites do segmento para saber se está dentro ou fora dele. Você pode aplicar algum limite porque bem em algum lugar no espaço provavelmente definido por um valor de ponto flutuante e você não deve atingir o valor exato. Exemplo em php

function getLineDefinition($p1=array(0,0), $p2=array(0,0)){
    
    $k = ($p1[1]-$p2[1])/($p1[0]-$p2[0]);
    $q = $p1[1]-$k*$p1[0];
    
    return array($k, $q);
    
}

function isPointOnLineSegment($line=array(array(0,0),array(0,0)), $pt=array(0,0)){
    
    // GET THE LINE DEFINITION y = k.x + q AS array(k, q) 
    $def = getLineDefinition($line[0], $line[1]);
    
    // use the line definition to find y for the x of your point
    $y = $def[0]*$pt[0]+$def[1];

    $yMin = min($line[0][1], $line[1][1]);
    $yMax = max($line[0][1], $line[1][1]);

    // exclude y values that are outside this segments bounds
    if($y>$yMax || $y<$yMin) return false;
    
    // calculate the difference of your points y value from the reference value calculated from lines definition 
    // in ideal cases this would equal 0 but we are dealing with floating point values so we need some threshold value not to lose results
    // this is up to you to fine tune
    $diff = abs($pt[1]-$y);
    
    $thr = 0.000001;
    
    return $diff<=$thr;
    
}
Sagan
fonte