Программирование игр, создание игрового движка, OpenGL, DirectX, физика, форум
GameDev.ru / Программирование / Форум / Сложный поворот тела с помошью кватернионов

Сложный поворот тела с помошью кватернионов

Страницы: 1 2 3 4 5 6 7 Следующая »
AlerrПостоялецwww24 авг. 201620:57#0
Привет всем, раньше пытался выяснить как такое можно сделать но безуспешно...
Сам потратил не 1 день, надеюсь кто-то поможет. Вот формулировка задачи:
Есть куб и игрок.
Игрок может брать и вращать куб по 1-й из 3-х осей относительно себя.

Куб должен свободно вращаться колесиком мыши по оси, которая в данный момент выбрана как ось вращения. По остальным осям куб должен “выравниваться”.

Что значит “выровнять” куб:
Если игрок вращает куб по OY, то это значит, что нижняя грань куба должна быть паралельна полу.
Если игрок вращает куб по оси OZ (forward , вращение от нас и к нам(да, у меня камера свернутна на 90 градусов и right соответствует forward)), то по оси OX он выравнивается относительно пола. По оси OY докручивается к нам так, что плоскость OYZ становится паралельна плоскости экрана.
Если игрок вращает куб по оси OX (right), то куб вращается перед нами по/прочив часовой стрелки. Ближайшая к нам грань должна быть паралельна плоскости экрана. По оси OZ куб становится на угол кратный 90 градусам (Пытается выровняться “по полу”).

Итого получается что куб вращается сам (самостоятельно докручивается) и под действием игрока (игрок вращает по заданной оси).
——————————
Теперь по коду:
Вот что я имею ввиду под выравниванием:

myTransform - player
rigidBody/rigidTransform - куб 
rotStep = Mathf.Sqrt (0.5f);// 90 degree

Этот код “выравнивает” куб.
void FixedUpdate(){
  Quaternion qq = rigidBody.rotation;

  qq.x = Mathf.Round (qq.x / rotStep) * rotStep;

  qq.y = Mathf.Round (qq.y / rotStep) * rotStep;

  qq.z = Mathf.Round (qq.z / rotStep) * rotStep;

  qq.w = Mathf.Round (qq.w / rotStep) * rotStep;

  rigidBody.rotation = Quaternion.Lerp (rigidBody.rotation, qq, 5f*Time.fixedDeltaTime);
}

————
Этот код позволяет крутить куб по выбранной оси:
delta - сюда поступает displacement с колесика мыши
void FixedUpdate(){
  if (axisIndex==0) {
        
    localAxis = rigidTransform.InverseTransformPoint (Vector3.up + rigidTransform.position);

        
    eulerAngleVelocity = localAxis * delta;

    Debug.Log ("OY");
      
  }  else {

          if (axisIndex==1) {// z axis

            localAxis = rigidTransform.InverseTransformPoint(myTransform.TransformPoint (Vector3.forward)+(rigidBody.position-myTransform.position));
       
     eulerAngleVelocity = localAxis * delta;
          
            Debug.Log ("OZ");
        
    }  else {
          
      if (axisIndex == 2) {
           
         localAxis = rigidTransform.InverseTransformPoint (myTransform.TransformPoint (Vector3.right) + (rigidBody.position - myTransform.position));

            
         eulerAngleVelocity = localAxis * delta;
            
         Debug.Log ("OX");
          
      }  else {
            
         eulerAngleVelocity = Vector3.zero;
         
      }
  
  }
  }
    Quaternion deltaRotation = Quaternion.Euler(eulerAngleVelocity * 5000f * Time.fixedDeltaTime);

    rigidBody.rotation *= deltaRotation;

}


Теперь какая стоит проблема:
1) ось по которой вращают куб не свободна, куб постоянно пытается выровнять себя по той оси, по которой его крутят.
2) ближайшая к игроку грань куба (при axisIndex == 1 || axisIndex == 2) не паралельна плоскости экрана.
Вот ссылк на видео того что получилось добиться через углы эйлера и что “почти” работает (вообщем-то так и должен выглядеть результат):
https://yadi.sk/i/Lp47twAPuRm7t
https://yadi.sk/i/16Fx4RNTuRmGy
Cсылка на проект с вращением куба: https://yadi.sk/d/OcFWQPbeuSwoG (все делал через кватернионы и куб передергивает при вращении)
По тем видео можно понять как все должно работать... Что скажете?

У меня проблемы с: 1) как запретить возвращать куб по той оси, по которой его вращает игрок 2) как сделать так, чтобы одна из граней куба была параллельна игроку, когда его не вращают по оси OY.

AlerrПостоялецwww24 авг. 201623:04#1
Даже не знаю что не работает... Сейчас пришел к выводу, что вот это:
qq.x = Mathf.Round (qq.x / rotStep) * rotStep;

  qq.y = Mathf.Round (qq.y / rotStep) * rotStep;

  qq.z = Mathf.Round (qq.z / rotStep) * rotStep;

  qq.w = Mathf.Round (qq.w / rotStep) * rotStep;
Дает сбои и из-за него, если повернуть тело по всем углам на +-90 градусов, образуется неверное вращение.
AlerrПостоялецwww25 авг. 20161:07#2
Похоже что qq.i = Mathf.Round (qq.i / rotStep) * rotStep;
 дает неверное итоговое вращение. Оси-то как бы привязываются к сетке углов по 90 градусов, но неверно привязываются. Когда оси мировые несоответствуют осям относительно которых вращают, происходят казусы. Вот первая проблема которую пока не знаю как преодолеть. Предположение такое: перебирать все орты (rigidTransform.(up/forward/right/-up...)) и среди них искать те, которые можно привязывать к сетке углов. Верно хоть мыслю?

Так же выяснил что из-за этой штуки "Quaternion deltaRotation = Quaternion.Euler(eulerAngleVelocity * 5000f * Time.fixedDeltaTime);
" банальный поворот вдоль eulerAngleVelocity не работал правильно. Исправил на:

Quaternion deltaRotation = Quaternion.AngleAxis(delta * 400f * Time.fixedDeltaTime, eulerAngleVelocity);
rigidBody.rotation *= deltaRotation;

AlerrПостоялецwww25 авг. 201612:15#3
Пытаюсь сделать самую базовую операцию: сделать чтобы нижняя грань куба была параллельна полу и чтобы его можно было вращать по оси OY. Важно, ось OY тут направлена вверх. Вот это не работает:
Quaternion qq = rigidBody.rotation;// куб
Quaternion  XZ = qq;// для того чтобы изьять вращение по Y, а по OX и OZ докрутить до ближайшего угла, кратного 90.

XZ.y = 0f;// убираем вращение по OY и оставляем только по OX и OZ
magnitude = Mathf.Sqrt(XZ.w*XZ.w + XZ.x*XZ.x+XZ.z*XZ.z);
if (magnitude != 0f) {
  XZ.w /= magnitude;// invert rotation
  XZ.x /= magnitude;
  XZ.z /= magnitude;
} else 
  XZ.x = 0f; XZ.z = 0f; XZ.w = 1f;

// Вот это, похоже, дает сбой. По идее тут я хочу сделать так чтобы нижняя грань куба была параллельна полу 
XZ.x = Mathf.Round (XZ.x / rotStep) * rotStep;
XZ.z = Mathf.Round (XZ.z / rotStep) * rotStep;
XZ.w = Mathf.Round (XZ.w / rotStep) * rotStep;

// Берем вращение по OY которое было у куба вначале
Quaternion OY = qq;
OY.x = 0f;// оставляем только вращение по OY:
OY.z = 0f;// 
magnitude = Mathf.Sqrt(OY.w*OY.w + OY.y*OY.y);
if (magnitude != 0f) {
  OY.w /= magnitude;// invert rotation
  OY.y /= magnitude;
} else
  OY.y = 0f; OY.w = 1f;

qq = XZ; // 1) новый rotation по XZ привязан к 90 грд?
qq *= OY;// 2) Докручиваем по OY, чтобы угол не изменился

// Крутим тело:
rigidBody.rotation = Quaternion.Lerp (rigidBody.rotation, qq, 5f*Time.fixedDeltaTime);
Quaternion deltaRotation = Quaternion.AngleAxis(delta * 400f * Time.fixedDeltaTime, Vector3.up);
rigidBody.rotation *= deltaRotation;
Где может быть проблема? Куб постоянно пытается встать на 0 грд по OY.
bykabakПостоялецwww25 авг. 201612:48#4
Alerr,

тебе ответили в прошлой теме, а ты её удалил.

Ты составь алгоритм работы программы без кода и ты увидишь где у тебя косяк.

У тебя логика работы неправильная  - if и else криво пользуешь по-моему

AlerrПостоялецwww25 авг. 201613:07#5
bykabak, проблема в том, что я не особо дружу с кватернионами (не понял что ответ был ответом), отсюда даже моя логика может ломаться.
Логика такая:
1) Определить по какой оси игрок(a) вращает тело и какие оси должны прицепиться к 90-градусным осям(b).
2) Изьять значение вращения по оси(a) из общего вращения и сохранить его.
3) Изьять значения  вражения по осям (b) из общего вращения и сохранить его.
4) Проделать операцию доворота до ближайшего вращения кратного 90 градусам (rotStep=Sqrt(0.5)) над (b) и записать его в (b).
5) Получить итоговое вращение: result = b*a; и body.rotation=Lerp(from,result,k);
6) добавить вращение по оси (а) к телу (это игрок крутит колесиком и вращает тело).

Такой алгоритм, где промахи? Судя по тому что у меня успешно работает только вращение вдоль оси, которую игрок выбрал, промахи с [2-5], но в чем?

bykabakПостоялецwww25 авг. 201613:19#6
в логике работы твоего алгоритма вращения.

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

AlerrПостоялецwww25 авг. 201613:24#7
Да, я думал над этим, но у меня до этого проблемы. Я завершал вращение воокруг любой другой оси, но тело передергивает или вообще непонятно куда выкручивает.
>Я тебе писал, что прежде чем начать вращение вокруг какой-нибудь оси, нужно завершить вращение вокруг любой оси.

Я читал что Q1*Q2!=Q2*Q1...
Вот одна из частей логики вращения более подробно:
Quaternion qq1 = body.rotation;
qq.x = Mathf.Round (qq.x / rotStep) * rotStep;//
qq.y = Mathf.Round (qq.y / rotStep) * rotStep;//
qq.z = Mathf.Round (qq.z / rotStep) * rotStep;//
qq.w = Mathf.Round (qq.w / rotStep) * rotStep;//
body.rotation = Quaternion.Lerp (rigidBody.rotation, qq, 5f*Time.fixedDeltaTime);

Она не работает. И я не понимаю почему. Я пробовал переводить в углы эйлера, привязывать к сетке из 90 грд., а потом назад в quaternion перегонял, но это не помогло... Что тут я делаю не так? Кватернионы нельзя так преобразовывать?

ArWikiНовичокwww25 авг. 201613:25#8
Слушай чел, ты заставил меня зарегаться)))) Так как в прошлой теме как упоминалось ранее, все доходчиво объяснили. Смотри мы не экстрасенсы и не можем увидеть весь твой код и какие значение переменные хранят. Ну да ладно мы переживем. Если хочешь наших советов, то пока могу я одно сказать из того что  я вижу, что в твоей функции FixedUpdate косяк с поворотами. Пересмотри ее еще раз в режиме отладки(debug) пошагово. И ты увидишь в чем прикол. Дальше я не понимаю зачем ты приводишь eulerAngleVelocity = Vector3.zero, из-за этого у тебя и проблемы с нулями.
AlerrПостоялецwww25 авг. 201613:33#9
Снес все что писал, вот FixedUpdate, тут тело просто вращается по осям:
void FixedUpdate () {
    delta = Input.GetAxis ("Mouse ScrollWheel");

    if (isRotatePlayer) {
      myTransform.rotation *= Quaternion.AngleAxis(1f, Vector3.up);
    }

    if (rigidBody){//myTransform.TransformVector(anchor)
      float k = (myTransform.position-rigidBody.position).sqrMagnitude>anchor.x*anchor.x ? 3f : 1f;
      currentAnchorScaled = Vector3.Lerp (currentAnchorScaled, anchor*rigidTransform.localScale.x, 5f*Time.fixedDeltaTime);
      rigidBody.MovePosition ( Vector3.Lerp(rigidBody.position, myTransform.TransformPoint( currentAnchorScaled ), k*LERP_K*Time.fixedDeltaTime) );//myTransform.localRotation * anchor


      Vector3 localAxis=Vector3.zero;

      Quaternion qq = rigidBody.rotation;

      if (axisIndex==0) {
        localAxis = rigidTransform.InverseTransformPoint (Vector3.up + rigidTransform.position);

        Debug.Log ("OY");
      }  else {

        if (axisIndex==1) {// z axis
          localAxis = rigidTransform.InverseTransformPoint(myTransform.TransformPoint (Vector3.forward)+(rigidBody.position-myTransform.position));

          Debug.Log ("OZ");
        }  else {
          localAxis = rigidTransform.InverseTransformPoint (myTransform.TransformPoint (Vector3.right) + (rigidBody.position - myTransform.position));

          Debug.Log ("OX");
        }
      }

      rigidBody.rotation = Quaternion.Lerp (rigidBody.rotation, qq, 5f*Time.fixedDeltaTime);

      Quaternion deltaRotation = Quaternion.AngleAxis(delta * 400f * Time.fixedDeltaTime, localAxis);
      rigidBody.rotation *= deltaRotation;
    }
  }
Далее попробую доворачивать до нужного угла.
AlerrПостоялецwww25 авг. 201613:51#10
Вот тут начинается мое непонимание:
void FixedUpdate () {
    delta = Input.GetAxis ("Mouse ScrollWheel");

    if (isRotatePlayer) {
      myTransform.rotation *= Quaternion.AngleAxis(1f, Vector3.up);
    }

    if (rigidBody){//myTransform.TransformVector(anchor)
      float k = (myTransform.position-rigidBody.position).sqrMagnitude>anchor.x*anchor.x ? 3f : 1f;
      currentAnchorScaled = Vector3.Lerp (currentAnchorScaled, anchor*rigidTransform.localScale.x, 5f*Time.fixedDeltaTime);
      rigidBody.MovePosition ( Vector3.Lerp(rigidBody.position, myTransform.TransformPoint( currentAnchorScaled ), k*LERP_K*Time.fixedDeltaTime) );//myTransform.localRotation * anchor


      Vector3 localAxis=Vector3.zero;

      Quaternion qq = rigidBody.rotation;
      Quaternion qq1 = qq;

      if (axisIndex==0) {
        localAxis = rigidTransform.InverseTransformPoint (Vector3.up + rigidTransform.position);

        // (1) получаем 0Y rotation:
        qq1.x = 0f;
        qq1.z = 0f;
        float magnitude = Mathf.Sqrt(qq.w*qq.w + qq.y*qq.y);
        if (magnitude != 0f) {
          qq1.w /= magnitude;// invert rotation
          qq1.y /= magnitude;
        }  else {
          qq1.y = 0f; qq1.w = 1f;
        }
        
        // (2) Remove OY rotation from body rotation:
        qq.y = 0f;
        magnitude = Mathf.Sqrt(qq.w*qq.w + qq.x*qq.x + qq.z*qq.z);
        if (magnitude != 0f) {
          qq.w /= magnitude;// invert rotation
          qq.x /= magnitude;
          qq.z /= magnitude;
        }  else {
          qq.x = 0f; qq.z = 0f; qq.w = 1f;
        }

        Debug.Log ("OY");
      }  else {
        ...
      }

      rigidBody.rotation = Quaternion.Lerp (rigidBody.rotation, qq*qq1, 5f*Time.fixedDeltaTime);

      Quaternion deltaRotation = Quaternion.AngleAxis(delta * 400f * Time.fixedDeltaTime, localAxis);
      rigidBody.rotation *= deltaRotation;
    }
  }
Тут я получаю вращение по оси OY (1) и по осям OXиOZ(2) котороые были у тела до того как я начинаю с ним работать.
То есть я вытащил из общего вращения вращение по OY и совместное вращение по OXиOZ.
В конце пытаюсь вернуться к исходному начальному вращению (qq*qq1), но ничего не получается. Где я неверно разделяю вращения?
AlerrПостоялецwww25 авг. 201613:55#11
Извиняюсь, верно тут все.
Совсем уже сьехал...
else{
          qq.x = 0f; qq.z = 0f; qq.w = 1f;
}
AlerrПостоялецwww25 авг. 201614:00#12
Хотя нет, не верно. Как только поворачиваю тело по любой оси кроме OY, тело ачинает вести себя странно. Почему вот это qq*qq1 не равно rigidBody.rotation, который был в самом начале?
AlerrПостоялецwww25 авг. 201614:09#13
bykabak, похоже что if-else дает сбой. Можете дать верный вариант?
bykabakПостоялецwww25 авг. 201614:40#14
Alerr,

Предлагаю вам на бумаге написать алгоритм работы поворотов.

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

Страницы: 1 2 3 4 5 6 7 Следующая »

/ Форум / Программирование игр / Физика

2001—2018 © GameDev.ru — Разработка игр