Distância entre ponto e reta

Para encontrar a distância entre um ponto em uma reta, a ideia é a seguinte.

Suponha uma reta definida pelos pontos a e b.

Suponha que o ponto que queremos calcular a distância para essa reta é p.

A estratégia apresentada aqui não é a mais simples, mas tem a vantagem de além de achar a distância, encontrar também o ponto na reta que é mais próximo de p.

A ideia geral é a seguinte:

  • Criamos um vetor de a para b (ab)
  • Criamos um vetor de a para p (ap)
  • Calculamos o produto escalar entre ab e ap. Pense que você colocou uma lanterna alinhada com o vetor ab. Aí vc deslocou essa lanterna por trás do ponto p, de tal forma que projeta uma sombra de ap em ab. Essa sombra formará um ângulo de 90º com ab.
    • Agora calcule o tamanho (magnitude) dessa sombra no eixo ab. E multiplique esse valor pela magnitude de ab. Ou seja, esse é o produto escalar. Ele verifica a contribuição de um vetor no sentido do outro e multiplica suas magnitudes.
    • Mas tem uma fórmula super simples para achar o produto escalar. O problema é que na verdade estamos interessados no tamanho da sombra. Mas não tem problema, vamos achar esse tamanho a partir do produto escalar.
  • Com o produto calculado, basta dividir pela magnitude |ab| para encontrar a magnitude da sombra.
  • Agora vemos o quanto a sombra é maior ou menor que ab.
  • Multiplicamos o vetor ab pela razão da sombra pelo seu tamanho. Ou seja, aumentaremos ou diminuiremos esse vetor de forma que ele chegue exatamente no ponto da sombra. Como a sombra forma um ângulo de 90º, o ponto é o ponto de menor distância.
  • Depois deslocamos o ponto a no sentido desse vetor ab escalado. Esse ponto deslocado, chamaremos de c, será o ponto na reta ab mais próximo de p.
  • Agora é só calcular a distância entre os dois pontos c e p.

Eu sei que parece complicado, mas veja o detalhamento a seguir para entender em detalhes cada etapa.

Veja o esquema da figura a seguir. Iremos encontrar a distância entre o ponto p e a reta r formada pelos pontos a e b. Além disso, iremos também encontrar exatamente qual o ponto c que está presente na reta r que é o mais próximo de p.

Primeiro, construímos os vetores ap e ab. Agora iremos fazer o produto escalar entre esses dois vetores. O produto escalar nos dá o quanto que um vetor está apontando na direção do outro e multiplica suas magnitudes. Isso pode ser aplicado por exemplo para o calculo de forças resultantes em problemas de engenharias e física (veja aqui alguns exemplos).

Como o produto escalar está sempre na direção do vetor, então ele nos dá apenas uma grandeza escalar, ou seja, um número. Na figura abaixo, representamos visualmente representamos esse produto escalar na linha tracejada ( ap * ab ). Perceba que nesse exemplo, o produto escalar teria uma magnitude que o colocaria distante dos vetores ab e ac, afinal, ele será calculado como a multiplicação de suas magnitudes.

Bem, para calcular o produto escalar, se conhecemos o ângulo theta do vértice do ponto "a", é dada por: ||ap|| ||ab|| cos(theta) (veja aqui como essa fórmula faz sentido). Para simplificar aqui, vamos dizer que:

projeção ap em ab = ||ap|| cos(theta)

Logo, o produto escalar será: ||ab|| (projeção ap em ab).

É importante perceber então que o produto escalar é obtido multiplicando o ||ab|| pela projeção.

Porém, uma outra forma mais simples de obter o produto escalar sem precisar do ângulo é utilizar as componentes do vetor (veja aqui como se chega nessa fórmula):

ap * ab = ap.x * ab.x + ap.y * ab.y

Ótimo, agora já sabemos calcular o produto escalar de forma bem fácil. Porém, o que queremos é encontrar o ponto c. Para isso, precisamos encontrar a projeção de ap na reta r que passa por a e b.

Aqui está uma sacada. Se olharmos para a primeira fórmula do produto escalar, veremos que multiplicamos a projeção ap em ab por ||ab||. Ou seja, se já temos o valor do produto escalar, sabemos o valor de ||ab||, então, para achar a projeção, basta dividir o produto escalar por ||ab||:

projeção de ap em ab = (ap * ab) / ||ab||

Pronto, agora já encontramos a projeção.

Para achar o ponto c, uma ideia é criar um vetor com a magnitude da projeção e a direção de ab. Depois, usamos esse vetor para deslocar o ponto a e obter o ponto c.

Para isso, se dividirmos a magnitude da projeção pela magnitude do vetor ab, iremos saber o quão maior ou menor a projeção é em relação a ab:

= ((ap * ab) / || ab|| ) / ||ab||

= ((ap * ab) / || ab||^2 )

com esse valor, basta aplicarmos uma transformação de escala no vetor ab, para fazer com que ele tenha exatamente a magnitude até o ponto c.

Depois, aplicamos uma operação de deslocamento de ponto para deslocar e obter c.

Por fim, para encontrar a distância entre p e c, basta aplicar a distância entre dois pontos.

O código abaixo, depois da figura, contém a implementação dessas ideias.

vec toVec(point a, point b)
{
    return vec(b.x - a.x, b.y -a.y);
}
vec scale(vec v, double factor)
{
    return vec( v.x * factor, v.y * factor );
}
point translate(point p, vec v)
{
    return point(p.x + v.x, p.y + v.y);
}
double dist(point p1, point p2)
{
    return hypot(p1.x - p2.x, p1.y- p2.y);
}
/*
https://sites.google.com/site/ldsicufal/disciplinas/programacao-avancada/distancia-entre-ponto-e-reta
Representa o quanto que de a está apontando na mesma direção de b.
*/
double dot(vec a, vec b)
{
    /*
    Uma outra forma de fazer isso seria: |a||b| cos(theta). São equivalentes.
    A forma simplificada abaixo é só uma derivação da forma acima
    */
    return a.x*b.x + a.y*b.y;
}
/*
Retorna o quadrado da norma de um vetor.
Perceba que usamos a norma ao quadrado, pq no local onde usamos
essa norma, (função distToLine), a gente precisa dividir pela norma
ao quadrado. Como a norma é a raiz quadrada, para evitar calcular a raiz
e depois elevar ao quadrado, evitamos essas duas operações.
A norma representa a magnitude do vetor.
*/
double norm_sq(vec a)
{
    return a.x * a.x + a.y * a.y;
}
/*
Retorna a distância do ponto p para as retas formadas pelos pontos a e b.
Além disso, também retorna qual o ponto na reta mais próximo de p.
*/
double distToLine(point p, point a, point b, point &c)
{
    /* Cria 2 vetores:
    ap e ab
    */
    vec ap = toVec(a,p);
    vec ab = toVec(a,b);
   
    double u = dot(ap, ab) / norm_sq(ab);
    /*
    Agora, com a proporção, basta deslocar o ponto da origem : 'a'
    com um vetor do mesmo sentido de ab e com magnitude calculada
    pela proporção
    */
    c = translate(a, scale(ab, u) );
    /* Achamos o ponto, agora é só calcular a distância */
    return dist (p, c);
}