você quer isso como uma string ou um enum? (sim, isso importa)
Philipp
Ou, uma vez que será usado nos dois sentidos :) Embora se eu tivesse que escolher, eu pegaria uma corda.
izb
1
Você também está preocupado com o desempenho ou apenas com concisão?
Marcin Seredynski
2
ângulo de var = Math.atan2 (y, x); return <Direction> Math.floor ((Math.round (ângulo / (2 * Math.PI / 8)) + 8 + 2)% 8); Eu uso este aqui
Kikaimaru
Conciso: marcado pela brevidade da expressão ou afirmação: livre de toda elaboração e detalhes supérfluos. Apenas jogando isso lá fora ...
Dialock 14/02
Respostas:
25
A maneira mais simples é provavelmente obter o ângulo do vetor usando atan2(), como sugere o Tetrad nos comentários, e depois escalá-lo e arredondá-lo, por exemplo (pseudocódigo):
// enumerated counterclockwise, starting from east = 0:enum compassDir {
E =0, NE =1,
N =2, NW =3,
W =4, SW =5,
S =6, SE =7};// for string conversion, if you can't just do e.g. dir.toString():conststring[8] headings ={"E","NE","N","NW","W","SW","S","SE"};// actual conversion code:float angle = atan2( vector.y, vector.x );int octant = round(8* angle /(2*PI)+8)%8;
compassDir dir =(compassDir) octant;// typecast to enum: 0 -> E etc.string dirStr = headings[octant];
A octant = round( 8 * angle / (2*PI) + 8 ) % 8linha pode precisar de alguma explicação. Em praticamente todas as línguas que conheço, a atan2()função retorna o ângulo em radianos. Dividir por 2 π converte-o de radianos em frações de um círculo completo e, multiplicando por 8, converte-o em oitavos de um círculo, que arredondamos para o número inteiro mais próximo. Por fim, reduzimos o módulo 8 para cuidar do contorno, de modo que 0 e 8 sejam mapeados corretamente para o leste.
A razão para o + 8que ignorei acima é que em alguns idiomas atan2()pode retornar resultados negativos (por exemplo, de - π a + π em vez de 0 a 2 π ) e o operador módulo ( %) pode ser definido para retornar valores negativos para argumentos negativos (ou seu comportamento para argumentos negativos pode ser indefinido). Adicionando8 (ou seja, uma volta completa) à entrada antes da redução garante que os argumentos sejam sempre positivos, sem afetar o resultado de nenhuma outra maneira.
Se o seu idioma não fornecer uma função conveniente de arredondar para o mais próximo, você poderá usar uma conversão de truncamento de números inteiros e apenas adicionar 0,5 ao argumento, desta forma:
int octant =int(8* angle /(2*PI)+8.5)%8;// int() rounds down
Observe que, em alguns idiomas, a conversão padrão de float para número inteiro arredonda as entradas negativas para cima em direção a zero, e não para baixo, o que é outro motivo para garantir que a entrada seja sempre positiva.
Obviamente, você pode substituir todas as ocorrências 8dessa linha por algum outro número (por exemplo, 4 ou 16, ou mesmo 6 ou 12, se você estiver em um mapa hexadecimal) para dividir o círculo em várias direções. Apenas ajuste a enumeração / matriz de acordo.
@ Sam: Opa, corrigido. Obviamente, atan2(x,y)também funcionaria se alguém listasse os cabeçalhos da bússola no sentido horário a partir do norte.
Ilmari Karonen
2
+1 por sinal, eu realmente acho que essa é a resposta mais direta e rigorosa.
sam Hocevar
1
@TheLima:octant = round(8 * angle / 360 + 8) % 8
Ilmari Karonen 21/09
1
Note que isso pode ser facilmente convertido para um 4-way bússola: quadtant = round(4 * angle / (2*PI) + 4) % 4e usando enum: { E, N, W, S }.
Spoike 13/05
10
Você tem 8 opções (ou 16 ou mais, se desejar uma precisão ainda mais fina).
Use atan2(y,x)para obter o ângulo do seu vetor.
atan2() funciona da seguinte maneira:
Então x = 1, y = 0 resultará em 0 e é descontínuo em x = -1, y = 0, contendo π e -π.
Agora, apenas precisamos mapear a saída atan2()para corresponder à da bússola que temos acima.
Provavelmente o mais simples de implementar é uma verificação incremental dos ângulos. Aqui estão alguns pseudo códigos que podem ser modificados facilmente para aumentar a precisão:
//start direction from the lowest value, in this case it's west with -πenum direction {
west,
south,
east,
north
}
increment =(2PI)/direction.count
angle = atan2(y,x);
testangle =-PI + increment/2
index =0while angle > testangle
index++if(index > direction.count -1)return direction[0]//roll over
testangle += increment
return direction[index]
Agora, para adicionar mais precisão, basta adicionar os valores à enumeração da direção.
O algoritmo funciona verificando valores crescentes ao redor da bússola para ver se nosso ângulo está em algum lugar entre a última verificação e a nova posição. É por isso que começamos com -PI + increment / 2. Queremos compensar nossas verificações para incluir espaço igual em cada direção. Algo assim:
West é dividido em dois por causa dos valores de retorno de atan2()West serem descontínuos.
Uma maneira fácil de "convertê-los em um ângulo" é usar atan2, embora tenha em mente que 0 graus provavelmente seria leste e não norte.
Tétrada
1
Você não precisa das angle >=verificações no código acima; por exemplo, se o ângulo for menor que 45, o norte já terá sido retornado, assim você não precisa verificar se o ângulo> = 45 para a verificação leste. Da mesma forma, você não precisa de nenhum cheque antes de retornar ao oeste - é a única possibilidade restante.
MrKWatkins
4
Eu não chamaria isso de uma maneira concisa de obter a direção. Parece bastante desajeitado e exigirá muitas alterações para adaptar isso a diferentes "resoluções". Para não falar de uma tonelada de ifdeclarações, se você quiser seguir 16 direções ou mais.
bummzack
2
Não é necessário normalizar o vetor: o ângulo permanece o mesmo com as mudanças de magnitude.
Kylotan
Obrigado @bummzack, editei a postagem para torná-la mais concisa e fácil aumentar a precisão apenas adicionando mais valores de enumeração.
MichaelHouse
8
Sempre que estiver lidando com vetores, considere operações fundamentais de vetores em vez de converter em ângulos em algum quadro específico.
Dado um vetor de consulta ve um conjunto de vetores de unidade s, o vetor mais alinhado é o vetor s_ique maximiza dot(v,s_i). Isso se deve ao fato de que o produto escalar dado comprimentos fixos para os parâmetros tem um máximo para vetores com a mesma direção e um mínimo para vetores com direções opostas, mudando suavemente entre eles.
Isso generaliza trivialmente em mais dimensões que duas, é extensível com direções arbitrárias e não sofre problemas específicos de quadros, como gradientes infinitos.
Em termos de implementação, isso se resumiria à associação de um vetor em cada direção cardinal a um identificador (enum, string, o que você precisar) representando essa direção. Em seguida, você percorreria seu conjunto de instruções, encontrando a que apresentasse o produto de ponto mais alto.
map<float2,Direction> candidates;
candidates[float2(1,0)]= E; candidates[float2(0,1)]= N;// etc.for each (float2 dir in candidates){float goodness = dot(dir, v);if(goodness > bestResult){
bestResult = goodness;
bestDir = candidates[dir];}}
Essa implementação também pode ser gravada sem ramificação e vetorizada sem muita dificuldade.
Promit
1
A mapcom float2como a chave? Isso não parece muito sério.
21813 Sam Hocevar
É "pseudo-código" de maneira didática. Se você deseja implementações otimizadas para pânico, o GDSE provavelmente não é o lugar para você usar sua cópia em massa. Quanto ao uso de float2 como chave, um float pode representar exatamente todos os números que usamos aqui, e você pode fazer um comparador perfeitamente bom para eles. As chaves de ponto flutuante são inadequadas apenas se contiverem valores especiais ou se você tentar procurar resultados computados. A iteração sobre uma sequência associativa é boa. Eu poderia ter usado uma pesquisa linear em uma matriz, com certeza, mas seria apenas uma confusão inútil.
Lars Viklund
3
Uma maneira que não foi mencionada aqui é tratar os vetores como números complexos. Eles não exigem trigonometria e podem ser bastante intuitivos para adicionar, multiplicar ou arredondar rotações, especialmente porque você já tem seus cabeçalhos representados como pares de números.
Caso você não esteja familiarizado com elas, as direções são expressas na forma de a + b (i), sendo um componente real eb (i) é o imaginário. Se você imaginar o plano cartesiano com o X sendo real e Y sendo imaginário, 1 seria leste (à direita), eu seria norte.
Aqui está a parte principal: As 8 direções cardinais são representadas exclusivamente com os números 1, -1 ou 0 para seus componentes reais e imaginários. Então, tudo o que você precisa fazer é reduzir as coordenadas X, Y como uma proporção e arredondar as duas para o número inteiro mais próximo para obter a direção.
NW (-1+ i) N (i) NE (1+ i)
W (-1)Origin E (1)
SW (-1- i) S (-i) SE (1- i)
Para a conversão de diagonal para a posição mais próxima, reduza X e Y proporcionalmente para que o valor maior seja exatamente 1 ou -1. Conjunto
Arredondar os dois componentes do que era originalmente (10, -2) fornece 1 + 0 (i) ou 1. Portanto, a direção mais próxima é leste.
O que foi dito acima não exige o uso de uma estrutura numérica complexa, mas pensar nelas como tal torna mais rápido encontrar as oito direções principais. Você pode fazer a matemática vetorial da maneira usual se quiser obter o cabeçalho líquido de dois ou mais vetores. (Como números complexos, você não adiciona, mas multiplica para o resultado)
Isso é incrível, mas comete um erro semelhante ao que cometi em minha própria tentativa. As respostas são próximas, mas não corretas. O ângulo limite entre E e NE é 22,5 graus, mas isso é cortado em 26,6 graus.
izb
Max(x, y)deve ser Max(Abs(x, y))trabalhar para os quadrantes negativos. Eu tentei e obtive o mesmo resultado que o izb - isso muda as direções da bússola em ângulos errados. Eu acho que ele mudaria quando o cabeçalho.y / o cabeçalho.x cruza 0,5 (então o valor arredondado muda de 0 para 1), que é arctan (0,5) = 26,565 °.
Amitp
Uma maneira diferente de usar números complexos aqui é observar que a multiplicação de números complexos envolve uma rotação. Se você construir um número complexo que represente 1/8 de uma rotação em torno de um círculo, toda vez que multiplicar por ele, moverá um octante. Então você poderia perguntar: podemos contar quantas multiplicações foram necessárias para ir do leste até o cabeçalho atual? A resposta para "quantas vezes precisamos multiplicar por isso" é um logaritmo . Se você procurar logaritmos para números complexos ... ele usa atan2. Então isso acaba sendo equivalente à resposta de Ilmari.
Provavelmente porque não há explicação por trás do seu código. Por que essa é a solução e como funciona?
Vaillancourt
você correu?
Ray Tayek 25/01
Não, e considerando o nome da turma, presumi que sim e funcionou. E isso é ótimo. Mas você perguntou por que as pessoas votaram negativamente e eu respondi; Eu nunca impliquei que não funcionava :)
Vaillancourt
-2
E = 0, NE = 1, N = 2, NW = 3, W = 4, SW = 5, S = 6, SE = 7
Por enquanto, esse é apenas um monte de personagens que não fazem muito sentido; por que essa é uma solução que funcionaria para a pergunta, como funciona?
Vaillancourt
Eu escrevo a fórmula como escrevi jn excel e está funcionando perfeitamente.
Isso fornece constantes utilizando campos de bits:
// main direction constants
DIR_E =0x1
DIR_W =0x2
DIR_S =0x4
DIR_N =0x8// mixed direction constants
DIR_NW = DIR_N | DIR_W
DIR_SW = DIR_S | DIR_W
DIR_NE = DIR_N | DIR_E
DIR_SE = DIR_S | DIR_E
// calculating the direction
dir =0x0if(x >0) dir |= DIR_E
if(x <0) dir |= DIR_W
if(y >0) dir |= DIR_S
if(y <0) dir |= DIR_N
return dir
Uma ligeira melhoria de desempenho seria colocar os <-checks no ramo else dos >-checks correspondentes , mas me abstive de fazer isso porque isso prejudica a legibilidade.
Desculpe, mas isso não dará exatamente a resposta que estou procurando. Com esse código, ele só produzirá "N" se o vetor estiver precisamente norte, e NE ou NW se x for qualquer outro valor. O que precisa é a direcção da bússola mais próximo, por exemplo, se o vector é para mais perto do que N NW, então ele irá produzir N.
izb
Isso realmente daria a direção mais próxima? Parece que um vetor de (0,00001,100) lhe daria o nordeste. edit: você me venceu izb.
CiscoIPPhone
você não disse que quer a direção mais próxima.
Philipp
1
Desculpe, eu escondi isso no título. Deveria ter sido mais claro no corpo da pergunta #
23413 izb
1
Que tal usar a norma infinita? Dividir por max (abs (vector.components)) fornece um vetor normalizado com relação a essa norma. Agora você pode escrever uma pequena tabela de check-up com base em if (x > 0.9) dir |= DIR_Etodo o resto. Deve ser melhor que o código original de Phillipp e um pouco mais barato do que usar a norma L2 e atan2. Talvez sim, talvez não.
Respostas:
A maneira mais simples é provavelmente obter o ângulo do vetor usando
atan2()
, como sugere o Tetrad nos comentários, e depois escalá-lo e arredondá-lo, por exemplo (pseudocódigo):A
octant = round( 8 * angle / (2*PI) + 8 ) % 8
linha pode precisar de alguma explicação. Em praticamente todas as línguas que conheço, aatan2()
função retorna o ângulo em radianos. Dividir por 2 π converte-o de radianos em frações de um círculo completo e, multiplicando por 8, converte-o em oitavos de um círculo, que arredondamos para o número inteiro mais próximo. Por fim, reduzimos o módulo 8 para cuidar do contorno, de modo que 0 e 8 sejam mapeados corretamente para o leste.A razão para o
+ 8
que ignorei acima é que em alguns idiomasatan2()
pode retornar resultados negativos (por exemplo, de - π a + π em vez de 0 a 2 π ) e o operador módulo (%
) pode ser definido para retornar valores negativos para argumentos negativos (ou seu comportamento para argumentos negativos pode ser indefinido). Adicionando8
(ou seja, uma volta completa) à entrada antes da redução garante que os argumentos sejam sempre positivos, sem afetar o resultado de nenhuma outra maneira.Se o seu idioma não fornecer uma função conveniente de arredondar para o mais próximo, você poderá usar uma conversão de truncamento de números inteiros e apenas adicionar 0,5 ao argumento, desta forma:
Observe que, em alguns idiomas, a conversão padrão de float para número inteiro arredonda as entradas negativas para cima em direção a zero, e não para baixo, o que é outro motivo para garantir que a entrada seja sempre positiva.
Obviamente, você pode substituir todas as ocorrências
8
dessa linha por algum outro número (por exemplo, 4 ou 16, ou mesmo 6 ou 12, se você estiver em um mapa hexadecimal) para dividir o círculo em várias direções. Apenas ajuste a enumeração / matriz de acordo.fonte
atan2(y,x)
nãoatan2(x,y)
.atan2(x,y)
também funcionaria se alguém listasse os cabeçalhos da bússola no sentido horário a partir do norte.octant = round(8 * angle / 360 + 8) % 8
quadtant = round(4 * angle / (2*PI) + 4) % 4
e usando enum:{ E, N, W, S }
.Você tem 8 opções (ou 16 ou mais, se desejar uma precisão ainda mais fina).
Use
atan2(y,x)
para obter o ângulo do seu vetor.atan2()
funciona da seguinte maneira:Então x = 1, y = 0 resultará em 0 e é descontínuo em x = -1, y = 0, contendo π e -π.
Agora, apenas precisamos mapear a saída
atan2()
para corresponder à da bússola que temos acima.Provavelmente o mais simples de implementar é uma verificação incremental dos ângulos. Aqui estão alguns pseudo códigos que podem ser modificados facilmente para aumentar a precisão:
Agora, para adicionar mais precisão, basta adicionar os valores à enumeração da direção.
O algoritmo funciona verificando valores crescentes ao redor da bússola para ver se nosso ângulo está em algum lugar entre a última verificação e a nova posição. É por isso que começamos com -PI + increment / 2. Queremos compensar nossas verificações para incluir espaço igual em cada direção. Algo assim:
West é dividido em dois por causa dos valores de retorno de
atan2()
West serem descontínuos.fonte
atan2
, embora tenha em mente que 0 graus provavelmente seria leste e não norte.angle >=
verificações no código acima; por exemplo, se o ângulo for menor que 45, o norte já terá sido retornado, assim você não precisa verificar se o ângulo> = 45 para a verificação leste. Da mesma forma, você não precisa de nenhum cheque antes de retornar ao oeste - é a única possibilidade restante.if
declarações, se você quiser seguir 16 direções ou mais.Sempre que estiver lidando com vetores, considere operações fundamentais de vetores em vez de converter em ângulos em algum quadro específico.
Dado um vetor de consulta
v
e um conjunto de vetores de unidades
, o vetor mais alinhado é o vetors_i
que maximizadot(v,s_i)
. Isso se deve ao fato de que o produto escalar dado comprimentos fixos para os parâmetros tem um máximo para vetores com a mesma direção e um mínimo para vetores com direções opostas, mudando suavemente entre eles.Isso generaliza trivialmente em mais dimensões que duas, é extensível com direções arbitrárias e não sofre problemas específicos de quadros, como gradientes infinitos.
Em termos de implementação, isso se resumiria à associação de um vetor em cada direção cardinal a um identificador (enum, string, o que você precisar) representando essa direção. Em seguida, você percorreria seu conjunto de instruções, encontrando a que apresentasse o produto de ponto mais alto.
fonte
map
comfloat2
como a chave? Isso não parece muito sério.Uma maneira que não foi mencionada aqui é tratar os vetores como números complexos. Eles não exigem trigonometria e podem ser bastante intuitivos para adicionar, multiplicar ou arredondar rotações, especialmente porque você já tem seus cabeçalhos representados como pares de números.
Caso você não esteja familiarizado com elas, as direções são expressas na forma de a + b (i), sendo um componente real eb (i) é o imaginário. Se você imaginar o plano cartesiano com o X sendo real e Y sendo imaginário, 1 seria leste (à direita), eu seria norte.
Aqui está a parte principal: As 8 direções cardinais são representadas exclusivamente com os números 1, -1 ou 0 para seus componentes reais e imaginários. Então, tudo o que você precisa fazer é reduzir as coordenadas X, Y como uma proporção e arredondar as duas para o número inteiro mais próximo para obter a direção.
Para a conversão de diagonal para a posição mais próxima, reduza X e Y proporcionalmente para que o valor maior seja exatamente 1 ou -1. Conjunto
Arredondar os dois componentes do que era originalmente (10, -2) fornece 1 + 0 (i) ou 1. Portanto, a direção mais próxima é leste.
O que foi dito acima não exige o uso de uma estrutura numérica complexa, mas pensar nelas como tal torna mais rápido encontrar as oito direções principais. Você pode fazer a matemática vetorial da maneira usual se quiser obter o cabeçalho líquido de dois ou mais vetores. (Como números complexos, você não adiciona, mas multiplica para o resultado)
fonte
Max(x, y)
deve serMax(Abs(x, y))
trabalhar para os quadrantes negativos. Eu tentei e obtive o mesmo resultado que o izb - isso muda as direções da bússola em ângulos errados. Eu acho que ele mudaria quando o cabeçalho.y / o cabeçalho.x cruza 0,5 (então o valor arredondado muda de 0 para 1), que é arctan (0,5) = 26,565 °.isso parece funcionar:
fonte
E = 0, NE = 1, N = 2, NW = 3, W = 4, SW = 5, S = 6, SE = 7
f (x, y) = mod ((4-2 * (1 + sinal (x)) * (1 sinal (y ^ 2)) - (2 + sinal (x)) * sinal (y)
fonte
Quando você quer uma string:
Isso fornece constantes utilizando campos de bits:
Uma ligeira melhoria de desempenho seria colocar os
<
-checks no ramo else dos>
-checks correspondentes , mas me abstive de fazer isso porque isso prejudica a legibilidade.fonte
if (x > 0.9) dir |= DIR_E
todo o resto. Deve ser melhor que o código original de Phillipp e um pouco mais barato do que usar a norma L2 e atan2. Talvez sim, talvez não.