Ориентация модели на цель

28

У меня есть два объекта (цель и игрок), оба имеют Положение (Vector3) и Вращение (Quaternion). Я хочу, чтобы цель вращалась и смотрела прямо на игрока. Цель, когда она стреляет, должна стрелять прямо в игрока.

Я видел множество примеров slerping к игроку, но я не хочу инкрементного вращения, ну, я полагаю, это будет нормально, если я могу сделать slerp 100%, и до тех пор, пока он действительно работает.

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

Примеры кода выполняются в классе цели, позиция = позиция цели, аватар = игрок.

РЕДАКТИРОВАТЬ

Сейчас я использую предоставленный им код Майка c #, и он отлично работает!

Марк
источник
4
Если вы делаете slerp 100%, вы не используете slerp. Вы просто устанавливаете вращение на 0*(rotation A) + 1*(rotation B)- другими словами, вы просто устанавливаете вращение на B долгое время. Slerp предназначен только для определения того, как должно выглядеть вращение (0% <x <100%) пути между ними.
двойник
Хорошо, имеет смысл, но цель все еще не поворачивается полностью к игроку ... "длинный путь" с этим кодом.
Марк

Ответы:

20

Есть несколько способов сделать это. Вы можете рассчитать абсолютную ориентацию или поворот относительно вашего аватара, что означает вашу новую ориентацию = avatarOrientation * q. Вот последний:

  1. Рассчитайте ось вращения, взяв перекрестное произведение вектора единицы вперед вашего аватара и вектора единицы от аватара к цели, нового вектора вперед:

    vector newForwardUnit = vector::normalize(target - avatarPosition);
    vector rotAxis = vector::cross(avatarForwardUnit, newForwardUnit);
  2. Рассчитать угол поворота с помощью точечного произведения

    float rotAngle = acos(vector::dot(avatarForwardUnit, newForwardUnit));
  3. Создайте кватернион, используя rotAxis и rotAngle, и умножьте его на текущую ориентацию аватара.

    quaternion q(rotAxis, rotAngle);
    quaternion newRot = avatarRot * q;

Если вам нужна помощь в поиске текущего вектора прямого аватара, введите 1. Просто стреляйте :)

РЕДАКТИРОВАТЬ: Расчет абсолютной ориентации на самом деле немного проще, используйте прямой вектор единичной матрицы вместо прямого вектора аватаров в качестве входных данных для 1) и 2). И не умножайте это в 3), вместо этого используйте это непосредственно как новую ориентацию:newRot = q


Важно отметить: решение имеет 2 аномалии, вызванные характером перекрестного продукта:

  1. Если прямые векторы равны. Решение здесь - просто вернуть кватернион

  2. Если векторы указывают точно в противоположном направлении. Решение здесь состоит в том, чтобы создать кватернион, используя аватары вверх по оси в качестве оси вращения и угол 180.0 градусов.

Вот реализация в C ++, которая заботится об этих крайних случаях. Преобразовать его в C # должно быть легко.

// returns a quaternion that rotates vector a to vector b
quaternion get_rotation(const vector &a, const vector &b, const vector &up)
{   
    ASSERT_VECTOR_NORMALIZED(a);
    ASSERT_VECTOR_NORMALIZED(b);

    float dot = vector::dot(a, b);    
    // test for dot -1
    if(nearly_equal_eps_f(dot, -1.0f, 0.000001f))
    {
        // vector a and b point exactly in the opposite direction, 
        // so it is a 180 degrees turn around the up-axis
        return quaternion(up, gdeg2rad(180.0f));
    }
    // test for dot 1
    else if(nearly_equal_eps_f(dot, 1.0f, 0.000001f))
    {
        // vector a and b point exactly in the same direction
        // so we return the identity quaternion
        return quaternion(0.0f, 0.0f, 0.0f, 1.0f);
    }

    float rotAngle = acos(dot);
    vector rotAxis = vector::cross(a, b);
    rotAxis = vector::normalize(rotAxis);
    return quaternion(rotAxis, rotAngle);
}

РЕДАКТИРОВАТЬ: исправленная версия кода XNA Марка

// the new forward vector, so the avatar faces the target
Vector3 newForward = Vector3.Normalize(Position - GameState.Avatar.Position);
// calc the rotation so the avatar faces the target
Rotation = Helpers.GetRotation(Vector3.Forward, newForward, Vector3.Up);
Cannon.Shoot(Position, Rotation, this);


public static Quaternion GetRotation(Vector3 source, Vector3 dest, Vector3 up)
{
    float dot = Vector3.Dot(source, dest);

    if (Math.Abs(dot - (-1.0f)) < 0.000001f)
    {
        // vector a and b point exactly in the opposite direction, 
        // so it is a 180 degrees turn around the up-axis
        return new Quaternion(up, MathHelper.ToRadians(180.0f));
    }
    if (Math.Abs(dot - (1.0f)) < 0.000001f)
    {
        // vector a and b point exactly in the same direction
        // so we return the identity quaternion
        return Quaternion.Identity;
    }

    float rotAngle = (float)Math.Acos(dot);
    Vector3 rotAxis = Vector3.Cross(source, dest);
    rotAxis = Vector3.Normalize(rotAxis);
    return Quaternion.CreateFromAxisAngle(rotAxis, rotAngle);
}
Майк Земдер
источник
Хорошо, я сделал это, как вы можете видеть по моей правке в вопросе, код предоставлен. Не совсем уверен, в чем проблема. Входы a и b являются прямыми векторами или, по крайней мере, предполагаются.
Марк
@ Марк видит мою исправленную версию кода XNA в моем ответе. Было 2 проблемы: 1) неправильный расчет нового прямого вектора, должен быть нормализован AvatarPosition - TargetPosition 2) rotAxis должен быть нормализован после перекрестного произведения в GetRotation
Maik Semder
@Marc, 2 незначительных изменения: 3) source и dest уже нормализованы, нет необходимости снова их нормализовать в GetRotation 4) не проверяйте абсолютное 1 / -1 в GetRotation, лучше используйте некоторую терпимость, я использовал 0.000001f
Maik Semder
Хм, это все еще не работает. То же самое происходит с моделью, и цель не вращается в направлении аватара (что я заметил в ваших комментариях: вы пытаетесь повернуть аватар в направлении цели ... должно быть наоборот) ... в основном, пытаясь чтобы получить моб лицом к игроку (аватар в камеру от третьего лица). Разве метод GetRotation не должен знать что-либо о текущих поворотах цели и аватара, как, по вашему мнению, newForward создается правильно?
Марк
Если объект масштабируется, это означает, что кватернион не имеет единичной длины, это означает, что rotAxis не нормируется. Вы добавили мое последнее изменение кода с нормализацией rotAxis? Однако, пожалуйста, покажите свой текущий код, а для примера, где он не работает, также опубликуйте значения: newForward rotAngle rotAxisи returned quaternion. Код для моба будет таким же, как только мы заставим его работать с аватаром, будет легко изменить заголовок любого объекта.
Майк Земдер