Класс для описания частицы, реализующей взрыв
Итак, создание нашего приложения мы начнем с разработки класса, описывающего частицу.Следующий класс, который мы разработает, будет имитировать простенький взрыв, оперируя большим количеством частиц, заданных первым разработанным классом. Класс, реализующий взрыв, будет хорошо параметризирован, тем самым демонстрируя то, как будет вести себя система частиц в данном приложении при различных настройках.
Создайте новый класс Partilce. Перейдите к редактированию файла Partilce.cs. Наш класс будет иметь ряд параметров, влияющих на расчет позиции частицы через определенный промежуток времени. Эти параметры могут быть установлены при инициализации частицы (передаются в конструктор класса), а также могут быть установлены напрямую.
Класс имеет ряд функций для изменения каких-либо параметров: например, изменение вектора скорости по одной из осей (необходимо для простого отражения частицы при столкновении).
Функция UpdatePosition принимает в качестве параметра текущий временной штамп и, используя разницу во времени после последнего обновления, вычисляет новую позицию частицы, а также отвечает за затухание ее ускорения. Текущие координаты частицы могут быть в любой момент времени получены с помощью функций GetPosition*().
Код класса довольно простой и полностью комментирован. Класс будет выглядеть следующим образом:
/*http://esate.ru, Anvi*/
namespace ParticlesSystems
{
class Partilce
{
// позиция частицы
private float[] position = new float[3];
// размер
private float _size;
// время жизни
private float _lifeTime;
// вектор гравитации
private float[] Grav = new float[3];
// ускорение частицы
private float[] power = new float[3];
// коэффициент затухания силы
private float attenuation;
// набранная скорость
private float[] speed = new float[3];
// временной интервал активации частицы
private float LastTime = 0;
// конструктор класса
public Partilce( float x, float y, float z, float size, float lifeTime, float start_time)
{
// записываем все начальные настройки частицы, устанавливаем начальный коэффициент затухания
// и обнуляем скорость и силу приложенную к частице
_size = size;
_lifeTime = lifeTime;
position[0] = x;
position[1] = y;
position[1] = z;
speed[0] = 0;
speed[1] = 0;
speed[2] = 0;
Grav[0] = 0;
Grav[1] = -9.8f;
Grav[2] = 0;
attenuation = 3.33f;
power[0] = 0;
power[0] = 0;
power[0] = 0;
LastTime = start_time;
}
// функция установки ускорения, действующего на частицу
public void SetPower( float x, float y, float z)
{
power[0] = x;
power[1] = y;
power[2] = z;
}
// инвертирование скорости частицы по заданной оси с указанным затуханием
// удобно использовать для простой демонстрации столкновений, например, с землей
public void InvertSpeed( int os, float attenuation)
{
speed[os] *= -1 * attenuation;
}
// получение размера частицы
public float GetSize()
{
return _size;
}
// установка нового значения затухания
public void setAttenuation( float new_value)
{
attenuation = new_value;
}
// обновление позиции частицы
public void UpdatePosition( float timeNow)
{
// определяем разницу во времени, прошедшего с последнего обновления
// позиции частицы (ведь таймер может быть не фиксированный)
float dTime = timeNow - LastTime;
_lifeTime -= dTime;
// обновляем последнюю отметку временного интервала
LastTime = timeNow;
// перерасчитываем ускорение, движущее частицу, с учетом затухания
for( int a = 0; a < 3; a++)
{
if (power[a] > 0)
{
power[a] -= attenuation * dTime;
if (power[a] <= 0)
power[a] = 0;
}
// перерасчитываем позицию частицы с учетом гравитации, вектора ускорения и прошедший промежуток времени
position[a] += (speed[a] * dTime + (Grav[a] + power[a]) * dTime * dTime);
// обновляем скорость частицы
speed[a] += (Grav[a] + power[a]) * dTime;
}
}
// проверка, не вышло ли время жизни частицы
public bool isLife()
{
if (_lifeTime > 0)
{
return true ;
}
else
{
return false ;
}
}
// получение координат частицы
public float GetPositionX()
{
return position[0];
}
public float GetPositionY()
{
return position[1];
}
public float GetPositionZ()
{
return position[2];
}
}
}
Теперь мы можем реализовывать различные поведения частиц. Мы реализуем класс, демонстрирующий «взрыв» на основе большого количества частиц. Класс будет отвечать за то, чтобы инициализировать массив частиц и в момент взрыва, поместив их в координаты взрыва, сгенерировать всем частицам случайное направление и случайное ускорение в пределах, заданных мощностью взрыва.
Далее выполняется просчет текущего взрыва и отрисовка частиц с использованием дисплейного списка:
/*http://esate.ru, Anvi*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tao.OpenGl;
using Tao.FreeGlut;
using Tao.Platform.Windows;
namespace ParticlesSystems
{
class Explosion
{
// позиция взрыва
private float[] position = new float[3];
// мощность
private float _power;
// максимальное количество частиц
private int MAX_PARTICLES = 1000;
// текущее установленное количество частиц
private int _particles_now;
// активирован
private bool isStart = false ;
// массив частиц на основе созданного ранее класса
private Partilce[] PartilceArray;
// дисплейный список для рисования частицы создан
private bool isDisplayList = false ;
// номер дисплейного списка для отрисовки
private int DisplayListNom = 0;
// конструктор класса, в него передаются координаты, где должен произойти взрыв, мощность и количество частиц
public Explosion( float x, float y, float z, float power, int particle_count)
{
position[0] = x;
position[1] = y;
position[2] = z;
_particles_now = particle_count;
_power = power;
// если число частиц превышает максимально разрешенное
if( particle_count > MAX_PARTICLES)
{
particle_count = MAX_PARTICLES;
}
// создаем массив частиц необходимого размера
PartilceArray = new Partilce[particle_count];
}
// функция обновления позиции взрыва
public void SetNewPosition( float x, float y, float z)
{
position[0] = x;
position[1] = y;
position[2] = z;
}
// установка нового значения мощности взрыва
public void SetNewPower( float new_power)
{
_power = new_power;
}
// создания дисплейного списка для отрисовки частицы (т.к. отрисовывать даже небольшой полигон такое количество раз - очень накладно)
private void CreateDisplayList()
{
// генерация дисплейного списка
DisplayListNom = Gl.glGenLists(1);
// начало создания списка
Gl.glNewList(DisplayListNom, Gl.GL_COMPILE);
// режим отрисовки треугольника
Gl.glBegin( Gl.GL_TRIANGLES);
// задаем форму частицы
Gl.glVertex3d(0, 0, 0);
Gl.glVertex3d(0.02f, 0.02f, 0);
Gl.glVertex3d(0.02f, 0, -0.02f);
Gl.glEnd();
// завершаем отрисовку частицы
Gl.glEndList();
// флаг - дисплейный список создан
isDisplayList = true ;
}
// функция, реализующая взрыв
public void Boooom( float time_start)
{
// инициализируем экземпляр класса Random
Random rnd = new Random();
// если дисплейный список не создан - надо его создать
if (!isDisplayList)
{
CreateDisplayList();
}
// по всем частицам
for ( int ax = 0; ax < _particles_now; ax++)
{
// создаем частицу
PartilceArray[ax] = new Partilce(position[0], position[1], position[2], 5.0f, 10, time_start);
// случайным образом генерируем ориентацию вектора ускорения для данной частицы
int direction_x = rnd.Next(1, 3);
int direction_y = rnd.Next(1, 3);
int direction_z = rnd.Next(1, 3);
// если сгенерировано число 2, то мы заменяем его на -1.
if (direction_x == 2)
direction_x = -1;
if (direction_y == 2)
direction_y = -1;
if (direction_z == 2)
direction_z = -1;
// задаем мощность в промежутке от 5 до 100% от указанной (чтобы частицы имели разное ускорение)
float _power_rnd = rnd.Next((int)_power/20, (int)_power);
// устанавливаем затухание, равное 50% от мощности
PartilceArray[ax].setAttenuation(_power / 2.0f);
// устанавливаем ускорение частицы, еще раз генерируя случайное число. Таким образом, мощность определится от 10 - до 100% полученной. Здесь же применяем ориентацию для векторов ускорения
PartilceArray[ax].SetPower(_power_rnd * ((float)rnd.Next(100, 1000) / 1000.0f) * direction_x, _power_rnd * ((float)rnd.Next(100, 1000) / 1000.0f) * direction_y, _power_rnd * ((float)rnd.Next(100, 1000) / 1000.0f) * direction_z);
}
// взрыв активирован
isStart = true ;
}
// калькуляция текущего взрыва
public void Calculate( float time)
{
// только в том случае, если взрыв уже активирован
if (isStart)
{
// проходим циклом по всем частицам
for ( int ax = 0; ax < _particles_now; ax++)
{
// если время жизни частицы еще не вышло
if (PartilceArray[ax].isLife())
{
// обновляем позицию частицы
PartilceArray[ax].UpdatePosition(time);
// сохраняем текущую матрицу
Gl.glPushMatrix();
// получаем размер частицы
float size = PartilceArray[ax].GetSize();
// выполняем перемещение частицы в необходимую позицию
Gl.glTranslated(PartilceArray[ax].GetPositionX(), PartilceArray[ax].GetPositionY(), PartilceArray[ax].GetPositionZ());
// масштабируем ее в соответствии с ее размером
Gl.glScalef(size, size, size);
// вызываем дисплейный список для отрисовки частицы из кэша видеоадаптера
Gl.glCallList(DisplayListNom);
// возвращаем матрицу
Gl.glPopMatrix();
// отражение от "земли"
// если координата Y стала меньше нуля (удар о землю)
if (PartilceArray[ax].GetPositionY() < 0)
{
// инвертируем проекцию скорости на ось Y,
// как будто частица ударилась и отскочила от земли
// причем скорость затухает на 40%
PartilceArray[ax].InvertSpeed(1,0.6f);
}
}
}
}
}
}
}