Здравствуйте, уважаемые подписчики! Прошу прощения: до четверга (22.11.2001) опрос о версии Delphi был неверно настроен, и результаты не сохранялись. Сейчас все настроено и проверено. Вы можете указать версию Delphi, которую Вы используете, по адресу program.dax.ru/delver.htm.
Сегодня поговорим об изменении формы окна. Подобный совет можно найти на большинстве сайтов по Delphi, поэтому форма окна будет не статическая. В прямоугольном окне будут вырезаны круги. Эти круги будут перемещаться, отскакивать от стенок и друг от друга (по всем правилам). При этом будет не проверка на каждом шагу, столкнулись ли уже круги, а точный метод.
Для изменения формы окна используется функция SetWindowRgn. Перед ее вызовом нужно при помощи функций CombineRgn, CreateEllipticRgn, CreateEllipticRgnIndirect, CreatePolygonRgn, CreatePolyPolygonRgn, CreateRectRgn, CreateRectRgnIndirect, CreateRoundRectRgn, ExtCreateRegion, InvertRgn и OffsetRgn создать саму форму. Вам требуется только handle региона, а полная информация о нем хранится в недрах Windows.
При перемещении кругов становятся видными разные части чужих окон. Им приходится перерисовываться. Поскольку процесс этот не всегда быстр, перемещение отверстий может оказаться не очень красивым.
И еще. По непонятной мне причине, круги иногда сцепляются. Если кто-нибудь поймет, в чем дело, напишите мне, пожалуйста, на subscribe@program.dax.ru.
Скачать необходимые для компиляции файлы проекта можно на program.dax.ru
uses math;
const
r = 60;
maxx = 400;
maxy = 300;
BallCount = 3;
OneStep = 1;
var
xo, yo, vx, vy: array [0..BallCount-1] of double;
function GetBumTime(var n: integer; var t: double): boolean;
function GetOneBumTime(index: integer): double;
begin
result := min(
max((maxx - r - xo[index]) / vx[index],
-(xo[index] - r) / vx[index]),
max((maxy - r - yo[index]) / vy[index],
-(yo[index] - r) / vy[index]));
end;
var
i: integer;
OneTime: double;
begin
OneTime := GetOneBumTime(0);
result := OneTime < t;
n := 0;
for i := 1 to BallCount - 1 do begin
OneTime := GetOneBumTime(i);
if OneTime < t then begin
t := OneTime;
n := i;
result := true;
end;
end;
end;
function GetCrashTime(var t: double; var n1, n2: integer): boolean;
var
i, j: integer;
D: double;
dx, dy, dvx, dvy: double;
t1, t2: double;
begin
result := false;
for i := 0 to BallCount - 2 dofor j := i + 1 to BallCount - 1 do begin
dvx := vx[i] - vx[j];
dvy := vy[i] - vy[j];
dx := xo[i] - xo[j];
dy := yo[i] - yo[j];
D := 2*dx*dvx*dy*dvy - sqr(dvx*dy) - sqr(dvy*dx) +
4*r*r*(dvx*dvx + dvy*dvy);
if D < 0 then continue;
if D > 1E-20 then D := sqrt(D);
t1 := -(dvx*dx + dvy*dy + D) / (sqr(dvx) + sqr(dvy));
t2 := -(dvx*dx + dvy*dy - D) / (sqr(dvx) + sqr(dvy));
if (t1 <= 1E-5) and (t2 <= 1E-5) then continue;
if (t1 > 0) and (t2 > 0)
then t1 := min(t1, t2)
else t1 := max(t1, t2);
if t1 < t then begin
t := t1;
n1 := i;
n2 := j;
result := true;
end;
end;
end;
procedure Step(dt: double);
var i: integer;
beginfor i := 0 to BallCount - 1 do begin
xo[i] := xo[i] + vx[i] * dt;
yo[i] := yo[i] + vy[i] * dt;
end;
end;
procedure Draw;
var
i: integer;
rgn, ball: hRgn;
begin
rgn := CreateRectRgn(0, 0, maxx - 1, maxy - 1);
ball := CreateEllipticRgn(0, 0, r*2 - 1, r*2 - 1);
for i := 0 to BallCount - 1 do begin
OffsetRgn(ball, round(xo[i] - r), round(yo[i] - r));
CombineRgn(rgn, rgn, ball, RGN_XOR);
OffsetRgn(ball, round(r - xo[i]), round(r - yo[i]));
end;
DeleteObject(ball);
SetWindowRgn(Form1.Handle, rgn, true);
end;
procedure FillBalls;
var
i, j: integer;
x, y: double;
allright: boolean;
begin
randomize;
for i := 0 to BallCount - 1 do beginrepeat
x := random(maxx - 2 * r) + r;
y := random(maxy - 2 * r) + r;
allright := true;
for j := 0 to i - 1 do beginif sqr(x - xo[j]) + sqr(y - yo[j]) <= 5 * r * r
then begin
allright := false;
break;
end;
end;
until allright;
xo[i] := x;
yo[i] := y;
vx[i] := random - 0.5;
vy[i] := random - 0.5;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
F, Fx, Fy, t, BumT, CrashT, a: double;
BumN, Crash1, Crash2: integer;
begin
Button1.Visible := false;
Form1.Width := maxx; Form1.Height := maxy;
Form1.Color := clBlack;
FillBalls;
repeat
t := 0;
repeat
BumT := OneStep - t;
CrashT := OneStep - t;
if (not GetBumTime(BumN, BumT)) and
(not GetCrashTime(CrashT, Crash1, Crash2)) then break;
if (CrashT < BumT) then begin
Step(CrashT);
a := arctan2(yo[Crash1] - yo[Crash2], xo[Crash1] - xo[Crash2]);
F := cos(a) * (vx[Crash2] - vx[Crash1]) +
(vy[Crash2] - vy[Crash1]) * sin(a);
Fx := F * cos(a);
vx[Crash1] := vx[Crash1] + Fx;
vx[Crash2] := vx[Crash2] - Fx;
Fy := F * sin(a);
vy[Crash1] := vy[Crash1] + Fy;
vy[Crash2] := vy[Crash2] - Fy;
t := t + CrashT;
end else begin
Step(BumT);
if max((maxx - r - xo[BumN]) / vx[BumN],
-(xo[BumN] - r) / vx[BumN]) <
max((maxy - r - yo[BumN]) / vy[BumN],
-(yo[BumN] - r) / vy[BumN])
then vx[BumN] := -vx[BumN]
else vy[BumN] := -vy[BumN];
t := t + BumT;
end;
until false;
Step(OneStep - t);
Draw;
Application.ProcessMessages;
until Application.Terminated;
end;
Полезные мелочи
Чтобы инициализировать переменную на стадии ее создания, нужно объявить ее, как типизированную константу. Таким способом можно инициализировать переменные простых типов, а также записи, массивы, множества. Пример:
procedure TForm1.FormCreate(Sender: TObject);
const
a: integer = 0;
p: TPoint = (x: 10; y: 20);
BoolStr: array [boolean] of string = ('false', 'true');
figures: set of char = ['0'..'9'];
begin
p := Form1.ClientToScreen(p);
SetCursorPos(p.x, p.y);
Form1.Caption := BoolStr[GetKeyState(VK_NUMLOCK) and 1 > 0];
end;