Реализация взрыва при помощи системы частиц (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
#Delphi, #эффекты, #particle system
18 октября 2003 (Обновление: 22 июня 2009)
Комментарии [2]