Как переместить объект по окружности другого объекта?

11

Я так не в математике, что это больно, но для некоторых из вас это должно быть просто пирогом. Я хочу переместить объект вокруг другого по его возрасту или окружности по простому круговому пути. На данный момент мой игровой алгоритм знает, как перемещать и размещать спрайт только на краю препятствия, и теперь он ожидает, когда следующая точка будет двигаться, в зависимости от различных условий.

Таким образом, математическая проблема здесь заключается в том, как получить (aX, aY) и (bX, bY) позиции, когда я знаю центр (cX, cY), положение объекта (oX, oY) и расстояние, необходимое для перемещения (d)

введите описание изображения здесь

Lumis
источник
1
Это dлинейное расстояние или дуга?
MichaelHouse
Это линейное расстояние в пикселях
Lumis
Вы вообще знакомы с векторами и основными операциями над ними?
Патрик Хьюз
@ Патрик Нет, я думаю, мне придется пройти курс по Векторам. Поскольку это покадровая анимация, код должен быть быстрым и оптимизированным.
Лумис

Ответы:

8

( ПРЕДУПРЕЖДЕНИЕ: я использую здесь два приближения: первое принимает d в качестве длины дуги, а второе принимает ее в качестве ортогональной длины. Оба этих приближения должны быть хороши для относительно небольших значений d, но они не удовлетворяют точный вопрос, уточненный в комментариях.)

Математика в этом, к счастью, относительно проста. Прежде всего, мы можем найти относительный вектор от нашей центральной позиции к нашей текущей позиции:

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

И когда у нас есть этот относительный вектор, мы можем узнать радиус круга, над которым мы работаем, найдя его длину:

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

Более того, из нашего относительного вектора мы можем найти точный угол, под которым находится линия от cX до oX:

curTheta = atan2(deltaX, deltaY);

Теперь все становится немного сложнее. Прежде всего, следует понимать, что окружность круга, то есть «длина дуги» дуги с угловой мерой 2π, составляет 2πr. В общем, длина дуги дуги с угловой мерой θ вдоль окружности радиуса r равна просто θr. Если бы мы использовали d на вашей диаграмме в качестве длины дуги, и, поскольку мы знаем радиус, мы можем найти изменение в тэте, чтобы получить нас в новой позиции, просто разделив:

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

Для случая, когда d должно быть линейным расстоянием, все немного сложнее, но, к счастью, немного. Здесь d - это одна сторона равнобедренного треугольника, две другие стороны которого являются радиусом окружности (от cX / cY до oX / oY и aX / aY соответственно), и деление этого равнобедренного треугольника дает нам два прямоугольных треугольника, каждый из которых имеет d / 2 в качестве одной стороны и радиус в качестве гипотенузы; это означает, что синус половины нашего угла равен (d / 2) / радиусу, и поэтому полный угол всего в два раза больше этого:

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

Обратите внимание, что если вы удалите asin из этой формулы и отмените 2, это будет то же самое, что и последняя формула; это то же самое, что сказать, что sin (x) приблизительно равен x для малых значений x, что полезно знать.

Теперь мы можем найти новый угол, просто сложив или вычтя:

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

Как только у нас будет новый угол, мы можем использовать базовый триггер, чтобы найти наш обновленный относительный вектор:

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

и из нашей центральной позиции и нашего относительного вектора мы можем (наконец) найти целевую точку:

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

Теперь, со всем этим, есть некоторые большие предостережения, о которых нужно знать. Во-первых, вы заметите, что эта математика в основном с плавающей запятой, и на самом деле она почти обязана; Попытка использовать этот метод для обновления в цикле и округления до целочисленных значений на каждом шаге может сделать все, начиная от того, что ваш круг не замыкается (либо вращается внутрь, либо наружу каждый раз, когда вы проходите цикл), до того, как он не запустился в первом цикле. место! (Если ваш d слишком маленький, то вы можете обнаружить, что округленные версии aX / aY или bX / bY находятся именно там, где была ваша начальная позиция oX / oY.) С другой стороны, это очень дорого, особенно из-за того, что он пытается делать; в общем, если вы знаете, что ваш персонаж будет двигаться по дуге окружности, вы должны планировать всю дугу заранее, а неОтметьте это от кадра к кадру, как это, так как многие из самых дорогих вычислений здесь могут быть загружены заранее, чтобы сократить расходы. Еще один хороший способ сократить расходы, если вы действительно хотите производить поэтапное обновление, как это, - это вообще не использовать триг; если d мало, и вам не нужно, чтобы он был точным, а просто очень близким, то вы можете сделать «трюк», добавив вектор длины d к oX / oY, ортогональный к вектору к вашему центру (обратите внимание, что вектор, ортогональный (dX, dY), определяется как (-dY, dX)), а затем сжимается до нужной длины. Я не буду объяснять этот код шаг за шагом, но, надеюсь, он будет иметь смысл, учитывая то, что вы видели до сих пор. Обратите внимание, что на последнем шаге мы неявно сокращаем новый дельта-вектор,

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;
Стивен Стадницки
источник
1
Стивен Я думаю, что сначала попробую приближение, так как это просто игра, в которой важнее чувствовать себя естественным и интересным, чем точным. Также имеет значение скорость. Спасибо за этот длинный и хороший урок!
Лумис
Ух ты, Стивен, твое приближение работает как сон! Можете ли вы сказать мне, как изменить свой код, чтобы получить BX, BY. Мне пока неясно, в чем заключается ваша ортогональная концепция ...
Lumis
2
Конечно! Вы действительно захотите понять векторную математику в какой-то момент, и как только вы это сделаете, я подозреваю, что это будет вторая натура; чтобы получить bX / bY, вам просто нужно пойти «другим путем» - другими словами, вместо добавления (определенного) ортогонального вектора, просто вычтите его. С точки зрения приведенного выше кода это будет 'newDeltaX = deltaX-orthoX; newDeltaY = deltaY-orthoY; ', за которым следует то же вычисление newLength, а затем' bX = cX + newDeltaX radius / newLength; bY = cY + newDeltaY radius / newLength; '.
Стивен Стадницки
По сути, этот код будет указывать newDeltaX / newDeltaY в направлении bX / bY (а не в направлении aX / aY), а затем обрезаться, чтобы соответствовать и добавляться к центру, как если бы вы получали aX / aY.
Стивен Стадницки
9

Сформируйте треугольник, используя две стороны, которые у вас уже есть (одна сторона от 'c' до 'o', другая от 'o' до 'a'), а третья сторона переходит от 'a' к 'c'. Вы еще не знаете, где находится «а», просто представьте, что сейчас есть смысл. Вам понадобится тригонометрия для расчета угла, противоположного стороне 'd'. У вас есть длина сторон c <-> o и c <-> a, потому что они оба - радиус круга.

Теперь, когда у вас есть длина трех сторон этого треугольника, которую вы еще не видите, вы можете определить угол, противоположный стороне 'd' треугольника. Вот формула SSS (боковая сторона), если вам нужно: http://www.teacherschoice.com.au/maths_library/trigonometry/solve_trig_sss.htm

Используя формулу SSS, вы получите угол (который мы назовем «j»), противоположный стороне «d». Итак, теперь мы можем вычислить (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) );

Убедитесь, что рассчитываемые углы всегда в радианах.

Если вам нужно вычислить радиус круга, вы можете использовать векторное вычитание, вычесть точку 'c' из точки 'o', а затем получить длину полученного вектора.

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

float radius = Math.sqrt(lengthSquared);

Я полагаю, что-то подобное должно сделать. Я не знаю Java, поэтому я угадал точный синтаксис.

Вот изображение, данное пользователем, Byte56чтобы проиллюстрировать, как может выглядеть этот треугольник: Цао треугольник

Ник Фостер
источник
1
Я делал ответ, но это все. Вы можете использовать изображение, которое я сделал :) i.imgur.com/UUBgM.png
MichaelHouse
@ Byte56: Спасибо, у меня не было ручки для редактирования изображений.
Ник Фостер
Обратите внимание, что радиус тоже должен быть вычислен; Должны быть более простые способы получения j, чем полный расчет SSS, поскольку у нас есть равнобедренный треугольник.)
Steven Stadnicki
Да, это кажется простым даже для меня! Android не имеет Vector2, поэтому я думаю, что я могу просто использовать значения отдельно. Интересно, что я нашел класс Vector2, созданный вручную для Android, здесь: code.google.com/p/beginning-android-games-2/source/browse/trunk/…
Lumis
(Я подправил свой собственный ответ, чтобы найти правильное линейное расстояние - второе вычисление deltaTheta там, как 2 * asin (d / (2 * radius)), это то, как вы найдете j здесь.)
Стивен Стадницки
3

Чтобы заставить obj2 вращаться вокруг obj1, попробуйте:

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;
Льюис
источник
Это не показывает, как получить угол в первую очередь, и вы, похоже, показываете, как двигаться по орбите, а не находите координаты, как задает вопрос.
MichaelHouse
1
Льюис, спасибо, что показал, как вращать объект вокруг точки. Это может пригодиться ...
Lumis