Мяч к мячу столкновения-обнаружение и обработка



С помощью сообщества Stack Overflow я написал довольно простой, но забавный физический симулятор.



alt text



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



моя следующая большая функция, которую я хочу добавить, - это столкновение мяча с мячом. Движение шара разбивается на вектор скорости x и Y. У меня есть гравитация (небольшое уменьшение вектора y на каждом шаге), у меня есть трение (небольшое уменьшение обоих векторов при каждом столкновении со стеной). Шары честно передвигаются на удивление реалистично.



Я думаю, мой вопрос состоит из двух частей:





  1. каков наилучший метод обнаружения столкновения мяча с мячом?

    У меня просто есть цикл O(n^2), который повторяется над каждым шаром и проверяет каждый другой шар, чтобы увидеть, перекрывается ли его радиус?


  2. какие уравнения я использую для обработки столкновения шара с шаром? Физика 101

    Как это влияет на скорость двух шаров X/Y векторов? В каком направлении движутся эти два шара? Как я могу применить это к каждому мячу?


alt text



обработка обнаружения столкновений "стен" и результирующих изменений вектора была легкой, но я вижу больше осложнений с столкновениями мяч-мяч. С помощью стен я просто должен был взять отрицание соответствующего вектора x или y, и он будет идти в правильном направлении направление. С мячами я не думаю, что это так.



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





Edit: ресурсы, которые я нашел полезными



2d физика шара с векторами:2-Мерные Столкновения Без Тригонометрии.pdf

2d пример обнаружения столкновения шаров:добавить Обнаружение Столкновений





успехов!



у меня есть обнаружение столкновения мяча и ответ работает отлично!



соответствующий код:



Обнаружение Столкновений:



for (int i = 0; i < ballCount; i++)  
{
for (int j = i + 1; j < ballCount; j++)
{
if (balls[i].colliding(balls[j]))
{
balls[i].resolveCollision(balls[j]);
}
}
}


это будет проверять столкновения между каждым шаром, но пропустить избыточные проверки (если вы должны проверить, если мяч 1 сталкивается с мячом 2, то вам не нужно проверять, если мяч 2 сталкивается с мячом 1. Кроме того, он пропускает проверку на наличие столкновений с сам.)



затем в моем классе ball у меня есть методы colliding() и resolveCollision ():



public boolean colliding(Ball ball)
{
float xd = position.getX() - ball.position.getX();
float yd = position.getY() - ball.position.getY();

float sumRadius = getRadius() + ball.getRadius();
float sqrRadius = sumRadius * sumRadius;

float distSqr = (xd * xd) + (yd * yd);

if (distSqr <= sqrRadius)
{
return true;
}

return false;
}

public void resolveCollision(Ball ball)
{
// get the mtd
Vector2d delta = (position.subtract(ball.position));
float d = delta.getLength();
// minimum translation distance to push balls apart after intersecting
Vector2d mtd = delta.multiply(((getRadius() + ball.getRadius())-d)/d);


// resolve intersection --
// inverse mass quantities
float im1 = 1 / getMass();
float im2 = 1 / ball.getMass();

// push-pull them apart based off their mass
position = position.add(mtd.multiply(im1 / (im1 + im2)));
ball.position = ball.position.subtract(mtd.multiply(im2 / (im1 + im2)));

// impact speed
Vector2d v = (this.velocity.subtract(ball.velocity));
float vn = v.dot(mtd.normalize());

// sphere intersecting but moving away from each other already
if (vn > 0.0f) return;

// collision impulse
float i = (-(1.0f + Constants.restitution) * vn) / (im1 + im2);
Vector2d impulse = mtd.multiply(i);

// change in momentum
this.velocity = this.velocity.add(impulse.multiply(im1));
ball.velocity = ball.velocity.subtract(impulse.multiply(im2));

}


Исходный Код: полный источник для шарика к коллайдеру шарика.



Если у кого-то есть предложения о том, как улучшить этот базовый физический симулятор, дайте мне знать! Одна вещь, которую я еще должен добавить, - это угловой момент, поэтому шары будут катиться более реалистично. Есть еще предложения? Оставьте комментарий!

880   12  

12 ответов:

чтобы определить, сталкиваются ли два шара, просто проверьте, является ли расстояние между их центрами меньше, чем в два раза радиус. Чтобы сделать идеально упругое столкновение между шарами, вам нужно только беспокоиться о компоненте скорости, которая находится в направлении столкновения. Другая составляющая (касательная к столкновению) останется неизменной для обоих шаров. Вы можете получить компоненты столкновения, создав единичный вектор, указывающий в направлении от одного шара к другому, затем взяв скалярное произведение с векторами скоростей шаров. Затем вы можете подключить эти компоненты в 1D совершенно упругое уравнение столкновения.

Википедия имеет довольно хороший резюме всего процесса. Для шаров любой массы новые скорости могут быть вычислены с использованием уравнений (где v1 и v2-скорости после столкновения, а u1, u2-до):

v_{1} = \frac{u_{1}(m_{1}-m_{2})+2m_{2}u_{2}}{m_{1}+m_{2}}

v_{2} = \frac{u_{2}(m_{2}-m_{1})+2m_{1}u_{1}}{m_{1}+m_{2}}

если шары имеют та же масса, то скорости просто переключаются. Вот какой код я написал, который делает что-то подобное:

void Simulation::collide(Storage::Iterator a, Storage::Iterator b)
{
    // Check whether there actually was a collision
    if (a == b)
        return;

    Vector collision = a.position() - b.position();
    double distance = collision.length();
    if (distance == 0.0) {              // hack to avoid div by zero
        collision = Vector(1.0, 0.0);
        distance = 1.0;
    }
    if (distance > 1.0)
        return;

    // Get the components of the velocity vectors which are parallel to the collision.
    // The perpendicular component remains the same for both fish
    collision = collision / distance;
    double aci = a.velocity().dot(collision);
    double bci = b.velocity().dot(collision);

    // Solve for the new velocities using the 1-dimensional elastic collision equations.
    // Turns out it's really simple when the masses are the same.
    double acf = bci;
    double bcf = aci;

    // Replace the collision velocity components with the new ones
    a.velocity() += (acf - aci) * collision;
    b.velocity() += (bcf - bci) * collision;
}

что касается эффективности, Райан Фокс прав, вы должны рассмотреть вопрос о разделении области на секции, а затем сделать обнаружение столкновений в каждой секции. Имейте в виду, что шары могут сталкиваться с другими шарами, на границах раздела, так это может сделать ваш код намного сложнее. Эффективность, вероятно, не будет иметь значения, пока у вас есть несколько сотен шаров, хотя. Для бонусные очки, вы можете запустить каждый раздел на другом ядре, или разделить обработку столкновений внутри каждого раздела.

много лет назад я сделал программу, как вы представили здесь.
Есть одна скрытая проблема (или много, зависит от точки зрения):

  • если скорость шарика слишком высоко, вы можете пропустить столкновение.

а также, почти в 100% случаев ваши новые скорости будут неправильными. Ну, не скорость, а позиции. Вы должны рассчитать новые скорости точно в правильном месте. В противном случае вы просто сдвиг шары на некотором небольшом" ошибочном " количестве, которое доступно с предыдущего дискретного шага.

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

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

Читать далее Двоичного Разбиения и Quadtrees

в качестве пояснения к предложению Райана Фокса разделить экран на регионы и проверять только столкновения внутри регионов...

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

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

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

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

хороший способ уменьшить количество проверок столкновений-разделить экран на разные секции. Затем вы сравниваете только каждый шар с шарами в том же разделе.

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

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

кроме того, как только вы нашли столкновение, вы должны продолжать оценивать столкновения, пока больше не останется. Проблема в том, что первый может вызвать другие, которые должны быть решены, прежде чем вы получите точную картину. Рассмотрим, что происходит, если мяч попадает в мяч на краю? Второй мяч попадает в край и сразу же отскакивает в первый мяч. Если вы врежетесь в кучу шаров в углу, у вас может быть довольно много столкновений, которые должны быть решены, прежде чем вы сможете повторить следующий цикл.

Что касается O (n^2), все, что вы можете сделать, это минимизировать стоимость отклонения тех, которые пропускают:

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

2) что-то, что может быть стоит сделать: разделите экран на несколько зон, но линии должны быть нечеткими-шары на краю зоны перечислены как находящиеся во всех соответствующих (может быть 4) зонах. Я бы использовал сетку 4x4, храните зоны как биты. Если и из зон двух шаров зон возвращает ноль, конец теста.

3) Как я уже упоминал, не делайте квадратный корень.

Я нашел отличную страницу с информацией об обнаружении столкновений и реакции в 2D.

http://www.metanetsoftware.com/technique.html

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

Edit: обновили ссылку

у вас есть два простых способа сделать это. Джей покрыл точный способ проверки от центра мяча.

более простой способ-использовать прямоугольную ограничительную рамку, установить размер вашей коробки на 80% от размера шара, и вы будете имитировать столкновение довольно хорошо.

добавить метод в класс мяча:

public Rectangle getBoundingRect()
{
   int ballHeight = (int)Ball.Height * 0.80f;
   int ballWidth = (int)Ball.Width * 0.80f;
   int x = Ball.X - ballWidth / 2;
   int y = Ball.Y - ballHeight / 2;

   return new Rectangle(x,y,ballHeight,ballWidth);
}

затем в цикле:

// Checks every ball against every other ball. 
// For best results, split it into quadrants like Ryan suggested. 
// I didn't do that for simplicity here.
for (int i = 0; i < balls.count; i++)
{
    Rectangle r1 = balls[i].getBoundingRect();

    for (int k = 0; k < balls.count; k++)
    {

        if (balls[i] != balls[k])
        {
            Rectangle r2 = balls[k].getBoundingRect();

            if (r1.Intersects(r2))
            {
                 // balls[i] collided with balls[k]
            }
        }
    }
}

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

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

Да, это два теста, но это будет быстрее в целом.

этой KineticModel реализация привел подход в Java.

я реализовал этот код в JavaScript, используя элемент HTML Canvas, и он произвел замечательные симуляции со скоростью 60 кадров в секунду. Я начал моделирование с набора дюжины шаров в случайных положениях и скоростях. Я обнаружил, что при более высоких скоростях скользящее столкновение между маленьким шаром и гораздо большим шаром заставило маленький шар казаться STICK к краю более большого шарика, и двинуло до около 90 градусов вокруг более большого шарика раньше отделяющий. (Интересно, наблюдал ли кто-нибудь еще такое поведение.)

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

dot_velocity = ball_1.velocity.dot(ball_2.velocity);
mtd_factor = 1. + 0.5 * Math.abs(dot_velocity * Math.sin(collision_angle));
mtd.multplyScalar(mtd_factor);

Я проверил, что до и после этого исправления общая кинетическая энергия была сохранена за каждое столкновение. Значение 0.5 в mtd_factor было приблизительно минимальным значением, которое всегда вызывало разделение шаров после столкновения.

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

Как я вижу здесь, лучший способ его реализации не упоминается. Event driven molecular dynamics Я отсылаю вас к "как имитировать бильярд и подобные системы" Бориса Д. Любачевского, доступного на arxiv:http://arxiv.org/abs/cond-mat/0503627 в прилагаемом изображении есть скриншот программы, которую я намерен сделать открытым исходным кодом, когда я закончу его. Даже на ранней стадии работает с 5000 сфер довольно гладко. Надеюсь, будет еще лучше, хотя я не хочу реализуйте секторирование, я хочу, чтобы код был легко понятен. Описание будет доступно на http://compphys.go.ro

позднее редактирование: код теперь доступен на GitHub:https://github.com/aromanro/EventMolecularDynamics Описание есть в блоге:http://compphys.go.ro/event-driven-molecular-dynamics/

Comments

    Ничего не найдено.