Вопрос или проблема
Я пытался создать простой двухмерный физический движок на C#. Следовал уроку от “Two-Bit Coding” на YouTube, но заметил, что квадраты с двумя контактными точками вообще не отскакивают. Причину этой проблемы я выяснил: импульсы были просто слишком маленькими при разрешении столкновений.
Я добавил немного упругости, умножив импульс на значение. Однако это не решение, так как если столкновение происходит очень близко к центру тела (в середине края квадрата), импульс будет огромным, что приведет к нарушению законов сохранения энергии.
Я попробовал другую формулу для разрешения столкновения, на этот раз из Википедии. Формула предназначена для 3D, но я предположил, что она будет работать и для 2D.
Формула из Википедии
Вот реализация на C#:
public void Resolve_collision_wiki(in Physics_manifold contact)
{
Physics_body body_a = contact.body_a;
Physics_body body_b = contact.body_b;
Physics_vector normal = contact.normal;
int contact_count = contact.contact_count;
float e = 1;
contact_list[0] = contact.contact_point_1;
contact_list[1] = contact.contact_point_2;
for (int i = 0; i < contact_count; i++)
{
Physics_vector ra = contact_list[i] - body_a.Position;
Physics_vector rb = contact_list[i] - body_b.Position;
center_a_contact_list[i] = ra;
center_b_contact_list[i] = rb;
Physics_vector relative_velocity = body_b.Linear_velocity - body_a.Linear_velocity + Physics_math.Cross_product(body_b.Angular_velocity, rb) - Physics_math.Cross_product(body_a.Angular_velocity, ra);
//() сначала, затем скалярное произведение?
float numerator = Physics_math.Dot_product(-(1 + e) * relative_velocity, normal);
if (numerator < 0)
return;
//https://www.cs.cmu.edu/~baraff/sigcourse/notesd2.pdf#page=16
numerator /= body_a.inverse_mass + body_b.inverse_mass + Physics_math.Dot_product(Physics_math.Cross_product(body_a.inverse_inertia * Physics_math.Cross_product(ra, normal), ra) + Physics_math.Cross_product(body_b.inverse_inertia * Physics_math.Cross_product(rb, normal), rb), normal);
numerator = MathF.Max(numerator, 0);
j_list[i] = numerator;
}
//Применение в первом цикле сделает второй импульс немного неправильным
for (int i = 0; i < contact_count; i++)
{
body_a.Linear_velocity -= j_list[i] * body_a.inverse_mass * normal;
body_a.Angular_velocity -= Physics_math.Cross_product(center_a_contact_list[i], normal * j_list[i]) * body_a.inverse_inertia;
body_b.Linear_velocity += j_list[i] * body_b.inverse_mass * normal;
body_b.Angular_velocity += Physics_math.Cross_product(center_b_contact_list[i], normal * j_list[i]) * body_b.inverse_inertia;
}
}
Реализация скалярного произведения и двумерного векторного произведения:
public static float Dot_product(Physics_vector a, Physics_vector b)
{
return a.x * b.x + a.y * b.y;
}
public static float Cross_product(Physics_vector a, Physics_vector b)
{
return a.x * b.y - a.y * b.x;
}
public static Physics_vector Cross_product(float a, Physics_vector v)
{
return new Physics_vector(-a * v.y, a * v.x);
}
Но высота отскока все равно драматически изменяется, когда я перемещаю контактные точки ближе друг к другу, и становится ниже, когда контактные точки находятся дальше друг от друга. Более того, значение e равное 1 не обеспечивает идеально упругое столкновение для квадратов, как это происходит для кругов.
Ответ или решение
Проблема с восстановлением импульса в 2D-движке физики для полигонов
Создание 2D-движка физики может оказаться сложной задачей, особенно когда речь идет о граничных случаях столкновений между фигурами, такими как квадраты. В вашем случае, где квадраты с двумя точками контакта не отскакивают из-за слишком малых импульсов, проблема часто заключается в недостаточной учёте коррекции импульса при столкновениях.
Понимание проблемы
Когда два тела сталкиваются, их скорость изменения импульса и восстановление энергии определяется калорийным уравнением, которое учитывает как массу, так и относительное положение точек контакта. Поскольку вы обнаружили, что импульсы становятся чрезмерно большими, когда точки контакта располагаются близко к центру, это указывает на необходимость более корректного расчёта момента импульса.
Использование формулы из Википедии
Формула, которую вы используете из Википедии, вероятно, не учитывает определённые 2D-особенности. Главная ошибка заключается в том, что вы неверно применяете эту формулу, которая изначально создана для 3D. Иногда такие упрощения могут привести к нежелательным результатам в 2D. Вы можете рассмотреть модификацию подхода, специализированного для 2D.
Рекомендации по корректировке кода
-
Коррекция расчётов момента импульса: Убедитесь, что вы правильно учитываете два момента (относительно каждого тела) при расчёте. Попробуйте использовать следующее изменение в основном методе
Resolve_collision_wiki
:float I_a = body_a.inverse_inertia; float I_b = body_b.inverse_inertia; float denominator = body_a.inverse_mass + body_b.inverse_mass + (I_a * Physics_math.Dot_product(ra, ra) + I_b * Physics_math.Dot_product(rb, rb)) * Physics_math.Dot_product(normal, normal);
Это позволит более точно рассчитывать восстановление, когда объекты находятся в движении.
-
Числовая стабильность: Проверьте, чтобы ваши значения инверсных масс и инверсных моментов не стали равными нулю. Используйте маленькие значения (например, 1e-6) для этих расчётов, чтобы избежать деления на ноль.
-
Эластичность и коэффициент восстановления: Убедитесь, что коэффициент восстановления (e) правильно внедрён. Значение, равное единице, при идеальном столкновении для всех форм не всегда будет достигнуто. Вы можете добавить переключатель для различных форм и сделать различные типы хотя бы для кругов и квадратов.
Проблема с изменением высоты отскока
Чтобы избежать проблемы с изменением высоты отскока при смещении точек контакта, рассмотрите использование различных подходов к реализации законов сохранения. Возможно, вам стоит перейти к,
системе "Impulse Resolution", которая рассчитывает отскок на основе не только абсолютных значений, но и относительных позиций объектов.
Заключение
Ваша цель заключается в создании стабильного и надежного 2D-движка физики, и устранение проблем, связанных с восстановлением импульсов при столкновении многоугольников, имеет критическое значение. Проверив свои расчёты импульса и обеспечив их достаточной стабильностью и точностью, вы сможете добиться желаемых результатов.
Используйте эти рекомендации для улучшения вашего кода и достижения более предсказуемого поведения в столкновениях. Удачи в реализации!