Начнем, как в задачнике по физике. Пусть тело массой 2 кг брошено с высоты 2 м со скоростью 35 м/с. На тело действует сила тяжести, с ускорением, равным 1 м/с'^2^' (ускорение пришлось сильно уменьшить, по сравнению с реальным 9,81 м/с'^2^', чтобы тело пролетело подольше). Посмотрим, как это будет выглядеть на экране.

Для определенности будем считать, что в задаче речь идет о мяче. Наш мяч — это частица, поэтому класс Ball, отвечающий за его описание, включает в себя объект класса Particle.

class Ball : public Application
{
    Particle particle;

    void render();

public:

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

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

Ball::Ball()
{
    // Задаем свойства частицы
    particle.setMass(2.0); // 2.0 кг
    particle.setVelocity(0.0, 0.0, 35.0); // 35 м/с
    particle.setAcceleration(0.0, -1.0, 0.0);
    particle.setDamping(0.99);
    particle.setPosition(0.0, 2.0, 0.0);

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

В методе update() рассчитываются новые значения координат и скорости мяча, а также проверяется, не вышел ли он за границы сцены.

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

    // Вычисляем состояние частицы спустя duration
    particle.integrate(duration);

    // Проверяем, не вышла ли частица за пределы сцены
    if (particle.getPosition().y < 0.0f || particle.getPosition().z > 200.0f) {
        return;
    }

    Application::update();
}

Шаг расчетов по времени duration определяется на основе длительности последнего кадра. Позднее мы еще вернемся к этому вопросу.

Метод display() непосредственно строит изображение. Через каждые 10 метров мы нарисуем линии, чтобы было видно, насколько далеко улетел мяч.

void Ball::display()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    gluLookAt(-25.0, 8.0, 5.0,  0.0, 5.0, 22.0,  0.0, 1.0, 0.0);

    // Рисуем линии для контроля дальности выстрела
    glColor3f(0.75f, 0.75f, 0.75f);
    glBegin(GL_LINES);
    for (unsigned i = 0; i < 200; i += 10) {
        glVertex3f(-5.0f, 0.0f, i);
        glVertex3f(5.0f, 0.0f, i);
    }
    glEnd();

    render();
}

Метод render() рисует положение частицы.

void Ball::render()
{
    Vector3 position;
    particle.getPosition(&position);

    // Рисуем частицу
    glColor3f(0, 0, 0);
    glPushMatrix();
    glTranslatef(position.x, position.y, position.z);
    glutSolidSphere(0.3f, 5, 4);
    glPopMatrix();

    // Рисуем тень частицы
    glColor3f(0.75, 0.75, 0.75);
    glPushMatrix();
    glTranslatef(position.x, 0, position.z);
    glScalef(1.0f, 0.1f, 1.0f);
    glutSolidSphere(0.6f, 5, 4);
    glPopMatrix();
}

Нам еще нужно не забыть функцию, создающую объект класса Ball в функции main():

Application* getApplication()
{
    return new Ball();
}

Файл main.cpp также подвергся изменениям: в него пришлось добавить вызовы функций, контролирующих время расчетов.

#include ...
#include "engine/timing.h" // подключаем функции-таймеры

...

void update()
{
    // Обновить значение счетчика времени
    TimingData::get().update();

    app->update();
}

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    TimingData::init();

    // #1: Инициализация и создание окна GLUT
    app = getApplication();
    createWindow(app->getTitle());

    app->initGraphics();

    // #2: Регистрация функций-обработчиков событий
    glutReshapeFunc(reshape);
    glutDisplayFunc(display);
    glutIdleFunc(update);

    // #3: Запуск основного цикла GLUT
    glutMainLoop();

    // Очистка ресурсов
    TimingData::deinit();
}

В результате получим:

След от приземлившегося мяча.

Скачать полный код примера

След от приземлившегося мяча можно увидеть на восьмой от левого края линии. Значит мяч пролетел 70 метров.

Проверим полученный результат. Сначала найдем время полета t из соотношения

высота_конечная = высота_начальная - g * t^2 / 2

(g — ускорение свободного падения). Высота в конце полета равна нулю, а значит время полета составило t = 2 с.

Дальность полета равна:

Дальность полета = скорость * t.

Отсюда находим, что дальность полета равна 70 м, как и получилось при моделировании.



Комментарии

comments powered by Disqus