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

Вначале рассмотрим случай, когда изменяется только normalLengthAS, но частицы к нити не добавляются.

Без добавления частиц

Процесс развертывания описывается классом WeightlessSegmentRope — подклассом класса Application.

const unsigned numberOfParticles = 2;


class WeightlessSegmentRope : public Application
{
    Particle particle[numberOfParticles];
    ForceRegistry registry;

    const double stiffness;
    double normalLengthAS;
    const double normalLength;

    const double deploymentVelosity;

    Vector3 anchor;
    AnchoredSpring anchoredSpring;
    Spring spring[2*(numberOfParticles-1)];

    double accumulator;
    const double dt;

    void render();

public:

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

По сравнению с классом Rope, в новом классе выделена в особую характеристику длина normalLengthAS. Для хранения скорости развертывания нити добавлено поле deploymentVelosity.

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

WeightlessSegmentRope::WeightlessSegmentRope()
: stiffness(20.0),
  normalLengthAS(0.1), // должно быть < (10.0/numberOfParticles)
  normalLength((10.0-normalLengthAS)/(numberOfParticles-1)), // numberOfParticles > 1
  deploymentVelosity(1.5),
  anchor(0.0, 20.0, 20.0),
  anchoredSpring(&anchor, stiffness, normalLengthAS),
  accumulator(0.0),
  dt(0.0001)
{
    for (unsigned i = 0; i < numberOfParticles; i++) {
        particle[i].setPosition(0.0, 20.0, 20+(numberOfParticles-i)*normalLength);
        particle[i].setVelocity(0.0, 0.0, 0.0);
        particle[i].setMass(2.0/numberOfParticles);
        particle[i].setDamping(0.9);
        particle[i].setAcceleration(0.0, -10.0, 0.0);
        particle[i].clearAccumulator();
    }

    ...

    registry.add(&particle[numberOfParticles-1], &anchoredSpring);

    ...
}

В методе update() добавилось вычисление новой длины normalLengthAS:

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

    if (duration > 0.01 )
        duration = 0.01; // максимальная длительность кадра, во избежание "спирали смерти"

    accumulator += duration;

    while (accumulator >= dt)
    {
        // Обнуляем действующие силы
        for (unsigned i = 0; i < numberOfParticles; i++)
            particle[i].clearAccumulator();

        // вычислим новое значение длины нерастянутой нити
        // для развертываемого отрезка
        normalLengthAS += deploymentVelosity * dt;
        anchoredSpring.init(&anchor, stiffness, normalLengthAS);

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

        for (unsigned i = 0; i < numberOfParticles; i++)
            particle[i].integrate(dt);

        accumulator -= dt;
    }

    Application::update();
}

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

Кадр из анимации развертывания нити без добавления частиц.

Добавляем частицы

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

Основной класс приложения MassRope отличается от своего предшественника WeightlessRope только тем, что в нем зарезервировано место под массивы particle и spring, зависящее от максимально возможного числа частиц — maxNumberOfParticles. numberOfParticles определяет количество частиц в данный момент времени.

const unsigned maxNumberOfParticles = 20;
unsigned numberOfParticles = 2;


class MassRope : public Application
{
    Particle particle[maxNumberOfParticles];
    ForceRegistry registry;

    const double stiffness;
    double normalLengthAS;
    const double normalLength;

    const double deploymentVelosity;

    Vector3 anchor;
    AnchoredSpring anchoredSpring;
    Spring spring[2*(maxNumberOfParticles-1)];

    ...
};

Соответствующие изменения внесены и в конструктор класса:

MassRope::MassRope()
: stiffness(20.0),
  normalLengthAS(0.1), // должно быть больше 10.0/maxNumberOfParticles
  normalLength((10.0-normalLengthAS)/(maxNumberOfParticles-1)),
  deploymentVelosity(1.5),
  anchor(0.0, 20.0, 20.0),
  anchoredSpring(&anchor, stiffness, normalLengthAS),
  accumulator(0.0),
  dt(0.0001)
{
    for (unsigned i = 0; i < numberOfParticles; i++) {
        particle[i].setPosition(0.0, 20.0, 20+(numberOfParticles-i)*normalLength);
        particle[i].setVelocity(0.0, 0.0, 0.0);
        particle[i].setMass(2.0/maxNumberOfParticles);
        particle[i].setDamping(0.9);
        particle[i].setAcceleration(0.0, -10.0, 0.0);
        particle[i].clearAccumulator();
    }

    ...

}

Основного внимания заслуживает метод update(). Сначала опишем процесс добавления частицы на псевдокоде.

while (accumulator >= dt)
{
    Обнуляем действующие силы.
    Если нить развернута не полностью, то
    {
        Вычислим новое значение длины нерастянутой нити
                                       для развертываемого отрезка.
        Если развертываемый отрезок достиг нужной длины
        и можно добавить к нити новую частицу, то
        {
            Удалим связь между ближайшей частицей и креплением.
            Добавим новую частицу.
            Вычислим и установим характеристики новой частицы.
            Соединим пружинами новую и ближайшую частицы.
            Вычислим длину связи новой частицы с креплением.
            Соединим новую частицу с креплением.
        }
    }
    Выполняем шаг моделирования.
}

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

void MassRope::update()
{
    ...

    while (accumulator >= dt)
    {
        // Обнуляем действующие силы
        for (unsigned i = 0; i < numberOfParticles; i++)
            particle[i].clearAccumulator();

        // Если нить развернута не полностью, то
        bool canAddNewParticle = numberOfParticles < maxNumberOfParticles;
        if (canAddNewParticle || (normalLengthAS < normalLength))
        {
            // вычислим новое значение длины нерастянутой нити
            // для развертываемого отрезка
            normalLengthAS += deploymentVelosity * dt;
            anchoredSpring.init(&anchor, stiffness, normalLengthAS);

            // Если развертываемый отрезок достиг нужной длины
            // и можно добавить к нити новую частицу, то сделаем это
            if (canAddNewParticle && (normalLengthAS > normalLength))
            {
                // удалим связь между ближайшей частицей и креплением
                registry.remove(&particle[numberOfParticles-1], &anchoredSpring);

                // вычислим координаты и скорость новой частицы и...
                Vector3 newParticlePosition =
                        particle[numberOfParticles-1].getPosition() - anchor;
                newParticlePosition.normalise();
                double length = normalLengthAS - normalLength;
                newParticlePosition *= length;
                newParticlePosition += anchor;

                Vector3 newParticleVelocity =
                        particle[numberOfParticles-1].getVelocity();
                newParticleVelocity.normalise();
                newParticleVelocity *= length;
                // установим ее характеристики
                particle[numberOfParticles].setPosition(newParticlePosition);
                particle[numberOfParticles].setVelocity(newParticleVelocity);
                particle[numberOfParticles].setMass(2.0/maxNumberOfParticles);
                particle[numberOfParticles].setDamping(0.9);
                particle[numberOfParticles].setAcceleration(0.0, -10.0, 0.0);
                particle[numberOfParticles].clearAccumulator();

                // соединим пружинами новую и ближайшую частицы
                int ind = 2*(numberOfParticles-1); // индекс, следующий за индексом последней пружины в spring[]
                spring[ind] = Spring(&particle[numberOfParticles], stiffness, normalLength);
                spring[ind+1] = Spring(&particle[numberOfParticles-1], stiffness, normalLength);

                registry.add(&particle[numberOfParticles-1], &spring[ind]);
                registry.add(&particle[numberOfParticles], &spring[ind+1]);

                // изменим длину связи с креплением
                normalLengthAS -= normalLength;
                anchoredSpring.init(&anchor, stiffness, normalLengthAS);

                // соединим новую частицу с креплением
                registry.add(&particle[numberOfParticles], &anchoredSpring);

                // увеличим счетчик частиц
                numberOfParticles++;
            }
        }

    ...
}

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

  1. новая частица помещается на отрезке между точкой крепления и ближайшей (до появления новой) частицы нити, на расстоянии (normalLengthAS - normalLength) от точки крепления;
  2. скорость новой частицы совпадает по направлению со скоростью ближайшей частицы и пропорциональна расстоянию между частицей и точкой крепления.

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

Кадр из анимации развертывания нити



Комментарии

comments powered by Disqus