В рассмотренном примере с бросанием мяча отсутствовали действующие на тело силы. Точнее, они присутствовали опосредованно, через ускорение.

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

Можно описывать каждую силу по отдельности, и прикладывать их перед интегрированием на каждом шаге update(). Однако, если сил будет достаточно много, то такой подход приведет к мессиву в коде. Поэтому мы поступим иначе: создадим абстрактный базовый класс Force, и каждая сила будет описываться классом-наследником от Force.

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

class Force
{
public:

    virtual void updateForce(Particle *particle, double duration) = 0;
};

Задача метода updateForce() — обновить значение силы и приложить ее к указанной частице. Кроме того, в метод передается параметр duration — промежуток времени, в течение которого прикладывается сила. Для описания движения частиц он нам не нужен, но может пригодиться при моделировании твердых тел.

Частицы, на которые действуют силы, не "знают" ничего о деталях описания сил. Для них это будут просто векторные величины, накапливающиеся в переменной forceAccum. Добавим в модуль particle метод addForce()

void Particle::addForce(const Vector3 &force)
{
    forceAccum += force;
}

Опишем теперь силу гравитации, как класс Gravity, основанный на классе Force.

class Gravity : public Force
{
    Vector3 gravity;

public:

    Gravity(const Vector3 &gravity);

    virtual void updateForce(Particle *particle, double duration);
};

Gravity::Gravity(const Vector3& gravity)
    : gravity(gravity)
{
}

void Gravity::updateForce(Particle* particle, double duration)
{
    // Тела с бесконечной массой исключаются из расчетов
    if (!particle->hasFiniteMass()) return;

    particle->addForce(gravity * particle->getMass());
}

Класс Gravity имеет единственное поле — вектор ускорения силы тяжести. Для приложения силы к частице используется метод addForce() частицы.

Классы Forces и Gravity поместим в модуль forces — своеобразную библиотеку сил.

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

#include <GL/glut.h>
#include "engine/app.h"
#include "engine/timing.h"
#include "engine/particle.h"
#include "engine/forces.h"

class Ball : public Application
{
    Particle particle;
    Gravity *gravity;

    void render();

public:

    Ball();
    virtual const char* getTitle() { return "Ball"; }
    virtual void update();
    virtual void display();
};

Ball::Ball()
{
    particle.setPosition(0.0, 2.0, 0.0);
    particle.setVelocity(0.0, 0.0, 35.0);
    particle.setMass(2.0);
    particle.setDamping(0.99);
    particle.clearAccumulator();

    const Vector3 g(0.0, -1.0, 0.0);
    gravity = new Gravity(g);
}

...

void Ball::update()
{
    // Обнуляем действующие силы
    particle.clearAccumulator();

    // Находим длительность последнего кадра, в секундах
    double duration = (double) TimingData::get().lastFrameDuration * 0.001;
    if (duration <= 0.0) return;

    // Выполняем шаг моделирования
    gravity->updateForce(&particle, duration);
    particle.integrate(duration);

    ...

    Application::update();
}

...

Код примера полностью

Хотя действие силы тяжести теперь моделируется при помощи объектов класса Gravity, мы сохраним поле acceleration в классе Particle. Польза от этого поля будет видна из дальнейшего изложения.

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

class AnchoredSpring : public Force
{
protected:
    // Координаты закрепленного конца пружины
    Vector3 *anchor;

    // Коэффициент жесткости пружины
    double springConstant;

    // Длина нерастянутой пружины
    double restLength;

public:
    AnchoredSpring();

    AnchoredSpring(Vector3 *anchor,
                   double springConstant,
                   double restLength);

    // Возвращает точку крепления
    const Vector3* getAnchor() const
    {
        return anchor;
    }

    // Устанавливает свойства пружины
    void init(Vector3 *anchor,
              double springConstant,
              double restLength);

    virtual void updateForce(Particle *particle, double duration);
};

Сила упругости описывается законом Гука

F = (restLength - magnitude) * springConstant * unit_vector

где restLength — длина ненатянутой пружины; magnitude — расстояние между частицей и точкой крепления; springConstant — коэффициент жесткости пружины; unit_vector — единичный вектор, направленный вдоль прямой, соединяющей частицу с точкой крепления.

AnchoredSpring::AnchoredSpring(Vector3 *anchor,
                               double sc, double rl)
    : anchor(anchor), springConstant(sc), restLength(rl)
{
}

void AnchoredSpring::init(Vector3 *anchor, double springConstant,
                          double restLength)
{
    AnchoredSpring::anchor = anchor;
    AnchoredSpring::springConstant = springConstant;
    AnchoredSpring::restLength = restLength;
}

void AnchoredSpring::updateForce(Particle* particle, double duration)
{
    // Вычисляем вектор расстояния между частицей и точкой крепления
    Vector3 force;
    particle->getPosition(&force);
    force -= *anchor;

    // Вычисляем величину силы
    double magnitude = force.magnitude();
    magnitude = (restLength - magnitude) * springConstant;

    // Вычисляем направление действия силы, умножаем на ее величину,
    // и прикладываем силу
    force.normalise();
    force *= magnitude;
    particle->addForce(force);
}

Рассмотрим теперь основной класс приложения — Pendulum. Обратите внимание на метод update().

class Pendulum : public Application
{
    Particle particle;

    Gravity *gravity;

    Vector3 anchor;
    AnchoredSpring *as;

    ...

public:

    Pendulum();
    ...
    virtual void update();
    ...
};

Pendulum::Pendulum()
: anchor(0.0, 15.0, 20.0)
{
    particle.setPosition(0.0, 15.0, 30.0);
    particle.setVelocity(0.0, 0.0, 0.0);
    particle.setMass(1.0);
    particle.setDamping(0.99);
    particle.clearAccumulator();

    const Vector3 g(0.0, -1.0, 0.0);
    gravity = new Gravity(g);

    as = new AnchoredSpring(&anchor, 1.0, 10.0);
}

void Pendulum::update()
{
    // Обнуляем действующие силы
    particle.clearAccumulator();

    // Находим длительность последнего кадра, в секундах
    double duration = (double) TimingData::get().lastFrameDuration * 0.001;
    if (duration <= 0.0) return;

    // Выполняем шаг моделирования
    gravity->updateForce(&particle, duration);
    as->updateForce(&particle, duration);

    particle.integrate(duration);

    Application::update();
}

Здесь яснее видны стоящие перед нами проблемы. На каждом шаге моделирования (update()) мы должны обновлять все действующие на систему силы. А если их не две, а десять? И, к тому же, в системе сотни частиц, причем на каждую действует свой набор сил? Значит необходим какой-то реестр для учета этих сил и частиц, на которые они действуют. Тогда в update() можно будет скомандовать: "обновить все зарегистрированные силы".

Код примера полностью

Кадр из анимации движения маятника.



Комментарии

comments powered by Disqus