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

Наш реестр будет содержать записи вида:

// Запись, указывающая какая сила к какой частице относится
struct ForceRegistration
{
    Particle *particle;
    Force *force;
};

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

class ForceRegistry
{
protected:

    struct ForceRegistration
    {
        ...
    };

    // Реестр действующих сил
    typedef std::vector<ForceRegistration> Registry;
    Registry registrations;

public:

    void add(Particle* particle, Force *force);
    void remove(Particle* particle, Force *force);
    void clear();
    void updateForces(double duration);
};

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

Реализация методов класса ForceRegistry достаточно очевидна:

void ForceRegistry::add(Particle* particle, Force *force)
{
    ForceRegistry::ForceRegistration registration;
    registration.particle = particle;
    registration.force = force;
    registrations.push_back(registration);
}

void ForceRegistry::updateForces(double duration)
{
    Registry::iterator i = registrations.begin();
    for ( ; i != registrations.end(); i++)
        i->force->updateForce(i->particle, duration);
}

Остальные методы нам пока не нужны.

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

class Pendulum : public Application
{
    Particle particle;
    ForceRegistry registry;

    const Vector3 g;
    Vector3 anchor;

    Gravity gravity;
    AnchoredSpring as;

    ...

public:

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

Pendulum::Pendulum()
: g(0.0, -1.0, 0.0),
  anchor(0.0, 15.0, 20.0),
  gravity(g),
  as(&anchor, 1.0, 10.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();

    registry.add(&particle, &gravity);
    registry.add(&particle, &as);
}

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

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

    // Выполняем шаг моделирования
    registry.updateForces(duration);

    particle.integrate(duration);

    Application::update();
}

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

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

Прежде всего опишем силу упругости, связывающую две частицы. Соответствующий класс Spring имеет вид:

class Spring : public Force
{
    // Частица, расположенная на другом конце пружины
    Particle *other;

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

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

public:

    Spring() {};
    Spring(Particle *other, double springConstant, double restLength);

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

Реализация методов этого класса мало чем отличается от приведенной в классе AnchoredSpring.

Приведем теперь наиболее существенные фрагменты кода класса ChainShot, реализующего поведение системы.

class ChainShot : public Application
{
    Particle particle[2];
    ForceRegistry registry;

    const Vector3 g;

    Gravity gravity;
    Spring spring[2];

    ...

public:

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

ChainShot::ChainShot()
: g(0.0, -1.0, 0.0),
  gravity(g)
{
    particle[0].setPosition(0.0, 2.0, 0.0);
    particle[0].setVelocity(0.0, 0.0, 35.0);
    particle[1].setPosition(0.0, 3.0, 0.0);
    particle[1].setVelocity(0.0, 0.0, 30.0);
    for (unsigned i = 0; i < 2; i++) {
        particle[i].setMass(2.0);
        particle[i].setDamping(0.99);
        particle[i].clearAccumulator();
    }

    for (unsigned i = 0; i < 2 - 1; i++) {
        spring[i] = Spring(&particle[i+1], 10.0, 1.0);
        spring[i+1] = Spring(&particle[i], 10.0, 1.0);
    }

    for (unsigned i = 0; i < 2; i++) {
        registry.add(&particle[i], &gravity);
        registry.add(&particle[i], &spring[i]);
    }
}

void ChainShot::update()
{
    // Обнуляем действующие силы
    for (unsigned i = 0; i < 2; i++)
        particle[i].clearAccumulator();

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

    // Выполняем шаг моделирования
    registry.updateForces(duration);

    for (unsigned i = 0; i < 2; i++)
        particle[i].integrate(duration);

    ...

    Application::update();
}

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

Кадр из анимации движения цепного книппеля.

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



Комментарии

comments powered by Disqus