Сила притяжения между 2D-шариками

У меня есть симуляция с несколькими кругами, движущимися в 2D-пространстве, с упругими столкновениями между ними.

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

Моя функция управления столкновениями выглядит так:

void manageCollision(Particle particleA, Particle particleB)
{
    float distanceX = particleA.Position.X - particleB.Position.X;
    float distanceY = particleA.Position.Y - particleB.Position.Y;
    double collisionAngle = Math.Atan2(distanceY, distanceX);
    double pA_magnitude = Math.Sqrt(particleA.Velocity.X * particleA.Velocity.X + particleA.Velocity.Y * particleA.Velocity.Y);
    double pB_magnitude = Math.Sqrt(particleB.Velocity.X * particleB.Velocity.X + particleB.Velocity.Y * particleB.Velocity.Y);
    double pA_direction = Math.Atan2(particleA.Velocity.Y, particleA.Velocity.X);
    double pB_direction = Math.Atan2(particleB.Velocity.Y, particleB.Velocity.X);
    double pA_newVelocityX = pA_magnitude * Math.Cos(pA_direction - collisionAngle);
    double pA_newVelocityY = pA_magnitude * Math.Sin(pA_direction - collisionAngle);
    double pB_newVelocityX = pB_magnitude * Math.Cos(pB_direction - collisionAngle);
    double pB_newVelocityY = pB_magnitude * Math.Sin(pB_direction - collisionAngle);
    double pA_finalVelocityX = ((particleA.Mass - particleB.Mass) * pA_newVelocityX + (particleB.Mass + particleB.Mass) * pB_newVelocityX) / (particleA.Mass + particleB.Mass);
    double pB_finalVelocityX = ((particleA.Mass + particleA.Mass) * pA_newVelocityX + (particleB.Mass - particleA.Mass) * pB_newVelocityX) / (particleA.Mass + particleB.Mass);
    double pA_finalVelocityY = pA_newVelocityY;
    double pB_finalVelocityY = pB_newVelocityY;
    particleA.Velocity = new Vector2((float)(Math.Cos(collisionAngle) * pA_finalVelocityX + Math.Cos(collisionAngle + Math.PI / 2) * pA_finalVelocityY), (float)(Math.Sin(collisionAngle) * pA_finalVelocityX + Math.Sin(collisionAngle + Math.PI / 2) * pA_finalVelocityY));
    particleB.Velocity = new Vector2((float)(Math.Cos(collisionAngle) * pB_finalVelocityX + Math.Cos(collisionAngle + Math.PI / 2) * pB_finalVelocityY), (float)(Math.Sin(collisionAngle) * pB_finalVelocityX + Math.Sin(collisionAngle + Math.PI / 2) * pB_finalVelocityY));
}

Каждый шар или частица появляются со случайной массой и радиусом.

Функция вызывается внутри метода типа обновления, например:

Vector2 globalGravity = new Vector2(0f, gravityScale / 6000);

    for (int i = 0; i < particles.Count(); i++)
{
    particles[i].Update((float)updateTimer.Interval, globalGravity);
    Vector2 position = particles[i].Position;
    Vector2 velocity = particles[i].Velocity;
    collisionWallCheck(ref position, ref velocity, particles[i].Radius);
    particles[i].Position = position;
    particles[i].Velocity = velocity;


    Particle pA = particles[i];
    for (int k = i + 1; k < particles.Count(); k++)
    {
        Particle pB = particles[k];
        Vector2 delta = pA.Position - pB.Position;
        float dist = delta.Length();

        if (dist < particles[i].Radius + particles[k].Radius && !particles[i].Colliding && !particles[k].Colliding)
        {
            particles[i].Colliding = true;
            particles[k].Colliding = true;
            manageCollision(particles[i], particles[k]);
            particles[i].initColorTable(); // Upon collision, change the color
            particles[k].initColorTable();
            totalCollisions++;
        }
        else
        {
            particles[i].Colliding = false;
            particles[k].Colliding = false;
        }
    }
}

Я сохраняю начальное положение, скорость и массу каждого шара.

Что мне, по-видимому, нужно сделать и не знаю, как реализовать, это:

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

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

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

void updateTimer_Tick(object sender, EventArgs e)
{
    const double G = 6.67398 * 0.00000000001;

    for (int i = 0; i < particles.Count(); i++)
    {
        double sumX = 0;
        double sumY = 0;

        Particle pA = particles[i];
        for (int k = i + 1; k < particles.Count(); k++)
        {
            Particle pB = particles[k];
            Vector2 delta = pA.Position - pB.Position;
            float dist = delta.Length();

            if (dist < particles[i].Radius + particles[k].Radius && !particles[i].Colliding && !particles[k].Colliding)
            {
                particles[i].Colliding = true;
                particles[k].Colliding = true;
                manageCollision(particles[i], particles[k]);
                particles[i].initColorTable();
                particles[k].initColorTable();
                totalCollisions++;
                particles[i].Colliding = false;
                particles[k].Colliding = false;
            }
            else
            {
                double distanceX = particles[i].Position.X - particles[k].Position.X;
                double distanceY = particles[i].Position.Y - particles[k].Position.Y;
                double r = Math.Sqrt(Math.Pow(distanceX, 2) + Math.Pow(distanceY, 2));
                double force = G * particles[i].Mass * particles[k].Mass / (r * r);
                double theta = Math.Tan(distanceY / distanceX);
                sumX += force * Math.Cos(theta);
                sumY += force * Math.Sin(theta);
                particles[i].Colliding = false;
                particles[k].Colliding = false;
            }
        }
        double netForce = Math.Sqrt(Math.Pow(sumX, 2) + Math.Pow(sumY, 2));
        double a = netForce / particles[i].Mass;
        double aTheta = Math.Tan(sumY / sumX);

        // Here we get accelerations for X and Y.  You can probably figure out velocities from here.
        double aX = a * Math.Cos(aTheta);
        double aY = a * Math.Sin(aTheta);
        Vector2 accel = new Vector2((float)aX, (float)aY);

        particles[i].Update((float)updateTimer.Interval, accel);
        //particles[i].Update((float)updateTimer.Interval, globalGravity);
        Vector2 position = particles[i].Position;
        Vector2 velocity = particles[i].Velocity;
        collisionWallCheck(ref position, ref velocity, particles[i].Radius);
        particles[i].Position = position;
        particles[i].Velocity = velocity + accel;
    }
    Draw();
}

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

        public void Update(float timeStep, Vector2 gravity)
        {
            velocity = velocity + timeStep * gravity;
            position = position + timeStep * velocity;
        }

Теперь я не уверен, как бороться с случаями 0.


person Edge    schedule 21.03.2013    source источник


Ответы (2)


Начните с расчета силы тяжести, действующей на каждый объект. Это дается

F = Gm1m2/r*r

где m1 и m2 – массы двух объектов, G – гравитационная постоянная, а r – расстояние между двумя объектами. .

Теперь r — это вектор, поэтому вы можете разделить его на отдельные компоненты — Fx и Fy. Вы можете сделать это следующим образом:

Fx = F * cos(theta)
Fy = F * sin(theta)

Для каждой массы рассчитайте силу тяжести, действующую на нее и на любой другой объект. Суммируйте векторы, чтобы получить результирующую силу гравитации. (Обратите внимание - эта ссылка доступна для вашего интереса, но занимает много времени, чтобы добраться до сути). В этот момент у вас будет результирующая сила, действующая на каждый объект, из которой вы сможете рассчитать ускорение. Вот код, чтобы добраться до этой точки:

const double G = 6.67398 * 0.00000000001;

for (int i = 0; i < particles.Count(); i++)
{
    double sumX = 0;
    double sumY = 0;

    for (int j = 0; j < particles.Count(); j++)
    {
        // Don't add attraction to self
        if (i == j)
            continue;

        double distanceX = particles[i].Position.X - particles[j].Position.X;
        double distanceY = particles[i].Position.Y - particles[j].Position.Y;
        double r = Math.Sqrt(Math.Pow(distanceX, 2) + Math.Pow(distanceY, 2));
        double force = G * particles[i].Mass * particles[j].Mass / (r * r);
        double theta = Math.Tan(distanceY / distanceX);
        sumX += force * Math.Cos(theta);
        sumY += force * Math.Sin(theta);
    }

    double netForce = Math.Sqrt(Math.Pow(sumX, 2) + Math.Pow(sumY, 2));
    double a = netForce / particles[i].Mass;
    double aTheta = Math.Tan(sumY / sumX);

    // Here we get accelerations for X and Y.  You can probably figure out velocities from here.
    double aX = a * Math.Cos(aTheta);
    double aY = a * Math.Sin(aTheta);
}

ПРИМЕЧАНИЯ

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

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

Еще одна вещь, которую стоит отметить: это алгоритм O (n ^ 2), поэтому, если у вас больше нескольких тел, потребуется много усилий. К сожалению, так оно и есть; если вы найдете быстрый способ рассчитать гравитационное притяжение для большого количества тел, вам, вероятно, следует позвонить в НАСА.

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

person Steve Westbrook    schedule 21.03.2013
comment
Очень хорошее объяснение, спасибо. Куда нужно добавить этот код? - person Edge; 21.03.2013
comment
Вы должны объединить код, а не просто вставить его. Материал, который я добавил, вычисляет ускорение силы тяжести для каждой частицы; вы захотите добавить это к ускорению из-за других сил (столкновений и т. д.). Затем используйте общее ускорение (которое должно быть Vector2), чтобы определить вашу новую скорость, а затем положение. Суть в том, что вы должны отделить вычисление того, где будут частицы, от назначения позиции — вычислить все в пакете, а затем обновить в пакете. - person Steve Westbrook; 21.03.2013
comment
Я попытался реализовать ваше предложение, однако шары теперь вообще не отображаются. Я предполагаю, что я сделал что-то не так. - person Edge; 21.03.2013
comment
aX и aY не являются числами. - person Edge; 21.03.2013
comment
netForce, похоже, остается на уровне 0 или на очень низком уровне. а затем делит 0 на массу частицы. - person Edge; 21.03.2013
comment
Хорошо, я не уверен, как бороться с постоянным появлением 0. - person Edge; 21.03.2013

Зная положение всех шариков и их массы, можно вычислить вектор силы, действующей между любыми двумя телами. Найдите вектор от шара «А» ко всем другим шарам — «А» к шару «В», «А» к «С», «А» к «D» и т. д. Затем просто добавьте все векторы А до получить окончательный вектор силы, действующей на A. Повторите для B -> A, B -> C и т. д., чтобы найти вектор B. Сделайте это для всех, рассчитайте новую скорость и отрегулируйте позиции на время между шагами.

person Steve    schedule 21.03.2013