Эффекты с палитрой

Сегодня хотел бы разговориться об эффектах с палитрой. Уже слышу вас: «Опять, Mike, пиво с можжевёловкой? У Speccy ведь никаких палитр нет!» Если не считать современных расширений — определённо правы. Но это нас не остановит.

Сначала скажем, что такое эта палитра и как бы мы могли её использовать на Spectrum. Напокуем, например, значение 7 в атрибутную часть видеопамяти. Что бы мы ни делали — у нас это всегда будет показывать белый цвет (на чёрном фоне, но для упрощения сосредоточимся только на INK). На сомнительных компьютерах разных марок, однако, это значение идёт в программируемую таблицу, в которой уже находится настоящий цвет. Изменением значения в таблице палитры меняется отображаемый цвет на экране без изменения единого байта в видеопамяти. А это звучит как интересная идея для эффекта.

Основой будет графический образец. Для простой реализации сгенерируем 768 байт графики со значениями от 0 до 15 и будем их «проецировать» в атрибутную видеопамять. Порядок, значит, такой: берём значение из образца, это значение используем как индекс в таблице палитры, и значение из таблицы сохраняем в видеопамять. После отображения всего экрана сдвигаем все значения в палитре на одну позицию и повторяем по кругу. Значения от 0 до 15 определяют размер таблицы палитры.

Покажем, значит, реализацию на ассемблере. Содержит две рутины: первая заполняет экран узором (нелогично названная CLS) и вторая, которая показывает сам эффект (ANIMATE):

org 32768

;vypln obr. vzorom z PATTERNu CLS ld hl,PATTERN ld de,16384 CLS2 ld a,(hl) CLS3 ld (de),a inc e jr nz,CLS3 inc hl inc d

ld a,d cp 48h jr nz,CLS2 ld hl,16384 ld de,16384+2048 ld bc,4095 ldir ret

;presun data z obrazovky do BUFFERu ;(vysvetlim neskor) ANIMATE ld hl,22528 ld de,BUFFER ld bc,768 ldir

;hl = graficke data ;de = videoram ANIMATE1 ld hl,BUFFER ld de,22528 ;COLORS - tabulka palety, zacina na ;okruhlej adrese XX00, takze nam staci ;inicializovat hodnotu v reg. B a reg. C ;bude indexovat farby hodnotami 0 až 15 ld b,COLORS/256

;vyzdvihni graficke data ANIMATE2 ld a,(hl)

;OFFSET "posuva" tabulku farieb OFFSET add a,0 and 15

ld c,a ;vyzdvihni farbu ld a,(bc) ;uloz do videoram ld (de),a inc l inc e

;tuto cast zopakujeme, aby sme usetrili ;par taktov a vyhli sa kolizii s lucom ld a,(hl) OFFSET2 add a,0 and 15 ld c,a ld a,(bc) ld (de),a

inc hl inc de

;opakuj pokial nevypadneme z videoram ld a,d cp 23296/256 jr nz,ANIMATE2

ei halt

;posun OFFSETy - taky isty efekt by sme ;dosiahli napr. LDIRovanim tabulky COLORS, ;ale takto menime menej bajtov v pamati ld a,(OFFSET+1) inc a

and 15 ld (OFFSET+1),a ld (OFFSET2+1),a

;stlaceny Enter? ld a,191 in a,(254) rra jr c,ANIMATE1

ret

PATTERN db 01100110b db 00000000b db 10011001b db 00000000b

db 01100110b db 00000000b db 10011001b db 00000000b

align 256 COLORS db 0, 1, 2, 3 db 4, 5, 6, 7 db 7, 6, 5, 4 db 3, 2, 1, 0

BUFFER ds 768

Как вы могли заметить, в ассемблере я не генерирую никаких графических данных. Не то чтобы нельзя — но пока бы я это запрограммировал, отладил, оптимизировал и не дай бог ещё прокомментировал, прошло бы несколько Foreverов. Поэтому использую инструмент, для этого как созданный — Basic. Он сгенерирует нам графические данные в видеопамять, так что видим, что при генерации происходит, и в начале рутины ANIMATE эти данные копируем в BUFFER. Basic найдёте на дискете под названием «Palette», и вот описание его главных частей:

Строки 30–80: Определения функций, которые генерируют графические данные. Подробнее опишу их позже.

Со строки 100: Меню для выбора узора.

Со строки 200: Генерация графических данных. С помощью двух циклов FOR генерирую координаты x и y и вызываю одну из функций FN a до FN d (по выбранному узору). Поскольку в Basicе не умею создать ссылку на функцию, которую мог бы менять по выбранному узору, использую функцию VAL, которая вычисляет basic-выражение в строке. При выборе узора, значит, в переменную r$ сохраняю имя соответствующей функции и через VAL её вызываю.

Опишем, значит, отдельные функции. Помимо функций отдельных узоров определяю ещё две:

  • Первая — FN m(v), которая вычисляет остаток от деления (modulo) на 16.

  • Вторая функция FN t(x,y) — вспомогательная для последнего узора; объясню её в соответствующем разделе.

A - Whirpool

Этот эффект я впервые увидел в демо FSRD (часть после greetingов), и он мне так понравился, что использовал его и в проигрывателе музыки для ForeverQ. Функция выглядит так:

FN a(x,y)=(ATN((12-y)/(16-x+(x=16)*.1)) +PI/2)/0.208884

Немного нечитаемо, но принцип простой: для каждой [x,y] вычисляем угол с центром экрана [16,12] и используем его как значение для генерации индекса в палитру. Формула для получения угла выглядит так:

/ y1 - y2 \ угол = ATN | ------- | \ x1 - x2 /

Нужно следить за знаменателем дроби, потому что математики придумали, что на ноль делить нельзя. Поэтому я добавил (x=16)*.1. Сравнение в скобках принимает значение 0 для всех чисел, отличных от 16, и 1 для x равного 16. Тем самым вместо нуля в знаменателе получаем «почти ноль» 0.1.

Функция арктангенс (ATN) принимает значения от −PI/2 до PI/2, так что прибавляем к ней PI/2, чтобы получить только положительные числа, и делим на болгарскую константу — получаем числа от 0 до 15. Это число должно выражаться как PI/15, но поскольку в нашем случае ATN не возвращает совсем точно PI/2, записано просто так некрасиво.

B - Round tunnel

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

FN b(x,y)=FN m(INT(SQR((16-x)(16-x) +(12-y)(12-y))))

Словесно: вычисляем разность координат от центра экрана (16−x) и (12−y), эти разности возводим в квадрат и сумму этих степеней извлекаем корень (SQR). Результат наконец передаём функции FN m(), которая вычислит остаток от деления на 16, т. е. значение от 0 до 15.

Возможно, думаете, почему для возведения в степень не использую конструкцию ^2. По какой-то причине степень в Sinclair Basic не умеет работать с отрицательными числами. Альтернативно можно было бы записать и так:

SQR(ABS(16-x)^2+ABS(12-y)^2)

C - Square tunnel

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

FN c(x,y)=FN m((ABS(x-16)<=ABS(y-12)) ABS(y-12)+(ABS(x-16)>ABS(y-12)) ABS(x-16))

Немного нечитаемо — давайте поправим. Уберём функцию modulo FN m() и получим эти два выражения:

Если ABS (x-16) <= ABS (y-12) верни ABS (y-12) Если ABS (x-16) > ABS (y-12) верни ABS (x-16) Что это значит? Одна картинка — тысяча слов, насладитесь немного ASCII-артом:

y| / | x<y / | / | / | / | / x>y |/ +------- 0,0 x

Та 45-градусная прямая — место, где x равно y, и делит нам график на две части. Там, где выполняется x<=y, будем сохранять значение координаты Y. Потому что оно горизонтально всегда одинаково и, значит, будет создавать горизонтальные чёрточки. В сравнении у нас помимо знака меньше и знак равно — чтобы и для граничных значений генерировать какие-то цвета, иначе в узоре были бы нежелательные артефакты. Аналогично для части x>y, где сохраняем значение X, т. е. вертикальные чёрточки.

А что означают те минус 16 и минус 12? Ими сдвигаем центр системы координат в центр экрана, так что центр туннеля тоже будет в центре экрана. Это, однако, также приведёт к тому, что придётся иметь дело с координатами с отрицательными значениями X и Y. Чтобы этого избежать, каждое выражение «оборачиваем» функцией ABS, которая возвращает абсолютное значение выражения, то есть убирает знак.

D - 3d plane

Изначально хотел генерировать один культовый демосценерский узор, но сложность решения была слишком высока. Поэтому взял с другой стороны и придумал, наоборот, один из простейших 3D-примеров — вид на наклонную плоскость. Результат, однако, довольно интересный.

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

0.1x-y+z=0

Плоскость образует 45-градусный угол с плоскостью осей YZ и слегка отклоняется от оси X. Если это не будет ясно из моего ASCII-арта — можете попробовать ввести в математический анализатор типа Wolfram Alpha.

z| | ----|------------ / | / / |---- / / | | /45) / +-------------- / / / x / / / ------- /-------o y/

На оси Y можете заметить одну точку. Выбрал её как [0, −32, 0]. Это место, из которого будем генерировать прямые, проходящие через прямоугольник, нарисованный между координатами X и Z. Напишем, значит, уравнения этих прямых:

x = sxt y = -32+t z = syt

Переменные sx и sy — координаты экрана. Заметьте, что sy подставляем в уравнение координаты z, потому что 3d-пространство странное. Если теперь уравнения прямых подставим в уравнение плоскости, получим уравнение:

0.1sxt+32-t+syt=0

и оттуда можем выразить значение t:

t = -32 / (0.1sx - 1 + sy)

Если подставим координаты sx и sy в это уравнение, получим параметр t, в котором прямая пересекается с плоскостью. Для этого вычисления у нас создана функция FN t, которая выглядит так:

DEF FN t(x,y)=-32/(.1*x-1+y+10)

Единственное изменение относительно нашей теории — добавленное +10, чтобы смотреть на чуть более интересное место. Предыдущее предложение не очень имеет смысл — что уже интересного на плоскости? Но хотя бы видите, что в Restore опубликуют любую чушь. Теперь знаем точку, откуда смотрим, знаем точку, в которой наша прямая пересекается с плоскостью для координат sx,sy, и для выражения цвета узора на этих координатах можем использовать расстояние этих двух точек. Для повторения:

bod1 = [0, -32, 0] bod2 = [sxt, -32+t, syt]

Расстояние двух точек мы уже сегодня считали; в пространстве это выглядит так:

SQR((x1-x2)^2+ (y1-y2)^2+ (z1-z2)^2)

Подставим, значит, координаты наших точек:

SQR((0-sxt)^2+ (-32+32-t)^2+ (0-syt)^2)

И после упрощения получим:

SQR((sxt)^2+t^2+(syt)^2)

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

FN d(x,y)=FN m(SQR(xFN t(x,y)xFN t(x,y) +FN t(x,y)FN t(x,y)+yFN t(x,y) y*FN t(x,y)))

Снова избегаю использования оператора ^. Как видно, функция FN t вызывается донельзя часто, так что скорость генерации можно было бы резко ускорить — но так можем вызывать эту функцию без больших изменений в нашей basic-программе.

А теперь подходим к концу сегодняшней статьи. Показали лишь простой эффект, когда палитра сдвигается в одном направлении. Могу представить, что использованный OFFSET мог бы меняться, например, по синусной таблице, и что наша палитра могла бы быть не статичной, а морфировать между несколькими палитрами. Также можете попробовать другие палитры, например:

RED_YELLOW_COLORS db 0, 0, 2, 3

db 6, 6, 7, 7 db 7, 7, 6, 6 db 3, 2, 0, 0

или

GREEN_BLUE_COLORS db 0, 1, 1, 4 db 4, 5, 5, 7 db 7, 5, 5, 4 db 4, 1, 1, 0

Маленький совет: эти палитры можете просто наPOKEовать с адреса 33024 без необходимости что-либо компилировать.

Желаю вам много запрограммированных палитровых эффектов и жду захватывающих демо! Keep coding!

mike/zeroteam

Поделитесь вашим мнением о статье