Анимируйте ваше приложение!

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

Итак, нижеприведенный код - это результат почти недели "тяжелой" работы. Вероятно много можно сделать для увеличения функциональности и повышения скорости данной программы. Часть кода для данной процедуры я подчерпнул в статье на сайте Delphi3000.com.

Небольшое описание используемых параметров:

Control: В качестве этой переменной используется любой компонент - наследник класса TControl . Если компонент не является наследником TControl, но имеет свойства Left и Top , все равно поместите его на месте TControl. Поскольку процедура обращается только к Свойствам Top и Left , то это сработает OK.
Dest: Величина типа TPoint , которая показывает , где остановится Control (на текущей форме) после анимации.
PixPTick: Pixels Per Tick. Любая величина (рекомендуется 2-4) от 0 и выше. Она не изменяет положение Control точно на каждый шаг (tick). Чем выше эта величина , тем быстрее движение.

procedure MoveControl(var Control: TControl; Dest: TPoint; PixPTick: Integer);  
var  
StartTick, OldTick, CurTick, TickDif: Cardinal;  
Steps: Integer;  
begin  
StartTick :=3D GetTickCount;  
CurTick :=3D StartTick;  
Steps :=3D 0;  
// Переводит Control в соответствии с Dest.y и PixPTick  
if Control.Top <> Dest.y then // Проводит необходимые вычисления  
if Control.Top < Dest.y then  
while (Control.Top <> 0) and (Dest.y <> 0) and // Устраняет деление на 0  
// Проверка на необходимость дальнейшей регулировки  
((Dest.y - Control.Top) mod PixPTick <> 0) do  
Control.Top :=3D Control.Top - 1  
else  
while (Control.Top <> 0) and (Dest.y <> 0) and // Устраняет деление на 0  
// Проверка на необходимость дальнейшей регулировки  
((Control.Top - Dest.y) mod PixPTick <> 0) do  
Control.Top :=3D Control.Top + 1;  
// Переводит Control в соответствии с Dest.x и PixPTick  
if Control.Left <> Dest.x then // Проводит необходимые вычисления  
if Control.Left < Dest.x then  
while (Control.Left <> 0) and (Dest.x <> 0) and // Устраняет деление на 0  
// Проверка на необходимость дальнейшей регулировки  
((Dest.x - Control.Left) mod PixPTick <> 0) do  
Control.Left :=3D Control.Left - 1  
else  
while (Control.Left <> 0) and (Dest.x <> 0) and // Устраняет деление на 0  
// Проверка на необходимость дальнейшей регулировки  
((Control.Left - Dest.x) mod PixPTick <> 0) do  
Control.Left :=3D Control.Left + 1;  
repeat  
Application.ProcessMessages; // Реакция приложения на ввод пользователя  
OldTick :=3D CurTick; // Время последнего фрейма  
CurTick :=3D GetTickCount; // Текущее время  
TickDif :=3D CurTick - OldTick; // Разница во времени  
// По крайней мере 1 миллисекунда прошла с момента показа последнего фрейма  
if TickDif > 0 then   
begin   
Inc(Steps);  
if Control.Left > Dest.x then  
Control.Left :=3D Control.Left - PixPTick  
else if Control.Left < Dest.x then  
Control.Left :=3D Control.Left + PixPTick;   
  
if (Control.Top > Dest.y) then  
Control.Top :=3D Control.Top - PixPTick  
else if (Control.Top < Dest.y) then  
Control.Top :=3D Control.Top + PixPTick;  
end;  
// Если прошло больше 150 шагов (что маловероятно), движение   
// остановится, чтобы не войти в бесконечный цикл. Измените данную величину  
// в соответствии с вашими требованиями, или удалите ее.  
until ((Control.Left =3D Dest.x) and (Control.Top =3D Dest.y)) or (Steps > 150);  
end;   

Добавлено: 17 Июля 2018 08:04:35 Добавил: Андрей Ковальчук

OpenGL и Delphi на практике

Любая теория хороша, если она может быть реализована на Delphi :-). Поэтому предлагаю не откладывая в долгий ящик написать первую программу на OpenGL - а потом, окрылившись успехом, вернуться к теории и как следует проштудировать все книги и сайты по сабжу, чтобы уж стать настоящими монстрами трехмерного моделирования.

Для начала придется проделать подготовительную работу:

настроить формат пикселей с учетом отображаемой информации;
создать контекст OpenGL и подготовить сам движок OpenGL к работе.
Формат пикселей удобно вынести в отдельную процедуру, которую мы оформим следующим образом:

procedure SetDCPixelFormat (dc: HDC);  
var pfd: TPixelFormatDescriptor;  
nPixelFormat: Integer;  
begin  
FillChar (pfd, SizeOf (pfd),0);  
with pfd do  
begin  
nSize:= sizeof (pfd);  
nVersion:= 1;  
dwFlags:= PFD_DRAW_TO_WINDOW or  
PFD_SUPPORT_OPENGL or  
PFD_DOUBLEBUFFER;  
iPixelType:= PFD_TYPE_RGBA;  
cColorBits:= 16;  
cDepthBits:= 64;  
iLayerType:= PFD_MAIN_PLANE;  
end;  
nPixelFormat:=ChoosePixelFormat (DC,@pfd);  
SetPixelFormat (DC, nPixelFormat,@pfd);  
end;

Здесь при заполнении структуры TPixelFormatDescriptor мы задаем параметры будущего графического отображения, в том числе количество цветовых бит, а также тип пикселей (iPixelType). Мы также задаем флаги, которые, как видно из названия, указывают, что наша программа будет поддерживать OpenGL, а также что мы будем рисовать в окне и использовать двойную буферизацию (параметр, необходимый для воспроизведения движущихся объектов).

Далее посредством вызова ChoosePixelFormat система выбирает подходящий формат пикселя - и мы присваиваем его (через SetPixelFormat) нашему окну.

Теперь нужно инициализировать контекст самого OpenGL посредством функций, содержащихся в модуле Windows, и произвести дополнительную настройку движка:
procedure TForm1.FormCreate (Sender: TObject);  
begin  
H:=Handle;  
DC:=GetDC (H);  
SetDCPixelFormat (DC);  
RC:=wglCreateContext (DC);  
wglMakeCurrent (DC, RC);  
glClearColor (0.6,0.6,0.6,1.0);  
glMatrixMode (GL_PROJECTION);  
glLoadIdentity;  
glFrustum (-1,1,-1,1,2,20);  
glMatrixMode (GL_MODELVIEW);  
glLoadIdentity;  
glTranslatef (0.0,-1.0,-6.0);  
BeginPaint;  
end;

Как видим, сначала мы задали для нашей графики необходимый формат пикселей. Теперь при помощи функции wglCreateContext создаем OpenGL-контекст, а впоследствии делаем его текущим контекстом. Далее, используя уже универсальные функции**, произведем настройку "мира", который будем создавать. Для этого через glClearColor очистим контекст и заполним ее 60-процентным черным цветом. Далее выберем матрицу проекций, которая определяет, как будут проецироваться трехмерные объекты на плоскость экрана (в оконные координаты) и через glLoadIdentity установим единичную матрицу и зададим границы плана в "мировых координатах" при помощи вызова glFrustum. После чего загрузим модельно видовую матрицу и произведем ее смещение (glTranslatef).

Что будем рисовать
Конечно, можно было нарисовать простую пирамиду или же куб. Но мы сделаем большее - нарисуем "признание в любви"** (рис. 1). Специально для этого методом "научного перебора" была разработана модель, описывающая соответствующую кривую:

Остается только перевести ее с языка математики на нормальный человеческий.

Прорисовка сцены

Подготовку сцены начнем с подключения разных дополнительных функций, без которых дальнейшая работа невозможна. Эти функции прописаны в методе BeginPaint, а также в методе FormResize (чтобы при изменении размера формы соответственно менялся размер объекта). Для этого используем функцию glEnable с соответствующими параметрами.

Далее в FormPaint используем подготовленные заранее методы DrawFace и DrawElement (см. листинг ниже) для отрисовки упомянутого объекта. А для придания ему еще большей "жары" используем возможности OpenGL по освещению сцены.

Итог
С точки зрения сложности освоения OpenGL сопоставим с другими подобными библиотеками. Так что с одной стороны нет разницы, в чем разбираться и что изучать. Но с точки зрения разумного подхода любой проект трехмерной графики должен как минимум поддерживать OpenGL в качестве одной из опций. Ведь серьезные вещи считаются и визуализируются, как правило, под Unix/IRIX/Linux/FreeBSD, и в то же время было бы неправильно игнорировать пользователей Windows. Так что OpenGL как раз и является тем универсальным языком и общим знаменателем, позволяющим вашим приложениям свободно мигрировать с одной платформы на другую.
unit MainForm;  
  
interface  
  
uses  
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,  
  Dialogs,OpenGL, StdCtrls, ExtCtrls;  
  
type  
  TForm1 = class(TForm)  
    Timer1: TTimer;  
    Label1: TLabel;  
    Label2: TLabel;  
    Label3: TLabel;  
    Label4: TLabel;  
    procedure FormCreate(Sender: TObject);  
    procedure FormDestroy(Sender: TObject);  
    procedure Timer1Timer(Sender: TObject);  
    procedure FormPaint(Sender: TObject);  
    procedure FormResize(Sender: TObject);  
  private  
   RC:HGLRC;  
   DC:HDC;  
   H:THandle;  
   procedure BeginPaint;  
    { Private declarations }  
  public  
    { Public declarations }  
  end;  
  
var  
  Form1: TForm1;  
  
const mat1_dif:Array[0..2] of Single = (0.8,0.8,0.0);  
const mat1_amb:Array[0..2] of Single = (0.2,0.2,0.2);  
const mat1_spec:Array[0..2] of Single = (0.6,0.6,0.6);  
const mat1_shininess = 0.5*128;  
  
procedure DrawElement(A,b,R0,r1:Single);  
procedure DrawFace(A,R:Single;Normal:Boolean);  
implementation  
procedure SetDCPixelFormat(dc:HDC);  
var pfd:TPixelFormatDescriptor;  
    nPixelFormat:Integer;  
begin  
FillChar(pfd,SizeOf(pfd),0);  
with pfd do  
 begin  
  nSize     := sizeof(pfd);  
  nVersion  := 1;  
  dwFlags   := PFD_DRAW_TO_WINDOW or  
               PFD_SUPPORT_OPENGL or  
               PFD_DOUBLEBUFFER;  
  iPixelType:= PFD_TYPE_RGBA;  
  cColorBits:= 16;  
  cDepthBits:= 64;  
  iLayerType:= PFD_MAIN_PLANE;  
 end;  
  
nPixelFormat:=ChoosePixelFormat(DC,@pfd);  
SetPixelFormat(DC,nPixelFormat,@pfd);  
end;  
  
procedure TForm1.BeginPaint;  
begin  
glEnable(GL_LIGHTING);  
glEnable(GL_LIGHT0);  
glEnable(GL_DEPTH_TEST);  
glEnable(GL_NORMALIZE);  
glEnable(GL_COLOR_MATERIAL);  
timer1.enabled:=true;  
end;  
{$R *.dfm}  
procedure TForm1.FormCreate(Sender: TObject);  
begin  
H:=Handle;  
DC:=GetDC(H);  
SetDCPixelFormat(DC);  
RC:=wglCreateContext(DC);  
wglMakeCurrent(DC,RC);  
glClearColor(0.6,0.6,0.6,1.0);  
glMatrixMode(GL_PROJECTION);  
glLoadIdentity;  
glFrustum(-1,1,-1,1,2,20);  
glMatrixMode(GL_MODELVIEW);  
glLoadIdentity;  
glTranslatef(0.0,-1.0,-6.0);  
BeginPaint;  
end;  
  
procedure TForm1.FormDestroy(Sender: TObject);  
begin  
wglMakeCurrent(0,0);  
wglDeleteContext(RC);  
ReleaseDC(H,DC);  
DeleteDC(DC);  
end;  
  
procedure TForm1.Timer1Timer(Sender: TObject);  
begin  
glRotatef(4.0,0.0,1.0,0.0);  
SwapBuffers(DC);  
InvalidateRect(H,nil,False);  
end;  
  
procedure DrawElement(a,b,r0,r1:Single);  
var x1b,y1b:Single;  
    x1e,y1e:Single;  
    x0b,y0b:Single;  
    x0e,y0e:Single;  
    t0,t1:Single;  
    dt:single;  
begin  
t0:=-3;t1:=3;  
dt:=0.06;  
while t0<=t1 do  
 begin  
  x0b:=a*sin(t0)*sin(t0)*sin(t0)*sin(t0)*cos(t0);  
  y0b:=a*abs(sin(t0)*cos(t0));  
  x0e:=a*sin(t0+dt)*sin(t0+dt)*sin(t0+dt)*sin(t0+dt)*cos(t0+dt);  
  y0e:=a*abs(sin(t0+dt)*cos(t0+dt));  
  x1b:=b*sin(t0)*sin(t0)*sin(t0)*sin(t0)*cos(t0);  
  y1b:=b*abs(sin(t0)*cos(t0));  
  x1e:=b*sin(t0+dt)*sin(t0+dt)*sin(t0+dt)*sin(t0+dt)*cos(t0+dt);  
  y1e:=b*abs(sin(t0+dt)*cos(t0+dt));  
  glBegin(GL_TRIANGLE_STRIP);  
   glNormal((x0b+x1e)/2,(y0b+y1e)/2,(r1+r0)/2);  
   glVertex3f(x0b,y0b,r0);  
   glVertex3f(x0e,y0e,r0);  
   glVertex3f(x1e,y1e,r1);  
   glVertex3f(x1b,y1b,r1);  
  glEnd;  
  t0:=t0+dt;  
 end;  
end;  
  
procedure DrawFace(A,R:Single;Normal:Boolean);  
var x,y:single; t0,t1,dt:Single;  
begin  
t0:=-3;t1:=3;  
dt:=0.06;  
glBegin(GL_POLYGON);  
while t0<=t1 do  
 begin  
  x:=a*sin(t0)*sin(t0)*sin(t0)*sin(t0)*cos(t0);  
  y:=a*abs(sin(t0)*cos(t0));  
  glVertex3F(x,y,r);  
  t0:=t0+dt;  
 end;  
 t0:=0;  
 x:=a*sin(t0)*sin(t0)*sin(t0)*sin(t0)*cos(t0);  
 y:=a*abs(sin(t0)*cos(t0));  
 if Normal then glNormal3f(x,y,-r) else glNormal3f(x,y,r);  
glEnd;  
end;  
  
procedure TForm1.FormPaint(Sender: TObject);  
var m,n:single;dm:Single;a:Single;df:Single;  
begin  
a:=25;  
df:=10;  
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);  
glColor(1.0,0.0,0.0,0.0);  
glMaterialfv(GL_FRONT,GL_AMBIENT,@mat1_amb);  
glMaterialfv(GL_FRONT,GL_DIFFUSE,@mat1_dif);  
glMaterialfv(GL_FRONT,GL_SPECULAR,@mat1_spec);  
glMaterialf(GL_FRONT,GL_SHININESS,mat1_shininess);  
m:=-1;n:=1;dm:=0.5;  
while m<=n do  
   begin  
    DrawElement(Sqrt(a-m*m),Sqrt(a-(m+dm)*(m+dm)),m/df,(m+dm)/df);  
    m:=m+dm;  
   end;  
DrawFace(Sqrt(a-(m)*(m)),(m)/df,True);  
m:=-1;  
DrawFace(Sqrt(a-(m)*(m)),(m)/df,True);  
end;  
  
procedure TForm1.FormResize(Sender: TObject);  
const lm:Array[0..3] of Single = (0.5,0.5,0.5,1.0);  
const  
   light_ambient:array[0..3] of glfloat = (0.0,0.0,0.0,1.0);  
   light_diffuse:array[0..3] of glfloat = (1.0,1.0,1.0,1.0);  
   light_specular:array[0..3] of glfloat = (2.0,2.0,2.0,1.0);  
   light_position:array[0..3] of glfloat = (2.0,1.0,3.0,1.0);  
   light_emission:array[0..3] of glfloat = (1.0,1.0,1.0,1.0);  
   light_spotdirection:array[0..3] of glfloat = (1.0,1.0,1.0,1.0);  
begin  
wglMakeCurrent(0,0);  
wglDeleteContext(RC);  
ReleaseDC(H,DC);  
DC:=GetDC(H);  
SetDCPixelFormat(DC);  
RC:=wglCreateContext(DC);  
wglMakeCurrent(DC,RC);  
glClearColor(0.6,0.6,0.6,0.0);  
glMatrixMode(GL_PROJECTION);  
glLoadIdentity;  
glFrustum(-1,1,-1,1,2,20);  
glMatrixMode(GL_MODELVIEW);  
glLoadIdentity;  
glTranslatef(0.0,-1.0,-6.0);  
glLightModel(GL_LIGHT_MODEL_LOCAL_VIEWER,Ord(True));  
glLightModelfv(GL_LIGHT_MODEL_AMBIENT,@lm);  
glLightfv(GL_LIGHT0,GL_AMBIENT,@light_ambient);  
glLightfv(GL_LIGHT0,GL_DIFFUSE,@light_diffuse);  
glLightfv(GL_LIGHT0,GL_SPECULAR,@light_specular);  
glLightfv(GL_LIGHT0,GL_POSITION,@light_position);  
glLightf(GL_LIGHT0,GL_SPOT_EXPONENT,8);  
glLightf(GL_LIGHT0,GL_SPOT_CUTOFF,170);  
glLightfv(GL_LIGHT0,GL_SPOT_DIRECTION,@light_spotdirection);  
glEnable(GL_LIGHTING);  
glEnable(GL_LIGHT0);  
glEnable(GL_DEPTH_TEST);  
glEnable(GL_NORMALIZE);  
glEnable(GL_COLOR_MATERIAL);  
end;  
end.

Добавлено: 13 Июля 2018 08:14:52 Добавил: Андрей Ковальчук