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

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

Автор: Max Stalker

Часть 1
Часть 2

Часть 1

Если вы решили реализовать взрыв какого-нибудь объекта и хотите сделать его не слишком сложным, то можно поступить следующим путем.

Представим, что наша модель сделана из треугольников или квадратов (так как это не существенно). Будем реализовывать взрыв таким образом, чтобы модель распадалась на отдельные части и они бы разлетались в зависимости от следующих параметров :

Положение самой части в пространстве
Положение "взрывного устройства"
Сила взрыва
Направление нормали к поверхности части
Гравитация

Все эти параметры нам нужны для следующего:

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

Будем действовать последовательно и для начала напишем "геометрическую" состовляющую нашего движка. А будет она выглядеть вот так:

unit GEOMETRY;
uses Windows,OpenGL;

Создадим тип для положения в пространстве

type
TVector3f=record 
  x:glfloat;
  y:glfloat;
  z:glfloat;
end;

Опишем также тип геометрических примитивов rTriangle:

rTriangle=record
  V:array [0..2] of TVector3f;
end; 

Далее напишем несколько процедур для более удобного обращения с типом Tvector3f. Вот прототипы наших процедур и функций:

procedure  Normalize3f (var a:TVector3f);
procedure       Swap3f (var a,b:TVector3f);
procedure SummVectors3f(var a,b:TVector3f);
procedure    Multiple3f(var v:TVector3f;cof:glfloat);

function      CreateVector3f (x,y,z : glfloat)   :TVector3f;
function      CrossProduct3f (a,b:TVector3f)     :TVector3f;
function    CalculateNormal3F(P1,P2:TVector3f)   :TVector3f;
function         DotProduct3f(v1,v2:TVector3f)   :glfloat;
function  CalculateDistance3f(P1,P2:TVector3f)   :glfloat;

А вот их реализация

procedure Normalize3f(var a:TVector3f);
  var d:glfloat;
begin
  d:=sqrt(sqr(a.x)+sqr(a.y)+sqr(a.z));
  if d = 0 then d :=1;
  a.x:=a.x/d;
  a.y:=a.y/d;
  a.z:=a.z/d;
end;

procedure Swap3f(var a,b:TVector3f);
  var
  temp:TVector3f;
begin
  temp:=a;
  a:=b;
  b:=temp;
end;

procedure SummVectors3f(var a,b:TVector3f);
  var
  temp:TVector3f;
begin
  temp.x:=a.x+b.x;
  temp.y:=a.y+b.y;
  temp.z:=a.z+b.z;
  a:=temp;
end;

procedure Multiple3f(var v:TVector3f;cof:glfloat);overload;
begin
  v.x:=v.x*cof;
  v.y:=v.y*cof;
   v.z:=v.z*cof;
end;


function CreateVector3f(x,y,z:glfloat):TVector3f;
begin
  result.x:=x;
  result.y:=y;
  result.z:=z;
end;

function CrossProduct3f(a,b:TVector3f):TVector3f;overload;
begin
  result.x:= a.y * b.z - a.z * b.y;
  result.y:= a.z * b.x - a.x * b.z;
  result.z:= a.x * b.y - a.y * b.x;
end;

function CalculateNormal3f(P1,P2:TVector3f):TVector3f;
  var
  p3:TVector3f;
  v1,v2,v3,temp:TVector3f;
begin
  p3.x:=p2.x-p1.x;
  p3.y:=p2.y-p1.y;
  p3.z:=p2.z-p1.z;

  v1:=CreateVector3f(p3.x-p1.x,p3.y-p1.y,p3.z-p1.z);
  v2:=CreateVector3f(p3.x-p2.x,p3.y-p2.y,p3.z-p2.z);
  temp:=CrossProduct3f(v1,v2);
  Normalize3f(temp);
  result:=temp;
end; 

function DotProduct3f(v1,v2:TVector3f):glfloat; 
begin
  result :=V1.X * V2.X + V1.Y * V2.Y + V1.Z * V2.Z;
end;

function CalculateDistance3f(P1,P2:TVector3f):real     ;overload;
  var
  t:TVector3f;
begin
  t:=CalcDifference3f(P1,P2);
  result:=sqrt(sqr(t.x)+sqr(t.y)+sqr(t.z));
end;

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

unit MODEL;
interface
uses
  Windows, OpenGL, GEOMETRY, SysUtils;
  {GEOMETRY - юнит,который мы описали в предыдущем пункте }
type
CGeomModel=class
  public
    TrioAmount             :integer;
    Triangles              :array of rTriangle;

    constructor Init;
    procedure   LoadFromFile(FileName:string);
    procedure   Render;
end;

function ConvertStrings(s1,s2,s3:string)  :TVector3f;

Функция ConvertStrings берет три значения считанных из файла и делает из них вектор положения в пространстве.
Опишем сначала функцию,а затем все методы класса.

var
F:text;   {файловая переменная,она нам понадобится позже}

function ConvertStrings(s1,s2,s3:string)  :TVector3f; 
begin
  result.x:=StrToFloat(s1);
  result.y:=StrToFloat(s2);
  result.z:=StrToFloat(s3);
end;

А теперь все методы по порядку. Начнем с конструктора. Он ничем не отличается от конструктора класс TObject и нужен лишь с эстетической точки зрения.

constructor CGeomModel.Init;
begin
 inherited Create;
end;

Далее у нас следует метод

procedure CGeomModel.LoadFromFile(FileName:string);
  var
  ch:char;
  i,j:integer;
  s:array [0..8] of string;
  st:string;
begin
  AssignFile(F,FileName);
  Reset(F);
  While not  SeekEof(F) do
  begin
    inc(TrioAmount);
    SetLength(Triangles,TrioAmount*SizeOF(rTriangle));
    i:=0;
    for j:=0 to 8 do
      s[j]:='';
    st:='';
    j:=0;

    while not SeekEoln(F) do
    begin
      read(f,st);
      for i:=1 to Length(st) do
      begin
        if st[i]=' ' then inc(j);
        s[j]:=s[j]+st[i];
      end;
    end;

    Triangles[TrioAmount].V[0]:=ConvertStrings(s[0],s[1],s[2]);
    Triangles[TrioAmount].V[1]:=ConvertStrings(s[3],s[4],s[5]);
    Triangles[TrioAmount].V[2]:=ConvertStrings(s[6],s[7],s[8]);

  end;
end;

В принципе, данный метод считывания не является быстрым. Лучше использовать BlockRead для чтения и BlockWrite для записи. Они побыстрее будут. Но здесь я хотел сделать считывание из текстового файла, так как оно самое простое. В это методе мы сначала считываем ВСЮ строку из файла, затем разбиваем ее на подстроки поиском пробела, преобразуем подстроки в значения при помощи процедуры ConvertStrings, увеличиваем размер динамического массива и записываем в него значения вершин треугольника, переходим на следующую строку. Если мы достигли конца файла, то тогда просто выходим их процедуры

procedure  CGeomModel.Render;
  var
  i:integer;
begin
  glBegin(gl_Triangles);
  for i:=1 to TrioAmount do
  begin
    glVertex3fv(@triangles[i].V[0]);
    glVertex3fv(@triangles[i].V[1]);
    glVertex3fv(@triangles[i].V[2]);
  end;
  glEnd;
end;

end.             {Завершающий END в данном юните}

Пожалуй пора рассказать о том, как мы будем реализовывать наш взрыв. А будем мы его реализовывать при помощи системы частиц. Все просто ! Когда взрыв не активен мы рисуем нашу модель, как только стал активен взрыв, мы начинаем рисовать частицы. Для того, чтобы все это выглядело похожим на изначальную модель мы из массива треугольников модели сделаем массив частиц. В принципе, можно изменять и координаты вершин МОДЕЛИ. Здесь, как кому нравится. Я считаю, что так будет пожалуй проще работать.

Итак, создадим класс для частиц

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

type
rModelParticle=record
  Trio       :rTriangle;      {"частичный" треугольник}
  Direction  :TVector3f;      {Направление движения}
  Life       :glfloat;      {Жизнь частицы.Нужно для затухания}
  Size       :glfloat;      {Текущий размер частицы}
  end;

CParticleSystem=class
  private
    Active         :boolean;       {Активен ли движок}
    Particles      :array of rModelParticle; {Массив частиц}
    procedure Swap(var a,b:rModelParticle);

  public
    Gravity        :TVector3f;       {Вектор гравитации}
    Amount         :integer;       {Число частиц}
    Texture        :gluint;        {текстура}
    Color          :TVector3f;       {Цвет}
    Fade           :glfloat;       {Скорость затухания}

    procedure Add(v1,v2,v3:TVector3f;Direct:TVector3f);
    procedure Delete(index:integer);
    procedure Change;
    procedure DrawSprite(index:integer);
    procedure Render;
end;

procedure glBindTexture(target: GLenum; texture: GLuint); stdcall;
   external opengl32;

Начинаем описывать методы. Метод Swap. Метод нужен для замены одной частицы другой. Он нужен будет нам при удалении частиц.

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

Частицы будем добавлять по трем вершинам и направлению. Так как у нас пока нет ни направления, ни вершин (ни будут задаваться в "взрывном" движке), то мы просто представим, что они у нас есть и запишем метод.

procedure CParticleSystem.Add(v1,v2,v3:TVector3f;Direct:TVector3f);
begin
  Inc(Amount);
  SetLength(Particles,Amount*SizeOf(rModelParticle));

  Particles[Amount].Trio.v[0]:=v1;
  Particles[Amount].Trio.v[1]:=v2;
  Particles[Amount].Trio.v[2]:=v3;
  Particles[Amount].Direction:=Direct;
  Particles[Amount].Life:=2;
  Particles[Amount].Size:=Size;
end;
Страницы: 1 2 Следующая »

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

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

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