Como mover um objeto ao longo da circunferência de outro objeto?

11

Estou tão sem matemática que dói, mas para alguns de vocês isso deve ser um pedaço de bolo. Quero mover um objeto em torno de outro ao longo de suas idades ou circunferências em um caminho circular simples. No momento, meu algoritmo de jogo sabe como mover e posicionar um sprite na beira de um obstáculo e agora aguarda o próximo ponto se mover, dependendo de várias condições.

Portanto, o problema matemático aqui é como obter as posições (aX, aY) e (bX, bY) , quando conheço o centro (cX, cY), a posição do objeto (oX, oY) e a distância necessária para mover (d)

insira a descrição da imagem aqui

Lumis
fonte
1
É duma distância linear ou é um arco?
MichaelHouse
É uma distância linear em pixels
Lumis
Você está familiarizado com o que são vetores e operações básicas neles?
Patrick Hughes
@ Patrick Não, acho que vou ter que fazer um curso sobre vetores. Como essa é uma animação quadro a quadro, o código deve ser rápido e otimizado.
precisa

Respostas:

8

( CAVEAT: Estou usando duas aproximações aqui: a primeira leva d como comprimento do arco e a segunda leva como comprimento ortogonal. Ambas as aproximações devem ser boas para valores relativamente pequenos de d, mas não cumprem a pergunta precisa, conforme esclarecido nos comentários.)

A matemática sobre isso, felizmente, é relativamente direta. Primeiro de tudo, podemos encontrar o vetor relativo da nossa posição central para a nossa posição atual:

deltaX = oX-cX;
deltaY = oY-cY;

E uma vez que tenhamos esse vetor relativo, podemos saber o raio do círculo em que estamos trabalhando, encontrando o comprimento dele:

radius = sqrt(deltaX*deltaX+deltaY*deltaY);

Além disso, do nosso vetor relativo, podemos encontrar o ângulo preciso em que a linha de cX a oX está:

curTheta = atan2(deltaX, deltaY);

Agora as coisas ficam um pouco mais complicadas. Antes de tudo, entenda que a circunferência de um círculo - ou seja, o 'comprimento do arco' de um arco com uma medida angular de 2π - é 2πr. Em geral, o comprimento do arco com uma medida angular de θ ao longo de um círculo de raio r é apenas θr. Se usarmos d no seu diagrama como o comprimento do arco, e como conhecemos o raio, podemos encontrar a mudança no teta para nos levar à nova posição apenas dividindo:

deltaTheta = d/radius; // treats d as a distance along the arc

Para o caso em que d precisa ser uma distância linear, as coisas são um pouco mais complicadas, mas felizmente não muito. Lá, d é um lado de um triângulo de isoceles cujos outros dois lados são o raio do círculo (de cX / cY a oX / oY e aX / aY, respectivamente), e dividir esse triângulo de isoceles nos dá dois triângulos retângulos, cada um dos quais tem d / 2 como um lado e raio como hipotenusa; isso significa que o seno da metade do nosso ângulo é (d / 2) / raio e, portanto, o ângulo completo é apenas o dobro disso:

deltaTheta = 2*asin(d/(2*radius)); // treats d as a linear distance

Observe como se você tirasse o asin desta fórmula e cancelasse os 2s, isso seria o mesmo que a última fórmula; é o mesmo que dizer que sin (x) é aproximadamente x para pequenos valores de x, o que é uma aproximação útil para se conhecer.

Agora podemos encontrar o novo ângulo apenas adicionando ou subtraindo:

newTheta = curTheta+deltaTheta; // This will take you to aX, aY. For bX/bY, use curTheta-deltaTheta

Depois de termos o novo ângulo, podemos usar alguns triggers básicos para encontrar nosso vetor relativo atualizado:

newDeltaX = radius*cos(newTheta);
newDeltaY = radius*sin(newTheta);

e da nossa posição central e do nosso vetor relativo, podemos (finalmente) encontrar o ponto alvo:

aX = cX+newDeltaX;
aY = cY+newDeltaY;

Agora, com tudo isso, existem algumas advertências importantes a serem observadas. Por um lado, você notará que essa matemática é principalmente de ponto flutuante e, na verdade, quase precisa ser; tentar usar esse método para atualizar em um loop e arredondar de volta para valores inteiros a cada etapa pode fazer de tudo, desde fazer o seu círculo não fechar (espiralando para dentro ou para fora toda vez que você percorre o loop) até não iniciar no primeiro Lugar, colocar! (Se seu d é muito pequeno, você pode descobrir que as versões arredondadas de aX / aY ou bX / bY estão exatamente onde estava a sua posição inicial oX / oY.) Por outro lado, isso é muito caro, especialmente pelo que está tentando Faz; em geral, se você sabe que seu personagem se moverá em um arco circular, planeje todo o arco com antecedência e nãomarque-o de quadro em quadro como este, pois muitos dos cálculos mais caros aqui podem ser carregados com antecedência para reduzir custos. Outra boa maneira de reduzir os custos, se você realmente deseja atualizar de forma incremental como essa, é não usar trigonometria em primeiro lugar; se d for pequeno e você não precisar que seja exato, mas apenas muito próximo, poderá fazer um 'truque' adicionando um vetor de comprimento d a oX / oY, ortogonal ao vetor em direção ao seu centro (observe que um o vetor ortogonal a (dX, dY) é dado por (-dY, dX)) e depois reduza-o para o comprimento certo. Não vou explicar esse código passo a passo, mas espero que faça sentido, considerando o que você viu até agora. Observe que "reduzimos" o novo vetor delta implicitamente na última etapa,

deltaX = oX-cX; deltaY = oY-cY;
radius = sqrt(deltaX*deltaX+deltaY*deltaY);
orthoX = -deltaY*d/radius;
orthoY = deltaX*d/radius;
newDeltaX = deltaX+orthoX; newDeltaY = deltaY+orthoY;
newLength = sqrt(newDeltaX*newDeltaX+newDeltaY*newDeltaY);
aX = cX+newDeltaX*radius/newLength; aY = cY+newDeltaY*radius/newLength;
Steven Stadnicki
fonte
1
Steven: Acho que vou tentar a aproximação primeiro, pois este é apenas um jogo em que é mais importante parecer natural e interessante do que preciso. Também a velocidade é importante. Obrigado por este tutorial longo e bom!
precisa
Uau, Steven, sua aproximação está funcionando como um sonho! Você pode me dizer como alterar seu código para obter bX, bY. Eu não estou claro ainda em seu conceito ortogonal ...
Lumis
2
Certo! Você realmente quer entender a matemática vetorial em algum momento e, uma vez que desconfio, isso será uma segunda natureza; para obter bX / bY, basta seguir o caminho inverso - em outras palavras, em vez de adicionar o vetor ortogonal (particular), basta subtraí-lo. Nos termos do código acima, seria 'newDeltaX = deltaX-orthoX; newDeltaY = deltaY-ortoY; ', seguido pelo mesmo cálculo de newLength e, em seguida,' bX = cX + newDeltaX raio / newLength; bY = cY + newDeltaY raio / newLength; '.
Steven Stadnicki
Basicamente, esse código apontaria newDeltaX / newDeltaY na direção de bX / bY (em vez de na direção de aX / aY);
Steven Stadnicki
9

Forme um triângulo usando os dois lados que você já possui (um lado é de 'c' a 'o', o outro de 'o' a 'a') e o terceiro lado vai de 'a' a 'c'. Você ainda não sabe onde 'a' está, apenas imagine que há um ponto lá por enquanto. Você precisará da trigonometria para calcular o ângulo do ângulo oposto ao lado 'd'. Você tem o comprimento dos lados c <-> o e c <-> a, porque ambos são o raio do círculo.

Agora que você tem o comprimento dos três lados deste triângulo que ainda não pode ver, é possível determinar o ângulo oposto ao lado 'd' do triângulo. Aqui está a fórmula do SSS (lado a lado) se você precisar: http://www.teacherschoice.com.au/maths_library/trigonometry/solve_trig_sss.htm

Usando a fórmula SSS, você tem o ângulo (que chamaremos de 'j') oposto ao lado 'd'. Então, agora podemos calcular (aX, aY).

// This is the angle from 'c' to 'o'
float angle = Math.atan2(oY - cY, oX - cX)

// Add the angle we calculated earlier.
angle += j;

Vector2 a = new Vector2( radius * Math.cos(angle), radius * Math.sin(angle) );

Verifique se os ângulos calculados estão sempre em radianos.

Se você precisar calcular o raio do círculo, poderá usar a subtração vetorial, subtrair o ponto 'c' do ponto 'o' e obter o comprimento do vetor resultante.

float lengthSquared = ( inVect.x * inVect.x
                      + inVect.y * inVect.y
                      + inVect.z * inVect.z );

float radius = Math.sqrt(lengthSquared);

Algo assim deve fazer, acredito. Como não conheço Java, adivinhei a sintaxe exata.

Aqui está a imagem fornecida pelo usuário Byte56para ilustrar a aparência desse triângulo: triângulo cao

Nic Foster
fonte
1
Eu estava dando uma resposta, mas é isso. Você está convidado a usar a imagem que eu fiz :) i.imgur.com/UUBgM.png
Michaelhouse
@ Byte56: Obrigado, eu não tinha nenhum editor de imagem para ilustrar.
22612 Nic Foster
Observe que o raio também deve ser calculado; deve haver maneiras mais simples de obter j do que o cálculo SSS completo, uma vez que temos um triângulo isoceles).
Steven Stadnicki
Sim, isso parece simples, até para mim! O Android não tem o Vector2, então acho que posso usar os valores separadamente. Intrestingly achei classe Vector2 criado manualmente para Android aqui: code.google.com/p/beginning-android-games-2/source/browse/trunk/...
Lumis
(Ajustamos minha própria resposta para encontrar a distância linear correta - o segundo cálculo do deltaTheta, como 2 * asin (d / (2 * raio)), é como você encontraria j aqui.)
Steven Stadnicki
2

Para fazer obj2 girar em torno de obj1, talvez tente:

float angle = 0; //init angle

//call in an update
obj2.x = (obj1.x -= r*cos(angle));
obj2.y = (obj1.y += r*sin(angle));
angle-=0.5;
Lewis
fonte
Isso não mostra como obter o ângulo em primeiro lugar, e você parece mostrar como orbitar, em vez de encontrar as coordenadas como a pergunta.
MichaelHouse
1
Lewis, obrigado por mostrar como orbitar um objeto em torno de um ponto. Pode vir útil ...
Lumis