Рассмотрим способ моделирования развертывания нити, при котором изменяется только длина связи между точкой крепления и ближайшей к ней частицей нити (длина пружины anchoredSpring
— normalLengthAS
), а длины связей между соседними частицами нити 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++;
}
}
...
}
Координаты и скорость новой частицы рассчитываются следующим образом. Предполагается, что
- новая частица помещается на отрезке между точкой крепления и ближайшей (до появления новой) частицы нити, на расстоянии
(normalLengthAS - normalLength)
от точки крепления; - скорость новой частицы совпадает по направлению со скоростью ближайшей частицы и пропорциональна расстоянию между частицей и точкой крепления.
Комментарии
comments powered by Disqus