Физические объекты «дрейфуют» при установлении непрерывного контакта

#c# #game-physics

#c# #игра-физика

Вопрос:

Я пытался создать простой 3D-физический движок в качестве упражнения. Моя проблема в том, что объекты «дрейфуют» при контакте с другим объектом, если их позиции не идеально выровнены.

В моем тестовом примере в верхнем окне включена физика, и на него влияет гравитация. Нижний блок статичен (его скорости фиксированы на 0). Если верхний ящик расположен точно над центром нижнего ящика (поэтому они имеют одинаковые координаты X и Z), верхний ящик приземляется на дно и остается совершенно неподвижным. Однако, если верхний блок немного смещен по оси X или Z по событию, он начинает набирать обороты в этом направлении после приземления, пока в конечном итоге не упадет, как видно здесь .

Я знаю, что вызывает это: при идеально центрированной посадке точка контакта, предоставляемая EPA (которую я реализовал на основе этого), расположена прямо под центром верхнего ящика. Это приводит к тому, что часть якобиана, которая определяет момент ограничения, который должен быть применен к верхнему блоку (r1 x normal), равна 0. Однако при смещении точка контакта больше не находится непосредственно под центром верхнего блока, что приводит к его небольшому вращению. Это, в свою очередь, приводит к тому, что нормаль контакта на следующем временном шаге слегка поворачивается. Верхняя коробка выдвигается вдоль этой контактной нормали, заставляя ее скользить. Я подтвердил это, поскольку либо отключение вращения, либо жесткое кодирование для нормального контакта с 0, -1,0 устраняет проблему.

Я думал, что реализация кэширования контактов исправит это, но это не так, как вы можете видеть на видео выше, где каждая фиолетовая точка представляет активный контакт. Я кэширую контакты в течение нескольких временных шагов, и всякий раз, когда контакт применяет силы к объекту, он обновляет глубину проникновения всех контактов, связанных с этим объектом (включая самого себя):

 private void ApplyForces(RigidBody Body, Time deltaTime, vec3 deltaVel, vec3 deltaRot)
{
    Body.ForcesConstraints  = deltaVel;
    Body.TorqueConstraints  = deltaRot;

    foreach (Constraint c in M.InvolvedConstraints)
        c.UpdateConstraint(Body, deltaTime, deltaVel, deltaRot);
}

public override void UpdateConstraint(RigidBody Body, Time deltaTime, vec3 deltaVel, vec3 deltaRot)
{
    //I understand that this way of computing the actual positional delta the deltaRot represents is incredibly bad, but I coulnd't get anything else to work (I'm a linear algebra newbie).
    var rot = (quat.FromAxisAngle((deltaTime * deltaRot).Length, deltaRot.NormalizedSafe) * contact) - contact;

    var deltaPos = (deltaVel * deltaTime)   rot;
    
    //The contact normal points from Body1 to Body2
    _pendepth  = (Body == Body1 ? 1 : -1) * vec3.Dot(deltaPos, _normal);
}
  

Вы можете найти мой код здесь (извините, это choas).

Ответ №1:

Проблема почти наверняка связана с тем, что очень маленькие числа имеют значение с течением времени. Скольжение довольно незначительное. Я клонировал ваш репозиторий, но у меня нет библиотек для его запуска, поэтому единственный совет, который я могу вам дать, — это просто прочитать код.

Однако подобные вещи заставляют меня очень нервничать, когда я смотрю на код:

  int index = -1;
 float maxDot = -9999;
 float td = -9999;
 float ti = -1;
  

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

Что касается вашей проблемы Якобиана, если я правильно помню свою физику, a когда якобиан равен 0, это особенность, и ваши управляющие силы вообще не могут ее перемещать.

Определенно кажется, что вы предполагаете, что действительно возникают из-за очень малых углов между поверхностями. Моя лучшая идея заключается в том, что если две поверхности почти параллельны (поэтому дельта-угол ниже некоторого порога, тогда вы можете отключить некоторую часть этого физического процесса. Вероятно, это будет связано с «привязкой» объекта к стабильному состоянию, которое вы описываете, где они точно выстраиваются.

итак:

 public static float MinAngleThreshold = 0.001f;
        public override void UpdateConstraint(RigidBody Body, Time deltaTime, vec3 deltaVel, vec3 deltaRot)
        {
            //I understand that this way of computing the actual positional delta the deltaRot represents is incredibly bad, but I coulnd't get anything else to work (I'm a linear algebra newbie).
            var rot = (quat.FromAxisAngle((deltaTime * deltaRot).Length, deltaRot.NormalizedSafe) * contact) - contact;

            if(rot < MinAngleThreshold)
            {
                //do something here, potentially just return without updating. 
            }

            var deltaPos = (deltaVel * deltaTime)   rot;
            
            //The contact normal points from Body1 to Body2
            _pendepth  = (Body == Body1 ? 1 : -1) * vec3.Dot(deltaPos, _normal);
        }
  

Хотя с вашим кодом, безусловно, есть некоторые проблемы, общая система довольно умна, и вы должны гордиться тем, что написали.

Комментарии:

1. Я реализовал что-то похожее на ваше предложение. Жесткие тела сохраняют момент ограничения ниже определенного порога в переменной в течение нескольких временных шагов. Переменная очень значительно уменьшается с каждым временным шагом. Если он или ограничивающий момент временного шага превышают пороговое значение, он добавляется к угловой скорости и устанавливается равным нулю. Это делает так, что тела должны получать определенное количество крутящего момента в секунду, прежде чем он начнет применяться. Это немного неудачно, но это работает. Спасибо за вашу помощь! (Также спасибо за комментарий в конце. Это действительно много значит для меня)

2. Совет на самом деле не является ответом. Это относится к комментариям.