Выбрать главу

Значение пиксела с таким цветом будет следующим (пробелы вставлены для удобочитаемости):

0001 1001 ОНО 0101

Все выглядит просто, имея значение трех составляющих, мы должны в пиксел заносить значение по следующей формуле:

blue + green * 2"5 + red * 2Л11 или blue + green * 64 + red * 4096

Операции умножения и деления с участием степени двойки лучше оптимизировать с помощью операции сдвига. Теперь окончательная формула выглядит так:

blue OR (green SHL 5) OR (red SHL 11)

Иллюстрация в виде примера последует позже, а сейчас задержимся на том, как вырезать из пиксела значения составляющих. Для этого применяются битовые маски. Так, для получения значения пяти битов красной составляющей надо использовать бинарное число

1111 1000 0000 0000

и логическую операцию AND для вырезания значения первых пяти битов. Вот так:

0001 1001 ОНО 0101 &

1111 1000 0000 0000

-------------------------------

0001 1000 0000 0000

Результат найден, как видим, верно, но ему предшествуют одиннадцать нулей. Чтобы получить значение составляющей, надо применить к этому выражению операцию битового сдвига вправо. Вот пример для красной составляющей:

Red : Byte;

Red := (pixel & $F800) SHR 11;

Или, если поменять порядок действий, вырезать ее можно так:

Red := (pixel SHR 11) AND $lf;

Маска в этом случае та же - пять единиц, но без завершающих одиннадцати нулей.

Перейдем к иллюстрации - проекту каталога Ех17. Работа его выглядит очень просто, на экране появляются вспышки синих и красных частиц. Работа с системой частиц во многом похожа на код предыдущего примера, но теперь воспользуемся концепцией ООП:

const

MAX ENERGY =60; // Максимальная энергия частицы

DEFAULT_SIZE =200; // Количество частиц во вспышке

DEFAULT_POWER =30; // Для зарядки энергии частицы

type

TParticle = record // Данные на отдельную частицу

X, Y : Single; // Позиция

SpeedX, SpeedY : Single; // Скорости по осям

Energy : Integer; // Энергия

Angle : Integer; // Направление движения

R, G, В : Byte; // Цвет

end;

TParticleSystem = class // Класс системы частиц

public

procedure Init (NewSize, Power : Integer); // Инициализация

procedure Calculate; // Пересчет положений частиц

function Render : HRESULT; // Отображение вспышки

private

Particle : Array [0..1000] of TParticle; // Массив частиц

Size : integer; // Размер

end;

Инициализация системы выглядит так:

procedure TParticleSystem.Init (NewSize, Power : Integer);

var

i : Integer;

X, Y : Integer; // Стартовая точка вспышки Speed : Single;

begin

Size := NewSize; // Устанавливаем размер системы

// Центр вспышки располагаем вдали от границ экрана

X := random (ScreenWidth - 80) + 40;

Y := random (ScreenHeight - 80) + 40;

for i := 0 to Size do begin // Частицы системы

Particle[i].X := X;

Particle[i].Y := Y;

Particle[i].Energy := random (MAX_ENERGY); // Энергия

Particle[i].Angle := random (360); // Угол движения

Speed := random (Power) - Power / 2;

Particle[i].SpeedX := sinAfParticle[i].Angle] * Speed;

Particle [i] . SpeedY := cosA[Particle [i] .Angle] * Speed;

Particle [i] . r := random (256); // Сине-красный цвет

Particle [i] . g := 0;

Particle[i] .b := random (256);

end;

end;

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

sinA : Array [0..360] of Single;

cosA : Array [0..360] of Single;

PS : TParticleSystem;

for j := 0 to 360 do begin // Для оптимизации, чтобы вычислять

sinA[j] := sin(j * Pi / 180); // только один раз

cosA[j] := cos(j * Pi / 180); end;

PS := TParticleSystem. Create; // Создание системы

PS.Init (DEFAULT_SIZE, DEFAULT_POWER) ; // Инициализация системы

В методе calculate класса вспышки пересчитываются текущие координаты частиц:

procedure TParticleSystem. Calculate;

var

i : Integer;

begin

for i := 0 to Size do begin

if Particle [i] .Energy > 0 then begin

Particle [i] .X := Particle [i] .X + Particle [i]. SpeedX;

// Частицы отскакивают от границ экрана

if Particle [i] .X >= ScreenWidth - 1 then begin

Particle [i ] .SpeedX :="-0.5 * Particle [i]. SpeedX;

Particle [i] .X := ScreenWidth - 1;

end;

if Particle [i] .X < 0 then begin

Particle [i] .SpeedX := -0.5 * Particle [i]. SpeedX;

Particle [i] .X := 0;

end;

Particle [i].Y := Particle [i] .Y + Particle [i] . SpeedY;

if Particle [i] .Y >= ScreenHeight - 1 then begin

Particle [i] .SpeedY := -0.3 * Particle [i] . SpeedY;

Particle[i] .Y := ScreenHeight - 1;

end;

if Particle [i] .Y < 0 then begin

Particle [i] .SpeedY := -Particle [i] . SpeedY;

Particle[i].Y := 0;

end;

Particle[i].Energy := Particle[i].Energy - 1;

Particle[i].SpeedY := Particle[i].SpeedY + 0.2;

end;

end;

end;

Самый главный для нас метод - воспроизведение частиц системы:

function TParticleSystem.Render : HRESULT;

var

i : Integer;

desc : TDDSURFACEDESC2;

hRet : HRESULT;

begin

ZeroMemory (@desc, SizeOf(desc));

desc.dwSize := SizeOf(desc);

hRet := frmDD.FDDSBack.Lock (nil, desc, DDLOCKJSAIT, 0);

if Failed (hRet) then begin Result := hRet;

Exit;

end;

// Очистка экрана

ZeroMemory (desc.IpSurface,

desc.lPitch * ScreenHeight * (ScreenBitDepth div 8));

// Заполняем пикселы в соответствии с состоянием системы частиц

for i := 0 to Size do

if (Particle[i].Energy > 0) then

PWord (Integer(desc.IpSurface) +

trunc (Particle[i].Y) * desc.lPitch +

trunc (Particle[i].X) * (ScreenBitDepth div 8))^ :=

Particle[i].B or (Particle[i].G shl 5) or (Particle[i].R shl 11);

Result := frmDD.FDDSBack.Unlock(nil) ;

end;

При каждой перерисовке экрана отображается текущее состояние системы:

function TfrmDD.UpdateFraine : HRESULT;

var

hRet : HRESULT;

begin

Result := DD_FALSE;

PS.Calculate; // Пересчитываем положения частиц

// Воспроизведение состояния системы

hRet := PS.Render;

if Failed (hRet) then begin

Result := hRet;

Exit;

end;

Time := Time + 1; // Простейший эмулятор таймера

if Time > 15 then begin // Прошел срок существования системы

PS.Init(DEFAULT_SIZE, DEFAULT_POWER); // Вспышка в новом месте

Time := 0;

end;

Result := DD_OK;

end;

Полупрозрачность

Такой прием часто используется в играх. Автоматизации полупрозрачности DirectDraw не предоставляет, все необходимо делать самому разработчику, попикселно накладывая данные источника и приемника.

В общем случае формула вычисления значения цветовых компонентов выглядит так:

Result = Alpha * srcColor + (1 - Alpha) * destColor

Здесь Alpha - коэффициент прозрачности, принимающий вещественное значение в пределах от нуля до единицы; srcColor - цвет источника; destColor - цвет приемника.

Если Alpha равно нулю, то получаем цвет приемника; если Alpha имеет единичное значение, источник совершенно непрозрачен. Если мы имеем дело с образом, двигающимся по поверхности, то под источником подразумеваем образ, а фон считаем приемником.

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