Войти
ПрограммированиеФорумОбщее

Вращение кватернионы, переход через полюс

#0
19:50, 14 окт. 2009

Делаю камеру на кватернионах. Нужно сделать как в ИЛ-2 в режиме F2 (вид сбоку, вращение вокруг объекта с переворачиванием через голову).  Все хорошо работает до момента когда я перехожу через полюса (северный или южный). Начинает колбасить камеру (локальная ось вверх всегда направлена к полюсу), т.е. камера всегда ориентируется вверх (ось вверх фиксированная). Кватернион ориентации строю из 3х ортогональных векторов:

направление = -нормированный вектор (положение цели - положение камеры)
вправо = векторное произведение направление на ОСЬ вверх (ОУ)
реальный вверх = векторное произведение вправо на направление

Решение проблемы: в момент перехода через полюс меняем вектор вверх на противоположный

код, что-то типа:

Vector3 up = равен Vector(0, 1, 0) или Vector(0, -1, 0) - хранится где-нибудь не в этой функции

if (camOffset != Vector3::ZERO)
{
  Vector3 zAdjustVec = -camOffset;
  zAdjustVec.normalise();
  // Вверх UNIT_Y

  if (camOffset.x == 0 || camOffset.z == 0) // будет более точная функция сравнения float`ов
  {
    // мы на полюсе, верхний y > 0, нижний - y < 0
    в момент перехода через полюс мы меняем ориентацию вектора вверх
    up = -up;
  }

  Vector3 xVec = up.crossProduct(zAdjustVec);
  xVec.normalise();

  Vector3 yVec = zAdjustVec.crossProduct(xVec);
  yVec.normalise();

  m3dal::AxesToQuaternion(y_vec, x_vec, z_vec, quat);
  mCamera->setOrientation(q);
}

это первое, что пришло мне в голову. Ваши мнения господа )))


#1
23:46, 14 окт. 2009

Похоже никто не решал эту проблему ))) а точнее лень написать может )) ладно будем продолжать монолог

предложенный выше вариант имеет проблему определения полюса (еще не запрогал его, но теоретически) если нам не хватит fps и мы перелетим точку полюса (без срабатывания условия), то будет косяк, ось вверх не проинвертируется + надо в условие проверки добавить проверку на то, что ось вверх в полюсе инвертируется первый раз иначе будет расколбас


Родился у меня еще один вариант как данный режим можно замутить. кватернион + вектор смещения камеры от цели...
ось вверх всегда (0, 1, 0) - зафиксировали
далее крутим кватеринон как в обычной ФПС камере (yaw, pitch)
ориентируем вектор смещения на кватернион
положение камеры = цель + повернутый вектор смещения

да кстати, когда будем крутить yaw у кватерниона, надо крутить не вдоль реальной оси вверх, а вдоль фиксированной, тангаж, вдоль вектора вправо - тогда никаких перекосов по роллу не будет

#2
12:16, 15 окт. 2009

Подсказка - крути вектор вверх на тот же тангаж что и смещение.

А если точнее - ось тангажа (T) получаешь кросом от смещения и вектора вверх, крутишь вверх вокруг (T) и (0, 1, 0), крутишь смещение также вокруг (T) и (0, 1, 0);

псевдокод

Vector3 vecPitchAxis = vecOffset.Cross(vecUp).Normalize();

vecUp = vecUp * Quat( vecPitchAxis, fPitch );
vecUp = vecUp * Quat( Vector3(0, 1, 0), fYaw );

vecOffset = vecOffset * Quat( vecPitchAxis, fPitch );
vecOffset = vecOffset * Quat( Vector3(0, 1, 0), fYaw );

занавес

ЗЫ. и сразу забудешь про что такое полюс.

#3
15:52, 16 окт. 2009

Shadero спасибо попробую, о результатах отпишусь

#4
17:45, 16 окт. 2009

m2codeGEN
> Кватернион ориентации строю из 3х ортогональных векторов:
> m3dal::AxesToQuaternion(y_vec, x_vec, z_vec, quat);

Эт. Можно код где ты кватернион по трём векторам строишь?

#5
21:11, 16 окт. 2009
  /// Преобразование из матрицы вращения в кватернион.
  /// @note
  /// Алгоритм Ken Shoemake's article in 1987 SIGGRAPH course notes
  /// статья "Quaternion Calculus and Fast Animation".
  template<typename T> nvSDK::nv::quaternion<T> MatrixToQuaternion(const nvSDK::nv::matrix4<T> &mat)
  {
    nvSDK::nv::quaternion<T> out_q(0, 0, 0, 1);
    T fTrace = mat(0,0) + mat(1,1) + mat(2,2), fRoot = 0;

    if (fTrace > 0)
    {
      // |w| > 1/2
      fRoot = sqrt(fTrace + T(1.0));
      out_q.w = T(0.5) * fRoot;
      fRoot = T(0.5) / fRoot;
      out_q.x = (mat(2,1) - mat(1,2)) * fRoot;
      out_q.y = (mat(0,2) - mat(2,0)) * fRoot;
      out_q.z = (mat(1,0) - mat(0,1)) * fRoot;
    }
    else
    {
      // |w| <= 1/2
      int iNext[3] = {1, 2, 0};
      int i = 0;
      if (mat(1,1) > mat(0,0))
        i = 1;
      if (mat(2,2) > mat(i,i))
        i = 2;
      int j = iNext[i];
      int k = iNext[j];

      fRoot = sqrt(mat(i,i) - mat(j,j) - mat(k,k) + T(1.0));
      T* apkQuat[3] = {&out_q.x, &out_q.y, &out_q.z};
      *apkQuat[i] = T(0.5) * fRoot;
      fRoot = T(0.5) / fRoot;
      out_q.w = (mat(k,j) - mat(j,k)) * fRoot;
      *apkQuat[j] = (mat(j,i) + mat(i,j)) * fRoot;
      *apkQuat[k] = (mat(k,i) + mat(i,k)) * fRoot;
    }

    m3dal::Normalize(out_q);
    return out_q;
  }


  /// Преобразование трех ортогональных векторов ориентации в кватернион
  /// @param vecUp: вектор оси вверх
  /// @param vecSide: вектор оси вбок/вправо
  /// @param vecDir: вектор оси направления (взгляда)
  /// @param useBase3DCoordsSystem: флаг использования оси координат Base3D, иначе классическая правосторонняя СК
  /// @return кватернион ориентации
  template<typename T> nvSDK::nv::quaternion<T> AxesToQuaternion(const nvSDK::nv::vec3<T> &vecUp,
    const nvSDK::nv::vec3<T> &vecSide, const nvSDK::nv::vec3<T> &vecDir, bool useBase3DCoordsSystem = true)
  {
    nvSDK::nv::matrix4<T> mat; mat.make_identity();

    if (useBase3DCoordsSystem)
    {
      mat(0, 0) = vecSide.y;
      mat(1, 0) = vecSide.z;
      mat(2, 0) = vecSide.x;

      mat(0, 1) = vecUp.y;
      mat(1, 1) = vecUp.z;
      mat(2, 1) = vecUp.x;

      mat(0, 2) = vecDir.y;
      mat(1, 2) = vecDir.z;
      mat(2, 2) = vecDir.x;
      nvSDK::nv::quaternion<T> tmp = m3dal::MatrixToQuaternion(mat);
      return nvSDK::nv::quaternion<T>(tmp.z, tmp.x, tmp.y, tmp.w);
    }
    else
    {
      mat(0, 0) = vecSide.x;
      mat(1, 0) = vecSide.y;
      mat(2, 0) = vecSide.z;

      mat(0, 1) = vecUp.x;
      mat(1, 1) = vecUp.y;
      mat(2, 1) = vecUp.z;

      mat(0, 2) = vecDir.x;
      mat(1, 2) = vecDir.y;
      mat(2, 2) = vecDir.z;
      return m3dal::MatrixToQuaternion(mat);
    }
  }

корректно работает для useBase3DCoordsSystem = false (совпадает с огром), при useBase3DCoordsSystem = true (ось OX - на север/вглубину экрана, OY - вправо, OZ-вверх)

баг фикс )) все нормально работает

#6
20:14, 21 окт. 2009

Shadero немножко не корректно сформулировал алгоритм, но за идею спасибо. Вот как надо (код для специфической системы координат useBase3DCoordsSystem см. выше)

CLKrd camPos, trgPos = m_ptrTrackingObject->GetPosition();
this->GetPosition(camPos);
CVec3f vecOffset(float(trgPos.x - camPos.x), float(trgPos.y - camPos.y), float(trgPos.h - camPos.h));
float distance = length(vecOffset);

if (distance > 0)
{
  CVec3f vecDir = normalize(vecOffset);
  CVec3f vecUp = this->GetUp();
  CVec3f vecRight = normalize(cross(vecUp, vecDir));

  if (IdentZero(rotateV_.y) == false)
  {
    CQuatf qyaw(CVec3f(0, 0, 1), (float)rotateV_.y);// m3dal::Normalize(qyaw);
    qyaw.mult_vec(vecUp);
    qyaw.mult_vec(vecRight);
    qyaw.mult_vec(vecDir);
  }

  if (IdentZero(rotateV_.p) == false)
  {
    CQuatf qpitch(vecRight, (float)rotateV_.p);
    qpitch.mult_vec(vecUp);
    qpitch.mult_vec(vecRight);
    qpitch.mult_vec(vecDir);
  }

  m_qOrientation = m3dal::AxesToQuaternion(vecUp, vecRight, vecDir);

  vecOffset = distance * vecDir;
  camPos = trgPos - vecOffset;
  this->SetPosition(camPos);

  CLKrd relative(vecOffset.x, vecOffset.y, vecOffset.z);
  this->SetRelativePosition(relative);
}

пояснения на пальцах ))
Вектор вверх взяли текущий из камеры
Вектор взягляда - нормализованный вектор смещения
Вправо - кросс апа на директион (хотя может зависеть от системы координат)

Далее, нам ВСЕ эти вектора повернуть, т.к. кватернион строится из трех ортогональных векторов.

ну а далее поставили позицию камере. И все ок.
А кто-нибудь по другому решал эту проблему???

#7
16:25, 29 окт. 2009

Можно еще, это же сделать и как я описал в посте #1 второй абзац )))

#8
16:22, 20 фев. 2010

Товарсчи! Комсомольцы! Друзья!

Кто-нибудь решал такую головоломку:
исходная ориентация задана кватернионом в левосторонней системе координат, для рендера нужен кватернион для правосторонней системы координта.
Отличия этих систем:
В левосторонней СК ось z направлена в глубину экрана, ПСК - на зрителя;
ЛСК положительные значения углов приводят к вращению по часовой стрелке если смотреть с положительного конца оси на начало координат, в ПСК - против часовой.

Пока ничего лучше не придумал, как преобразую исходный кв ЛСК в углы Эйлера, инвертирую yaw, pitch (ролл, почему то совпадает, хотя возможно именно здесь кроется ошибочка) и строю из частично проинвертированных углов кватеринон для ПСК.

Хотя наверное достаточно было просто вычислить сопряженный/обратный кватернион для ЛСК (но не срабатывает, возможно из-за ролла)

#9
20:03, 20 фев. 2010

в кватернионе для ПСК инвертируем значения w, z (угол в противоположную сторону и направление оси дирекшина)

ПрограммированиеФорумОбщее

Тема в архиве.