¿Cómo mover un objeto a lo largo de una circunferencia de otro objeto?

11

Estoy tan sin matemáticas que duele, pero para algunos de ustedes esto debería ser pan comido. Quiero mover un objeto alrededor de otro a lo largo de sus edades o circunferencia en un camino circular simple. Por el momento, mi algoritmo de juego sabe cómo mover y colocar un sprite justo al borde de un obstáculo y ahora espera a que se mueva el siguiente punto, dependiendo de varias condiciones.

Entonces, el problema matemático aquí es cómo obtener las posiciones (aX, aY) y (bX, bY) , cuando conozco el Centro (cX, cY), la posición del objeto (oX, oY) y la distancia requerida para mover (d)

ingrese la descripción de la imagen aquí

Lumis
fuente
1
¿Es duna distancia lineal o es un arco?
MichaelHouse
Es una distancia lineal en píxeles
Lumis
¿Está familiarizado con los vectores y las operaciones básicas en ellos?
Patrick Hughes
@Patrick No, supongo que tendré que hacer un curso sobre Vectores. Como se trata de una animación cuadro por cuadro, el código debe ser rápido y optimizado.
Lumis

Respuestas:

8

( CAVEAT: estoy usando dos aproximaciones aquí: la primera toma d como una longitud de arco, y la segunda lo toma como una longitud ortogonal. Ambas aproximaciones deberían ser buenas para valores relativamente pequeños de d, pero no cumplen la pregunta precisa como se aclara en los comentarios).

La matemática sobre esto, afortunadamente, es relativamente sencilla. En primer lugar, podemos encontrar el vector relativo desde nuestra posición central hasta nuestra posición actual:

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

Y una vez que tengamos este vector relativo, entonces podemos saber el radio del círculo en el que estamos trabajando al encontrar su longitud:

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

Además, desde nuestro vector relativo podemos encontrar el ángulo preciso en el que se encuentra la línea de cX a oX:

curTheta = atan2(deltaX, deltaY);

Ahora las cosas se ponen un poco más complicadas. En primer lugar, comprenda que la circunferencia de un círculo, es decir, la 'longitud del arco' de un arco con una medida angular de 2π, es 2πr. En general, la longitud del arco de un arco con una medida angular de θ a lo largo de un círculo de radio r es solo θr. Si tuviéramos que usar la d en su diagrama como la longitud del arco, y dado que conocemos el radio, podemos encontrar el cambio en theta para llevarnos a la nueva posición simplemente dividiendo:

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

Para el caso donde d necesita ser una distancia lineal, las cosas son un poco más complicadas, pero afortunadamente no mucho. Allí, d es un lado de un triángulo isoceles cuyos otros dos lados son el radio del círculo (de cX / cY a oX / oY y aX / aY respectivamente), y la división de este triángulo isoceles nos da dos triángulos rectángulos, cada uno de los cuales tiene d / 2 como un lado y radio como la hipotenusa; Esto significa que el seno de la mitad de nuestro ángulo es (d / 2) / radio, por lo que el ángulo completo es solo el doble de esto:

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

Observe que si elimina el asin de esta fórmula y cancela los 2, esto sería lo mismo que la última fórmula; Esto es lo mismo que decir que sin (x) es aproximadamente x para valores pequeños de x, lo cual es una aproximación útil para saber.

Ahora podemos encontrar el nuevo ángulo simplemente sumando o restando:

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

Una vez que tengamos el nuevo ángulo, podemos usar algunos trigonometría básica para encontrar nuestro vector relativo actualizado:

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

y desde nuestra posición central y nuestro vector relativo podemos (finalmente) encontrar el punto objetivo:

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

Ahora, con todo esto, hay algunas advertencias importantes a tener en cuenta. Por un lado, notará que esta matemática es principalmente de punto flotante, y de hecho casi tiene que serlo; intentar usar este método para actualizar en un bucle y redondear de nuevo a valores enteros en cada paso puede hacer todo, desde hacer que su círculo no se cierre (ya sea en espiral hacia adentro o hacia afuera cada vez que se da la vuelta al bucle) hasta no comenzarlo en el primer ¡sitio! (Si su d es demasiado pequeña, entonces puede descubrir que las versiones redondeadas de aX / aY o bX / bY están exactamente donde estaba su posición inicial oX / oY). Por otro lado, esto es muy costoso, especialmente por lo que está tratando de hacer; en general, si sabes que tu personaje se va a mover en un arco circular, debes planear todo el arco por adelantado y nomarque de fotograma a fotograma de esta manera, ya que muchos de los cálculos más caros aquí se pueden cargar por adelantado para reducir los costos. Otra buena manera de recortar los costos, si realmente desea actualizar de forma incremental de esta manera, es no utilizar trigonométricos en primer lugar; si d es pequeño y no necesita que sea exacto pero muy cercano, entonces puede hacer un 'truco' agregando un vector de longitud d a oX / oY, ortogonal al vector hacia su centro (tenga en cuenta que un el vector ortogonal a (dX, dY) viene dado por (-dY, dX)), y luego lo reduce a la longitud correcta. No explicaré este código tan paso a paso, pero espero que tenga sentido dado lo que has visto hasta ahora. Tenga en cuenta que 'reducimos' el nuevo vector delta implícitamente en el último paso,

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
fuente
1
Steven: Creo que primero voy a probar la aproximación, ya que este es solo un juego donde es más importante sentirse natural e interesante que preciso. También la velocidad importa. ¡Gracias por este largo y buen tutorial!
Lumis
¡Wow, Steven, tu aproximación funciona como un sueño! ¿Me puede decir cómo cambiar su código para obtener bX, bY? Todavía no tengo claro tu concepto ortogonal ...
Lumis
2
¡Por supuesto! Realmente querrás entender las matemáticas vectoriales en algún momento, y una vez que lo hagas sospecho que esto será una segunda naturaleza; para obtener bX / bY solo tienes que ir 'al revés'; en otras palabras, en lugar de sumar el vector ortogonal (particular), solo resta. En términos del código anterior, esto sería 'newDeltaX = deltaX-orthoX; newDeltaY = deltaY-orthoY; ', seguido del mismo cálculo de newLength, y luego' bX = cX + newDeltaX radius / newLength; bY = cY + newDeltaY radio / newLength; '.
Steven Stadnicki
Básicamente, ese código apuntaría newDeltaX / newDeltaY en la dirección de bX / bY (en lugar de en la dirección de aX / aY), luego se recortará para ajustarse y se agregará al centro tal como lo haría para obtener aX / aY.
Steven Stadnicki
9

Forma un triángulo usando los dos lados que ya tienes (un lado es de 'c' a 'o', el otro de 'o' a 'a'), y el tercer lado va de 'a' a 'c'. Todavía no sabes dónde está 'a', imagina que hay un punto allí por ahora. Necesitará trigonometría para calcular el ángulo del ángulo opuesto al lado 'd'. Tienes la longitud de los lados c <-> o y c <-> a, porque ambos son el radio del círculo.

Ahora que tiene la longitud de los tres lados de este triángulo que aún no puede ver, puede determinar el ángulo opuesto al lado 'd' del triángulo. Aquí está la fórmula SSS (lado a lado) si lo necesita: http://www.teacherschoice.com.au/maths_library/trigonometry/solve_trig_sss.htm

Usando la fórmula SSS tiene el ángulo (que llamaremos 'j') que es opuesto al lado 'd'. Entonces, ahora 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) );

Asegúrate de que los ángulos que estás calculando estén siempre en radianes.

Si necesita calcular el radio del círculo, puede usar la resta del vector, restar el punto 'c' del punto 'o', luego obtener la longitud del vector resultante.

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

float radius = Math.sqrt(lengthSquared);

Algo como esto debería hacer, creo. No conozco Java, así que adiviné la sintaxis exacta.

Aquí está la imagen dada por el usuario Byte56para ilustrar cómo se vería este triángulo: triángulo cao

Nic Foster
fuente
1
Estaba respondiendo, pero esto es todo. Puedes usar la imagen que hice :) i.imgur.com/UUBgM.png
MichaelHouse
@ Byte56: Gracias, no tenía ningún identificador de editor de imágenes para ilustrar.
Nic Foster
Tenga en cuenta que el radio también debe calcularse; debería haber formas más directas de obtener j que el cálculo completo de SSS, ya que tenemos un triángulo isoceles.)
Steven Stadnicki
Sí, eso parece simple, ¡incluso para mí! Android no tiene Vector2, así que supongo que solo puedo usar los valores por separado. Interesantemente encontré la clase Vector2 creada manualmente para Android aquí: code.google.com/p/beginning-android-games-2/source/browse/trunk/…
Lumis
(He ajustado mi propia respuesta para encontrar la distancia lineal correcta: el segundo cálculo de deltaTheta allí, como 2 * asin (d / (2 * radio)), es cómo encontrarías j aquí.)
Steven Stadnicki
2

Para hacer que obj2 gire alrededor de obj1, quizás intente:

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;
Luis
fuente
En primer lugar, esto no muestra cómo obtener el ángulo, y parece que estás mostrando cómo orbitar, en lugar de encontrar las coordenadas como hace la pregunta.
MichaelHouse
1
Lewis, gracias por mostrar cómo orbitar un objeto alrededor de un punto. Puede ser útil ...
Lumis