ПрограммированиеСтатьиГрафика

Реализация взрыва при помощи системы частиц (2 стр)

Часть 2

В добавлении частицы присутствует процедура изменения размера динамического массива - SetLength. Используется она следующим образом. У нас есть массив, размер которого 0. В качестве параметров, задаем массив и новый размер. Размер массива считается, как произведение

Количество_Частиц * Размер_Записи_Одной_Частицы

При удалении мы меняем местами "индексовую" частицу и последнюю, а затем обрезаем массив, таким образом ненужная частица остается "за бортом" и удаляется из памяти.

procedure CParticleSystem.Delete(index:integer);
begin
  Swap(Particles[Amount],Particles[index]);
  Dec(Amount);
  SetLength(Particles,Amount*SizeOf(rModelParticle));
end;

При изменении частицы мы меняем ее яркость, положение и вектор движения. Вектор движения смещается посредством вектора гравитации. Положение изменяется вектором движения, а яркость - величиной затухания.

procedure CParticleSystem.Change;
var
  i,j:integer;
  dir:TVector3f;
  pos:TVector3f;
begin
  if Amount = 0 then exit;
  for i:=1 to Amount do
  begin
    for j:=0 to 2 do
    SummVectors3f(Particles[i].trio.v[j],Particles[i].Direction);
    SummVectors3f(Particles[i].Direction,Gravity);
    Particles[i].Life:=Particles[i].Life-Fade;
  end;
end;

Отрисовка частицы. Опять-таки ничего сложного. Просто рисуем частицу из массива.

procedure CParticleSystem.DrawSprite(index:integer);
var
  temp:rModelParticle;
begin

  temp:=Particles[index];

  glColor4f(Color.x,Color.y,Color.z,temp.Life);
   
  glBegin(GL_TRIANGLES);
    glTexCoord2f(0,0);  glVertex3fv(@temp.Trio.V[0]);
    glTexCoord2f(1,0);  glVertex3fv(@temp.Trio.V[1]);
    glTexCoord2f(0,1);  glVertex3fv(@temp.Trio.V[2]);
  glEnd;

end;

А Render просто автоматизирует процесс отрисовки всех частиц.

procedure CParticleSystem.Render;
var
  i:integer;
begin

  glEnable(gl_texture_2d);
  glBindTexture(gl_texture_2d,Texture);
 
  for i:=1 to Amount do
    DrawSprite(i);

end;

Ну и наконец, главная часть "взрывной" движок :). Мы будем добавлять в него массив модель и если взрыв будет активен, то мы будем изменять частицы.

unit BlowEngine;

interface
uses GEOMETRY,Windows,OpenGL,BlowParticles,MODEL;

type
CDynamite=class
  private
    procedure   CalcParticle(index:integer;Model:CGeomModel);
    function    CalcDirection(Tri:rTriangle):TVector3f;

  public
    Active         :boolean;             {Активен ли движок}
    Strength       :glfloat;             {Сила взрыва}
    Position       :TVector3f;           {Точка взрыв}         

    PSystem        :array of CParticleSystem;  {массив систем частиц}  
    PSAmount       :integer;             {количество систем частиц}

    Models    :array of CGeomMOdel;      {массив моделей}
    ModelAmount    :integer;             {количество моделей} 

    procedure   AddModel(Model:CGeomModel);
    procedure   DeleteModel(index:integer);
    procedure   Swap(var a,b:CParticleSystem);
    procedure   SwapModels(var a,b:CGeomModel);
    procedure   PSystemChange;
    procedure   Render;
    procedure   Switcher;
    procedure   Loop;

end;

Данный метод необходим для того, чтобы посчитать в каком направлении будет двигаться частица. Направление будем считать по трем векторам проведенным к каждой вершине частицы. Сложим их и разделим на 3.Получим вектор направления частицы.

function    CDynamite.CalcDirection(Tri:rTriangle):TVector3f;
var
  t,t2,t3,t4:TVector3f;
begin
  t:=CalcDifference3f(Tri.V[0],Position);
  t2:=CalcDifference3f(Tri.V[1],Position);
  t3:=CalcDifference3f(Tri.V[2],Position);
  SummVectors3f(t4,t);
  SummVectors3f(t4,t2);
  SummVectors3f(t4,t3);
  Multiple3F(t4,1/3);
  result:=t4;
end;

А саму частицу будем вводить по индексному треугольнику из модели.

implementation

procedure   CDynamite.CalcParticle(index:integer;Model:CGeomModel);
var
  trio:rTriangle;
  temp:TVector3f;
  cosin:glfloat;
  Normal:TVector3f;
begin
  trio:=Model.Triangles[index];
  temp:=CalcDirection(trio);
  Normalize3f(temp);

  SummVectors3f(Normal,trio.Normal[0]);
  SummVectors3f(Normal,trio.Normal[1]);
  SummVectors3f(Normal,trio.Normal[2]);
  Multiple3F(Normal,1/3);
  Normalize3f(Normal);

  {Считаем угол между нормалью треугольника и
  направлением его движения}
  cosin:=DotProduct3f(temp,Normal);

  temp:=CalcDifference3f(temp,Position);
  Multiple3F(temp,Strength*cosin);
      

  PSystem[PSAmount].Add(trio.v[0],trio.v[1],trio.v[2],temp);
end;

Метод для добавления модели в массив. Все просто, также как и с системами частиц.

procedure   CDynamite.AddModel(Model:CGeomModel);
var
  i:integer;
begin
  Inc(PSAmount);
  SetLength(PSystem,SizeOf(CParticleSystem)*PSAmount);
  PSystem[PSamount]:=CParticleSystem.Create;
  PSystem[PSAmount].Color:=CreateVector3f(0,0.5,1);

  Inc(ModelAmount);
  SetLength(Models,SizeOf(CGEomModel)*ModelAmount);
  Models[ModelAmount]:=CGeomModel.Create;
  Models[ModelAmount]:=Model;
     
  for i:=1 to Model.TrioAmount do
    CalcParticle(i,Model);
end;

Удаление аналогично. Меняем местами, изменяем размер. Все.

procedure   CDynamite.DeleteModel(index:integer);
begin
  Swap(PSystem[PSAmount],PSystem[index]);
  Dec(PSAmount);  
  SetLength(PSystem,SizeOf(CParticleSystem)*PSAmount);

  SwapModels(Models[ModelAmount],Models[index]);
  Dec(ModelAmount);  
  SetLength(Models,SizeOf(CGeomModel)*ModelAmount);

end;

Интересно, а сколько раз надо переписать Swap, чтобы понять его :)

procedure   CDynamite.Swap(var a,b:CParticleSystem);
var
  temp:CParticleSystem;
begin
  temp:=a;
  a:=b;
  b:=temp;
end;

procedure   CDynamite.SwapModels(var a,b:CGeomModel);
var
  temp:CGeomModel;
begin
  temp:=a;
  a:=b;
  b:=temp;
end;

Запускаем изменение всех систем частиц.

procedure   CDynamite.PSystemChange;
var
  i:integer;
begin
  for i:=1 to PSAmount do
  PSystem[i].Change;
end;

Если активен взрыв, то рисуем частицы. Если "не активен", то модель.

procedure   CDynamite.Render;
var
  i:integer;
begin
  if Active then
    For i:=1 to PSAmount do
      PSystem[i].Render
  else
    For i:=1 to ModelAmount do
      Models[i].Render;
end;

Простой Loop (цикл) для изменения мира.

procedure   CDynamite.Loop;
begin
  if Active then PSystemChange;
  Render;
end;

Метод для переключения "флага активности" на противоположное значение.

procedure   CDynamite.Switcher;
begin
  if Active=true then Active:=false
  else Active:=true;
end;

Ну и наконец реализация. Все просто. Объявляем движок и меняем его параметры. Затем запускаем на обработку и смотрим на результат.

unit Main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs,GEOMETRY,OpenGL,MODEL,BlowEngine,StdCtrls,ComCtrls;

........
{Оконные переменные и методы}
........
  {Процедура отрисовки нашей сцены}
  procedure MainScene;

Далее описываем переменные и реализацию наших процедур.

var
  DC:HDC;      {Контекст устройства}
  RC:HGLRC;    {Рендеринговый контекст}
  Tri:CGeomModel;  {Модель}
  Dyn:CDynamite;   {Взрывной движок}
  FileName:string; {Имя файла модели}
  F:text;      {Текстовый файл для считывания значений}
  Tex:gluint;  {Текстура}
  Texname:string;  {Имя текстуры}
  angle:glfloat;   {Угол поворота}
  streng:glfloat;  {Сила взрыва}
  s:string;    {Строковая переменная для временных нужд}

procedure MainScene;
begin
  {очищаем экранный буфер и буфер глубины}
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  glLoadIdentity;          
  glTranslatef(0,0,-30);      {отодвигаемся назад}
  glScalef(0.1,0.1,0.1);      {изменяем масштаб}
 
  glEnable(gl_blend);         {включаем смешивание цветов}
  glClearStencil(0);          {забиваем стенсил буфер нулями}
  glClear(gl_stencil_buffer_bit);      {очищаем стенсил}

  glEnable(GL_STENCIL_TEST);  {сключаем проверку стенсил буфера}

  {стенсил-тест проходит всегда,значения 1}
  glStencilFunc(GL_ALWAYS, 1, 1);

  {если стенсил прошел - оставляем буфер
  если глубинный прошел - оставляем 
  если и то и другое - заменяем значения}
  glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

  {способ отрисовки двухсторонняя заливка}
  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
  {цвет бирюзовый}
  glColor3f(0,0.5,1);

  Dyn.Loop;                 {главный цикл взрывного движка}

  {стенсил-тест проходит,если входные значения не
  сравнимы с исходными}
  glStencilFunc(GL_NOTEQUAL, 1, 1);           
  
  glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); {операции теже}

  glLineWidth(3);               {толщина обводки = 3}
  
  {способ рисования - только края}
  glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
  glColor3f(1,1,1);               {цвет белый}

  Dyn.Loop;      {и снова рисуем главный цикл}

  {изменяем значение угла,если совершаем ротацию}
  angle:=angle+1;
  {во избежания переполнения изменяем угол}
  if angle>360 then Angle:=360-angle;

  SwapBuffers(DC);   {свапим :) буферы}

end;

Стенсил буфер используется для того, чтобы обвести все наши треугольники по краям, так оно лучше смотрится.

А вот что должно быть написанно в процедуре создании формы:

procedure TForm1.FormCreate(Sender: TObject);
begin
  DC:=GetDC(form1.Handle);
  SetPixelFormat;
  rc:=wglCreateContext(DC);
  wglMakeCurrent(DC,RC);

  Tri:=CGeomModel.Create;      {создаем еденицу класса}

  AssignFile(F,'options.txt');    {открываем файл с опциями}
  Reset(f);          {-//-}
  readln(F,Filename);      {Считываем имя файла с моделью}
  readln(f,TexName);      {Считываем имя файла-текстуры}
  readln(f,s);        {считываем силу взрыва}
  CloseFile(F);        {закрываем файл}

  if not (Filename='') then      {если имя файла не нулевое,то...}
  tri.LoadFromFile(Filename);    {...грузим модель из этого файла}

  Dyn:=CDynamite.Create;      {создаем единицу взрывного движка}
  Dyn.Strength:=StrToFloat(s);    {сила из считанной строки}
  Dyn.Position:=CreateVector3f(0,0,0);      {Положение взрыва}
  Dyn.AddModel(Tri);      {Добавляем в список модель}

  Dyn.Active:=false;      {взрыв не активен}

  {вектор гравитации у 1-ой модели}
  Dyn.PSystem[Dyn.PSAmount].Gravity:=CreateVector3f(0,0,0);

  {текстура у первой модели}
  Dyn.PSystem[Dyn.PSAmount].Texture:=Tex;

  Dyn.PSystem[Dyn.PSAmount].Fade:=0.003;           {скорость затухания}
  Dyn.PSystem[Dyn.PSAmount].Color:=CreateVector3f(0,0.4,0.8);{Цвет частиц}

  {режим смешивания цветов основан на входящем значении alpha-компоненты}
  glBlendFunc(gl_src_alpha,gl_one); 
end;

Запускаем. Нажимаем SPACE и...вуаля. Мы получили взрыв.

Есть здесь некоторые недочеты. К примеру, если нажать на пробел в то время, как модель расходится под действием взрыва, то программа будет рисовать модель, а не ту стадию расхождения, на которой она остановилась. Но это просто исправить, если запретить после запуска взрыва изменение поля Active до тех пор, пока в массиве «частичных движков» не будет ни одного элемента.

ИзображениеИзображение

Исходный код: 20031013.zip

Страницы: 1 2

#Delphi, #эффекты, #particle system

18 октября 2003 (Обновление: 22 июня 2009)

Комментарии [2]