Импульс и порядок проблем обновления в моем физическом движке

22

введите описание изображения здесь

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


Если вы не хотите читать предыдущий вопрос, вот краткое описание того, как работает мой физический движок:

Каждый физический объект хранится в классе с именем SSSPBody.

Поддерживаются только AABB.

Каждый SSSPBody хранится в классе под названием SSSPWorld, который обновляет каждое тело и обрабатывает гравитацию.

Каждый кадр, SSSPWorld обновляет каждое тело.

Каждое обновленное тело ищет близлежащие тела в пространственном хеше, проверяет, нужно ли им обнаруживать столкновения с ними. Если да, они вызывают событие «столкновения» и проверяют, нужно ли им разрешать столкновения с ними. Если да, они рассчитывают вектор проникновения и направленное перекрытие, затем меняют свое положение, чтобы разрешить проникновение.

Когда тело сталкивается с другим, оно передает свою скорость другому, просто устанавливая скорость тела на свою собственную.

Скорость тела установлена ​​в 0, если она не изменила положение с последнего кадра. Если он также сталкивается с движущимся телом (например, лифтом или движущимися платформами), он рассчитывает разницу движения лифта, чтобы увидеть, не было ли тело перемещено из своего последнего положения.

Кроме того, тело вызывает «раздавленное» событие, когда все его углы AABB перекрывают что-либо в кадре.

Это ПОЛНЫЙ исходный код моей игры. Он разделен на три проекта. SFMLStart - это простая библиотека, обрабатывающая ввод, отрисовку и обновление сущностей. SFMLStartPhysics является наиболее важным, где классы SSSPBody и SSSPWorld. PlatformerPhysicsTest - игровой проект, содержащий всю игровую логику.

И это метод "update" в классе SSSPBody, прокомментированный и упрощенный. Вы можете взглянуть только на это, если вам не хочется смотреть на весь проект SFMLStartSimplePhysics. (И даже если вы это сделаете, вы все равно должны взглянуть на это, поскольку он прокомментирован.)


.Gif показывает две проблемы.

  1. Если тела размещены в другом порядке, получаются разные результаты. Ящики слева идентичны ящикам справа, размещены только в обратном порядке (в редакторе).
  2. Оба ящика должны быть продвинуты к верхней части экрана. В ситуации слева ящики не приводятся в движение. Справа только один из них. Обе ситуации являются непреднамеренными.

Первая проблема: порядок обновления

Это довольно просто понять. В ситуации слева самый верхний ящик обновляется раньше другого. Даже если ящик снизу «передает» скорость другому, ему нужно подождать, пока следующий кадр не сдвинется. Поскольку он не двигался, скорость нижнего ящика установлена ​​на 0.

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

Как основные физические движки (Box2D, Bullet, Chipmunk) обрабатывают порядок обновления?


Вторая проблема: только один ящик движется к потолку

Я пока не понимаю, почему это происходит. То, что делает «пружинная» сущность, это установить скорость тела на -4000 и переместить ее поверх самой пружины. Даже если я отключу код повторного позиционирования, проблема все равно возникает.

Моя идея состоит в том, что когда нижний ящик сталкивается с верхним, его скорость устанавливается равной 0. Я не уверен, почему это происходит.


Несмотря на возможность выглядеть как кто-то, кто сдается из-за первой проблемы, я разместил весь исходный код проекта выше. У меня нет ничего, чтобы доказать это, но, поверьте мне, я изо всех сил пытался это исправить, но я просто не мог найти решение, и у меня нет никакого предыдущего опыта с физикой и столкновениями. Я пытался решить эти две проблемы больше недели, и теперь я в отчаянии.

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

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

Витторио Ромео
источник
Когда бы вы ни складывали боксы, вы могли бы объединить их физику, чтобы они считались одним объектом?
CiscoIPPhone

Ответы:

12

На самом деле, порядок задач обновления довольно распространен для обычных движков импульсной физики, вы не можете просто отложить применение силы, как предполагает Vigil, вы в конечном итоге нарушите сохранение энергии, когда объект одновременно сталкивается с двумя другими. Обычно им удается создать нечто, что кажется довольно реальным, даже если другой порядок обновлений привел бы к существенно другому результату.

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

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

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

Вы также можете создать эффект трения, затормозив объект в перпендикулярном направлении столкновения, величина торможения должна быть равна величине перекрытия.

Вы можете легко обойти понятие массы, если все объекты будут иметь одинаковую массу, а неподвижные объекты будут работать как бесконечная масса, если вы просто пренебрегаете их ускорением.

Некоторый псевдокод, на случай, если вышеизложенное не достаточно ясно:

//Presuming that you have done collision checks between two objects and now have  
//numbers for how much they overlap in each direction.
overlapX
overlapY
if(overlapX<overlapY){ //Do collision in direction X
    if(obj1.X>obj2.X){
        swap(obj1,obj2)
    }
    //Spring effect:
    obj1.addXvelocity-=overlapX*0.1 //Constant, the lower this is set the softer the  
                                    //collision will be.
    obj2.addXvelocity+=overlapX*0.1
    //Dampener effect:
    velocityDifference=obj2.Xvelocity-obj1.Xvelocity
    //velocityDifference=min(velocityDifference,0) //Uncomment to only dampen when  
                                                   //objects move towards each other.
    obj1.addXvelocity+=velocityDifference*0.1 //Constant, higher for more dampening.
    obj2.addXvelocity-=velocityDifference*0.1
    //Friction effect:
    if(obj1.Yvelocity>obj2.Yvelocity){
        swap(obj1,obj2)
    }
    friction=overlapX*0.01
    if(2*friction>obj2.Yvelocity-obj1.Yvelocity){
        obj1.addYvelocity+=(obj2.Yvelocity-obj1.Yvelocity)/2
        obj2.addYvelocity-=(obj2.Yvelocity-obj1.Yvelocity)/2
    }
    else{
        obj1.addYvelocity+=friction
        obj2.addYvelocity-=friction
    }
}
else{ //Do collision in direction Y

}

Смысл свойств addXvelocity и addYvelocity заключается в том, что они добавляются к скорости их объекта после того, как вся обработка столкновений выполнена.

Редактировать:
Вы можете делать вещи в следующем порядке, где каждая пуля должна быть выполнена на всех элементах, прежде чем будет выполнена следующая:

  • Обнаружение коллизий, они могут быть устранены, как только они будут обнаружены.
  • Добавьте значения addVelocity к значениям скорости, добавьте гравитацию Yvelocity, сбросьте значения addVelocity на 0, переместите объекты в соответствии с их скоростью.
  • Визуализируйте сцену.

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

Edit2:
очень простой физический движок:

  • Столкновения и гравитация создают силу / ускорение. acceleration = [Complicated formulas]
  • Сила / ускорение добавляются к скорости. velocity += acceleration
  • Скорость добавлена ​​к позиции. position += velocity
AAAAAAAAAAAA
источник
Выглядит хорошо, никогда не думал о массовой весне для платформеров.
Большие
Я постараюсь реализовать это через несколько часов, когда вернусь домой. Должен ли я перемещать (Position + = Velocity) тела одновременно, затем проверять наличие столкновений или перемещать и проверять наличие столкновений одно за другим? [Кроме того, я должен вручную изменить положение, чтобы разрешить столкновения? Или об этом позаботится изменение скорости?]
Витторио Ромео
Я не совсем уверен, как интерпретировать ваш первый вопрос. Разрешение столкновения изменит скорость, и, таким образом, только косвенно повлияет на положение.
аааааааааааа
Дело в том, что я перемещаю объекты, вручную устанавливая их скорость на определенное значение. Чтобы устранить перекрытия, я убираю расстояние перекрытия от их положения. Если я воспользуюсь вашим методом, мне придется перемещать сущности, используя силы или что-то еще? Я никогда не делал этого раньше.
Витторио Ромео
Технически, да, вам придется использовать силы, но в моем фрагменте кода это немного упрощено, так как все объекты имеют вес 1, и поэтому сила равна ускорению.
аааааааааааа
14

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

Прежде всего, позиции и скорости устанавливаются повсеместно, с точки зрения подсистемы физики, это рецепт катастрофы. Кроме того, при изменении целых вещей различными подсистемами создайте закрытые методы, такие как «ChangeVelocityByPhysicsEngine», «ChangeVelocityBySpring», «LimitVelocity», «TransferVelocity» или что-то в этом роде. Это добавит возможность проверки изменений, сделанных определенной частью логики, и придаст дополнительное значение этим изменениям скорости. Таким образом, отладка будет проще.

Первая проблема

На сам вопрос. Теперь вы просто применяете фиксированные положения и скорости «по ходу» в порядке появления и логики игры. Это не будет работать для сложных взаимодействий без тщательного кодирования физики каждой сложной вещи. Отдельный физический движок тогда не нужен.

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

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

Могут появиться дополнительные вещи, такие как борьба с рывками, отказ от суммирования, когда FPS маленький, или другие подобные вещи, будьте готовы :)

Вторая проблема

Вертикальная скорость обоих этих «дедвейтов» ящиков никогда не меняется от нуля. Странно, но в цикле обновления PhysSpring вы назначаете скорость, но в цикле обновления PhysCrate она уже равна нулю. Можно найти линию, где скорость идет не так, как надо, но я перестал отлаживать здесь, потому что это ситуация «пожнешь то, что ты шьешь». Пришло время прекратить кодирование и начать переосмысливать все, когда отладка становится сложной. Но если доходит до того, что даже автору кода невозможно понять, что происходит в коде, то ваша кодовая база уже мертва без вашего осознания этого :)

Третья проблема

Я думаю, что что-то не так, когда вам нужно воссоздать часть Farseer, чтобы сделать простой платформер на основе плиток. Лично я бы подумал о вашем нынешнем движке как об огромном опыте, а затем полностью отказался бы от него для более простой и понятной физики на основе тайлов. При этом было бы разумно воспользоваться такими вещами, как Debug.Assert и, может быть, даже, о ужас, юнит-тестами, так как можно было бы ловить неожиданные вещи раньше.

EnoughTea
источник
Мне понравилось это сравнение "Леса бурых водорослей".
День
На самом деле, мне немного стыдно использовать такие слова, но я чувствовал, что если это приведет к рефакторингу или двум, то это будет оправдано.
Достаточно
Только с одним тестом в t не всегда будет вероятность, что это произойдет? Я полагаю, что вам нужно интегрировать скорости в момент времени t, а затем проверить наличие столкновений в момент времени t + 1, прежде чем устанавливать какие-либо скорости равными 0?
Джонатан Коннелл
Да, мы обнаруживаем столкновения впереди после интегрирования начального состояния вперед от t до t + dt, используя Runge-Kutta или что-то еще.
достаточно
«интегрируйте скорость, используя все силы, действующие на тела», «оставьте расчеты скорости вашему физическому движку» - я понимаю, что вы пытаетесь сказать, но я понятия не имею, как это сделать. Есть ли какой-нибудь пример / статья, которую вы можете мне показать?
Витторио Ромео
7

Когда тело сталкивается с другим, оно передает свою скорость другому, просто устанавливая скорость тела на свою собственную.

Ваша проблема в том, что это в корне неверные предположения о движении, поэтому то, что вы получаете, не похоже на движение, как вы с ним знакомы.

Когда тело сталкивается с другим, импульс сохраняется. Думать об этом как «A бьет B» по сравнению с «B бьет A» - значит применять переходный глагол к непереходной ситуации. А и Б сталкиваются; результирующий импульс должен быть равен начальному импульсу. То есть, если A и B имеют одинаковую массу, они оба теперь путешествуют со средним значением их исходных скоростей.

Вам также, вероятно, понадобится некоторое столкновение и итеративный решатель, иначе вы столкнетесь с проблемами стабильности. Возможно, вам следует прочитать некоторые презентации Эрин Катто на GDC.


источник
2
Они получат среднее значение исходной скорости только в том случае, если столкновение будет полностью неэластичным, например, A и B - куски теста.
Микаэль Осман
«просто установив скорость тела на свою». Это такие заявления, которые освещают, почему это не работает. В общем, я всегда обнаруживал, что неопытные люди пишут физические системы, не понимая основополагающих принципов. Вы никогда не «просто устанавливаете скорость» или «просто ...». Каждая модификация свойств тела должна быть прямым применением законов динамики; включая сохранение импульса, энергии и т. д. Да, всегда будут факторы выдумки, чтобы компенсировать нестабильность, но ни в коем случае вы не можете просто волшебным образом изменить скорость тела.
MrCranky
Во-первых, проще всего предположить неэластичные тела, пытаясь запустить двигатель: чем сложнее, тем лучше для решения проблем.
Патрик Хьюз
4

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

  1. Широкая фаза : переберите все объекты - проведите быстрый тест (например, AABB), чтобы определить, какие объекты могут сталкиваться - отбросьте те, которые не сталкиваются.
  2. Узкая фаза : переберите все сталкивающиеся объекты - рассчитайте вектор проникновения для столкновения (например, используя SAT).
  3. Реакция на столкновение : Прокрутите список векторов столкновения - рассчитайте вектор силы на основе массы, затем используйте его для вычисления вектора ускорения.
  4. Интеграция : переберите все векторы ускорения и интегрируйте положение (и вращение, если необходимо).
  5. Рендеринг : цикл по всем рассчитанным позициям и рендеринг каждого объекта.

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

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

  • Обнаружение столкновений с широкой фазой : пространственное хеширование .
  • Обнаружение узких фазовых столкновений : Для простой физики плиток вы можете просто применить тесты пересечения с помощью выровненной оси (AABB). Для более сложных фигур вы можете использовать теорему о разделении осей . Какой бы алгоритм вы ни использовали, он должен возвращать направление и глубину пересечения между двумя объектами (так называемый вектор проникновения).
  • Реакция на столкновение : используйте Projection для разрешения взаимного проникновения.
  • Интеграция : Интегратор является главным фактором, определяющим стабильность и скорость двигателя. Два популярных варианта - интеграция Verlet (быстрая, но простая) или RK4 (точная, но медленная). Использование интеграции верлетов может привести к чрезвычайно простому дизайну, так как большинство физических поведений (отскок, вращение) просто работают без особых усилий. Одна из лучших ссылок на изучение интеграции с RK4, которую я видел, - это серия Глена Фидлера по физике для игр .

Хорошее (и очевидное) место для начала - это законы движения Ньютона .

оборота лукеванин
источник
Спасибо за ответ. Как мне передать скорость между телами? Это происходит на этапе интеграции?
Витторио Ромео
В некотором смысле, да. Передача скорости начинается с фазы реакции на столкновение. Именно тогда вы рассчитываете силы, действующие на тела. Сила переводится в ускорение по формуле ускорение = сила / масса. Ускорение используется на этапе интегрирования для расчета скорости, которая затем используется для расчета положения. Точность фазы интегрирования определяет, насколько точно скорость (и впоследствии положение) изменяется во времени.
Люк Ван в