#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. Совет на самом деле не является ответом. Это относится к комментариям.