Итак, начнем реализовывать задачи, поставленные в статье. Я опишу только ключевые моменты, все остальное вы найдете в исходниках.
Я буду использовать графическую библиотеку SFML версии 2.3.2 и некоторые элементы библиотеки OpenGL (все необходимое уже включено в SFML).
Для моделирования частицы я создал класс Particle
#include <iostream> #include <SFML\Graphics.hpp> #include <math.h> #include <vector> #pragma once using namespace std; class Particle { private: vector <sf::Vector2f> forces; // Скорости, которые воздействуют на частицу sf::Vector2f position; // Позиция частицы sf::Vector2f main_force; // Результирующая сила sf::Vector2f speed; // Скорость частицы int mass; // Масса частицы public: Particle(); Particle(int m, sf::Vector2f pos); sf::Vector2f getSpeed() noexcept { return speed; }; sf::Vector2f getPosition() const noexcept { return position; } sf::Vector2f getMainForce() noexcept; // Находит результирующую силу void cler_force () noexcept; // Очищает std::vector, когда силы перестают действовать void addForce(sf::Vector2f & n_force) noexcept; // Добавляет новую силу void update(float && t) noexcept; // Обновляет все характеристики нашей точки ~Particle(); };
Теперь определимся, сколько точек мы будет отрисовывать и каким именно образом.
Я решил остановиться на 100`000 точек, хотя можно взять и намного больше, скажем 1`000`000.
Рисовать наши частицы (по факту - точки) можно несколькими способами. Однако мы сразу отбросим два из них - это рисование с помощью стандартных контейнеров SFML, таких как sf::Vertex и sf::VertexArray, они слишком неэффективны, да и предназначены несколько для другого.
Рисовать мы будем с помощью OpenGL, однако и тут есть несколько вариантов.
1. Рисовать каждую из 100`000 отдельно, используя функцию glVertex2d(x,y).
2. Рисовать сразу массив точек с помощью glVertexPointer() и glDrawArrays().
Второй вариант работает намного быстрее, так что выбираем его.
Для этого создаем двумерный динамический массив типа float (динамический - чтобы ненароком не наступить в переполненный стек)
float ** coords = new float * [NUMB_OF_BLOCK]; for (int i = 0; i < NUMB_OF_BLOCK; ++i) coords[i] = new float[PART_IN_BLOCK * 2]; //NUMB_OF_BLOCK и PART_IN_BLOCK - константы из const.h равные 100 и 1000
Чтобы было веселее - раскрасим наши точки, причем не единожды. Будем менять цвет точек в зависимости от их скорости.
Создаем похожий массив, только теперь тип его элементов - unsigned char
unsigned char ** colors = new unsigned char *[NUMB_OF_BLOCK]; for (int i = 0; i < NUMB_OF_BLOCK; ++i) colors[i] = new unsigned char[PART_IN_BLOCK * 3];
Комрад! Не забудь высвободить динамическую память!
Рассмотрим теперь, как именно добавлять силы к нашим частицам.
for (auto it = 0; it < all_part.size(); ++it) { if (LB_press) { sf::Vector2f mouse = sf::Vector2f(sf::Mouse::getPosition(window)); // Записываем координаты мышки относительно нашего окна all_part[it].addForce((mouse - all_part[it].getPosition()) * (G / pow(getDistance(move(mouse), all_part[it].getPosition()) + 5, 2))); all_part[it].addForce(-all_part[it].getSpeed()*100.0f); // Тормозящая сила (сила сопротивления), вместе 100*f можно подставить любую магическую константу, чем больше - тем меньшее ускорение у частиц. } all_part[it].update(move(frame_time)); // Передает в update информацию о том, сколько времени ушло для вычисления всей информации на предыдущем кадре if (LB_press) all_part[it].cler_force(); // Очищает вектор скоростей }
Отдельно рассмотрим эту строчку
all_part[it].addForce((mouse - all_part[it].getPosition()) * (G / pow(getDistance(move(mouse), all_part[it].getPosition()) + 5, 2)));
В статье есть формула F = (N*G)/D^2
Здесь N = (mouse - all_part[it].getPosition()).
getDistance(move(mouse), all_part[it].getPosition()) - расстояние между точкой нажатия мышки и частицей it. После вычисления расстояния к нему прибавляется некоторая константа. Это сделано для того, чтобы при расстоянии существенно меньше единицы, сила, которую мы добавляем, не возрастала.
В начале я упоминал, что частицы будут изменять свой цвет в зависимости от скорости.
Реализуется это следующим образом:
for (int i = 0; i < NUMB_OF_BLOCK; ++i) { for (int j = 0; j < PART_IN_BLOCK; ++j) { // Цвета подобраны почти случайно, каждый может менять как горазд colors[i][3 * j] = 152; colors[i][3 * j + 1] = 50 + static_cast <int> (modOfVector(all_part[j + i*PART_IN_BLOCK].getSpeed())); // Находим модуль вектора скорости colors[i][3 * j + 2] = 10 + static_cast <int> (modOfVector(all_part[j + i*PART_IN_BLOCK].getSpeed())) ; } }
Функция modOfVector() просто определяет модуль вектора. В нашем случае - модуль вектора скорости. По хорошему - получившееся значение надо округлить по модулю 256, однако реализация функции OpenGL, которая будет работать с этим массивом, самостоятельно займется этим.
Source.cpp
#include <iostream> #include <math.h> #include <vector> #include <SFML\Graphics.hpp> #include "Particle.h" #include "const.h" #include <SFML\OpenGL.hpp> using namespace std; float modOfVector(sf::Vector2f & v) { // Находим модуль вектора return sqrt(v.x*v.x + v.y*v.y); } float getDistance(sf::Vector2f && v1, sf::Vector2f && v2) noexcept { // Растояние между двумя точками return sqrt(pow((v2.x - v1.x), 2) + pow((v2.y - v1.y), 2)); } void particleTransform(vector <Particle>const & part, float ** & i_vec){ // Функция переписывает координаты частиц из вектора, в двумерный массив for (int i = 0; i < NUMB_OF_BLOCK; ++i) { for (int j = 0; j < PART_IN_BLOCK; ++j) { i_vec[i][j * 2] = part[j + i*PART_IN_BLOCK].getPosition().x; i_vec[i][j * 2 + 1] = part[j + i*PART_IN_BLOCK].getPosition().y; } } } int main() { sf::ContextSettings set; set.antialiasingLevel = 8; // Устанавливаем уровень сглаживания, хотя можно и без него обойтись sf::RenderWindow window(sf::VideoMode(winW, winH), "PS", sf::Style::Default, set); // Создаем окно, в котором будем рисовать. winW & winH - константы из const.h vector <Particle> all_part; // Вектор частиц float ** coords = new float * [NUMB_OF_BLOCK]; // Двумерный динамический массив координат точек, используется for (int i = 0; i < NUMB_OF_BLOCK; ++i) coords[i] = new float[PART_IN_BLOCK * 2]; unsigned char ** colors = new unsigned char *[NUMB_OF_BLOCK]; // Аналогичный массив, хранящий информацию о цвете каждой точки for (int i = 0; i < NUMB_OF_BLOCK; ++i) colors[i] = new unsigned char[PART_IN_BLOCK * 3]; for (int i = 0; i < NUMB_OF_BLOCK; i++) { for (int j = 0; j < PART_IN_BLOCK; j++) {// sf::Vector2f(200, 100) - смешение для точек srand(time(0)); int mass = 10 + rand() % 70; // Выбираем случайую массу в диапазоне от 10 до 79 Particle tmp(mass, sf::Vector2f(20, 100) + sf::Vector2f(j*0.6, i*3)); // Создаем новую частицу all_part.push_back(tmp); coords[i][2*j] = tmp.getPosition().x; // Добавляем координаты частицы в массив coords[i][2*j +1] = tmp.getPosition().y; } } ////////////////// OpenGL settings glEnable(GL_POINT_SMOOTH); //Три функции приводят нашу квадратную точку к круглой glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glPointSize(1); // Размер точки - 1 пиксель glOrtho(0, winW, winH, 0, 1, -1); // Ориентирование нашей сцены ////////////////////////////////// bool LB_press = 0; sf::Clock clock; // Часы, чтобы замерять сколько времени уходит на обработку каждого кадра float frame_time = 0.1f; sf::Event event; while (window.isOpen()) { while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); } if (sf::Mouse::isButtonPressed(sf::Mouse::Left))// Тригер для нажатия ЛКМ LB_press = 1; else LB_press = 0; glClear(GL_COLOR_BUFFER_BIT); // Очистка буфера for (auto it = 0; it < all_part.size(); ++it) { if (LB_press) { // Если нажата ЛКМ, то добавляем силы к частицам sf::Vector2f mouse = sf::Vector2f(sf::Mouse::getPosition(window)); // Записываем координаты мышки относительно нашего окна all_part[it].addForce((mouse - all_part[it].getPosition()) * (G / pow(getDistance(move(mouse), all_part[it].getPosition()) + 5, 2))); all_part[it].addForce(-all_part[it].getSpeed()*100.0f); // Тормозящая сила (сила сопротивления), вместе 100*f можно подставить любую магическую константу, чем больше - тем меньшее ускорение у частиц. } //Обновляем частицу all_part[it].update(move(frame_time)); // Передает в update информацию о том, сколько времени ушло для вычисления всей информации на предыдущем кадре if (LB_press) all_part[it].cler_force(); // Очищаем вектор скоростей } glPushMatrix(); particleTransform(all_part, coords); // Переносим обновленные данные из вектора в массив glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); for (int i = 0; i < NUMB_OF_BLOCK; ++i) { for (int j = 0; j < PART_IN_BLOCK; ++j) { // Цвета подобраны почти случайно, каждый может менять как горазд colors[i][3 * j] = 152; colors[i][3 * j + 1] = 50 + static_cast <int> (modOfVector(all_part[j + i*PART_IN_BLOCK].getSpeed())); // Находим модуль вектора скорости colors[i][3 * j + 2] = 10 + static_cast <int> (modOfVector(all_part[j + i*PART_IN_BLOCK].getSpeed())) ; } } for (int i = 0; i < NUMB_OF_BLOCK; ++i) { glVertexPointer(2, GL_FLOAT, 0, coords[i]); // Передаем кол-во координат точки, формат координат и указатель на массив координат glColorPointer(3, GL_UNSIGNED_BYTE, 0, colors[i]); // Тоже самое, только для цвета. glDrawArrays(GL_POINTS, 0, PART_IN_BLOCK); // Функция отображения массива элементов. Передаем тип отображаемого объекта, смещение до следующего элемента (у нас 0, т.к. массив линейный) и размер массива. } glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_COLOR_ARRAY); glPopMatrix(); glFlush(); window.display(); // Отображаем кадр. frame_time = clock.restart().asSeconds(); // Записываем прошедшее время и перезапускаем часы. } for (int i = 0; i < NUMB_OF_BLOCK; ++i) // Освобождаем динамическую память. delete[] coords[i]; for (int i = 0; i < NUMB_OF_BLOCK; ++i) // Освобождаем динамическую память. delete[] colors[i]; return 0; }
Particle.h
#include <iostream> #include <SFML\Graphics.hpp> #include <math.h> #include <vector> #pragma once using namespace std; class Particle { private: vector <sf::Vector2f> forces; sf::Vector2f position; sf::Vector2f main_force; sf::Vector2f speed; int mass; public: Particle(); Particle(int m, sf::Vector2f pos); sf::Vector2f getSpeed() noexcept { return speed; }; sf::Vector2f getPosition() const noexcept { return position; } sf::Vector2f getMainForce() noexcept; void cler_force () noexcept; void addForce(sf::Vector2f & n_force) noexcept; void update(float && t) noexcept; ~Particle(); };
Particle.cpp
#include "Particle.h" Particle::Particle() // Стандартный конструктор не имеет реализации, т.к. не пригодился { } Particle::Particle(int m, sf::Vector2f pos) { // Конструктор инициализирует массу и начальное положение mass = m; position = pos; speed = sf::Vector2f(0,0); // Начальная скорость - 0 } void Particle::cler_force() noexcept { // Очищает std::vector от векторов силы. Т.е. убирает все силы, которые действуют на точку forces.clear(); } void Particle::addForce(sf::Vector2f & n_force) noexcept { // Добавляет вектор силы в std::vector. На точку теперь воздейтсвует новая сила forces.push_back(n_force); } sf::Vector2f Particle::getMainForce() noexcept {// Подсчитывает результирующую силу. Т.к. силу можно однозначно характеризовать вектором, мы просто складываем все вектора в std::vector sf::Vector2f res(0, 0); for (auto it = forces.begin(); it != forces.end(); ++it) res += *it; return res; } void Particle::update(float && t) noexcept { sf::Vector2f prevPos = position; // Сохраняем текущую позицию точку sf::Vector2f res_force = getMainForce(); // Находим результирующую силу position += 0.5f*res_force*t*t / static_cast <float>(mass) + speed*t; // Обновляем позицию на текущем кадре // Оба варианта определения скорости корректны - но второй работает несколько быстрее //speed = (res_force / static_cast <float>(mass)) * t + speed; // Обновляем скорость speed = (position - prevPos) / t; // Обновляем скорость } Particle::~Particle() { }
const.h
#pragma once const int winW = 800; // Ширина окна const int winH = 500; // Высота окна #ifdef DEBUG const int NUMB_OF_BLOCK = 10; const int PART_IN_BLOCK = 1000; #endif // DEBUG #ifdef RELEASE const int NUMB_OF_BLOCK = 100; const int PART_IN_BLOCK = 1000; #endif // RELEASE const float G = 300000;
Прикрепленный файл | Размер |
---|---|
Particle_System.zip | 1020.95 кб |
Комментарии
добрый день!
при попытке компиляции выдает
Source.obj : error LNK2001: неразрешенный внешний символ "__imp_glPointSize"
1>Source.obj : error LNK2001: неразрешенный внешний символ "__imp_glPopMatrix"
1>Source.obj : error LNK2001: неразрешенный внешний символ "__imp_glEnableClientState"
1>Source.obj : error LNK2001: неразрешенный внешний символ "__imp_glClear"
1>Source.obj : error LNK2001: неразрешенный внешний символ "__imp_glVertexPointer"
1>Source.obj : error LNK2001: неразрешенный внешний символ "__imp_glEnable"
1>Source.obj : error LNK2001: неразрешенный внешний символ "__imp_glDisableClientState"
1>Source.obj : error LNK2001: неразрешенный внешний символ "__imp_glBlendFunc"
1>Source.obj : error LNK2001: неразрешенный внешний символ "__imp_glDrawArrays"
1>Source.obj : error LNK2001: неразрешенный внешний символ "__imp_glFlush"
1>Source.obj : error LNK2001: неразрешенный внешний символ "__imp_glColorPointer"
1>Source.obj : error LNK2001: неразрешенный внешний символ "__imp_glPushMatrix"
1>Source.obj : error LNK2001: неразрешенный внешний символ "__imp_glOrtho"
Подскажите что не так.