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