En ^ Ru

AVS Programming Guide
 Version 1.1
Tom Young (A.K.A. PAK-9)
 
http://avs.chat.ru/avspg.htm
http://avs.chat.ru/avspg.zip
 
Contents:
Part 0.  Introduction
Part 1.  The Fundamentals
Part 2.  The Superscope
Part 3.  Movements & Dynamic Movements
Part 4.  The Third Dimension
Part 5.  Registers
Part 6.  Megabuf and loops
Part 7.  Coding Effect Lists
Part 8.  Mouse Input
Part 9.  The Triangle APE
Part 10. Advanced 3D
Part 11. Tips & Tricks
Part 12. Advanced Exercises
 
Примечание: Содержимое этого документа являются интеллектуальной собственностью Thomas Young; Вы можете свободно распространять этот документ, не изменяя его никаким образом. Этот документ не может включаться в любой коммерческий продукт для извлечения выгоды или иным способом без специального разрешения автора. Код содержащийся в этом документе может быть использован свободно с или без модификации. Advanced Visualization Studio защищена авторским правом Nullsoft.
 
Introduction
 
Добро пожаловать в Программное руководство AVS, документ написанный для начинающих AVS-художников, чтобы помочь им изучить основы языка программирования AVS. Этот документ предполагает, что Вы проэкспериментировали, по крайней мере, несколько раз с эффектами AVS, но при этом никакого предшествующего опыта программирования не требуется.
 
Имейте в виду, что это не справочный документ, так это он не заменяет подсказки по выражениям, которые я рекомендую Вам использовать для ознакомления с основами синтаксиса и операторов. Это также не математический документ, - многие люди думают, что комплекс AVS пресетов требует много математики, тем не менее это случается редко. Скажем, что нам нужно использовать математические выражения иногда, чтобы решить проблемы программирования, и подготовиться к некоторой теории.
 
Формат документа разрабатывается так, чтобы имелась возможность пройти новые темы в быстром темпе, но если первичные темы кажутся вам слишком подробными, то можно свободно перейти к следующим частям. Философией подхода является обучение на собственном опыте, так что большинство частей - краткое описание с примером или двумя, а также упражнения, которые я рекомендую вам, чтобы попытаться улучшить понимание темы.
 
Я не получаю достаточно много отзывов относительно этого руководства, так что могу только преполагать, что есть нечто неправильное в нём, - если Вы не согласны с чем-либо или имеете предложения для улучшения документа (любые отзывы приветствуются), то пишите мне на email: PAK.nine*gmail.com
 
Философия обучения, которой я рекомендую вам следовать:
 Прочитайте и забудьте
 Запишите и запомните
 Сделайте и поймите
 

Part 1: The Fundamentals
 
Окно AVS является по существу холстом, на котором объекты размещаются и изменяются для создания эффектов. Основные элементы разделяются на две категории: Render и Trans элементы. Render элементы буквально рендерятся в окно так, что пиксели "помещаются" в окне (возможно смешиваются с тем, что там уже есть). Элементы Trans влияют на существующие пиксели на определенном пути, сдвигая их или изменяя их значение (запомните, пиксели являются просто красной, зеленой и синей величиной).
 
Само окно AVS распределяется от (-1) до (1) по X и Y. Здесь X - горизонтальное ось, где (-1) является крайним левым, а (1) является крайним правым размещением. Y - вертикальное ось, где (-1) есть верх, а (1) есть низ. Следовательно, очевидно, что рендеринг пикселя в (0,0) произведет точку точно в центре окна AVS.
 
Программирование в AVS разбито на 4 этапа выполнения (для точек, в которых выполняется код), Init, Frame, Pixel (или Point) и Beat. Init - основание инициализации, код в этом боксе выполняется, когда пресет запущен (есть исключение из этого, но в данный момент примите это как есть). Frame выполняется один раз для каждого фрейма, когда рендерится AVS. Сумма времени на выполнение Pixel кода изменяется в зависимости от элемента, например, SuperScope с n точками выполняется n раз за каждый фрейм. Часто люди делают ошибку, вставляя в бокс Pixel код, который нужно размещать в боксе Frame, что означает, что код выполняется многого больше времени чем необходимо, и мы адресуем эту проблему для всего руководства. Beat код выполняется, когда обнаружен такт в музыке.
 
Мы не будем более вдаваться в детали относительно функций, предусмотренных AVS, поскольку они документированы в помощи выражения; тем не менее, хорошая идея посмотреть на них для понимания имеющихся возможностей. Вместо этого, Вы только должны гарантировать, что знакомы со следующим:
 
 * Переменные создаются в назначении, и MyVar=0 создаст переменную MyVar. Таким образом MyVar=MyVar+1 достаточно, чтобы создать приращение переменной.
 * Назначение сделано с использованием знака (=). Так MyVar=1 установит величину 1 в переменную MyVar.
 * Функции возвращают значения, поэтому MyVar=sin(2) вызывает функцию sin, и возвращает величину, которая устанавливается в MyVar. Это относится к большинству функций, хотя многие просто возвращают 1 или 0.
 * Каждое утверждение должно заканчиваться точкой с запятой. Так MyVar=0;
 
Всё это - только напоминания, - смотрите в помощи выражения для большего количества деталей об основном синтаксисе AVS, и некоторые из операторов, которые доступны.
 

Part 2: The Superscope
 
Большинство людей согласилось бы, что SuperScope - главный render объект в AVS, и это чрезвычайно мощный инструмент. Некоторые важные переменные:
 
 n - Представляет число точек для рендеринга.
 b - (beat) Только для чтения, и имеет значение 1, если есть бит, иначе 0.
 i - Значение которое изменяется от 0 до 1, согласно текущей позиции рендеринга (i может быть определено как 1/n*p, где p - текущий пункт рендеринга, и, таким образом, например, 3 точки SuperScope получат значения 0, 0.5 и 1).
 v - Значение формы волны или спектра (как определено) в текущем пункте. Данные волны или спектра - это сигнал от 0 до 1.
 red - Количество красного цвета, от 0 до 1, может быть прописано в любом боксе.
 blue - Аналогично с синим.
 green - Аналогично с зеленым.
 linesize - Если эффект рендерится как линии, то ширина линии, от 0 до 255.
 skip - Если установлено больше ноля, текущий пункт не будет предоставлен.
 drawmode - Если не ноль, будят рендериться линии (Lines), иначе будут рендериться точки (Dots).
 
Вы не должны помнить всего, они приведены просто как справка. Эффект некоторых из этих переменных может фактически копироваться, например v, который может копироваться из getspec() или getosc().
 
Достаточно скучной теории, попытаемся получить код. Первый шаблон, который мы собираемся сделать, является простой областью simple scope, поэтому запустите AVS, создайте новый пресет и добавьте SuperScope, убедитесь, что "Чистить каждый кадр" помечен в Main (это очистит окно AVS в начале каждого фрейма). После чего наберите следующее в кодовых боксах:
 
 Init:
n=800;
 Point:
X=2*i-1;
Y=v*0.5;
 
У нас есть наша область, теперь взглянем на код. Прежде всего мы установили n в 800 значений, мы хотим 800 пунктов для нашей суперобласти, и если Вы измените значение на 8, то заметите, на сколько еще не готова область, когда мы представляем её с меньшим количеством пунктов. Мы помещаем этот код в Init, только потому, что хотим указать AVS количество пунктов, - переменная установлена один раз, и мы не должны больше об этом волноваться.
 
Мы устанавливаем X в 2*i-1 … почему? Поскольку мы хотим, чтобы область охватила целое окно, мы не можем просто использовать i, потому как этот диапазон охватывает только от 0 до 1, а мы нуждаемся в диапазоне от -1 до 1. Таким образом, мы умножаем переменную на 2 (теперь, она охватывает от 0 до 2), и вычитаем 1 (теперь от -1 до 1).
 
Наконец мы устанавливаем Y в v*0.5, и мы могли бы использовать только v, но это выглядит как "грязное" заполнение целого экрана, таким образом мы умножаем переменную на 0.5, теперь вместо того, чтобы охватить диапазон от -1 до 1, область охватывает от -0.5 до 0.5 … намного лучше.
 
Помните, я говорил, что мы могли бы копировать v? Хорошо, попробуем, и если мы нуждаемся в данных осциллографа, то будем использовать getosc(), синтаксис - getosc(band,width,channel), где band - полоса образца (0..1), width - ширина образца (0..1), и channel - канал (0=center, 1=left, 2=right). Не волнуйтесь, если это слишком сложно для Вас, важная часть - band, width будет 0.1, и channel будет 0 (центр).
 
[getosc returns waveform data centered at 'band', sampled 'width' wide, return value is (-1..1)]
 
Замените свой пойнткод:
 
 Point:
X=2*i-1;
Y=getosc(x,0.1,0);
 
Теперь мы поместили x в функцию getosc(), чтобы получать данные области, но кое-что является неправильным, band должно иметь значение от 0 до 1, а мы отдаём величину от -1 до 1. Посмотрите, можете ли Вы придумать решение прежде, чем продолжите.
 
Решение состоит в том, чтобы умножить и переместить x таким образом, чтобы переменная находилась в пределах от 0 до 1, а вопрос в том, как нам преобразовать -1..1 в 0..1? Хорошо, прежде всего, величина в 2 раза больше, таким образом x*0.5 - это хорошее начало, но теперь мы имеем -0.5..0.5, и нам нужно переместить значение к 0.5 ... получаем x*0.5+0.5
 
 Point:
X=2*i-1;
Y=getosc(x*0.5+0.5,0.1,0);
 
Вы только что сделали свою собственную версию v, примите поздравления.
 
Exercises:
 
Сделайте область, которая является вертикальной вместо горизонтальной. (Exercise 02A.avs)
Сделайте область, которая охватывает диапазон от -0.5 до 0.5 по оси X. (Exercise 02B.avs)
 
[line]
 
Теперь попробуем перейти на другую область, создавая суперобласть, которая чертит линию из произвольного пункта до другого произвольного пункта. Выглядит как простая проблема, но мы будем смотреть на некоторые новые функции, чтобы осуществить это. Прежде всего, мы собираемся убрать i … правильно, i для этой задачи не нужен. Сделайте новый пресет, добавьте SuperScope и установите n=2, потому что нам потребуется только 2 пункта, в начале и конце. Теперь помните, что point code выполнен один раз для каждого пункта, таким образом всё, в чём мы нуждаемся, является способ определения, какой пункт в настоящее время рендерится. Введите следующий код:
 
 Init:
n=2;
 Frame:
drawmode=1;
CurPoint=0;
 Point:
CurPoint=CurPoint+1;
 
Теперь на каждом фрейме происходит следующее:
 
CurPoint установлен в 0 (frame code)
CurPoint установлен в 1 (point code)
 Пойнткод выполнен
CurPoint установлен в 2 (point code)
 Пойнткод выполнен
 
Важно, чтобы Вы ухватили эту концепцию, так как она является ключом к оперированию с SuperScope, frame code выполнен, затем point code выполнен дважды (потому что n=2), и мы увеличиваем CurPoint в pixel code, чтобы дать нам способ идентифицировать то, какая точка рендерится.
 
Теперь, когда у нас есть способ узнать, какой пункт рендерится, мы можем определить, где записать рендер с использованием утверждения "если" …
 
 Point:
CurPoint=CurPoint+1;
X=if(equal(CurPoint,1),-0.5,0.5);
Y=0;
 
Здесь мы говорим, что эти X должен быть установлен в -0.5, если величина CurPoint=1, иначе установливаем в 0.5. Так, если мы расширим эту идею до немного более полного решения:
 
 Init:
n=2;
X1=-0.5; Y1=0.25;
X2=0.5; Y2=-0.25;
 Frame:
drawmode=1;
CurPoint=0;
 Point:
CurPoint=CurPoint+1;
X=if(equal(CurPoint,1),X1,X2);
Y=if(equal(CurPoint,1),Y1,Y2);
 
Теперь у нас есть суперобласть, которая будет чертить линию от X1,Y1 к X2,Y2. Теперь попытаемся сделать это решение немного лучше, мы можем фактически удалить equal() из каждого утверждения "если", потому что утверждение только проверяет - является ли первый аргумент 0 или нет, чтобы оценить параметр. Так как у нас есть переменная, которая является или 1, или 2, мы можем сделать маленькое урегулирование, чтобы сделать её или 0, или 1, и передать непосредственно в утверждение "если".
 
 Point:
X=if(CurPoint,X1,X2);
Y=if(CurPoint,Y1,Y2);
CurPoint=CurPoint+1;
 
Перемещая добавление единицы в последнюю строку, мы получаем Curpoint=0 для первого пункта и 1 для второго.
 
Exercises:
 
Сделайте суперобласть, которая протягивается между 3 произвольными пунктами. (Exercise 02C.avs)
 
[square]
 
Прекрасно, линии это очень хорошо, я уверен, что Вы уже попытались сделать волну синуса и возможно некоторые другие хитрости, но что относительно "твердых" объектов? Я уверен, что Вы видели пресеты, где люди производят кубы и другие геометрические объекты, - попытаемся сделать это самостоятельно. Мы будем придерживаться 2D, потому что Вы можете применить те же самые принципы в 3D, и это включено в нижеследующюю главу.
 
Ключевой принцип "твердой" суперобласти то, что она сделана из линий, и надо надеяться, что это не слишком большое допущение, но линии - это всё, что SuperScope может сделать (и, конечно, точки). Таким образом, "хитрость" должна произвести серию линий, которые так сильно упакованы, что они обеспечивают появление "твердого" предмета. Давайте, для начала, попробуем "твердый" квадрат, сделаем новый шаблон с SuperScope и впишем следующее:
 
 Frame:
n=w;
drawmode=1;
switch=0.5;
 Point:
switch=-switch;
x=0;
y=switch;
 
Здесь несколько моментов, которые необходимо отметить, - прежде всего мы переместили n в frame box и установили его как выражение, а не как константу. w - это переменная, которая содержит ширину окна AVS, таким образом, мы устанавливали число пунктов к тому значению. Это количество пунктов может показаться слишком большим, но чтобы предмет выглядел "твердыми", нам нужно много линий. Переменная находится в frame box, потому что мы хотим, чтобы w осталось неизменным, даже если пользователь изменит размеры окна AVS, и если бы код был в Init, то он не был бы исполнен после рестарта пресета.
 
Вы можете разобраться, что делает переменная switch? Она изменяется от 0.5 до -0.5 для каждого противоположного пункта. "Хорошо, но где мой твердый квадрат?!" Я слышу, что Вы вопрошаете, и мы надеемся, Вы сообразили, что в настоящее время суперобласть отдает много линий от 0.5 до -0.5 и обратно, поэтому, если мы изменим x на (i-0.5), то что получим? Правильно, прекрасный "твердый" квадрат!
 
Но он выглядит немного "мягким", поэтому добавим несколько штрихов:
 
 Point:
switch=-switch;
x=i-0.5; y=switch;
blue=cos(x*2); red=0; green=0;
 
Мне не нужно объяснять, что делает функция cos(), но замечу, что мы передали x как аргумент так, чтобы интенсивность цвета изменилась с осью X. Попытаемся вместо этого изменять цвет через Y:
 
 Point:
switch=-switch;
x=i-0.5; y=switch;
blue=cos(y*2); red=0; green=0;
 
Однако, это вообще не выглядит очень уж хорошо, знаете, почему цвет не изменяется по оси Y? Мы тянем линии между Y=-0.5 и Y=0.5, и цвет выбран для каждого пункта, таким образом, мы получаем только colurs blue=cos(-0.5*2) и cos(0.5*2). Другими словами, получаемый цвет приблизительно равен blue=0.54.
 
Одно заключительное примечание по оптимизации, вместо того, чтобы иметь n=w, попробуем n=w*0.5. Выглядит немного "линейно", но легко выйти из затруднительного положения, просто изменив linesize на 2. В этом варианте ширина линий даст компенсацию на промежутки:
 
 Frame:
n=w*0.5;
linesize=2;
drawmode=1;
switch=0.5;
 
Мы делаем это, потому что пункты суперобласти являются весьма дорогостоящими с точки зрения частоты кадров (frame rate), и поэтому мы хотим удержать их количество по минимуму.
 
Это также подходящая ситуация, чтобы проиллюстрировать переменную skip, которая позволяет отключать правую сторону твердого квадрата:
 
 Point:
switch=-switch;
x=i-0.5; y=switch;
skip=above(x,0);
blue=cos(x*2+col); red=0; green=0;
 
Здесь мы устанавливаем skip=1, когда x больше, чем ноль, используя функцию above(), попытайтесь сами изменить её на below(), и Вы вероятно сможете предположить эффект, который будет иметь такая замена. Теперь уберём кусок квадрата из середины:
 
 Point:
switch=-switch;
x=i-0.5; y=switch;
skip=band(above(x,-0.25),below(x,0.25));
blue=cos(x*2+col); red=0; green=0;
 
Здесь мы используем функцию band(), которая означает, что будет возвращено 1, если x будет больше (-0.25) и меньше (0.25). Помните, мы не могли применить это же к оси Y, потому что мы можем только пропустить пункты, но не можем разорвать выстраиваемую линию на части.
 
Exercises:
 
Создайте твердый квадрат со штриховкой вдоль Оси Y. (Exercise 02D.avs)
Используйте linesize, чтобы сделать твердый квадрат с 2 пунктами суперобласти. (Exercise 02E.avs)
 
[shading]
 
Твердая суперобласть, которую мы сможем раскрасить по обоим осям, является нашей следующей задачей, для чего мы должны будем разделить каждую из наших линий на части.
 
На сколько частей мы делим каждую линию, является вопросом личного предпочтения, хотя мы не хотим выбирать слишком большое число, поскольку наша частота кадров создаст большую нагрузку на процессор.
 
У нас также должно быть постоянное число пунктов (а не n=w*0.5 или подобное) так, чтобы мы не столкнулись с проблемами вычисления размеров нашего твердого предмета. Это может быть сделано с динамическим числом пунктов, но тогда код будет значительно более сложным.
 
Как вы, возможно, предположили, мы снова собираемся убрать i, потому что нуждаемся в немного большем контроле над нашими позициями точки. Прежде, чем мы начнем, мы должны сделать некоторые вычисления, позволяющими сказать, что мы собираемся иметь 101 линию, каждая из которых будет разделена на 11 пунктов за линию (эти значения, возможно, не кажутся очень уж круглыми, но позже вы сможете увидеть логику), и какое же здесь должно быть значение n? Я предложу Вам подумать об этом, в то время как мы продолжим выполнение …
 
Перейдём к коду, создайте новый пресет, уcтановите чистить каждый фрейм и добавьте Superscope.
 
 Init:
n=800;
 Frame:
drawmode=1;
line=-1; linepos=-1;
 Point:
x=line; [x=line*h/w;]
y=linepos;
 
Это значение n является неправильным в настоящее время, но мы изменим его позже, а пока, пытается увидеть, почему у нас есть переменные line и linepos. Переменная line будет вертикальной линией, с которой мы имеем дело, а linepos будет "куском" линии, которую мы в настоящее время рисуем. Поскольку мы установили линию и linepos в (-1), не трудно увидеть, что мы собираемся нарисовать линию от верхнего левого до нижнего правого угла. Теперь добавьте этот код в дополнение строк point box:
 
line=if(equal(linepos,1),line+0.02,line);
linepos=if(equal(linepos,1),-1,linepos+0.2);
 
[image001.gif] "Skip" these solid lines
 
Мы надеемся, что Вы можете следовать за этим кодом, и мы по существу добавляем 0.2 к linepos каждый раз, таким образом помещая пункт далее вниз экрана, пока не достигаем основания экрана, где мы задерживаем его на (-1) так, чтобы мы могли начать новую линию. Непосредственно переменная line проходит только когда последняя линия была закончена, то есть, когда linepos=1. Остается только одна проблема, мы все ещё тянем диагональные соединительные линии как показано серым цветом на диаграмме выше. Мы должны "пропустить" их, поэтому мы заменяем код на:
 
skip=equal(linepos,-1);
line=if(equal(linepos,1),line+0.02,line);
linepos=if(equal(linepos,1),-1,linepos+0.2);
 
Заметьте, что порядок, в котором мы размещаем эти команды, очень важен; если бы skip был в конце, то мы пропустили бы неправильный кусок линии. Теперь мы по существу закончили, но вы вероятно заметили, что ваш твердый объект только отчасти нарисован, таким образом мы должны высчитать правильное значение n. Поскольку у нас есть 101 линия и 11 пунктов на линии, мы можем использовать в нашем коде хорошие круглые числа вроде 0.02, и 0.2 … помните, чтобы нарисовать 10 линий сегмента нужно 11 пунктов. Вы вероятно привыкли к значениям n, являющимся вопросом предпочтения или результатом небольших догадок, но с проблемой подобно этой, число пунктов - это уже нетривиальная проблема, и если мы имеем немного или слишком много точек, то получим наложение или разрывы. В данном случае, мы можем обоснованно высчитать необходимые пункты просто потому, что у нас есть хороший чистый алгоритм (даже если я сам действительно так говорю), это - просто число линий, помноженное на число пунктов линии 101*11=1111.
 
Теперь давайте добавим небольшую ремарку, где equal(linepos,1) была бы уместна как отдельная переменная, также мы должны изменить linesize, чтобы заполнить промежутки, и должны просчитать квадрат по маштабу для лучшего зрительного восприятия. Окончательная версия похожа на это:
 
 Init:
n=1111;
 Frame:
drawmode=1; linesize=w*0.01;
line=-1; linepos=-1;
 Point:
x=line; y=linepos;
newline=equal(linepos,1);
skip=equal(linepos,-1);
line=if(newline,line+0.02,line);
linepos=if(newline,-1,linepos+0.2);
x=x*0.75; y=y*0.75;
 
Получилось вполне неплохо и аккуратно. Теперь позволим себе добавить некоторую штриховку, добавив, к уже записанному в point box, строки:
 
red=sin(x*2); blue=sin(y+0.5); green=sin(x-0.6)*sin(y-0.5);
 
Вы можете сказать, что штриховка по оси Y является достаточно блочной, даже при том, что у нас суперобласть с более чем 1000 пунктов. Другими словами, высококачественная твердая суперобласть очень дорога с точки зрения fps. Конечно, вы всегда можете увеличить число фрагментов линии …
 
Exercises:
 
Доработайте твердую суперобласть, чтобы иметь 21 пункт на линию (Exercise 02F.avs)
Аппроксимируйте твердую суперобласть с w*0.5 линиями и пунктами h*0.5 на линию (Exercise 02G.avs)
 

Part 3: Movements & Dynamic Movements
 
Movements и Dynamic Movements (DM) являются двумя из самых сильных инструментов trans, которые существуют в AVS, и мы начнём с рассмотрения Movement, а затем обратим свой взгляд на DM. Главное различие между Movement и DM в том, что Вы не можете использовать изменяемые переменные в Movement, - код составлен один раз, и после этого исполняется для каждого пиксела каждого фрейма.
 
Movement использует следующие переменные:
d - (depth) значение "глубины" каждого пиксела;
r - (rotation) значение "вращения" каждого пиксела;
x - значение x каждого пиксела;
y - значение y каждого пиксела;
 
Переменные d и r могут использоваться, только если чекбокс "прямоугольные координаты" снят, а x и y могут использоваться, только если он отмечен. Так происходит потому, что у Movement есть по существу 2 способа, чтобы сделать определенные операции (такие как вращение); Чтобы понять эти различные "способы" действия, мы должны смотреть на системы координат, и если Вы уже знакомы с прямоугольными и полярными системами координат, то можете пропустить это краткое введение.
 
A brief look at coordinate systems
 
Вы должны уже быть знакомыми с прямоугольной системой координат, так как это стандартная система координат AVS, и чтобы установить точку по этому методу, мы определяем x и y компоненты, от 0 до 1 в каждом случае. Это типичная и, мы надеемся, очень интуитивно понятная система координат, также Вы могли слышать как она называется декартовой системой координат. Вам может быть любопытно относительно того, почему ось Y рассматривает верхнюю часть окна как (-1) и основания (1), а не наоборот, но на практике это не имеет никакого реального значения, обозначается как "right handed" система координат (более подробно об этом в разделе 4), и это - просто проектный выбор, который был принят создателями AVS.
 
[image002.gif]
 
Однако, другая общая система координат - это полярная система координат, где пункт определен расстоянием от центра (d) и вращением (r). В центре окна d=0, так как это - центр, в пунктах, определенных кругом, касающимся края окна, d=1. Значение r колеблется от 0 до определённого угла вращения в радианах (r увеличения против часовой стрелки и уменьшается по часовой стрелке), где, например, r=pi - это вращение на 180 градусов. Есть уравнение для преобразования полярных координат в прямоугольные (и обратно), тем не менее, я не хотел бы сейчас останавливаться на этом, потому что понимать принципиальную схему важне.
 
Здесь приведено несколько примеров соответствия значений в 2 системах координат, попытайтесь увидеть интуитивно (используйте диаграммы для подсказки), почему они эквивалентны. Помните, что r=0 - то же самое, что отрицательная ось Y (другой проектный выбор AVS, как обычно бывает, r=0 - то же самое, что положительная ось X).
 
[image011.gif]
 
Rectangular (Cartesian) .. Polar
x=0; y=0;    .. d=0;   r=0;
x=0; y=-1;   .. d=1;   r=0;
x=0; y=-0.5; .. d=0.5; r=0;
x=0; y=1;    .. d=1;   r=pi;
x=1; y=0;    .. d=1;   r=-pi/2;
 
Если вы желаете увидеть это более наглядно в действии, откройте AVS пресет "Demonstration - Coordinate Systems.avs" в папке AVS programming guide.
 
Закончим с теорией систем координат, вспомним, что это не математический документ, так что, если у Вас есть проблема с пониманием этой принципиальной схемы, то предлагаю поискать в другом месте лучшее описание. Переходим к кодированию…!
 
Начнём с нового пресета, чистим каждый фрейм, добавляем SuperScope со следующим кодом:
 
 Init:
n=5;
 Frame:
drawmode=1; linesize=10;
point=0;
 Point:
x=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3);
y=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3);
x=x*0.5; y=y*0.5;
point=if(equal(point,3),0,point+1);
 
Надеюсь, Вы видите, что так будет нарисован квадрат, и помните, что trans элементы только усиливают то, что уже отрендерилось, а самостоятельно Movement ничего не делает. Теперь добавьте Trans-Movement, выберите (user defined) из списка и введите следующий код:
 
 User defined code:
d=d*2;
 
Заметьте, что теперь квадрат уменьшился вдвое. Почему? Хорошо, мы говорим, что для каждого пиксела (в полярных координатах) мы берем пиксел, который на удвоенном расстояние от оригинала, таким образом для каждого пиксела, где d=1 мы берем пиксел из d=2 (конечно нет никаких пикселов в d=2, хотя, таким образом мы просто берем черный пиксел).
 
[Фактически изображение зуммируется или смещается. Освобождающееся пространство заполняется базовым цветом.]
 
Тот же самый эффект может быть достигнут установкой флажка "прямоугольные координаты" и входом по:
 
 User defined code:
x=x*2;
y=y*2;
 
Здесь мы делаем нечто очень подобное предыдущему, но только в прямоугольных координатах, то есть, для каждого пиксела берем пиксел, который дважды удалён от оригинала по x и y. По началу всё это может выглядеть несколько противоречиво, потому как люди часто думают: "Хорошо, если я умножу каждое положение на два, то оно будет в два раза большим", однако помните, что Вы назначаете новое положение пиксела, основанное на текущих координатах. Итак, основываясь на том, что я только что сказал, в каком направление этот код переместит пикселы …?
 
 User defined code:
x=x+0.5;
 
Ответ - влево, потому что мы назначаем каждому пикселу значение пиксела 0.5 направо от него. Помните, что 0.5 - координатная величина, но не число пикселов, таким образом x=x+1 переместит всё полностью за экран. "Но я хочу переместить все пикселы n", говорите вы, хорошо, чтобы переместить постоянное число пикселов, Вы сначала должны вычислить размер одного пиксела с точки зрения системы координат, и я предлагаю вам попытаться решить эту проблему самостоятельно (это является частью одного из заключительных упражнений в разделе 12).
 
Теперь уберите чекбокс "прямоугольные координаты" и войдите с:
 
 User defined code:
r=r+$pi*0.25;
 
Здесь мы поворачиваем экран на 45 градусов, где $pi - константа для пи, которую мы можем использовать вместо того, чтобы набирать вручную. Почему мы не можем сказать r=r+45? Поскольку помним, что полярная система координат использует радианы, а не градусы, если вам нужно преобразовать угол из градусов в радианы, то как раз умножьте значение на pi/180. И да, 45*pi/180 = pi*0.25.
 
[full screen gradient]
 
Посмотрим ещё на очень полезную (и обычно используемую) особенность Movements, создавать полноэкранные градиенты. Создайте новую суперобласть:
 
 Init:
n=800;
 Frame:
drawmode=1; linesize=1;
 Point:
x=2*i-1; y=0;
red=cos(x*$pi+$pi*0.5); green=cos(x*$pi); blue=cos(x*$pi-$pi*0.5);
 
Мы имеем простую суперобласть линии, которая производит градиент красного, зеленого и синего; однако мы хотели бы, чтобы суперобласть заполнила весь экран, а не была бы только линией. Давайте попытаемся думать о проблеме из перспективы перемещения Movement (то есть, какие координаты мы хотим назначить каждому пикселу, основываясь на их текущих координатах?). Хорошо, мы хотим, чтобы для каждого пиксела, значение (x) осталась тем же самым, но чтобы значение (y) каждого пиксела равнялась пункту на суперобласти линии. Так как суперобласть линии сидит на y=0, мы можем сказать для каждого пиксела x=x и y=0, но, конечно же, x=x является лишним, таким образом мы можем указать только y=0. Нужно просто продолжить, добавить Movement после SuperScope, отметить прямоугольные координаты и добавить код:
 
 User defined code:
y=0; [y=y*0.00000000000000001;]
 
Результат достигнут, и этот тип эффекта очень полезен, потому что заполняет целый экран всего только несколькими линиями кода, и выполняется чрезвычайно быстро. Как фон к пресету, градиент - это хороший выбор (не используйте плохой RGB градиент хотя, экспериментируйте, чтобы найти что-то более изысканное.)
 
[circular gradient]
 
Давайте теперь расширим эту идею, сделав круговой градиент, то есть, где цвет изменяется однородно от центра к краю окна. Надо надеяться, вы понимаете, что сделать это будет намного проще с использованием полярных координат, но вы вероятно найдёте это решение более трудным. И вы также уже, возможно, предположили, что мы собираемся использовать r=0, но давайте разберём проблему логически.
 
Если Вы представляете полярную систему координат в своем уме, то значение d и r различно для каждого пиксела, теперь представьте, что случится, если значение r константа. В этом случае мы говорим, что значение d может изменяться, поэтому цвет пиксела можно поменять, основываясь на его расстоянии от начала, но цвет пиксела будет тем же самым для каждого отдельного значения d. Если мы хотим получить градиент, используя Movement с r=0, то мы должны создать суперобласть от d=0 до d=1, при r=0, где это? Хорошо, смотрим снова на диаграмму:
 
[image012.gif]
 
Таким образом, тогда должно быть довольно ясно, что мы нуждаемся в суперобласти в прямоугольных координатах от (0,0) к (0,-1). Никаких проблем, выберите SuperScope и добавьте следующий код:
 
 Init:
n=800;
 Frame:
drawmode=1; linesize=1;
 Point:
y=-i; x=0;
red=cos(y*$pi*2+$pi*0.5+$pi); green=cos(y*$pi*2+$pi); blue=cos(y*$pi*2-$pi*0.5+$pi);
 
Затем добавьте наш Movement с кодом:
 
 User defined code:
r=0; [r=r*0.00000000000000001;]
 
Итого, прекрасный круглый градиент. И это всё, на что мы собираемся смотреть в Movements, согласен, что основательно углубился в проблематику прямоугольных/полярных координат, но это действительно важно для понимания механики Movement и Dynamic Movement.
 
[dynamic movement]
 
Далее одно очевидное положение:
Dynamic Movement - это Movement, которое к тому же является динамическим.
 
Почему это важно? Поскольку, если Вы можете сделать что-то в Movement, Вы можете сделать это и в DM, поэтому всё, что мы сейчас раскрыли относительно Movement, также действительно и здесь. Принципиальное различие в том, что теперь у нас могут быть переменные, которые изменяются, обозначая путь, по которому задействованные пикселы также могут изменяться динамически.
 
Нечто заслуживающее напоминания - что хотя Вы можете использовать такой же (static Movement) код в Movement и DM, качество в DM будет хуже; это потому, что DM разбивает экран на блоки для большей эффективности. Каждый блок приблизителен, а не точно высчитан; однако Вы можете улучшить точность, увеличивая размер сетки "grid size". И если Вы увеличиваете размер сетки, то хорошей идеей будет использовать мультипликаторы 2 (multiples of 2), потому что компьютеры работают быстрее с многократными цепями 2 [16; 32; 64], чем с произвольными числами.
 
DM компилирован на аналогичные этапы, как и в SuperScope, на init, frame, beat и pixel, а это означает, что Вы имеете достаточно большой контроль над движением пикселов. Начните новый шаблон с SuperScope:
 
 Init:
n=5;
 Frame:
drawmode=1; linesize=10;
point=0;
 Point:
x=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3);
y=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3);
x=x*0.5; y=y*0.5;
point=if(equal(point,3),0,point+1);
 
Это тот самый наш SuperScope квадрат, теперь добавим Dynamic Movement после SuperScope, со следующим кодом:
 
 Frame:
move=move+0.01;
 Pixel:
x=x+move;
 
Теперь включите Прямоугольные координаты (rectangular coordinates) и Обертку (wrap). Заметьте, как квадрат преодолевает экран и "оборачивается" вокруг экрана, когда квадрат уходит одной из сторон. Также мы можем вращать изображение, используя переменную r, отключите прямоугольные координаты и введите:
 
 Frame:
move=move+0.01;
 Pixel:
r=r+move;
 
[alpha blending]
 
Всё это вполне нам знакомо, потому как мы просто применяем эффекты аналогичные тем, которые пробовали в Movement, за исключением переменных, которые изменяются. Попытайтесь поставить флажок Смесь (blend) и заметьте, как изображение смешивается с оригиналом. Фактически, это очень мощная функция, поскольку мы можем использовать её для смешивания определенных частей изображения, используя переменную alpha. Альфа установлена в значения от 0 до 1, где 1 [и более] есть 100% непрозрачность нового изображения, а 0 [и менее] есть 100% непрозрачность старого изображения. Замените код пиксела этим:
 
 Pixel:
x=x+move;
alpha=sin(move);
 
Теперь мы исчезаем между квадратом суперобласти, и нашими изменёнными (или "перемещенными") пикселами. Теперь давайте попробуем это:
 
 Pixel:
x=x+move;
alpha=above(y,0);
 
Здесь мы говорим, что если y>0, то alpha=1, иначе alpha=0, таким образом мы раскололи экран на две половины.
 
[buffer save]
 
Альфа-смешивание может также использовать буфер, и независимо от того, что было раньше, начните полностью новый пресет, составленный как:
 
  Main
     Misc / Buffer Save
     Render / Clear screen
     Trans / Dynamic Movement
 
Установите Clear screen в White, отметьте Blend в Dynamic Movement и поставьте исходный комбобокс (combo box) "Что.." в "Buffer 1". Процедура будет использовать всё что угодно, находящееся в буфере, как данные, которые будут смешаны с имеющимся материалом, поэтому ничего не изменяя, мы смешиваем 50% черного (the buffer) с 50% белого (the clear screen), то есть получаем серый цвет. Надеемся, вы угадали, что:
 
 Pixel:
alpha=0;
 
Даст нам белый; а это …
 
 Pixel:
alpha=1;
 
… даст нам черный. Что относительно:
 
 Pixel:
alpha=d;
 
Мы знаем, что d изменяется от 0 в центре к 1 на краях окна, таким образом мы получаем хороший градиент от белого к черному. Теперь давайте попробуем:
 
 Pixel:
alpha=above(d,0.5);
 
Как можно было бы предположить, изображение является заполненным кругом (если вы не видите, почему так, то вернитесь ненадолго к краткому обзору системы координат), но выглядит оно довольно ужасно, не правда ли? Это часто упоминается как печально известный "уродливый синдром края" (ugly edge syndrome), связанный с DM. Так происходит, когда вы имеете очень высокую частоту изменения в цвете, поскольку DM даёт только приблизительные значения для каждого квадрата решетки (grid square). Мы можем уменьшить эффект, изменяя наш размер сетки (grid size), попробуйте 32x32, выглядит немного лучше, но всё ещё довольно неровно. Попробуйте 256x256, и, безусловно, так намного лучше…, но теперь посмотрите на ваш счетчик частоты кадров (frame rate counter). Падение в частоте кадров до такой величины неприемлемо, Вы можете думать, что всё плохо, но помните, что код в нашем pixel box теперь высчитывается в геометрической прогрессии больше раз. Для того чтобы проиллюстрировать ситуацию, попробуйте изменить ваш код:
 
 Pixel:
loop(10,
assign(a,sin(rand(100)))
);
alpha=above(d,0.5);
 
Не волнуйтесь о синтаксисе, полный код loop делает 10 произвольных операций синуса, но взгляните на вашу частоту кадров теперь. Держу пари на частоту, меньше 10fps, и это только от добавления 10 вычислений синуса. Мысль, которую я пытаюсь донести, - нужно держать свой размер сетки как можно меньшим, но это компромисс между качеством и скоростью. Я предложил бы никогда не использовать gridsize выше 128x128, и для многих применений, очень низкие значения, такие как 4x4, или 8x8 являются совершенно приемлемыми.
 
В завершение здесь написаны уравнения для того, чтобы преобразовать полярные координаты в прямоугольные и наоборот, они написаны как код AVS, а не как математические уравнения:
 
 Rectangular to polar:
r=atan2(-x,-y);
d=sqrt(sqr(x)+sqr(y);
 Polar to rectangular:
x=-d*sin(r);
y=-d*cos(r);
 

Part 4: The Third Dimension
 
Могу представить, что многие сразу перешли к этому разделу, потому как 3D - очень популярный метод AVS, с которым у людей часто есть трудности. Я подчеркивал, что это программный гид и не математический документ, но боюсь нам придётся углубиться в некоторую математику для этой главы.
 
[rotating square]
 
Прежде всего, позвольте узнать, как вращать что-либо в 2D, и уже слышу как вы говорите "это легко, нужно только использовать r… r=r+1, и всё готово!", - жаль, но неправильно. Мы здесь должны работать с прямоугольными координатами, чтобы когда мы перейдём в третье измерение, нам не пришлось столкнулись с проблемами в выражниях нашей 3D проекции. Ключ к вращению - это матрица вращения, то есть матрица, в которой мы можем передать x, y, суммарное вращение, и извлечь переведенные пиксели. Первая матрица вращения:
 
x=x*cos(rot)-y*sin(rot);
y=x*sin(rot)+y*cos(rot);
 
Где rot - угол вращения в радианах. Не волнуйтесь о понимании в частности математики за этим, только примите к сведению, что это способ поворачивать предметы на плоскости. Теперь реализуем это в пресете, сделаем новый пресет, чистим каждый фрейм, и добавим SuperScope со следующим:
 
 Init:
n=5;
 Frame:
rot=0; point=0;
 Point:
x1=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3);
y1=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3);
x1=x1*0.5; y1=y1*0.5;
x=x1*cos(rot)-y1*sin(rot); y=x1*sin(rot)+y1*cos(rot);
point=if(equal(point,3),0,point+1);
 
Не переживайте, если вам кажется, что здесь большое количество кода, попробуйте просмотреть его шаг за шагом. Первые три строки pixel box определяют квадрат (если вы не видите как это получается, то вернитесь к части 2), а следующие две строки просто изменяют его размеры, чтобы он был немного меньше. Заметьте, что мы теперь используем x1 и y1 вместо непосредственно x и y, и это не строго необходимо в данном случае, но это хорошая практика, -ссылаться на переменные, а не пикселы напрямую, как вы увидите позже. Последние две строки - это непосредственно вращение, где используются промежуточные x1, y1 и переменная rot, которая определена в frame box. Попытайтесь изменить значение rot на что-то отличное от 0, а затем попробуйте rot=rot+0.01; Так у нас получился вращающийся квадрат, что потребовало небольших усилий, но, я уверен, вы справились с этим.
 
Теперь попытаемся сделать то же самое в DM, устанавим обратно rot=0 в SuperScope, для того чтобы сделать его снова простым квадратом, затем добавляем DM после SuperScope, отмечаем прямоугольные координаты и добавляем следующий код:
 
 Frame:
rot=rot+0.01;
 Point:
x1=x; y1=y;
x=x1*cos(rot)-y1*sin(rot);
y=x1*sin(rot)+y1*cos(rot);
 
Отметьте, что в этом случае мы ДЕЙСТВИТЕЛЬНО должны поместить x и y в переменные, потому что мы влияем на пикселы по мере того как мы обрабатываем их. Попробуйте:
 
 Point:
x=x*cos(rot)-y*sin(rot);
y=x*sin(rot)+y*cos(rot);
 
Оцените результат.
 
Exercises:
 
Создайте SuperScope квадрат, и определите его вращение в градусах. (Exercise 04A.avs)
Создайте DM, который изменяет размеры экрана до 50% и поворачивает на pi/5 радиан. (Exercise 04B.avs)
 
[3D]
 
Теперь, когда мы справились с 2D вращением, можем перейти к 3D, а для этого мы нуждаемся ещё в двух матрицах вращения, чтобы иметь три измерения. Далее следуют матрицы вращения:
 
x1=x; y1=y; z1=z;
x2=x1*cos(rotz)-y1*sin(rotz); y2=x1*sin(rotz)+y1*cos(rotz); z2=z1;
x3=x2*cos(roty)+z2*sin(roty); y3=y2; z3=-x2*sin(roty)+z2*cos(roty);
x4=x3; y4=y3*cos(rotx)-z3*sin(rotx); z4=y3*sin(rotx)+z3*cos(rotx);
 
Кажется, что кода слишком много, но вы можете видеть, как некий принцип, что это одна наша матрица вращения, только примененная дважды. Заметьте, что вход для матриц вращения является результатом предыдущего. Хорошо, протяните свою левую руку так, чтобы ваша ладонь была перпендикулярна вам; поставьте свой большой палец вверх, ваш указательный палец направляется от вас, ваш [средний] палец указывает вправо (перпендикулярно вашей ладони), а оставшиеся два пальца прижмите к вашей ладони. Вы должны сделать жест, как это показано на рисунке:
 
[image014.gif]
Thumb (y axis)        - Большой палец вверх (ось y);
Index finger (z axis) - Указательный палец от себя (ось z);
Fore finger (x axis)  - [Средний] палец перпендикулярно ладони (ось x);
 
Переменные rotx, roty и rotz - количество вращения вокруг этих осей. Таким образом, вращение вокруг оси Y (изменение roty), соответствует вращению вашей ладони относительно большого пальца, указывающего вверх. Вращение вокруг Z (изменение rotz), соответствует вращению вашей руки относительно указательного пальца, направленного от вас, и т.д.
 
Один последний шаг необходим прежде, чем мы сможем осуществить всё это, и как только мы передали наши значения в матрицы вращения, мы должны "спроектировать" пикселы на нашу 2D область, что немного похоже на интерпретацию результата соответственно нашему окну. Проекцией является следующее:
 
x=x4/(z4+2);
y=y4/(z4+2);
 
Значение "2" фактически является константой, которая представляет масштабирование окончательного результата, поэтому значение "1" было бы вполне приемлемо, но приводило бы к слишком большому изображению (поскольку мы делим наши окончательные значения x и y на него). Хорошо, достаточно теории, давайте осуществим уже! Сделайте новый пресет, очистите каждую фрейм, и добавьте SuperScope с кодом:
 
 Init:
n=5;
 Frame:
linesize=2; drawmode=2;
rotx=rotx+0.01; roty=roty+0.02; rotz=rotz+0.03;
point=0;
 Point:
x1=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3);
y1=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3);
z1=0;
x2=x1*cos(rotz)-y1*sin(rotz); y2=x1*sin(rotz)+y1*cos(rotz); z2=z1;
x3=x2*cos(roty)+z2*sin(roty); y3=y2; z3=-x2*sin(roty)+z2*cos(roty);
x4=x3; y4=y3*cos(rotx)-z3*sin(rotx); z4=y3*sin(rotx)+z3*cos(rotx);
x=x4/(z4+2); y=y4/(z4+2);
point=if(equal(point,3),0,point+1);
 
Итак мы имеем наш вращающийся квадрат, не слишком плохой. Но этот код довольно неэффективный, поэтому мы должны от него отказаться. Заметьте, что это дополнительная оптимизация, поэтому не беспокойтесь, если вы её не понимаете. Прежде всего, все те синусы и косинусы не нужны для вычисления каждого пиксела, по мере того как только наше вращение изменяется каждый фрейм, поэтому мы можем высчитать значения в pixel box.
 
 Frame:
linesize=2; drawmode=2;
rotx=rotx+0.01; roty=roty+0.02; rotz=rotz+0.03;
crotx=cos(rotx); srotx=sin(rotx);
croty=cos(roty); sroty=sin(roty);
crotz=cos(rotz); srotz=sin(rotz);
point=0;
 Point:
x1=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3);
y1=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3);
z1=0;
x2=x1*crotz-y1*srotz; y2=x1*srotz+y1*crotz; z2=z1;
x3=x2*croty+z2*sroty; y3=y2; z3=-x2*sroty+z2*croty;
x4=x3; y4=y3*crotx-z3*srotx; z4=y3*srotx+z3*crotx;
x=x4/(z4+2); y=y4/(z4+2);
point=if(equal(point,3),0,point+1);
 
Кроме того, деления являются дорогостоящими в плане частоты кадров, поэтому хорошей идеей будет минимизировать затраты, и, таким образом, мы можем заменить:
 
x=x4/(z4+2); y=y4/(z4+2);
 на
z5=1/(z4+2); x=x4*z5; y=y4*z5;
 
Хотя, здесь больше линий кода, косвенно это фактически более эффективно, потому что мы удалили деление и заменили умножением. Наконец, резервные переменные, назначенные как x4=x3, могут быть удалены, и мы можем снова использовать переменные, такие как x1 вместо того, чтобы создать новые каждый раз. Выходит намного более опрятно:
 
 Init:
n=5;
 Frame:
linesize=2; drawmode=2;
rotx=rotx+0.01; roty=roty+0.02; rotz=rotz+0.03;
crotx=cos(rotx); srotx=sin(rotx); croty=cos(roty); sroty=sin(roty); crotz=cos(rotz); srotz=sin(rotz);
point=0;
 Point:
x1=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3);
y1=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3);
z1=0;
x2=x1*crotz-y1*srotz; y2=x1*srotz+y1*crotz;
x3=x2*croty+z1*sroty; z3=-x2*sroty+z1*croty;
y1=y2*crotx-z3*srotx; z1=y2*srotx+z3*crotx;
z2=1/(z1+2); x=x3*z2; y=y1*z2;
point=if(equal(point,3),0,point+1);
 
Возможно, немного тяжелее для чтения, но как только вы схватите основы, то уже сможете следовать за логикой. Хорошей идеей будет написать свой собственный код, используя всё приведённое в качестве справки, и как гарантию того, что вы понимаете изложенные здесь принципы.
 
[ray trace]
 
Если же мы хотим произвести трехмерный DM, то процесс немного отличается, - мы все еще нуждаемся в наши матрицах вращения, но поскольку мы работаем с каждым пикселом, то нам нужно пройти каждый пиксел в матрицах. Так, вместо того, чтобы определить форму с X1, Y1 и Z1, мы проходим в x и y текущего пиксела, с константой для z. Создайте новый пресет, очистите каждый фрейм, добавьте Render Picture (подойдёт любое изображение), и добавьте Trans Dynamic Movement. Установите в DM Прямоугольные координаты и Обертка, затем введите следующее:
 
 Frame:
ox=0; oy=0; oz=-1;
rotx=rotx+0.01; roty=roty+0.02; rotz=rotz+0.03;
crotx=cos(rotx); srotx=sin(rotx); croty=cos(roty); sroty=sin(roty); crotz=cos(rotz); srotz=sin(rotz);
 Pixel:
x1=x; y1=y; z1=1;
x2=x1*crotz-y1*srotz; y2=x1*srotz+y1*crotz;
x3=x2*croty+z1*sroty; z3=-x2*sroty+z1*croty;
y1=y2*crotx-z3*srotx; z1=y2*srotx+z3*crotx;
 
Ключевая концепция состоит в том, что когда мы имеем дело с трехмерным DM, мы изменяем окно к нашей собственной форме, представьте, что окно - это сетка, мы можем исказить её для того, чтобы сформировать формы и проекции. Вообразите сетку, на которую вы надавливаете своим пальцем, чтобы сформировать форму, - ваш палец - это "луч", который говорит сетке, где быть на данном этапе. Это довольно плохая аналогия для "трассировки луча", которая является только сутью того, что мы делаем, чтобы определить 3D DM. Три переменные, которые мы добавили, ox, oy и oz, являются "оригиналом" следа нашего луча, другими словами, положение камеры в нашем 3D мире. Последняя стадия следа луча отсутствует в нашем DM, потому что мы не решили чего мы всё же хотим показать, давайте попробуем плоскость, потому что это легко. Самая простая плоскость определяется посредством выражений:
 
t=-oz/z1;
x=x3*t-ox;
y=y1*t-oy;
 
Где t - это параметр трассировки луча, добавьте этот код в конце DM и посмотрите на результат. Может показаться, что есть две плоскости, но каждая - фактически призрак реальной плоскости. Заметьте, что oz установлен в (-1), потому что наша плоскость z=0 и так, если бы oz был 0, то наша камера была бы врезана в плоскости. Чтобы удалить призрачную плоскость, и удалить искажение, где плоскость простирается в бесконечность, нам нужно добавить некоторое затемнение расстояния. Добавьте clear screen и buffer save к вашему пресету следующим образом:
 
[image015.gif]
  Main
     Render / Picture
     Misc / Buffer Save
     Render / Clear screen
     Trans / Dynamic Movement
 
Измените источник (Source) DM (Что...) на Buffer 1, отметьте Смесь (Blend) и добавьте следующий код к концу вашего pixel box:
 
alpha=z1;
 
DM должен теперь выглядеть намного лучше. Мы смешали DM с Clear screen, чтобы произвести впечатление дальнего тумана; изображение сохранено в буфер, затем экран очищен, затем DM рендерит плоскость, но смешивает всё по норме z1. Поскольку z1 - это последнее значение z, которое мы получаем от наших матриц вращения, оно представляет дистанцию далеко от камеры. Протяните свою руку и укажите тремя пальцами так, как я показал вам прежде, теперь удерживайте указательный палец от себя и поворачивайте ваше тело, ваш указательный палец представляет окончательное значение z1.
 
Итак, как вы создадите другие формы? Хорошо, вы должны сесть с карандашом и бумагой, получить математическое уравнение для формы, которую вы хотите трассировать, и решить это уравнение, чтобы найти ваш параметр трассировки (t). Понимаю, что это не большая помощь, но вам будет нужно поискать в другом месте более подробное руководство по вычерчивания луча.
 
Exercises:
 
Сделайте 3D SuperScope заштрихованный квадрат. (Exercise 04C.avs)
Сделайте DM плоскость, где x=0. (Exercise 04D.avs)
 

Part 5: Registers
 
Иногда необходимо иметь глобальную переменную, которая может быть прочитана любым элементом AVS пресета. Для этого мы используем регистры, ряд переменных от reg00 до reg99, которые обрабатываем также как стандартные переменные.
 
Давайте начнем новый пресет, очистим каждый фрейм и добавим Render / Texer II. Texer2 очень подобен точечной суперобласти, за исключением того, что мы можем выбрать изображение для показа вместо многоточия. Введите следующее:
 
 Init:
n=1;
 Frame:
pos1=pos1+speed1; pos2=pos2+speed2;
Xpos=sin(pos1); Ypos=sin(pos2);
 Beat:
speed1=rand(10)/100; speed2=rand(10)/100;
 Point:
X=Xpos; Y=Ypos;
 
Теперь мы имеем шарик с довольно неустойчивым движением, главным образом потому, что мы изменяем скорость на каждом бите. Теперь, если мы хотим сделать SuperScope, которая рисует вертикальную линию через шарик, то мы должны будем использовать регистры, чтобы сохранить x позицию шарика. Измените frame code:
 
 Frame:
..
Reg00=Xpos;
 
Теперь, если мы откроем окно отладки Настройки > Отладка (Settings > Debug Window) и посмотрим на регистр 0, то увидим, что он обновляет положение X нашей точки. Теперь добавляем SuperScope и вводим следующий код:
 
 Init:
n=2;
 Frame:
Xpos=reg00;
Drawmode=2;
 Point:
X=Xpos; Y=2*i-1;
 
Там мы без проблем имеем синхронизированный Texer 2 и SuperScope. Обычно использование регистров требует наличия control scope (области контроля), являющейся суперобластью, которая ничего не отдает, но вместо этого содержит код, влияющий на пресет. Например, это может быть предварительное вычисление синусов и косинусов вращений для трехмерного пресета и хранение их в регистрах.
 
rotx=rotx+0.01; roty=roty+0.02; rotz=rotz+0.03;
crotx=cos(rotx); srotx=sin(rotx);
croty=cos(roty); sroty=sin(roty);
crotz=cos(rotz); srotz=sin(rotz);
reg01=crotx; reg02=srotx; reg03=croty; reg04=sroty; reg05=crotz; reg06=srotz;
 
Это позволяет вычислять синусы и косинусы только один раз за фрейм.
 
Exercises:
 
Сделайте control scope для 3D квадрата. (Exercise 05A.avs)
 

Part 6: Megabuf and loops
 
Megabuf - массив из миллиона элементов, которые позволяют хранить большие объемы данных и, что важно, обрабатывать их с циклами. К сожалению, синтаксис мегабуфера немного отличается от стандартного AVS:
 
Retrieving data:
MyVar=megabuf(index);
 
Entering data:
assign(megabuf(index),MyVar);
 
Где index - пункт мегабуфера (1 к 1 миллиону), чтобы получить данные или же ввести внутрь. Теперь также будем смотреть на циклы, потому что мегабуфер довольно бессмысленен, если циклы не используются. Синтаксис loops:
 
loop(count, statement)
 
Однако, потому что мы, вероятно, хотим выполнить больше чем одно выражение в цикле, мы также должны использовать:
 
exec2(parm1, parm2)
 
Эта функция оценивает сначала parm1, затем parm2, и возвращает значение parm2. Может показаться, что здесь слишком много новых функций, чтобы понять всё за один раз, но, к сожалению, все они необходимы. Давайте будем идти через пример, чтобы понять, как все они совмещаются…
 
Мы собираемся сделать SuperScope со 100 пунктами, которые хранят значение спектра на один этап в течение долгого времени, так, чтобы у нас могла быть область, которая прокручивается слева направо. Первая вещь, которую мы должны сделать, - это поместить значение спектра в megabuf(1), что мы делаем подобно этому:
 
assign(megabuf(1),-getspec(0.1,0.1,0));
 
Теперь мы должны создать циклы через 100 элементов мегабуфера и заполнить каждый предыдущим элементом, то есть, megabuf(i)=megabuf(i-1). Мы должны образовать циклы от 100-го до 2-го элемента, а не наоборот, иначе мы будем перезаписывать данные в процессе счёта. Вот код:
 
index=101;
loop(99,
  exec2(
    assign(index,index-1),
    assign(megabuf(index),megabuf(index-1))
  )
);
 
Всё выглядит весьма запутанным, но проследуем за этим логически. Сначала мы определили переменную, названную index, - она будет текущим элементом мегабуфера, с которым мы работаем, поэтому сначала устанавливаем её в 101. Затем начинаем нашу петлю и устанавливаем первый аргумент в 99, потому что хотим выполнить следующие части 99 раз, для элементов от 100 до 2. Мы не должны делать элемент 1, потому что устанавливаем его к значению спектра. Затем у нас есть exec2, потому что мы хотим выполнить 2 действия… декрементировать индекс, и устанавить megabuf item (пункт мегабуфера). Помните, что я сказал, что мы, вероятно, всегда будем хотеть сделать больше чем одно утверждение в цикле? Причина этого состоит в том, что мы вообще нуждаемся в способе идентифицировать текущий элемент, с которым мы работаем, и в этом случае переменная index должна быть увеличена или уменьшена. К сожалению, мы не можем просто сказать index=index-1, а должны использовать assign(index,index-1), потому что находимся внутри функции exec2. Наконец, непосредственно операция assign(megabuf(index),megabuf(index-1)), которая переносит каждый элемент мегабуфера вперед на единицу. Теперь мы должны вывести наружу элементы мегабуфера, поэтому в point box мы помещаем:
 
 Point:
index=index+1;
x=2*i-1;
y=megabuf(index);
 
Но нам нужно установить index в 0, прежде чем этот код будет выполнен, таким образом в конце frame box мы добавляем index=0. Окончательный SuperScope код похож на это:
 
 Init:
n=100;
 Frame:
drawmode=1;
assign(megabuf(1),-getspec(0.1,0.1,0));
index=101;
loop(99,
  exec2(
    assign(index,index-1),
    assign(megabuf(index),megabuf(index-1))
  )
);
index=0;
 Point:
index=index+1;
x=2*i-1; y=megabuf(index);
 
Теперь, в заключение, некоторые интересные моменты. Также есть ещё gmegabuf, то есть глобальный мегабуфер; и единственное различие между ними двумя - это то, что к gmegabuf можно получить доступ из любого элемента AVS, таким же образом, как это может регистр reg. Помните, что часто область контроля control scope используется в, пресете, чтобы сохранить весь сложный код и держать важные кодовые элементы в одном месте. Часто бывает более оправданно использовать gmegabuf для сложных операций и поместить код в область контроля для свободного доступа. Можно было бы утверждать, что регистры чрезвычайно избыточны с глобальным миллионным массивом доступных пунктов, однако регистры вообще делают для более легкого чтения кода (и при этом не требуется ужасного утверждения assign).
 
Exercises:
 
Сделайте 100-точечный Texer2, который заполняет megabuf 200 случайными значениями от -1 до 1 и использует каждую пару чисел как x и y для пункта. (Exercise 06A.avs)
 

Part 7: Coding Effect Lists
 
Относительно недавнее дополнение к AVS - способность кодировать списки эффектов, давая автору способность контролировать активацию списка эффектов и, до некоторой степени, его вход и выход. Давайте перейдём прямо к примеру, создадим новый пресет, очистим каждый фрейм, добавим Effect list, снимем отметку Включить (enabled), отметим Чистить каждый кадр (clear every frame) и Использовать оценку (use evaluation override). "Использовать оценку" указывает AVS, что мы хотим поместить код в боксы, и если этого не сделать, то код будет проигнорирован. Теперь добавьте объект Render / Text [с текстом "Inside effect list"] в Effect list и поместите "внутрь списка эффектов", перемещая его немного от центра так, чтобы образовалось некоторое свободное пространство [слева].
 
Сейчас список эффекта выключен, так что добавим некоторый код, который иногда будет включать его, для чего в список эффекта впечатаем следующее:
 
 Frame:
counter=counter+0.1;
enabled=above(sin(counter),0);
 
Переменная enabled - зарезервированное слово, которое мы используем, чтобы указать AVS, используем ли мы или нет список эффекта, устанавливая enabled в 0 или 1. Код, который мы вписали, просто производит волну синуса из переменного счетчика, и включает список эффекта, когда волна синуса больше чем 0. Теперь замените код:
 
 Frame:
counter=if(equal(beat,1),1,counter-0.05);
enabled=above(counter,0);
 
Здесь мы устанавливаем счетчик в 1, когда есть beat (beat - другое зарезервированное слово, которое AVS устанавливает в 1, когда есть удар), иначе мы уменьшаем его на 0.05. Список эффекта позволен, когда счетчик больше чем 0, а результирующий эффект состоит в том, что список эффекта позволен на некоторое время каждый раз, когда есть удар. Теперь мы можем включать и выключать списки эффекта когда захотим, поэтому давайте переходить к смешиванию (blending). Есть две важных переменные, когда мы имеем дело с blending effect lists:
 
[blending]
 
 Alphain  - количество входящих данных, которое нужно смешать с содержимым списка эффекта. Переменная имеет эффект только когда Входное смешивание (input blending) установлено в Adjustable.
 
 Alphaout - количество содержания списка эффекта, которое нужно смешать с данными за пределами списка эффекта. Переменная имеет эффект только когда Выходное смешивание (output blending) установлено в Adjustable.
 
Эти описания довольно сухи, и трудно представить результаты операций blend, пока вы немного не поэкспериментируете с ними. Давайте добавим другой текстовый объект за пределами нашего списка эффекта, таким образом мы сможем понять как blending работает. Добавьте текстовый объект перед списком эффекта с текстом "Outside effect list" и опустите немного текст от центра. Установите Выходное смешивание списка эффекта на Adjustable и замените код списка эффекта:
 
 Frame:
enabled=1;
counter=counter+0.01;
alphaout=abs(sin(counter));
 
Заметьте, как тексты усиливаются и ослабевают, потому что мы смешиваем список эффекта in и out. Также отметьте, что в коде у нас есть функция abs(), которая возвращает абсолютную величину её аргумента (то есть, отрицательные числа изменены на положительные), что должно гарантировать пребывание alphaout между 0 и 1.
 
Теперь попробуйте изменить Входное смешивание на Maximum. Заметьте, что данные вне списка эффекта всегда видимы, почему так? Потому что, когда список эффекта не видим, текст будет видим, поскольку он вне списка эффекта; а когда список эффекта видим, он содержит текст из outside (maximum blended) с текстом внутри списка эффекта.
 
Теперь установите Входное в Adjustable, а Выходное на Replace, и введите следующий код в список эффекта:
 
 Frame:
enabled=1;
counter=counter+0.01;
alphain=abs(sin(counter));
 
Здесь мы держим данные в списке эффекта видимыми и только усиливаем in и out данные, потому что наш output установлен на replace, внешний текст перемешивается с черными пикселами, когда не смешивается в списке эффекта... Вы, возможно, уже уяснили для себя, что иногда есть больше чем один способ достигнуть того же самого эффекта, но в более сложных расположениях списков эффекта, что будет гораздо менее правильным.
 
Exercises:
 
Сделайте три списка эффекта, которые смешиваются друг в друга последовательно, так что 1 смешивается в 2, затем 2 смешивается в 3, затем 3 смешивается в 1 и т.д. (Подсказка: registers) (Exercise 07A.avs)
 

Part 8: Mouse Input
 
Использование мыши - мощная особенность AVS, которая позволяет художникам добавлять интерактивность к их пресетам и даёт возможность использовать меню и другие окна как функциональные возможности. Мы не заинтересованы разбирать здесь правильное и неправильное использование для входа мыши (но было бы разумно вам самим рассмотреть, что даёт пресету контроль за мышью), вместо этого, мы посмотрим на то, как кодировать mouse input. Главная функция, которая нам требуется - это Getkbmouse(), и в зависимости от аргумента, который мы передаем ей, получаем различные значения:
 
Getkbmouse(1) Returns the X position of the cursor
Getkbmouse(2) Returns the Y position of the cursor
Getkbmouse(3) Returns the mouse left button state (0 up, 1 down)
Getkbmouse(4) Returns the mouse right button state (0 up, 1 down)
Getkbmouse(5) Returns the SHIFT state (0 up, 1 down) [Shift, а не средняя кнопка мыши, как это неправильно заявлено в помощи выражения]
 
Так, давайте сразу войдём в пример, сделаем новый пресет, очистим каждый фрейм, и добавим Render / Texer II со следующим кодом:
 
 Init:
N=1;
 Point:
x=getkbmouse(1);
y=getkbmouse(2);
 
Предельно просто, мышь контролирует точку. Но будем честными, точка, которую вы можете перемещать со своим курсором, не особенно полезна, поэтому давайте попробуем кое-что, что мы ещё можем использовать… это кнопка! Создайте новый пресет, очистите каждый фрейм, и добавьте SuperScope со следующим:
 
//button
 Init:
N=5;
 Frame:
drawmode=1;
point=1;
 Point:
point=(point+1)%4;
x=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3);
y=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3);
x=x*0.4; y=y*0.2;
 
Таким способом мы формируем нашу кнопку. Заметьте, что мы используем оператор модуля (modulus operator), чтобы создать счетчик вместо обычного утверждения "if", для того чтобы показать, что есть больше чем один способ приблизиться к большинству проблем. Теперь мы нуждаемся в некотором способе идентифицировать - была ли кнопка был нажата, поэтому добавьте вторую суперобласть со следующим кодом:
 
//button fill superscope
 Init:
N=2;
 Frame:
drawmode=1;
linesize=h*0.2;
 Point:
point=bnot(point);
x=if(point,-0.4,0.4); y=0;
red=0.3; blue=0.1; green=0.1;
 
Здесь мы используем linesize, чтобы сделать твердую суперобласть и заполнить пространство внутри зоны нашей кнопки, и при этом гарантировать, что новая область находится перед предыдущей, иначе мы превысим ограничения по линии кнопки. Теперь, наконец, мы нуждаемся в области контроля, и это всегда хорошая идея - создать область контроля, имея дело с контролем над мышью, потому что обычно, входной код мыши связывается с различными частями пресета, и вы не хотите потерять её следов. Так что, добавьте ещё SuperScope и сотрите все боксы, чтобы оставить пустую суперобласть, которую мы можем заполнить. Только теперь мы можем получить код, и первая вещь, которую мы должны сделать, - обнаружение мыши в области кнопки, так что добавьте следующее к управляющей SuperScope:
 
//control scope
 Frame:
inbox=
band(
band(above(getkbmouse(1),-0.4),below(getkbmouse(1),0.4)),
band(above(getkbmouse(2),-0.2),below(getkbmouse(2),0.2))
);
 
Мы здесь несколько раз используем band(), потому что хотим проверить много условий, которые происходят сразу, поскольку mouse x должна быть выше -0.4 и ниже 0.4, mouse y… и т.д. Чистый результат всех наших "и" - это то, что наша переменная inbox будет 1, если все условия будут верны и 0, если любое из условий ложно. Теперь уже мы можем использовать эту переменную, чтобы произвести эффект, значит добавляем следующее после вышеуказанного кода:
 
reg01=inbox;
 
Если хотите, можете посмотреть в окне отладки (debug) и увидеть, что reg01 изменяется от 0 до 1, когда мышь находится в области, но для намного более легкого изменения индикации, просто измените код области заполнения объема кнопки (button fill superscope) на следующее:
 
//button fill superscope
 Point:
point=bnot(point);
x=if(point,-0.4,0.4); y=0;
red=0.3+reg01*0.2; blue=0.1; green=0.1;
 
Здесь мы (косвенно) добавляем значение нашей переменной inbox к цвету, чтобы получить приятный эффект "mouse over". Единственной вещью, которую остаётся добавить теперь, является фактический нажим кнопки. Для этого мы сделаем так, что кнопка изменит цвет когда "нажато", поэтому доработайте область контроля (control scope) следующим образом:
 
//control scope
 Init:
Bstate=0;
 Frame:
inbox=
band(
band(above(getkbmouse(1),-0.4),below(getkbmouse(1),0.4)),
band(above(getkbmouse(2),-0.2),below(getkbmouse(2),0.2))
);
reg01=inbox;
Pressing=band(getkbmouse(3),inbox);
bstate=if(pressing,bnot(bstate),bstate);
reg02=bstate;
 
У нас теперь появились две новых переменные, pressing - переменная, которая будет установлена в 1, когда пользователь нажмет кнопку (то есть, когда левая кнопка мыши нажата, и мышь находится в области кнопки), и bstate - положение кнопки, которая или 1, или 0, мы говорим, если кнопка нажата, изменить состояние на противоположное, так, если она 0, то изменится на 1, и наоборот. Мы сохраняем положение кнопки в регистре, потому что собираемся использовать его в button fill superscope, изменяем button fill superscope на:
 
//button fill superscope
 Point:
point=bnot(point);
x=if(point,-0.4,0.4); y=0;
red=0.3+reg01*0.2; blue=0.1+reg02; green=0.1;
 
Теперь кнопка изменит цвет, когда будет нажата. Однако, вы заметили проблему? Наша кнопка мерцает, когда мы нажимаем ее, и, кажется, изменяется к случайному положению, можете сказать, почему это? Это из-за следующей линии в нашей области контроля:
 
bstate=if(pressing,bnot(bstate),bstate);
 
Поскольку код выполняется каждый кадр, он каждый кадр переключает bstate на bnot(bstate), пока мы удерживаем левую кнопку нажатой мыши в области. Нам нужен некоторый способ делать изменение положения только изменением один раз за нажатие клавиши мыши, и есть различные способы достигнуть этого, изменяем код области контроля на следующее:
 
//control scope
 Frame:
inbox=
band(
band(above(getkbmouse(1),-0.4),below(getkbmouse(1),0.4)),
band(above(getkbmouse(2),-0.2),below(getkbmouse(2),0.2))
);
reg01=inbox;
Pressing=band(getkbmouse(3),inbox);
bstate=if(band(pressing,unlocked),bnot(bstate),bstate);
reg02=bstate;
unlocked=bnot(getkbmouse(3));
 
Новые линии выделены красным цветом, всё что мы сделали, ввели переменную, которая установлена в конце кадра, и равняется 1, если мышь отпущена, и 0, если мышь нажата. Теперь мы изменяем наше состояние, только если pressed и unlocked равны 1. Если вам сложно понять, как работает переменная unlocked, последуйте за кодом от пользователя, не нажимающего кнопку, к пользователю, нажимающему её для нескольких фреймов, после этого не нажимая её вовсе. Помните, тот факт, что строка находится в конце кода, очень важен.
 
Теперь это наша законченная кнопка, и для того, чтобы показать её потенциальную пользу, давайте добавим список эффекта, который включается и выключается согласно положению кнопки. Добавьте effect list [после SuperScope], снимите Включить (enabled), поставьте Чистить каждый кадр (clear every frame), отметьте Использовать оценку (evaluation override), установите Входное и Выходное смешивание на Maximum, и добавьте следующий код:
 
 Frame:
Enabled=reg02;
 
Теперь добавьте текстовый объект в список эффекта с текстом "enabled" и попробуйте вашу кнопку. Просто или нет? Поскольку мы использовали область контроля и регистры, мы должны просто сослаться на регистр, для того чтобы получить положение кнопки. Effect lists и Mouse input - очень сильная комбинация, экспериментирйте с ними, чтобы увидеть, что вы можете произвести.
 

Part 9: The Triangle APE
 
Triangle APE был создан TomyLobo как вариант рендеринга треугольников в AVS, и учитывая наше предыдущее обсуждение по solid superscopes, польза от такой возможности должна быть очевидной. Способ, которым мы отдаем треугольники на экран, немного различается в SuperScope или Texer2, потому что у нас есть три координаты, чтобы работать с каждым треугольником; таким образом мы ссылаемся на x1, y1, x2, y2, x3, y3, чтобы определить углы нашего треугольника. Например, начните новый preset, очистите каждый фрейм и добавьте Render / Triangle со следующим кодом:
 
 Init:
n=1;
 Triangle:
x1=-0.5; y1=-0.5;
x2=0.5; y2=-0.5;
x3=0; y3=0.5;
 
Заметьте, что в ape треугольника, n представляет количество треугольников, которые мы хотим представить, а не число пунктов. Здесь мы описали простой треугольник, тем не менее точное написание координат треугольника имеет ограниченную пользу, поскольку мы вероятно хотим представить большее их количество. Из-за природы треугольников и определения трех координат, код имеет тенденцию быть более сложным чем в SuperScopes или Texer2’s и часто требует циклов. Но не переживайте, мы уже охватили все принципиальные понятия и сопутствующий синтаксис, и теперь просто должны применить его к более сложной проблеме.
 
Давайте попытаемся решить простую проблему: предоставление линий треугольников. Для большинства пресетов, включающих треугольники непосредственно, вы можете сесть с ручкой и бумагой и примерно вычислить, где вы хотите, чтобы треугольники находились, но это - простая проблема, которую мы просто перескочим...
 
Нам нужен ряд треугольников, поэтому логично было бы определить один треугольник, затем его переместить, отрендерить, переместить и т.д. Так что, давайте определим наш одиночный треугольник, нечто маленькое и смещённое  так, чтобы начать наш ряд, для чего доработайте ваш preset:
 
 Triangle:
x1=-0.9; y1=0.1;
x2=-0.8; y2=0.1;
x3=-0.85; y3=-0.1;
 
Итак мы продвигаемся, небольшой треугольник получен, теперь, если мы введём переменную, которая увеличивается каждый раз, когда проходит через код треугольника, то сможем добавить её к значениям x, чтобы выполнить наше смещение shift…
 
 Frame:
shift=0;
 Triangle:
x1=-0.9+shift; y1=0.1;
x2=-0.8+shift; y2=0.1;
x3=-0.85+shift; y3=-0.1;
shift=shift+0.1;
 
Здесь мы каждый кадр сбразываем shift на ноль, затем в течение каждого раза, когда код треугольника выполняется (один раз за triangle), значение shift увеличивается на 0.1. Теперь измените значение n в боксе init на 18 (18 треугольников), и мы получили наш ряд! И ради небольшой забавы, заставим фигуры ответить на музыку, изменяя значение y3…
 
 Triangle:
x1=-0.9+shift; y1=0.1;
x2=-0.8+shift; y2=0.1;
x3=-0.85+shift; y3=getosc(x1*0.5+0.5,0.1,0);
shift=shift+0.1;
 
Также мы можем добавить к треугольникам кодирование цвета, однако воспользуемся red1, blue1, green1 (вам приз, если сможете угадать почему так):
 
 Triangle:
x1=-0.9+shift; y1=0.1;
x2=-0.8+shift; y2=0.1;
x3=-0.85+shift; y3=getosc(x1*0.5+0.5,0.1,0);
shift=shift+0.1;
red1=abs(x1); blue1=1-x1; green1=0.6;
 

Part 10: Advanced 3D
 
Вспомним часть 4, где мы рассмотрели 3D, но это были только основы, теперь мы собираемся посмотреть на некоторые более сложные элементы работы в третьем измерении. Это самая продвинутая секция данного программного руководства, и если вы не чувствуете себя комфортно, занимаясь этими темами, то лучше больше времени потратить на ознакомление непосредственно с основами; нет никакого смысла пробираться через все эти трудности, не обучаясь чему-нибудь. Многое в этой части - теория, которую вы можете найти несколько недостаточной; однако главная цель здесь состоит в том, чтобы дать вам инструменты, чтобы решить программные задачи, с которыми вы, вероятно, столкнетесь.
 
При работе с 3D, крайне важно чтобы вы полность понимали среду, которую вы создаете, мы уже рассмотрели матрицы и проекции вращения, поэтому вам должно быть легко разобраться с идеей ваших трёх осей x, y и z, которые определяют ваш виртуальный мир. Один из самых полезных математически инструментов в нашем распоряжении - векторы, и вы можете знать или не знать, что такое вектор, но, по существу, это способ описать положение и направление. Однако в нашем графическом программировании мы имеем тенденцию меньше интересоваться положением, поэтому обычно используем векторы как раз для того, чтобы описать направление, например:
 
MyVector = [0x,0y,1z]
 
Есть много описаний для векторов, но здесь я говорю, что X компонент моего вектора 0, Y компонент 0, и Z компонент 1 (это - математическое описание, а не код AVS). Если вы представляете 3D пространство, вектор MyVector описывает направление положительного Z, то есть указывает ось Z вниз.
 
MyVector = [0x,1y,1z]
 
Теперь MyVector описывает направление 45 градусов между Y и Z осями.
 
[image020.png]
 
Принципиальная схема является довольно простой; теперь, одна вещь, которую мы могли бы захотеть сделать с вектором, - вычислить его длину. Мы делаем это, используя теорему Пифагора, с которой вы может быть уже знакомы; не хочу заниматься детализацией этого, а только приведу уравнение, которое говорит, что "длина вектора равняется квадратному корню суммы квадратов компонентов":
 
lengthv=sqrt(sqr(vx)+sqr(vy)+sqr(vz));
 
Где ваш вектор определен через vx, vy, vz.
 
Теперь, когда мы производим вычисления с векторами, не плохо бы их нормализовать, что это означает? Это значит, что x, y и z составляют в целом 1 (вектор длины 1 известен как вектор единицы unit vector), а способ, которым мы достигаем это, - деление каждого компонента на длину вектора. Так в AVS коде это было бы:
 
vx=vx/lengthv; vy=vy/lengthv; vz=vz/lengthv;
 
Теперь мы немного знаем о векторах, и можем посмотреть на некоторое их использование; так давайте переходить на нормальные вектора. Что есть нормальные? Нормальное направление связано с наблюдателем, и если у вас есть наблюдатель, нормальным для него является вектор, который определяет направление взгляда. Например, держите свою руку с плоской ладонью, и если ваша ладонь - это поверхность (такая как треугольник или квадрат), нормальным к вашей ладони является вектор, который описывает направление от вашей руки вдаль.
 
Нормальные вектора очень полезны по множеству причин; вообразите, например, что вы хотите осветить куб, если знаете только местоположение всех поверхностей на кубе, то, действительно, не имеете достаточно данных для того чтобы произвести любое пристойное освещение. Если же вы знаете направление каждой поверхности, то можете сказать, оказывается ли каждая сторона перед светом или нет.
 
[solid 3D square]
 
Давайте сделаем пресет, который отдает твердый трехмерный квадрат и показывает его нормаль как линию, для чего начните новый preset, и добавьте SuperScope со следующим кодом:
 
//control scope
 Frame:
rotx=rotx+0.01; roty=roty+0.02; rotz=rotz+0.03;
reg00=rotx; reg01=roty; reg02=rotz;
 
Это очень простая область контроля (control scope), которую мы используем, чтобы сохранить наши значения вращения, мы нуждаемся в этом, потому что эти значения будут использоваться в больше чем одной суперобласти (вернитесь к части 5, если незнакомы с регистрами). Теперь добавьте вторую суперобласть, чтобы представить квадрат:
 
//square
 Init:
n=1000;
 Frame:
rotx=reg00; roty=reg01; rotz=reg02;
linesize=2; drawmode=2;
crotx=cos(rotx);srotx=sin(rotx);croty=cos(roty);
sroty=sin(roty);crotz=cos(rotz);srotz=sin(rotz);
switch=0.5;
 Point:
switch=-switch;
y1=i-0.5; x1=switch; z1=0;
x2=x1*crotz-y1*srotz; y2=x1*srotz+y1*crotz;
x3=x2*croty+z1*sroty; z3=-x2*sroty+z1*croty;
y1=y2*crotx-z3*srotx; z1=y2*srotx+z3*crotx;
z2=1/(z1+2); x=x3*z2; y=y1*z2;
 
У Вас не должно быть никакой проблем c пониманием этого кода, если это не так, то вернитесь к части 4 и освежите свою память на основах 3D (это фактически решение exercise 4C и 5A). Вы, возможно, заметили, что матрицы вращения немного ужаты, но это только для того, чтобы оставить свободное место и сделать код немного меньше.
 
Таким образом у нас есть вращающийся квадрат, теперь мы должны определить нормаль к его поверхности. Поскольку наша поверхность является плоскостью к оси Z (z1=0), мы фактически уже знаем наше нормальное направление - это z=1 или z=-1. Почему может быть два значения? Поскольку это только отдельный квадрат, то он может располагаться или вверх, или вниз, и это, действительно, не имеет значения, мы только должны принять решение и придерживаться выбранного. Если бы квадрат был частью объекта, такого как куб, то мы хотели бы, чтобы он позиционировался от центра куба, и мы назначили бы его нормаль, основываясь на этом. Так что давайте скажем произвольно, что нормальным направлением к этой поверхности является z=1, и чтобы показать его как линию, мы должны провести линию  через матрицы вращения, с тем же самым вращением, как и поверхность. Это потому, что поскольку поверхность вращается, нормаль вращается также, таким образом мы должны её пересчитывать.
 
Добавьте новый SuperScope со следующим кодом:
 
 Init:
n=2;
 Frame:
linesize=2; drawmode=2;
rotx=reg00; roty=reg01; rotz=reg02;
crotx=cos(rotx);srotx=sin(rotx);croty=cos(roty);
sroty=sin(roty);crotz=cos(rotz);srotz=sin(rotz);
 Point:
y1=0; x1=0; z1=0;
x2=x1*crotz-y1*srotz;y2=x1*srotz+y1*crotz;
x3=x2*croty+z1*sroty;z3=-x2*sroty+z1*croty;
y1=y2*crotx-z3*srotx;z1=y2*srotx+z3*crotx;
z2=1/(z1+2); x=x3*z2; y=y1*z2;
 
Эта вторая суперобласть пока ничего не производит, но 90% нашей работы сделано, потому что все, что мы должны - добавить немного кода, который чертит линию от начала к пункту определенному нормальным, то есть от 0,0,0 до 0,0,1, замените код:
 
//normal
 Init:
n=2;
 Frame:
linesize=2; drawmode=2;
rotx=reg00; roty=reg01; rotz=reg02;
crotx=cos(rotx);srotx=sin(rotx);croty=cos(roty);
sroty=sin(roty);crotz=cos(rotz);srotz=sin(rotz);
point=0;
 Point:
y1=0; x1=0; z1=point; point=bnot(point);
x2=x1*crotz-y1*srotz;y2=x1*srotz+y1*crotz;
x3=x2*croty+z1*sroty;z3=-x2*sroty+z1*croty;
y1=y2*crotx-z3*srotx;z1=y2*srotx+z3*crotx;
z2=1/(z1+2); x=x3*z2; y=y1*z2;
red=1; blue=0; green=0;
 
Там у нас есть наши поверхность и нормаль. Это было довольно простым случаем, потому что мы знаем нормальное направление, плюс оно было центризовано на начало, однако, надеюсь, что теперь вы понимаете принципиальную схему немного лучше.
 
[cross product, vector multiplication, векторное произведение, векторное умножение]
 
В примере мы только попробовали, мы уже знали нормаль из нашей поверхности, что было очевидно, и это часто имеет место, если вы делаете простые геометрические формы; например у куба будут стороны, где нормаль: x=1, x=-1, y=1 и т.д., если вы не будете определять его без сторон ориентированных к оси. Однако, что вы можете сделать, если как раз не сможете угадать нормальный стороны? Ответ - довольно изящная формула/уравнение, известная как векторное произведение. Если вы имеете сторону, то можете определить два вектора, которые лежат на той стороне и высчитать векторное произведение, для того чтобы получить нормаль. Векторное произведение определено как:
 
Cx = v1(y) * v2(z) - v1(z) * v2(y)
Cy = v1(z) * v2(x) - v1(x) * v2(z)
Cz = v1(x) * v2(y) - v1(y) * v2(x)
 
Так вы сводите векторы v1 и v2, и результатом является вектор Cx, Cy, Cz (вектор, который перпендикулярен v1 и v2). Как вы определяете два вектора, которые лежат на стороне? Это, действительно, довольно легко, вы просто берете два пункта, которые находятся на стороне (вы могли бы также использовать вершины, так как у вас они уже есть), и вычитаете их, чтобы получить вектор, который находится на поверхности.
 
Например, мы имеем треугольник, который является стороной, чью нормаль мы желаем вычислить с гранями:
 
x1 y1 z1 (Вершина 1)
x2 y2 z2 (Вершина 2)
x3 y3 z3 (Вершина 3)
 
(Вершина означает просто угол, на всякий случай, если вы не знали). Здесь наш треугольник в 3D, чтобы вычислить нормаль, мы делаем 2 вектора из вершины 1 к 2, и из вершины 1 к 3:
 
v1x=x2-x1; v1y=y2-y1; v1z=z2-z1;
v2x=x3-x1; v2y=y3-y1; v2z=z3-z1;
 
Таким образом, у нас есть два наших вектора, которые мы нашли, вычитая определённые из вершин, одну от другой, и мы можем выбрать различные комбинации вершин, пока закончим тем, что определим два вектора, которые лежат на стороне. Теперь включаем те векторы в общую формулу векторного произведения (cross product formula), чтобы найти нормаль:
 
nx = v1y*v2z - v1z*v2y;
ny = v1z*v2x - v1x*v2z;
nz = v1x*v2y - v1y*v2x;
 
Есть наша нормаль, определённая nx, ny, nz; хотя на этой стадии было бы хорошей идеей нормализовать её, потому что она может быть очень большой или малой, в зависимости от размера треугольника; хотя, мы уже разбирали это, поэтому я не буду повторять это здесь. Если хотите видеть это в действии, то можете загрузить пресет "Demonstration - Arbitrary Normal" в директории AVS Programming Guide. Пресет создает случайный треугольник и показывает его нормаль, и вы можете заметить, что начало линии нормали базируется не на треугольнике; это потому, что, если вы помните, мы интересуемся использованием нормали для определения направления, но не положения. Вы должны быть в состоянии прочитать код и следовать за шагами, которые мы только что прошли.
 
[dot product, scalar multiplication, скалярное произведение, скалярное умножение]
 
Заключительной темой, которую мы собираемся исследовать, является скалярное произведение, формула, которая скажет нам угол между двумя векторами. Мы уже знаем направление стороны, но в настоящее время мы имеем её выраженние только в виде вектора; в реальном программном контексте это не очень полезно, потому что мы почти всегда хотим знать угол между нормалью поверхности и чем-то ещё. Например, если мы хотим знать, направлена ли поверхность от камеры (так, чтобы мы могли избежать её рендеринга для скорости счёта), для чего нам необходимо выяснить угол между нормалью поверхности и вектором от камеры к поверхности.
 
Формула для скалярного произведения двух векторов:
 
Dot = (v1x*v2x+v1y*v2y+v1z*v1z)
 
Другими словами, умножьте каждый компонент вектора вместе и суммируйте результат, значение, которое вы получаете в итоге, является косинусом угла между векторами (умноженный их длинами, но это сейчас для нас не важно). Так, в терминах неспециалистов, результат есть значение между -1 и 1, где 1 означает, что они направлены каждый в свою сторону, -1 означает, что они смотрят в одном направлении, и 0 означает, что они на 90 градусах один к другому (по крайней мере, это всё о чём мы действительно заботимся). И это уже другая ситуация, где нормализация векторов важна, иначе результат, который вы получаете, не будет попадать между -1 и 1.
 
Вы когда-либо слышали о законе косинусов Ламбера (Lambert's cosine law)? Вероятно нет, но он только заявляет, что кажущаяся яркость стороны пропорциональна косинусу угла между нормалью из поверхности и направлением света. Это удобно, не так ли, - если мы определим свет как вектор (указывающий путь света), и установим цвет нашей стороны как скалярное произведение нормального и светового векторов, то сторона будет освещена довольно точно без любой дополнительной работы с нашей стороны.
 
Давайте сделаем вращающийся квадрат с некоторым освещением, чтобы продемонстрировать эту идею, сделайте новый preset и добавить SuperScope со следующим кодом:
 
 Init:
n=1000;
 Frame:
linesize=2; drawmode=2;
rotx=rotx+0.01; roty=roty+0.02; rotz=rotz+0.03;
crotx=cos(rotx);srotx=sin(rotx);croty=cos(roty);
sroty=sin(roty);crotz=cos(rotz);srotz=sin(rotz);
switch=0.5;
 Point:
switch=-switch; y1=i-0.5; x1=switch; z1=0;
x2=x1*crotz-y1*srotz; y2=x1*srotz+y1*crotz;
x3=x2*croty+z1*sroty; z3=-x2*sroty+z1*croty;
y1=y2*crotx-z3*srotx; z1=y2*srotx+z3*crotx;
z2=1/(z1+2); x=x3*z2; y=y1*z2;
 
Здесь простой вращающийся квадрат, и первая вещь, которую мы должны добавить, чтобы сделать некоторое освещение, - вычислить нормаль, мы уже знаем как сделать это, так что добавьте следующий код в конце frame box:
 
//define the normal
nx=0;ny=0;nz=1;
//rotate the normal
x1=nx; y1=ny; z1=nz;
x2=x1*crotz-y1*srotz; y2=x1*srotz+y1*crotz;
x3=x2*croty+z1*sroty; z3=-x2*sroty+z1*croty;
y1=y2*crotx-z3*srotx; z1=y2*srotx+z3*crotx;
 
Так мы можем получить доступ к вращающейся нормали как к вектору (x3,y1,z1), и снова мы определили, что нормаль будет z=1, однако z=-1 также будет не плохо. Затем мы должны определить наш свет как вектор, и мы определим его, а после этого нормализуем; добавьте следующий код к frame box:
 
//define the light
lx=1; ly=0; lz=0;
//normalise it
length=sqrt(sqr(lx)+sqr(ly)+sqr(lz));
lx=lx/length; ly=ly/length; lz=lz/length;
 
Таким образом, это наш свет, вектор, который указывает вдоль положительной оси X. Теперь, надеюсь, вы видите, что нам действительно не нужно нормализовать этот вектор, потому что сумма его компонентов уже 1. Причина того, что мы вставляем код внутрь, состоит в том, чтобы вы могли играть с значениями светового вектора, например, мы могли бы сделать световую поток под углом 45 градусов между осями X и Y, устанавливая lx и ly на 1, код нормализации (normalization code) превратит его в единичный вектор (unit vector). Заметьте, что мы не вращали световой вектор, потому что хотим, чтобы свет был постоянным и блестел на вращающейся плоскости, если бы мы вращали световой вектор с тем же самым вращением как и поверхность (как мы это сделали для нормали), то получилось бы так, будто свет присоединен к поверхности, и, таким образом, освещение не изменялось бы.
 
Теперь заключительный этап, скалярное произведение светового вектора с нормалью поверхности, для того чтобы узнать угол между ними, поэтому добавьте следующий код к концу frame box:
 
//dot product the light and the normal
dot=x3*lx+y1*ly+z1*lz;
 
Это не должно быть ничем слишком удивительным, мы просто включаем нормаль поверхности и световые векторы в dot product formula, представленную ранее. Так, а как мы используем это значение? Ответ, мы можем просто назначить насыщенность цвета суперобласти непосредственно этой переменной, так как dot будет 1, когда поверхность стоит перед светом и <0, когда она отворачивается от него. Добавьте следующий код к концу point box:
 
red=dot; blue=dot; green=dot;
 
Всё готово. Заметьте, как, кажется, будто поверхность освещена, поскольку она стоит перед нашим виртуальным источником света, и вы можете играть с величинами, чтобы видеть влияние, который они имеют, устанавливая свет в 0,0,1 (вектор, указывающий вниз положительной оси Z), тогда появится эффект освещения в направлении камеры, что поможет вам увидеть этот эффект.
 
[realistic lighting system]
 
Хорошо, это ваше введение в освещение; здесь мы увидели один тип простого освещения, лучшей аналогией для эффекта, который мы создали, является "солнце", потому что наши стороны освещены однородно, в зависимости от вращения. Другими словами, местоположение нашей стороны не важно, как будто свет находится очень далеко, но светит очень ярко. Более реалистическая система освещения определила бы расстояние между светом и поверхностью, и кое-что, чтобы сделать это, у вас уже есть, потому что мы рассмотрели подробно то, что относится к содержимому векторов. Один метод для определения расстояния между светом и поверхностью был бы:
 
 Определить местоположение света, вектор (a);
 Определить местоположение поверхности, вектор (b);
 Вычесть (a) из (b), чтобы создать вектор (c);
 Найти длину (c), используя теорему Пифагора (Pythagoras).
 
То значение длины есть расстояние между светом и поверхностью, и вы использовали бы это значение для того чтобы доработать цвет вашей стороны. Мы не будем углубляться более в детали об этом хотя, в виду того, что вам решать, хотите ли вы экспериментировать.
 

Part 11: Tips & Tricks
 
Этот раздел содержит некоторые полезные подсказки и уловки кодирования, которые не относятся ни к какому определенному объекту в AVS, но удобны для того, чтобы отшлифовать ваши пресеты и разрешить некоторые проблемы.
 
* Sine Waves [Волны синуса]
 
Синусоидальные эффекты очень полезны в AVS; синусоидальное движение приятно человеческому глазу и регулярно происходит в природе. Важно понять основы sin и cos, чтобы эффективно их использовать в пресетах, (базовое) уравнение волны синуса является следующим:
 
y=alfa*sin(2*Pi*f*x)
 
Это есть амплитуда синуса двух пи на частоту f и параметр x. Чтобы произвести волну синуса амплитудой 0.5 и частотой 2, мы можем сделать SuperScope, содержащую:
 
 Point:
x=2*i-1; y=0.5*sin(2*$pi*2*x);
 
Помните, что ось X в AVS охватывает от -1 до 1, таким образом вы будете видеть 4 периода, а не 2, как вы могли бы ожидать. Экспериментируйте немного с волнами синуса и синусоидальным движением так, чтобы вы были в состоянии запланировать и осуществить эффект, который хотите, вместо того, чтобы беспорядочно бросать в код sin и cos.
 
* Aspect ratio correction [Коррекция коэффициента сжатия]
 
Исправление формата изображения - это запись в пресете, например, для DM или SuperScope, самым простым методом коррекции изображения [по оси X] будет:
 
 Frame:
asp=h/w;
 Point/Pixel:
x=x*asp;
 
Где x - ваш окончательный x после перемещения, вращения и т.д. Однако, это предполагается для окна AVS, которое больше по ширине, чем по высоте, иначе, вероятно, данные выйдут за края экрана. Вы можете применить более сложные функции, чтобы создать исправление формата изображения для окна любого размера:
 
 Point/Pixel:
x=x*if(above(w,h),h/w,1);
y=y*if(above(h,w),w/h,1);
 
* Frame rate independent Movement [Независимое движение показателя частоты кадров]
 
Чтобы избежать слишком быстро перемещения в пресете на быстрых компьютерах, вы можете ограничить скорость движения объектов, базируя их на времени, а не на частоте кадров.
 
 Frame:
passed=gettime(0)-last;
MyVar=MyVar+passed;
last=gettime(0);
 
Где ваше движение основано на MyVar. Для того чтобы иметь различные элементы, перемещающиеся с различными скоростями, вы можете маштабировать величину passed, например умножая его случайным коэффициентом, изменяемым на каждом ударе.
 
* Inactive effect lists [Бездействующие списки эффекта]
 
Списки эффекта, которые усиливаются и уменьшаются, или имеют alphaout изменение любого вида, должны становиться неактивными, как только alphaout становится достаточно малым, что в итоге благоприятно влияет на частоту кадров:
 
 Frame:
enabled=above(alpha,0.01);
alphaout=alpha;
 
* Caching static data [Кэширование статических данных]
 
Статические данные, например, фон градиента в пресете, могут "кэшироваться" в буфер в начале пресета, для того чтобы улучшить частоту кадров. Просто поместите эффекты, которые нужно кэшировать, в список эффекта с сохранением в буфер (buffer save) в конце, а затем используйте следующий код в списке эффекта:
 
 Init:
hi=0; wi=0;
 Frame:
enabled=bor(1-equal(hi,h),1-equal(wi,w));
hi=h; wi=w;
 
Это включит пресет, когда он запускается, и (что еще более важно) когда окно AVS изменено (чтобы данные могли быть повторно вычислены, что, вероятно, будет необходимо). Вы можете использовать buffer save с restore, чтобы получить ваши сохраненные данные на любом этапе.
 

Part 12: Advanced Exercises
 
Предполагается, что к этой главой вы уже приобрели довольно основательное понимание программирования AVS и в состоянии перевести большинство ваших идей в код. В конце концов, веская причина для изучения языка AVS состоит в том, чтобы позволить вам лучше реализовывать свои идеи и воспроизводить то, что вы представляете в своей голове. Один из самых больших навыков, в которых нуждается любой программист, является решение проблемы, имея проблему, вы должны быть в состоянии придумать творческое и изящное решение, которое даёт реальное удовольствие от программирования.
 
Эта последняя глава содержит некоторые упражнения, которыми вы должны вплотную заняться, прочитать подсказки, если вдруг столкнётесь с трудностями, но обязательно сначала попытаться решить их самостоятельно. Также, обычно есть больше чем одно решение проблемы, поэтому не думайте, что ваше решение должно соответствовать моему, если оно работает. Мы надеемся, это поощрит вас думать творчески и развивать свой собственный подход к решению проблем AVS. А когда находитесь в сомнении… используйте expression help!
 
Exercises:
 
Создайте точечную суперобласть, которая производит "твердый" квадрат 10 на 10 пикселов в центре окна AVS. Квадрат должен остаться 10x10, независимо от размера окна AVS. (Exercise 12A.avs)
 
Создайте SuperScope на 10,000 пунктов, которая показывает данные осциллографа (oscilloscope data), но обновляется только один раз в секунду. (Exercise 12B.avs)
 
Tips for Exercise 12A:
 
- Сначала решите, как определить 1 пиксел, это будет связано с высотой и шириной окна AVS.
- Попытайтесь произвести что-либо фиксированной ширины прежде, чем займетесь вторым измерением.
- Используйте за основу solid SuperScope из главы 2, но перестройте его к своим потребностям.
- У идеального решения будет n=100, ваш вариант?
 
Tips for Exercise 12B:
 
* Sample the oscilloscope data using getosc(), divide your loop counter by 10,000 for the band.
 
- Разбейте проблему на меньшие проблемы… вы можете сделать переменную равной 1, когда секунда истекла?
- Нужно использовать megabuf, чтобы хранить значения осциллографа.
- Что является максимальным пределом для цикла (даю подсказку, это ниже 10,000!)? Если не удаётся использовать один цикл, то возможно получится использовать два.
- Попробуйте данные осциллографа, используя getosc(), разделите свой счётчик цикла на 10,000 для полосы.

Перевод: А.Панов.
http://avs.chat.ru

counter
Panow©



En ^ Ru

AVS Programming Guide
Version 1.1
Tom Young (A.K.A. PAK-9)
 
http://www.visbot.net/category/documents
http://www.deviantart.com/download/11200891/AVS_Programming_Guide.zip
 
Contents:
Introduction
Part 1: The Fundamentals
Part 2: The Superscope
Part 3: Movements & Dynamic Movements
Part 4: The Third Dimension
Part 5: Registers
Part 6: Megabuf and loops
Part 7: Coding Effect Lists
Part 8: Mouse Input
Part 9: The Triangle APE
Part 10: Advanced 3D
Part 11: Tips & Tricks
Part 12: Advanced Exercises
 
Note: The contents of this document are the intellectual property of Thomas Young; you may distribute this document freely provided it is not modified in any way. This document may not be included in any commercial product for profit or otherwise without the express permission of the author. The code contained in this document may be used freely with or without modification. Advanced Visualization Studio is copyrighted by Nullsoft.
 
Introduction
 
Welcome to the AVS programming guide, a document written for novice AVS artists to help them learn the basics of the AVS programming language. This document assumes you have experimented at least a little bit with the effects of AVS but no prior programming experience is necessary. Note that this is not a reference document, so it is not a replacement for the expression help, which I recommend you use to familiarize yourself with the basics of syntax and operators. This is also not a maths document, many people think complex AVS presets require a lot of maths, however this is rarely the case. Having said that, we need to use maths sometimes to solve programming problems so be prepared for a little theory.
 
The layout of the document is designed to introduce new topics at a steady pace, but if the early topics seem too basic, feel free to skip to later parts. The philosophy is learning by doing, so most parts are a brief description with an example or two, as well as exercises that I recommend you attempt to improve your understanding of the topic.
 
I don’t get much feedback about this guide so I can only assume that there is nothing wrong with it, if you disagree or have any suggestions for improvement (feedback of all kinds is appreciated) email me at: PAK.nine@gmail.com
 
A learning philosophy I recommend you subscribe to:
 Read  and forget
 Write  and remember
 Do  and understand
 
Part 1: The Fundamentals
 
The AVS window is essentially a canvas onto which objects are placed and manipulated to produce effects. The main elements are split into two categories, ‘Render’ elements and ‘Trans’ elements. ‘Render’ elements literally render to the window, that is, pixels are ‘placed’ in the window (possibly blended with what is already there). ‘Trans’ elements affect the existing pixels in a certain way, by shifting them or changing their value (remember, pixels are simply a red, green and blue value).
 
The AVS window itself spans from -1 to 1 in the X and Y. The X is the horizontal axis, -1 being the far left and 1 being the far right. The Y is the vertical axis, -1 being the top and 1 being the bottom. Therefore it should be obvious that rendering a pixel at 0,0 will produce a dot in the exact centre of the AVS window.
 
The programming in AVS is broken into 4 execution times (points at which the code is run), ‘Init’, ‘Frame’, ‘Pixel’ (or Point) and ‘Beat’. ‘Init’ stands for initialization, code in this box is executed when the preset is started (there is an exception to this but for now assume this is the case). ‘Frame’ is executed once for every frame that AVS renders. The amount of times the ‘Pixel’ code will execute will vary depending on the element, for example a superscope with ‘n’ points will execute ‘n’ times every frame. Often people make the mistake of putting code in the ‘Pixel’ box which could go in the ‘Frame’ box, which means the code is executed many more times than necessary, we will address this problem throughout the guide. ‘Beat’ code is executed when there is a detected beat in the music.
 
We will not go into great detail regarding the functions provided by AVS, as these are documented in the expression help; however it is a good idea to look at these for a quick idea of what is possible. Instead you should just ensure you are familiar with the following:
 
* Variables are created on assignment, that is MyVar=0 will create the variable MyVar. So MyVar=MyVar+1 is enough to create an incrementing variable
* Assignment is done using the = sign. So MyVar=1 will put the value of 1 into the variable MyVar.
* Functions return values, so MyVar=sin(2) is calling the sin function, and returning a value that is put into MyVar. This applies to most functions, although many simply return a 1 or 0.
* Every statement should end with a semicolon. So MyVar=0;
 
 These are just reminders, look in the expression help for more detail about the basic syntax of AVS and some of the operators that are available.
 
Part 2: The Superscope
 
Most people would agree that the superscope is the main render object in AVS, and it is extremely powerful. Some important variables are:
 
n - Represents the number of points to render
b - Can only be read, 1 if there is a beat, otherwise 0
i - A value that changes from 0 to 1 according to the point being rendered,
  ‘i’ can be defined as 1/n*p where p is the current point being rendered.
v - The value of the waveform or spectrum (as specified) at the current point.
The wave/spec data is a signal from 0 to 1, so for example a 3 point superscope will take data from 0, 0.5 and 1.
red - The amount of red colour, from 0 to 1, this can be set in any box.
blue - As above with blue
green - As above with green
linesize- If rendered as lines, the line width, 0 to 255
skip - If set above zero, the current point will not be rendered
drawmode If non  zero, will render lines, otherwise it will render dots.
 
Phew, well you don’t need to remember all off those, they are just there as a reference. The effect of some of these variables can actually be replicated, for example ‘v’, which can be replicated with getspec() or getosc().
 
Okay enough of boring theory, lets get coding. The first thing we are going to make is a simple scope, so fire up AVS and create a new preset and add a superscope, ensure clear ever frame is ticked in main (this will clear the AVS window at the start of every frame). Then type the following into the code boxes:
 
Init
n=800
Frame
Beat
Point
X=2*i-1;
Y=v*0.5;
 
Okay there we have our scope, now lets look at the code. First of all we have set n to 800 meaning we want 800 points for our superscope, if you change it to 8 you will notice how much more crude the scope is, because we are representing the scope with less points. We put this code in the init because we only want to tell AVS how many points there are once, that variable is set and we don’t need to worry about it any more.
 
We then set X to 2*i-1… why? Because we want the scope to span the whole window, we can’t just use i because it only spans 0 to 1, and we need -1 to 1. So we multiply it by 2 (now it spans 0 to 2) and subtract 1 (now -1 to 1).
 
Finally we set Y to v*0.5, we could have used just v, but it looks a bit messy filling the whole screen, so we multiply it by 0.5, now rather than spanning -1 to 1 it spans -0.5 to 0.5… much nicer.
 
Remember I said we could replicate v? Well lets give it a go, we need the oscilloscope data so we will use getosc(), the syntax is getosc(band,width,channel), where band is the point to sample from 0 to 1, width is the width of the sample from 0 to 1 and channel is the channel (left, right or centre). Don’t worry if that doesn’t mean too much to you, the important part is the ‘band’, ‘width’ will be 0.1 and channel will be 0 (centre). Replace your point code with this:
 
Point
X=2*i-1;
Y=getosc(x,0.1,0);
 
Now we are feeding x into the getosc() function to retrieve scope data, but something is wrong, ‘band’ should be a value from 0 to 1 and we are feeding it a value from -1 to 1. See if you can think of a solution before you continue.
 
The solution is to multiply and shift the x to make it fall within 0 and 1, so how do we convert -1 to 1 to 0 to 1? Well first of all its 2 times too big, so x*0.5 is a good start, but now we have -0.5 to 0.5 so we need to shift it by 0.5… x*0.5+0.5
 
Point
X=2*i-1;
Y=getosc(x*0.5+0.5,0.1,0);
 
Tada! You just made your own version of ‘v’, congratulations.
 
Exercises:
 
Make a scope that is vertical instead of horizontal.   (Exercise 2A)
Make a scope that spans from -0.5 to 0.5 in the X.   (Exercise 2B)
 
Okay lets move onto a different area, creating a superscope that draws a line from an arbitrary point to another arbitrary point. Seems like a simple problem, but we will look at some new functions in order to implement it. First of all we are going to drop i… that’s right, no i for this problem. Make a new preset, add a superscope and set n to 2, because we are only going to need 2 points, a start and a finish. Now remember that the ‘point’ code is executed once for each point, so all we need is a way to determine which point is currently being rendered. Enter the following code:
 
Init
n=2
Frame
drawmode=1;
CurPoint=0;
Beat
Point
CurPoint=CurPoint+1;
 
Now each frame the following happens:
CurPoint is set to 0 (frame code)
CurPoint is set to 1 (point code)
Rest of the ‘Point’ code is executed
CurPoint is set to 2 (point code)
Rest of the ‘Point’ code is executed
 
It is important that you grasp this concept as is it key to the operation of a superscope, the frame code is executed, then the point code is executed twice (because n=2) and we are incrementing CurPoint in the pixel code to give ourselves a way of identifying what point is being rendered.
 
Okay now that we have a way of knowing what point is being rendered we can specify where to render using an ‘if’ statement…
 
Point
CurPoint=CurPoint+1;
X=if(equal(CurPoint,1),-0.5,0.5);
Y=0;
 
Here we are saying the X should be set to -0.5 if the value of CurPoint is 1, otherwise set it to 0.5. So if we just extend this idea to a slightly more complete solution:
 
Init
n=2;
X1=-0.5;
Y1=0.25;
X2=0.5;
Y2=-0.25;
Frame
drawmode=1;
CurPoint=0;
Beat
Point
CurPoint=CurPoint+1;
X=if(equal(CurPoint,1),X1,X2);
Y=if(equal(CurPoint,1),Y1,Y2);
 
Now we have a superscope that will draw a line from X1,Y1 to X2,Y2. Okay now lets make this solution a bit better, we can actually remove that equal() from each of the if statements because an if statement just checks if the first argument is a 0 or not in order to evaluate. Since we have a variable that is either 1 or 2, we can make a small adjustment to make it either 0 or 1 and pass it straight into the if statement.
 
Point
X=if(CurPoint,X1,X2);
Y=if(CurPoint,Y1,Y2);
CurPoint=CurPoint+1;
 
By moving the addition to the last line, Curpoint will be 0 for the first point and 1 for the second point.
 
Exercises:
 
Make a superscope that draws between 3 arbitrary points.  (Exercise 2C)
 
Okay lines are all very well and good, I’m sure by now you’ve tried making a sine wave and maybe some other tricks by now, but what about solid objects? I’m sure you have seen presets where people produce cubes and other geometric objects, well lets have a go ourselves. We’ll stick to 2D because you can apply the same principles in 3D and it’s covered in a later chapter.
 
The key principle to ‘solid’ superscope’s is that they are made of lines, hopefully this isn’t too much of a let down, but lines is all that a superscope can do (and dots of course). So the ‘trick’ is to produce a series of lines that are so tightly packed that they have the appearance of a solid object. Let’s try a solid square to start off with, make a new preset with a superscope and enter the following:
 
Init
Frame
n=w;
drawmode=1;
switch=0.5;
Beat
Point
switch=-switch;
x=0;
y=switch;
 
Okay there are few things to note, first of all we have moved ‘n’ into the frame box and set it to an expression rather than a constant. ‘w’ is a variable that contains the width of the AVS window, so we have set the number of points to that value. This may seem like a lot of points but in order to make out object appear solid we need a lot of lines. It is in the frame box because we want it to remain w even if the user resizes the AVS window, if the code was in init it would not be executed after the start of the preset.
 
Can you work out what the variable ‘switch’ does? It changes from 0.5 to -0.5 for each alternate point. “Okay, but where’s my solid square!” I hear you cry, well hopefully you have worked out that currently the superscope is rendering lots of lines from 0.5 to -0.5 and back again, so if we change the x to i-0.5 what do we get? That’s right a lovely solid square!
 
But it looks a bit bland so lets add some shading:
 
Point
switch=-switch;
x=i-0.5;
y=switch;
blue=cos(x*2);
red=0;
green=0;
 
I don’t need to explain what the cos() function does, but notice that we have passed x as an argument so that the colour intensity will change with the x axis. Lets try changing the colour across the Y instead:
 
Point
switch=-switch;
x=i-0.5;
y=switch;
blue=cos(y*2);
red=0;
green=0;
 
Oh dear, that doesn’t look very good at all, do you know why the colour isn’t changing in the Y axis? Well we are drawing lines between Y=-0.5 and Y=0.5, and the colour is set for each point, so we are only getting the colurs blue=cos(-0.5*2) and cos(0.5*2). In other words, the colour your getting is approximately blue=0.54.
 
One final note on optimizations, rather than have n=w, lets try n=w*0.5. Looks a bit ‘liney’ doesn’t it, well there is an easy fix, change the linesize to 2. This way the width of the lines will compensate for the gaps:
 
Frame
n=w*0.5;
linesize=2;
drawmode=1;
switch=0.5;
 
We do this because superscope points are costly in terms of frame rate so we want to keep them to a minimum.
 
This solid superscope is also a good situation in which to illustrate the ‘skip’ variable, lets cut off the right hand side of the solid square:
 
Point
switch=-switch;
x=i-0.5;
y=switch;
skip=above(x,0);
blue=cos(x*2+col);
red=0;
green=0;
 
Here we are setting skip to 1 when x is greater than zero using the above() function, try changing it to below(), you can probably guess the effect it will have. Okay now lets cut out a chunk from the middle:
 
Point
switch=-switch;
x=i-0.5;
y=switch;
skip=band(above(x,-0.25),below(x,0.25));
blue=cos(x*2+col);
red=0;
green=0;
 
Here we are using the band()  function, which means it will return 1 if x is above -0.25 AND below 0.25. Remember, we could not apply this to the Y axis because we can only skip points, we cannot chop lines up into pieces.
 
Exercises:
 
Create a solid square with shading along the Y axis.   (Exercise 2D)
Use linesize to make a solid square with a 2 point superscope. (Exercise 2E)
 
A solid superscope that we can colour in both axis is our next challenge, for this we will need to divide each of our lines into pieces.
 
How many pieces we divide each line into is a matter of personal preference, although we do not want to pick a number too large or our frame rate will take a big hit.
 
We should also have a fixed number of points (rather than n=w*0.5 or something) so that we don’t encounter problems calculating the dimensions of our solid object. It can be done with a dynamic number of points, but the code is significantly more complex.
 
As you might have guessed, we are going to ditch ‘i’ again, because we need a little more control over our point positions. Before we start we need to do some calculations, lets say we are going to have 101 lines, each one divided into 11 points per line (these values may not seem very round, but later you will see the logic), what should are ‘n’ value be? I’ll let you think about it while we get on with the implementation…
 
Right, onto the coding, create a new preset, clear every frame and add a Superscope.
 
Init
n=800;
Frame
drawmode=1;
line=-1;
linepos=-1;
Point
x=line;
y=linepos;
 
That n value is wrong at the moment but we will change it later, for now, try to see why we have the variables ‘line’ and ‘linepos’. The ‘line’ will be which vertical line we are dealing with and ‘linepos’ will be the ‘chunk’ of the line we are currently drawing. Because we have set line and linepos to -1 you should be able to see we are going to draw from the top left to the bottom right. Now add this code to the bottom of the point box:
 
line=if(equal(linepos,1),line+0.02,line);
linepos=if(equal(linepos,1),-1,linepos+0.2);
 
Hopefully you can follow this code, what we are essentially doing is adding to linepos 0.2 each time, thereby putting a point further down the screen, until we reach the bottom of the screen where we set it back to -1 so that we can start a new line. The ‘line’ itself only moves along when the last line has been finished, i.e. linepos =1. Only one problem remains, we are still drawing the diagonal connecting lines as shown in grey in the diagram above. We need to ‘skip’ these, so we replace the code with:
 
skip=equal(linepos,-1);
line=if(equal(linepos,1),line+0.02,line);
linepos=if(equal(linepos,1),-1,linepos+0.2);
 
Note that the order in which we put these commands is very important; if the skip was at the end we would be skipping the wrong chunk of the line. Okay we are essentially finished, but you have probably noticed your solid object is only partly drawn, so we need to calculate the correct ‘n’ value. Notice that because we have 101 lines and 11 points per line, we can use nice round numbers like 0.02 and 0.2 in our code… remember, a 10 segment line needs 11 points to draw. You are probably used to ‘n’ values being a matter of preference or using a little guesswork, but with a problem like this the number of points is a non-trivial problem, if we have to few or too many we will get overlap or under-run. In this particular case we can work out the points needed fairly simply because we have a nice clean algorithm (even if I do say so myself ;) ) it is simply the number of lines times the number of line points 101*11=1111.
 
So let’s add a little tidying up, that equal(linepos,1) would be a bit nicer as a separate variable, we need to change the linesize to fill in the holes, and we should scale the square to a more visible size. The final version looks like this:
 
Init
n=1111;
Frame
drawmode=1;
linesize=w*0.01;
line=-1;
linepos=-1;
Point
x=line;
y=linepos;
newline=equal(linepos,1);
skip=equal(linepos,-1);
line=if(newline,line+0.02,line);
linepos=if(newline,-1,linepos+0.2);
x=x*0.75;
y=y*0.75;
 
There, nice and tidy. Now lets add some shading to the end of the point box…
 
red=sin(x*2);
blue=sin(y+0.5);
green=sin(x-0.6)*sin(y-0.5);
 
You can tell that the shading in the y axis is pretty blocky, even though we have a superscope with over 1000 points. In other words, high quality solid superscope’s are very expensive in terms of fps. Of course you could always increase the number of line divisions…
 
Exercises:
 
Modify the solid superscope to have 21 points per line  (Exercise 2F)
Approximate a solid superscope with w*0.5 lines and h*0.5 points per line 
        (Exercise 2G)
 
Part 3: Movements & Dynamic Movements
 
Movements and Dynamic Movements (DM’s) are two of the most powerful trans objects that exist in AVS, we will start by looking at movement and then extend into DM’s. The main difference between a movement and a DM is that you cannot use changing variables in a movement, the code is compiled once and then executed for each pixel each frame.
 
A movement uses the following variables:
 
d - This value is the ‘depth’ of each pixel
r - This value is the rotation of each pixel
x - The x value of each pixel
y - The y value of each pixel
 
The d and r values can only be used if the ‘rectangular coordinates’ checkbox is unchecked and the x and y can only be used if it is checked. This is because a movement has essentially 2 modes, to make certain operations easier (such as rotation); To understand these different ‘modes’ of operation we need to take a look at coordinate systems, if you are already familiar with rectangular and polar coordinate systems you can skip this brief introduction.
 
A brief look at coordinate systems
 
You should already be familiar with the rectangular coordinate system since it is the standard coordinate system of AVS, in order to reference a point with this method we specify an x and a y component, from 0 to 1 in each case. This is a typical and hopefully very intuitive coordinate system, you may also hear it referred to as a Cartesian coordinate system. You may be curious as to why the y axis considers the top of the window -1 and the bottom 1 rather than vice versa, well in practical terms it makes no real difference, it is referred to as a ‘right handed’ coordinate system (more on this in part 4) and it is simply a design choice that was taken by the makers of AVS.
 
However another common coordinate system is the polar coordinate system, where a point is defined by the distance from the origin (d) and the rotation (r). At the centre of the window, d=0 since that is the origin, at the points defined by a circle touching the edge of the window, d=1. The value of r ranges from 0 to defining the rotation in radians (r increases anticlockwise and decreases clockwise), where r=for example is a 180 degree rotation. There is an equation to convert polar to rectangular coordinates (and back) however I would rather not introduce it at this point because understanding the concept is more important.
 
Here are a few examples of equivalent values in the two coordinate systems, try to see intuitively (use the diagrams to help) why they are equivalent. Remember that r=0 is the same as the negative y axis (another AVS design choice, r=0 is more commonly the same as the positive x axis)
 
If you wish to see this in action more closely open the AVS preset ‘Demonstration – Coordinate Systems’ in the AVS programming guide folder.
 
Okay that’s the end of coordinate systems theory, remember this isn’t a maths document so if you are having trouble understanding this concept I suggest you look elsewhere for a better description. On to the coding…!
 
Let’s start with a new preset, clear every frame, add a superscope with the following code:
 
Init
n=5
Frame
drawmode=1;
linesize=10;
point=0;
Beat
Point
x=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3);
y=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3);
x=x*0.5;
y=y*0.5;
point=if(equal(point,3),0,point+1);
 
Hopefully you can see that this will draw a square, remember trans elements only affect what is already rendered so a movement on its own will do nothing. Now add a movement, select (user defined) from the list and enter the following code:
 
User defined code:
d=d*2;
 
Notice that the square is now half the size. Why? Well we are saying that for each pixel (in polar coordinates) we take the pixel which is two times as far away from the origin, so for each pixel where d=1 we take the pixel at d=2 (of course there are no pixels at d=2 though so we just take a black pixel).
 
The same effect can be achieved by checking the ‘rectangular coordinates’ box and entering:
 
User defined code:
x=x*2;
y=y*2;
 
Here we are doing something very similar except in rectangular coordinates, i.e. for each pixel, take the pixel that is twice as far away from the origin in the x and the y. This can be a little counter-intuitive at first because people often think “Well if I multiply each position by two it will be twice as big” however remember you are assigning a new pixel position based on the current coordinates. So based on what I have just said, in which direction will this code move the pixels…?
 
User defined code:
x=x+0.5;
 
The answer is left, because we assign each pixel the value of a pixel 0.5 to the right of it. Remember that 0.5 is a coordinate value, not a number of pixels, so x=x+1 will shift everything completely off the screen. “But I want to move everything ‘n’ pixels” you say, well to move a fixed number of pixels you first need to calculate the size of one pixel in terms of the coordinate system, I’ll let you try to solve that problem yourself (its part of one of the final exercises in part 12).
 
Now uncheck the ‘rectangular coordinates’ box and enter:
 
User defined code:
r=r+$pi*0.25;
 
Here we are rotating the screen by 45 degrees, the value $pi is a constant for pi that we can use instead of typing it out. Why can’t we say r = r + 45 ? Because remember the polar coordinate system uses radians rather than degrees, if you need to convert an angle from degrees to radians, just multiply the value by pi/180. And yes, 45 * pi/180 = pi*0.25.
 
Lets look at a very useful (and commonly used) feature of movements, creating full screen gradients. Create a new superscope:
 
Init
n=800
Frame
drawmode=1;
linesize=1;
Beat
Point
x=2*i-1;
y=0;
red=cos(x*$pi+$pi*0.5);
green=cos(x*$pi);
blue=cos(x*$pi-$pi*0.5);
 
There we have a simple line superscope that produces a gradient of red, green and blue; however we would like it to fill the whole screen rather than just be a line. Let us try to think about the problem from the perspective of a movement (i.e. what coordinates do we want to assign each pixel based on their current coordinates?). Well for each pixel, we want the x value to remain the same, but we want the y value of each pixel to equal a point on the line superscope. Since the line superscope is sitting on y=0, we can say for each pixel x=x and y=0, but of course that x=x is superfluous so we can just say y=0. That was simple, go ahead and add a movement after the superscope, check rectangular coordinates and add the code:
 
User defined code:
y=0;
 
There we have it, this type of effect is very useful because it fills the whole screen with just a few lines of code, and it runs extremely fast. So as a background to a preset, a gradient is a good choice (don’t use a nasty RGB gradient though, experiment to find something more subtle.)
 
Let’s expand this idea to make a circular gradient, that is, where the colour varies uniformly from the centre to the edge of the window. Hopefully you can see that it will be a lot simpler using polar coordinates, but you will probably find it harder to envisage the solution. You may have also guessed already that we are going to use r=0, but let’s step through the problem logically.
 
If you imagine the polar coordinate system in your mind, the value of d and r is different for every pixel, now imagine what happens if the value of r is a constant. In this case we are saying the value of d can vary, so the pixel colour can vary based on its distance from the origin, but the pixel colour will be the same for each separate value of d. If we want a gradient using movement of r=0, then we need to create a superscope from d=0 to d=1 where r=0, where is this? Well looking back at the diagram:
 
So it should be pretty clear then that we need a superscope from rectangular coordinates 0,0 to 0,-1. No problem, make a superscope and add the following code:
 
Init
n=800
Frame
drawmode=1;
linesize=1;
Beat
Point
y=-i;
x=0;
red=cos(y*$pi*2+$pi*0.5+$pi);
green=cos(y*$pi*2+$pi);
blue=cos(y*$pi*2-$pi*0.5+$pi);
 
Then afterwards add our movement with the code:
 
User defined code:
y=0;
 
Hooray, a lovely circular gradient. That’s all we are going to look at for movements, I realize I labored the rectangular/polar coordinates issue but it really is important for understanding the mechanics of movements and DM’s.
 
Right, it’s state-the-obvious time:
 
A Dynamic Movement is a movement, except it is dynamic
 
Why is this important? Because if you can do it in a movement, you can do it in a DM, therefore everything we just covered regarding movement is still valid here. The big difference is that now we can have variables which change, meaning the way in which pixels are affected can change dynamically too.
 
Something worth noting is that although you can use the same (static movement) code in movements and DM’s, the quality will be worse in a DM; this is because the DM breaks the screen down into blocks for efficiency. Each block is approximated rather than exactly calculated; however you can improve the accuracy by increasing the ‘grid size’. If you increase the grid size it is a good idea to use multiples of 2 because computers are faster at working with multiples of 2 than arbitrary numbers.
 
DM’s are compiled in similar stages to a superscope, on init, frame, beat and pixel, meaning you have a lot of control over the movement of pixels. Start a new preset with a superscope:
 
Init
n=5
Frame
drawmode=1;
linesize=10;
point=0;
Beat
Point
x=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3);
y=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3);
x=x*0.5;
y=y*0.5;
point=if(equal(point,3),0,point+1);
 
This is just our superscope square, now add a Dynamic Movement after it with the following code:
 
Init
Frame
move=move+0.01;
Beat
Pixel
x=x+move;
 
Now turn on rectangular coordinates and wrap. Notice how the square moves across the screen and ‘wraps’ around the screen when it leaves one side. We can also rotate an image using the r variable, try un-checking rectangular coordinates and entering:
 
Init
Frame
move=move+0.01;
Beat
Pixel
r=r+move;
 
This should all be fairly familiar because we are simply applying similar effects as we tried in the movement except with variables that are changing. Try checking the ‘blend’ box and notice how the image is blended with the original. This is actually a very powerful function as we can use it to blend certain parts of the image using the ‘alpha’ variable. The alpha is set to a value from 0 to 1, 1 being 100% opacity of the new image and 0 being 100% opacity of the old image. Replace the pixel code with this:
 
Pixel
x=x+move;
alpha=sin(move);
 
Now we are fading between the superscope square, and our modified (or ‘moved’) pixels. Now let’s try this:
 
Pixel
x=x+move;
alpha=above(y,0);
 
Here we are saying, if y is greater than zero, alpha is 1 otherwise it is zero, so we have split the screen into halves. Alpha blending can also use a buffer rather than whatever came before it, start a new preset arranged like so:
 
Set the Clear screen to white, check ‘blend’ in the dynamic movement and set the source combo box to ‘Buffer 1’. What we are doing is using whatever is in the buffer as the data to be blended with, so unchanged we are blending 50% black (the buffer) with 50% white (the clear screen), i.e. grey. So hopefully you have guessed that this:
 
Pixel
alpha=0;
 
Will give us white; and this…
 
Pixel
alpha=1;
 
…will give us black. What about:
 
Pixel
alpha=d;
 
Well, we know that d varies from 0 at the centre to 1 at the edges of the window, so we get a nice gradient from white to black. Now let’s try:
 
Pixel
alpha=above(d,0.5);
 
Yuck, that’s supposed to be a filled circle (if you don’t see why go back to ‘a brief look a coordinate systems’), looks pretty horrible right? This is often referred to as the notorious ‘ugly edge syndrome’ associated with DM’s. It occurs when you have a very high frequency change in colour, since the DM is only approximating the value of each grid square. We can reduce the effect by changing our grid size, try 32x32, looks a bit better but its still pretty jagged. Try 256x256, ah yes that’s much better…now look at your frame rate counter. A drop in frame rate of that magnitude is unacceptable, you may think its not that bad, but remember that the code in our pixel box is now being calculated exponentially more times. To illustrate the point try changing your pixel code to:
 
Pixel
loop(10,
assign(a,sin(rand(100)))
);
alpha=above(d,0.5);
 
Don’t worry about the syntax, all the loop code does is 10 arbitrary sine operations, but look at your frame rate now. I’ll wager its under 10fps, and that’s just from adding 10 sine calculations. The point I am trying to make is that you have to keep your grid size as small as possible, but it is a tradeoff between quality and speed. I would suggest you never need a gridsize above 128x128, and for many applications very low values like 4x4 or 8x8 are perfectly acceptable.
 
Finally here are the equations for converting polar to rectangular coordinates and vice versa, they are written as AVS code rather than mathematical equations:
 
Rectangular to polar:
r=atan2(-x,-y);
d=sqrt(sqr(x)+sqr(y);
 
Polar to rectangular:
x=-d*sin(r);
y=-d*cos(r);
 
Part 4: The Third Dimension
 
I can imagine a lot of people skipping to this section, because 3D is a very popular AVS technique that people often have difficulty with. I’ve stressed that this is a programming guide and not a maths document but I’m afraid we will need to delve into some maths for this chapter.
 
First of all, lets learn how to rotate something in 2D, “that’s easy” I hear you say “just use r… r = r + 1, all done!” bzzzpt wrong, sorry. We need to work with rectangular coordinates here so that when we extend into the third dimension we don’t encounter problems expressing our 3D projection. The key to rotation is rotation matrices, that is, a matrix to which we can pass our x, y and rotation amount and retrieve the translated pixels. The first rotation matrix is:
 
x=x*cos(rot)-y*sin(rot);
y=x*sin(rot)+y*cos(rot);
 
Where ‘rot’ is the angle of rotation in radians. Don’t worry about understanding the maths behind this particularly, just accept that this is how you rotate things. Okay lets implement this into a preset, make a new preset, clear every frame and add a superscope with the following:
 
Init
n=5;
Frame
Rot=0;
Point=0;
Beat
Point
x1=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3);
y1=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3);
x1=x1*0.5;
y1=y1*0.5;
x=x1*cos(rot)-y1*sin(rot);
y=x1*sin(rot)+y1*cos(rot);
point=if(equal(point,3),0,point+1);
 
Okay don’t be put off if it seems like a lot of code, let’s just look through it a step at a time. The first three lines of the pixel box are defining a square (if you don’t see how go back to part 2), and the next two lines simply resize it to be a bit smaller. Notice that we are now using x1 and y1 instead of just x and y, this isn’t strictly necessary in this case, but it is good practice to reference variables rather than the pixels themselves as you will see later on. The last two lines are the rotations themselves, they use x1 and y1 and the variable ‘rot’ that is defined in the frame box. Try changing the value of rot to something other than 0, now try rot=rot+0.01; There we have our rotating square, it took a little work but I’m sure you followed it.
 
Now lets try it in a DM, set rot=0 in that superscope to make it just a plain square again, then add a DM after it, rectangular coordinates and add the following code:
 
Init
Frame
rot=rot+0.01
Beat
Point
x1=x;
y1=y;
x=x1*cos(rot)-y1*sin(rot);
y=x1*sin(rot)+y1*cos(rot);
 
Note that in this case we DO need to place the x and y into variables, because we are affecting the pixels as we are processing them. Try:
 
Point
x=x*cos(rot)-y*sin(rot);
y=x*sin(rot)+y*cos(rot);
 
Notice the zany results.
 
Exercises:
 
Create a superscope square that allows you to specify its rotation in degrees.
        (Exercise 4A)
Create a DM that resizes the screen to 50% and rotates it by pi/5 radians (rect cords)         (Exercise 4B)
 
Now that we have mastered rotation in 2D we can move onto 3D, for this we need two more rotation matrices, so that we have 3. The following are the rotation matrices:
 
X1=x;
Y1=y;
Z1=z;
 
x2=x1*cos(rotz)-y1*sin(rotz);
y2=x1*sin(rotz)+y1*cos(rotz);
z2=z1;
x3=x2*cos(roty)+z2*sin(roty);
y3=y2;
z3=-x2*sin(roty)+z2*cos(roty);
 
x4=x3;
y4=y3*cos(rotx)-z3*sin(rotx);
z4=y3*sin(rotx)+z3*cos(rotx);
 
Seems like a lot of code, but you can see how it is the some principle as our one rotation matrix, just applied twice more. Note that the input for the rotation matrices is the result of the previous one. Okay, hold out your left hand so that your palm is perpendicular to you; make your thumb face up, your index finger face away from you and your forefinger point right (perpendicular to your palm) and clasp your remaining two fingers to your palm. You should be making a gesture like this:
 
The variables rotx, roty and rotz are the amount of rotation around those axis. So rotation around the y axis (changing variable roty) is like keeping your thumb pointing up and rotating your hand. Rotating around z (changing rotz) is like keeping your index finger facing away from you and rotating your hand etc…
 
One last step is needed before we can implement this, once we have passed our values through the rotation matrices we need to ‘project’ the pixels onto our 2D area, this is a little like interpreting the result to fit our window. The projection is:
 
x=x4/(z4+2);
y=y4/(z4+2);
 
The value 2 is actually a constant that represents the scaling of the final result, so a value of 1 would be acceptable but would result in the image being larger (as we are dividing our final x and y values by it). Alright that’s enough theory, let’s implement already! Make a new preset, clear every frame and add a superscope with the code:
 
Init
n=5
Frame
linesize=2;
drawmode=2;
rotx=rotx+0.01;
roty=roty+0.02;
rotz=rotz+0.03;
point=0;
Beat
Point
x1=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3);
y1=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3);
z1=0;
x2=x1*cos(rotz)-y1*sin(rotz);
y2=x1*sin(rotz)+y1*cos(rotz);
z2=z1;
x3=x2*cos(roty)+z2*sin(roty);
y3=y2;
z3=-x2*sin(roty)+z2*cos(roty);
x4=x3;
y4=y3*cos(rotx)-z3*sin(rotx);
z4=y3*sin(rotx)+z3*cos(rotx);
x=x4/(z4+2);
y=y4/(z4+2);
point=if(equal(point,3),0,point+1);
 
And there we have our rotating square, not too bad. But this code is rather inefficient, so we’re going to have to tidy it up. Note these optimizations are optional so don’t worry if you don’t understand them. First of all those sine’s and cosines do not need to be calculated every pixel, as our rotation only changes every frame, so we can calculate the values in the pixel box.
 
Init
n=5
Frame
linesize=2;
drawmode=2;
rotx=rotx+0.01;
roty=roty+0.02;
rotz=rotz+0.03;
crotx=cos(rotx);
srotx=sin(rotx);
croty=cos(roty);
sroty=sin(roty);
crotz=cos(rotz);
srotz=sin(rotz);
point=0;
Beat
Point
x1=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3);
y1=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3);
z1=0;
x2=x1*crotz-y1*srotz;
y2=x1*srotz+y1*crotz;
z2=z1;
x3=x2*croty+z2*sroty;
y3=y2;
z3=-x2*sroty+z2*croty;
x4=x3;
y4=y3*crotx-z3*srotx;
z4=y3*srotx+z3*crotx;
x=x4/(z4+2);
y=y4/(z4+2);
point=if(equal(point,3),0,point+1);
 
Also, divisions are costly on the frame rate so it is a good idea to minimize them, so we can replace
 
x=x4/(z4+2);
y=y4/(z4+2);
 
…with…
 
z5=1/(z4+2);
x=x4*z5
y=y4*z5;
 
Although this is more lines of code it is actually marginally more efficient because we have removed a division and replaced it with a multiplication. Finally, the redundant variable assignments such as x4=x3 can be removed, and we can re-use variables such as x1 rather than creating a new one each time. Leaving us with a much tidier:
 
Init
n=5
Frame
linesize=2;
drawmode=2;
rotx=rotx+0.01;
roty=roty+0.02;
rotz=rotz+0.03;
crotx=cos(rotx);
srotx=sin(rotx);
croty=cos(roty);
sroty=sin(roty);
crotz=cos(rotz);
srotz=sin(rotz);
point=0;
Beat
Point
x1=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3);
y1=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3);
z1=0;
x2=x1*crotz-y1*srotz;
y2=x1*srotz+y1*crotz;
x3=x2*croty+z1*sroty;
z3=-x2*sroty+z1*croty;
y1=y2*crotx-z3*srotx;
z1=y2*srotx+z3*crotx;
z2=1/(z1+2);
x=x3*z2;
y=y1*z2;
point=if(equal(point,3),0,point+1);
 
A little harder to read perhaps, but once you grasp the basics, you can follow the logic. It is a good idea to write your own code using this as a reference in order to ensure you understand the principles involved.
 
If we want to produce a 3D DM, the process is slightly different, we still need our rotation matrices but because we are working with every pixel, we need to pass each pixel into the matrices. So rather than defining a shape with X1,Y1 and Z1, we pass in the x and y of the current pixel, and a constant for z. Create a new preset, clear every frame, add a picture (any picture will do) and a DM. Set the DM to rectangular coordinates and wrap, then enter the following:
 
Init
Frame
ox=0;
oy=0;
oz=-1;
rotx=rotx+0.01;
roty=roty+0.02;
rotz=rotz+0.03;
crotx=cos(rotx);
srotx=sin(rotx);
croty=cos(roty);
sroty=sin(roty);
crotz=cos(rotz);
srotz=sin(rotz);
Beat
Pixel
x1=x;
y1=y;
z1=1;
x2=x1*crotz-y1*srotz;
y2=x1*srotz+y1*crotz;
x3=x2*croty+z1*sroty;
z3=-x2*sroty+z1*croty;
y1=y2*crotx-z3*srotx;
z1=y2*srotx+z3*crotx;
 
The key concept when dealing with a 3D DM is that we are reshaping the window to our own shape, imagine the window is a mesh, we can distort it in order to form shapes and projections. Imagine a mesh that you push with your finger to form a shape, your finger is the ‘ray’ that tells the mesh where to be at a given point. This is a rather poor analogy for ‘ray-tracing’ which is essentially what we are doing to define a 3D DM. The three variables we have added, ox, oy and oz are the ‘origin’ of our ray trace, in other words the position of the camera in our 3D world. The last stage of the ray trace is missing in our DM, because we have not decided what we want to show yet, let’s try a plane because it is easy. The simplest plane is defined by:
 
t=-oz/z1;
x=x3*t-ox;
y=y1*t-oy;
 
Where ‘t’ is what’s know as the ray trace parameter, add this code to the end of the DM and notice the result. It may appear that there are two planes but one is actually a ghost of the real plane. Notice that oz is set to -1 because our plane is z=0 and so if oz were 0 our camera would be embedded in the plane. In order to remove the ghost plane, and remove the distortion as the plane extends to infinity, we need to add some distance fogging. Add a clear screen and a buffer save to your preset like so:
 
Change your DM ‘source’ to Buffer 1, check ‘blend’ and add the following code to the end of your pixel box:
 
alpha=z1;
 
The DM should now look a lot nicer. What we have done is blended the DM with a clear screen to give the impression of distance fog; the picture is saved into the buffer, then the screen is cleared, then the DM renders the plane but blends out everything at a rate of z1. Because z1 is the last value of z we get from our rotation matrices, it represents the distance away from the camera. Hold out your hand and point your three fingers as I showed you before, now keep your index finger facing away from you and rotate your body, your index finger is representing the final z1.
 
So how do you create other shapes? Well you need to sit down with a pencil and paper, get the mathematical equation for the shape you want to ray trace and solve it to find your ray trace parameter (t). I realize this isn’t much help, but you will need to look elsewhere for a more comprehensive ray tracing guide.
 
Exercises:
 
Make a solid square 3D superscope     (Exercise 4C)
Make a DM plane where x=0     (Exercise 4D)
 
Part 5: Registers
 
Sometimes it is necessary to have a variable which is global, that is a variable which can be read by any element of your AVS preset. For this we use registers, a series of variables from reg00 to reg99 that we treat exactly like standard variables.
 
Let’s start a new preset, clear every frame and add a Texer2. Texer2 is very similar to a dot superscope, except that we can pick the picture to display instead of just a dot. Enter the following into the Texer2:
 
Init
n=1;
Frame
pos1=pos1+speed1;
pos2=pos2+speed2;
Xpos=sin(pos1);
Ypos=sin(pos2);
Beat
speed1=rand(10)/100;
speed2=rand(10)/100;
Point
X=Xpos;
Y=Ypos;
 
Now we have a blob with rather erratic movement, mainly because we are changing the speed on beat. Now if we want to make a superscope that draws a vertical line through the blob we will need to use registers, to store the x position of the blob. Change the frame code of the Texer2 to:
 
Frame
pos1=pos1+speed1;
pos2=pos2+speed2;
Xpos=sin(pos1);
Ypos=sin(pos2);
Reg00=Xpos;
 
Now if we open the debug window (Settings > Debug Window…) and look at register 0 we can see that it is updating with the X position of our dot. Now if we add a superscope and enter the following code:
 
Init
n=2;
Frame
Xpos=reg00;
Drawmode=2;
Beat
Point
X=Xpos;
Y=2*i-1;
 
There we have a synchronized Texer 2 and superscope, easy. A common use for registers is to have a ‘control scope’, that is a superscope that does not render anything but instead contains code that affects the preset. For example pre-calculating the sines and cosines of rotations for 3D presets and storing them in registers.
 
rotx=rotx+0.01;
roty=roty+0.02;
rotz=rotz+0.03;
crotx=cos(rotx);
srotx=sin(rotx);
croty=cos(roty);
sroty=sin(roty);
crotz=cos(rotz);
srotz=sin(rotz);
reg01=crotx;
reg02=srotx;
reg03=croty;
reg04=sroty;
reg05=crotz;
reg06=srotz;
 
This way the sine’s and cosines are only calculated once per frame.
 
Exercises:
 
Make a control scope for a 3D square.    (Exercise 5A)
 
Part 6: Megabuf and loops
 
The megabuf is a million element array, which allows you to store large volumes of data and, importantly, process it with loops. Unfortunately the syntax of the megabuf is a little different to the standard AVS:
 
Retrieving data:
MyVar=megabuf(index);
 
Entering data:
assign(megabuf(index),MyVar);
 
Where ‘index’ is the megabuf item (1 to 1 million) to retrieve or enter data into. We will look at loops now as well because the megabuf is pretty pointless if you don’t use loops. The syntax of loops is:
 
loop(count, statement)
 
However because we are likely to want to execute more than one statement in a loop, we also need to use:
 
exec2(parm1, parm2)
 
This evaluates parm1, then parm2, and returns the value of parm2. This may seem like a lot of new functions to get to grips with all in one go, but unfortunately they are all necessary. Let’s work through an example to help understand how they all fit together…
 
We are going to make a 100 point superscope that stores the value of the spectrum at one point over time, so that we can have a scope that scrolls from left to right. The first thing we need to do is put the value of the spectrum into megabuf(1), which we do like so:
 
assign(megabuf(1),-getspec(0.1,0.1,0));
 
Now we need to loop through 100 elements of the megabuf and fill each one with the previous element, that is, megabuf(i)=megabuf(i-1). We have to loop from the 100th element to the 2nd rather than vice versa otherwise we will be overwriting ourselves as we go. Here is the code:
 
index=101;
loop(99,
  exec2(
    assign(index,index-1),
    assign(megabuf(index),megabuf(index-1))
  )
);
 
Okay don’t panic! It looks confusing but let’s follow it logically. First we have defined a variable called ‘index’, this will be the current element of the megabuf we are working with, so to start off we set it to 101. Then we begin our loop, we set the first argument to 99, because we want to execute the next part 99 times, for elements 100 to 2, we don’t need to do element 1 because we are setting that to the value of the spectrum. Next we have an exec2, because we want to perform 2 actions… decrementing the index, and setting the megabuf item. Remember I said we are always likely to want to do more than one statement in a loop? The reason for this is that we generally need a way of identifying the current element we are working with, in this case the ‘index’ variable, which needs to be incremented or decremented. Unfortunately we can’t just say ‘index=index-1’, we have to use ‘assign(index,index-1)’ because we are inside an exec2 function. Finally the operation itself ‘assign(megabuf(index),megabuf(index-1))’, this shifts each megabuf element along by one. Phew! But we’re not finished yet I’m afraid, we need to output the megabuf elements, so in the point box we put:
 
Point:
index=index+1;
x=2*i-1;
y=megabuf(index);
 
But we need to set the index to 0 before this code gets run, so at the end of the frame box we add index=0. The final superscope code looks like this:
 
Init
n=100;
Frame
drawmode=1;
assign(megabuf(1),-getspec(0.1,0.1,0));
index=101;
loop(99,
  exec2(
    assign(index,index-1),
    assign(megabuf(index),megabuf(index-1))
  )
);
index=0;
Beat
Point
index=index+1;
x=2*i-1;
y=megabuf(index);
 
Now for some closing points of interest: There is also a ‘gmegabuf’, that is, a global megabuf; the only difference between the two is that the gmegabuf can be accessed from any AVS element, in the same way in which a register can. Remember that often a ‘control scope’ is used in presets to store all of the complex code and keep important code elements in one place, frequently it is tidier to use the gmegabuf for complicated operations and place the code in a control scope for easy access. One might argue that registers are essentially redundant with a global million item array available, however registers generally make for easier to read code (and do not require ugly ‘assign’ statements).
 
Exercises:
 
Make a 100 point Texer2 that fills the megabuf with 200 random values from -1 to 1 and uses each pair of numbers as the x and y for a point.             (Exercise 6A)
 
Part 7: Coding Effect Lists
 
A relatively recent addition to AVS is the ability to code effect lists, giving the author the ability to control the activation of an effect list and (to some extent) its input and output. Let’s jump straight into an example, create a new preset, clear every frame, add an effect list, uncheck ‘enabled’, check ‘clear every frame’ and check ‘use evaluation override’. ‘Use evaluation override’ tells AVS that we want to put code into the boxes, if you do not tick it your code will be ignored. Now add a ‘text’ object inside the effect list and type “Inside effect list”, move it up a little from the centre so we have some room for more text.
 
Now the effect list is currently disabled, so let’s add some code that will enable it occasionally, type the following into the effect list:
 
Frame
counter=counter+0.1;
enabled=above(sin(counter),0);
 
The variable ‘enabled’ is a reserved word we use to tell AVS whether or not to enable the effect list, by setting it to 0 or 1. The code we entered simply produces a sine wave from the variable counter, and enables the effect list when the sine wave is greater than 0. Now replace the code with:
 
Frame
counter=if(equal(beat,1),1,counter-0.05);
enabled=above(counter,0);
 
Here we are setting the counter to be 1 when there is a beat (‘beat’ is another reserved word that AVS sets to 1 when there is a beat), otherwise we reduce it by 0.05. The effect list is enabled when counter is greater than zero, the net effect is that the effect list is enabled for a little while each time there is a beat. Right, now we can enable and disable effect lists as we want, let’s move onto blending. There are two important variables when dealing with blending effect lists:
 
Alphain This is the amount of the incoming data to blend with the contents of the effect list. This variable only has an effect when the input blending is set to ‘Adjustable’.
Alphaout This is the amount of the contents of the effect list to blend with the data outside of the effect list. This variable only has an effect when the output blending is set to ‘Adjustable’.
 
These descriptions are rather dry, and it is difficult to envisage the results of the blend operations until you have experimented with them a bit. Let’s add another text object outside of our effect list so we can get an idea of how the blending works. Add a text object before the effect list with the text “Outside effect list” and move it down from the centre a little. Set the output of the effect list to adjustable and replace the effect list code with:
 
Frame
enabled=1;
counter=counter+0.01;
alphaout=abs(sin(counter));
 
Notice how the texts fade in and out because we are blending the effect list in and out. Also note in the code we have an abs() function, which returns the absolute value of its argument (that is, negative numbers are changed to positive), this is to ensure our alphaout stays between 0 and 1.
 
Now try changing the input of the effect list to maximum. Notice that the data outside the effect list is always visible, why is this? It is because when the effect list is not visible, the text will be visible as it is outside the effect list; and when the effect list is visible, it contains the text from outside (maximum blended) with the text inside the effect list.
 
Okay, now set the input blending to adjustable and the output to replace and enter the following code into the effect list:
 
Frame
enabled=1;
counter=counter+0.01;
alphain=abs(sin(counter));
 
Here we are keeping the data inside the effect list visible and just fading in and out the data from outside, because our output is set to ‘replace’, the outside text is overwritten with black pixels when not blended into the effect list.. You may have already spotted that there is sometimes more than one way to achieve the same effect, but in more complex arrangements of effect lists, this becomes less and less true.
 
Exercises:
 
Make three effect lists which blend into each other sequentially, such that 1 blends into 2, then 2 blends into 3, then 3 blends into 1 etc…(Hint: registers)
        (Exercise 7A)
 
Part 8: Mouse Input
 
Mouse input is a powerful feature of AVS, it allows artists to add user interactivity to their presets and introduces the possibility of menus and other window like functionality. We aren’t concerned here with what the correct and incorrect uses are for mouse input (but you would be wise to consider what mouse control brings to a preset) instead we will look at how to code for mouse input. The main function we require is Getkbmouse(), depending on the argument we pass to it, we will receive a different value:
 
Getkbmouse(1) Returns the X position of the cursor
Getkbmouse(2) Returns the Y position of the cursor
Getkbmouse(3) Returns the mouse left button state (0 up, 1 down)
Getkbmouse(4) Returns the mouse right button state (0 up, 1 down)
Getkbmouse(5) Returns the SHIFT state (0 up, 1 down) [NOT middle mouse as incorrectly stated in the expression help]
 
So let’s jump into an example, make a new preset, clear every frame and add a Texer2 with the following code:
 
Init
N=1;
Frame
Beat
Point
x=getkbmouse(1);
y=getkbmouse(2);
 
Gosh, that was simple, a mouse controllable dot. But to be honest a dot that you can move around with your cursor isn’t particularly useful, so let’s try something we can use…a button! Create a new preset, clear every frame, and add a superscope with the following:
 
 Init
N=5;
Frame
drawmode=1;
point=1;
Beat
Point
point=(point+1)%4;
x=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3);
y=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3);
x=x*0.4;
y=y*0.2;
 
There we have our button. Notice we are using the modulus operator to create a counter instead of the usual ‘if’ statement, just to show that there is more than one way to approach most problems. Now we are going to need some way of identifying if it has been pressed, so add another superscope with the following code:
 
Init
//button fill superscope
N=2;
Frame
drawmode=1;
linesize=h*0.2;
Beat
Point
point=bnot(point);
x=if(point,-0.4,0.4);
y=0;
red=0.3;
blue=0.1;
green=0.1;
 
Here we are using linesize to make a solid superscope and filling in our button area, ensure that this scope is before the previous one or we will overdraw our button outline. Now finally we need a control scope, it is always a good idea to have a control scope when dealing with mouse control because mouse input code is usually associated with various parts of the preset and you don’t want to lose track of it. So add a superscope and erase all of the boxes to leave an empty superscope we can fill in. Right, now we can get coding, the first thing we need to do is detect if the mouse is inside the button area, so add the following to the control superscope:
 
Frame
inbox=
band(
band(above(getkbmouse(1),-0.4),below(getkbmouse(1),0.4)),
band(above(getkbmouse(2),-0.2),below(getkbmouse(2),0.2))
);
 
We are using band() a lot here, because we want to check that lots of conditions are occurring at once, so the mouse x must be above -0.4 and the mouse x must be below 0.4 and the mouse y… etc… The net result of all our ands is that our variable ‘inbox’ will be 1 if all the conditions are true and 0 if any of the conditions are false. Now we can use this one variable to produce an effect already, add the following after the above code:
 
reg01=inbox;
 
If you wish you can look in the debug window and see it change from 0 to 1 when your mouse is in the area, but for a much easier indication simply change the button fill scope code to the following:
 
Point
point=bnot(point);
x=if(point,-0.4,0.4);
y=0;
red=0.3+reg01*0.2;
blue=0.1;
green=0.1;
 
Here we are (indirectly) adding the value of our ‘inbox’ variable to the colour to get a pleasant ‘mouse over’ effect. The only thing left to add now is the actual pressing of the button. For this we will make it so that the button changes colour when pressed, so modify the control scope to be:
 
Init
Bstate=0;
Frame
inbox=
band(
band(above(getkbmouse(1),-0.4),below(getkbmouse(1),0.4)),
band(above(getkbmouse(2),-0.2),below(getkbmouse(2),0.2))
);
reg01=inbox;
Pressing=band(getkbmouse(3),inbox);
bstate=if(pressing,bnot(bstate),bstate);
reg02=bstate;
 
We have two new variables now, ‘pressing’ is a variable that will be set to 1 when the user is pressing the button (that is, when the left mouse button is down and the mouse is in the button area). ‘Bstate’ is the state of the button, either 1 or 0, we are saying if the button is being pressed, change the state to not the current state, so if it is 0 it changes to 1 and vice versa. We are saving the button state in a register because we are going to use it in the button fill superscope, change the button fill superscope to become:
 
Point
point=bnot(point);
x=if(point,-0.4,0.4);
y=0;
red=0.3+reg01*0.2;
blue=0.1+reg02;
green=0.1;
 
Now our button will change colour when it is pressed. Have you noticed a problem though? Our button flickers when we press it and seems to change to a random state, can you tell why this is? It is because of the following line in our control scope:
 
bstate=if(pressing,bnot(bstate),bstate);
 
Because the code is executed every frame, it is switching bstate to bnot(bstate) every frame we hold down the left mouse button in the area. We need some way of making the state only change once per press of the mouse key, there are various ways of achieving this, change the control scope code to the following:
 
Frame
inbox=
band(
band(above(getkbmouse(1),-0.4),below(getkbmouse(1),0.4)),
band(above(getkbmouse(2),-0.2),below(getkbmouse(2),0.2))
);
reg01=inbox;
Pressing=band(getkbmouse(3),inbox);
bstate=if(band(pressing,unlocked),bnot(bstate),bstate);
reg02=bstate;
unlocked=bnot(getkbmouse(3));
 
The new lines are highlighted in red, what we have done is introduced a variable which is set at the end of the frame, and equals 1 if the mouse is up, 0 if the mouse is down. Now we only change our state if the variable ‘pressed’ is 1 and our ‘unlocked’ variable is 1. If you are having trouble seeing how the ‘unlocked’ variable works, follow the code through from the user not pressing the button, to the user pressing it for a few frames, then not pressing it. Remember, the fact that it is at the end of the code is very important.
 
Well that is our button completed, just to show a potential use, let’s add an effect list which is enabled and disabled according to the button state. Add an effect list, uncheck enabled, check clear every frame, check use evaluation override, set the input and output blending to maximum and add the following code:
 
Frame
Enabled=reg02;
 
Now add a text object inside the effect list with the text ‘enabled’ and try out your button. Simple or what? Because we used a control scope and registers, we simply have to reference a register to get the button state. Effect lists and mouse input are a very powerful combination, experiment with them to see what you can produce.
 
Part 9: The Triangle APE
 
 The triangle APE was created by TomyLobo as a way to render triangles in AVS, given our previous discussion on solid superscopes the usefulness of such a feature should be obvious. The way in which we render triangles to the screen is slightly different to a superscope or Texer2 because we have three coordinates to work with for each triangle; so we reference x1,y1,x2,y2 and x3,y3 to define the corners of our triangle. For example, start a new preset, clear every frame and add a Render – Triangle with the following code:
 
Init
n=1;
Triangle
x1=-0.5;
y1=-0.5;
x2=0.5;
y2=-0.5;
x3=0;
y3=0.5;
 
Note that in the triangle ape ‘n’ represents the number of triangles we want to render, not the number of points. Here we have described a simple triangle, however explicitly writing the coordinates of a triangle has limited uses, as we probably want to render a lot of them. Because of the nature of triangles and defining three coordinates the code tends to be more complicated than superscopes or Texer2’s and frequently requires loops. Don’t be frightened though, we have already covered all of the concepts and syntax involved, we simply have to apply it to a more sophisticated problem.
 
Let us try to solve a simple problem: rendering a line of triangles. For most presets involving triangles you would be wise to sit down with a pen and paper and roughly calculate where you want triangles to be, but this is a simple problem so we will just jump right in…
 
We need a row of triangles, so the logical approach would be to define a single triangle, shift it, render it, shift it, render it etc… So let’s define our single triangle, something small and offset to start our row, modify your preset to:
 
Triangle
x1=-0.9;
y1=0.1;
x2=-0.8;
y2=0.1;
x3=-0.85;
y3=-0.1;
 
There we go, a little triangle, now if we add a variable that increases each pass through the triangle code, we can add that to the ‘x’ values to perform our ‘shift’…
 
Frame
shift=0;
Triangle
x1=-0.9+shift;
y1=0.1;
x2=-0.8+shift;
y2=0.1;
x3=-0.85+shift;
y3=-0.1;
shift=shift+0.1;
 
Here we are resetting ‘shift’ to zero each frame, then for each time the ‘triangle’ code is executed (once per triangle), the value of ‘shift’ is increased by 0.1. Now change the value of ‘n’ in the init box to 18 (18 triangles) and there we have our row! And for a bit of fun let’s make it respond to the music by changing the value of ‘y3’…
 
Triangle
x1=-0.9+shift;
y1=0.1;
x2=-0.8+shift;
y2=0.1;
x3=-0.85+shift;
y3=getosc(x1*0.5+0.5,0.1,0);
shift=shift+0.1;
 
We can also add colour coding to the triangles, however we use ‘red1’, ‘blue1’ and ‘green1’ (bonus points if you can work out why):
 
Triangle
x1=-0.9+shift;
y1=0.1;
x2=-0.8+shift;
y2=0.1;
x3=-0.85+shift;
y3=getosc(x1*0.5+0.5,0.1,0);
shift=shift+0.1;
red1=abs(x1);
blue1=1-x1;
green1=0.6;
 
Part 10: Advanced 3D
 
Well back in Part 4 we covered 3D, but that was only the basics, now we are going to look at some of the more sophisticated elements of working in the third dimension. This is the most advanced section of the programming guide, if you do not feel comfortable tackling the topics here it is best to spend more time familiarizing yourself with the basics; there’s no point struggling through and not learning anything. A lot of this part is theory, which you may find a little disappointing; however the main aim here is to give you the tools to solve programming tasks that you are likely to encounter.
 
When working in 3D it is crucial that you fully understand the environment you are creating, we have already covered rotation matrices and projections, so you should be at ease with the idea of your three axes x, y and z defining your world. One of the most useful mathematical tools at our disposal is vectors, you may or may not be aware of what a vector is, essentially it is a way to describe a position and a direction. But in our graphics programming we tend to be less interested in the position, so we usually just use vectors to describe a direction, for example:
 
MyVector =
 
There are lots of notations for vectors, here I am saying the x component of my vector is 0, the y component is 0 and the z component is 1 (this is a mathematical notation not AVS code). If you imagine a 3D space, the vector ‘MyVector’ is describing a direction of positive z, i.e. pointing down the z axis.
 
MyVector =
 
Now MyVector is describing a direction 45 degrees between the y and z axes.
 
Hopefully this concept is fairly straightforward; now, one thing we might want to do with a vector is calculate its length. We do this using Pythagoras theorem which you may be familiar with; I don’t want to go into much detail about it, rather I will just state the equation:
 
“Aaah! The equations have started!” I hear you cry. Don’t worry its very easy; it simply says ‘the length of a vector equals the square root of the sum of the squares of the components.’ In AVS this would be:
 
lengthv=sqrt(sqr(vx)+sqr(vy)+sqr(vz));
 
Where your vector is defined by vx, vy, vz.
 
Now when we do our calculations with vectors it is rather nice to have them normalized, what does that mean? It means that the x, y and z add up to 1 (a vector of length 1 is known as a ‘unit vector’), the way we achieve this is by dividing each component by the length of the vector. So in AVS code that would just be:
 
vx=vx/lengthv;
vy=vy/lengthv;
vz=vz/lengthv;
 
Right, now we know a little about vectors we can start looking at some uses for them; so let’s move onto normals. What is a normal? A normal is related to a face, if you have a face, the normal of that face is a vector which defines the direction it is facing. For example, hold out your hand with your palm flat, if you imagine your palm is a face (such as a triangle or square) the normal to your palm is a vector that describes the direction away from your hand.
 
Normals are very useful for a variety of reasons; imagine, for example you want to light a cube, if you only know the location of all the faces on the cube you don’t really have enough data to produce any decent lighting. If you know the direction of each face, you can tell whether or not each side is facing the light or not.
 
Let’s make a preset that renders a solid 3D square and shows its normal as a line, start a new preset, and add a superscope with the following code:
 
Init
Frame
rotx=rotx+0.01;
roty=roty+0.02;
rotz=rotz+0.03;
reg00=rotx;
reg01=roty;
reg02=rotz;
Beat
Point
 
This is a very simple ‘control scope’ that we are using to store our rotation values, we need this because the values will be used in more than one superscope (go back to part 5 if you are unfamiliar with registers). Now add another superscope to render a square:
 
Init
n=1000;
Frame
rotx=reg00;
roty=reg01;
rotz=reg02;
linesize=2;
drawmode=2;
crotx=cos(rotx);srotx=sin(rotx);croty=cos(roty);
sroty=sin(roty);crotz=cos(rotz);srotz=sin(rotz);
switch=0.5;
Beat
Point
switch=-switch;
y1=i-0.5;
x1=switch;
z1=0;
x2=x1*crotz-y1*srotz;y2=x1*srotz+y1*crotz;
x3=x2*croty+z1*sroty;z3=-x2*sroty+z1*croty;
y1=y2*crotx-z3*srotx;z1=y2*srotx+z3*crotx;
z2=1/(z1+2);
x=x3*z2;
y=y1*z2;
 
You should have no problem understanding this code, if you do, go back to part 4 and refresh your memory on the basics of 3D (this is actually the solution to exercise 4C and 5A). You may have noticed that my rotation matrices are a little crushed together, this is just to save space and make the code a bit smaller.
 
Okay so we have a rotating square, now we need to define the normal to that face. Because our face is flat to the z axis (z1=0) we actually already know our normal, it is z=1 or z=-1. Why can it be two values? Because it is just a square on its own it can be ‘facing’ either up or down it doesn’t really matter, we just have to make a decision and stick to it. If the square was part of an object such as a cube we would want it to face away from the centre of the cube, and we would assign its normal based on that. So let us say arbitrarily that the normal to this face is z=1, in order to display that as a line we need to put the normal through rotation matrices with the same rotation as the face. This is because as the face rotates the normal is also rotating, so we need to recalculate it.
 
Add a new superscope with the following code:
 
Init
n=2;
Frame
linesize=2;
drawmode=2;
rotx=reg00;
roty=reg01;
rotz=reg02;
crotx=cos(rotx);srotx=sin(rotx);croty=cos(roty);
sroty=sin(roty);crotz=cos(rotz);srotz=sin(rotz);
Beat
Point
y1=0;
x1=0;
z1=0;
x2=x1*crotz-y1*srotz;y2=x1*srotz+y1*crotz;
x3=x2*croty+z1*sroty;z3=-x2*sroty+z1*croty;
y1=y2*crotx-z3*srotx;z1=y2*srotx+z3*crotx;
z2=1/(z1+2);
x=x3*z2;
y=y1*z2;
 
This second superscope does not do anything yet, but 90% of our work is done, because all we have to do is add the little bit of code that draws a line from the origin to the point defined by the normal, i.e. from 0,0,0 to 0,0,1, replace the code with:
 
Init
n=2;
Frame
linesize=2;
drawmode=2;
rotx=reg00;
roty=reg01;
rotz=reg02;
crotx=cos(rotx);srotx=sin(rotx);croty=cos(roty);
sroty=sin(roty);crotz=cos(rotz);srotz=sin(rotz);
point=0;
Beat
Point
y1=0;
x1=0;
z1=point;
point=bnot(point);
x2=x1*crotz-y1*srotz;y2=x1*srotz+y1*crotz;
x3=x2*croty+z1*sroty;z3=-x2*sroty+z1*croty;
y1=y2*crotx-z3*srotx;z1=y2*srotx+z3*crotx;
z2=1/(z1+2);
x=x3*z2;
y=y1*z2;
red=1;
blue=0;
green=0;
 
There we have it, our face and normal. This was a rather simple case because we know our normal, plus it was centred at the origin, however I hope you now understand the concept a little better.
 
In the example we just tried, we already knew the normal of our face, it was obvious, this is often the case if you are making simple geometric shapes; for example a cube will have faces where the normals are x=1, x=-1, y=1 etc… unless you have defined it without the faces aligned to the axis. However what can you do if can’t just guess the normal of a face? The answer is a rather nifty formula/equation known as the cross product. If you have a face, you can define two vectors which lie on that face and calculate the cross product to give you the normal. The cross product is defined as:
 
Cx = v1(y) * v2(z) - v1(z) * v2(y)
Cy = v1(z) * v2(x) - v1(x) * v2(z)
Cz = v1(x) * v2(y) - v1(y) * v2(x)
 
Where you provide the vectors v1 and v2, and the result is the vector Cx,Cy,Cz (a vector which is perpendicular to v1 and v2). So how do you define two vectors that lie on the face? Well it’s really quite easy, you simply take two points which are on the face (you might as well use vertices since you already have them) and subtract them to get a vector which lies on the face.
 
For example, we have a triangle which is the face whose normal we wish to calculate with vertices:
 
x1 y1 z1 (Vertex 1)
x2 y2 z2 (Vertex 2)
x3 y3 z3 (Vertex 3)
 
(A vertex simply means a corner just in case you didn’t know) There is our triangle in 3D, to calculate the normal we make 2 vectors from vertex 1 to 2 and vertex 1 to 3:
 
v1x=x2-x1; 
v1y=y2-y1; 
v1z=z2-z1;
 
v2x=x3-x1;
v2y=y3-y1;
v2z=z3-z1;
 
So we have our two vectors, which we found by subtracting some of the vertices from each other, we can chose a different combination of vertices as long as we end up defining two vectors that lie on the face. Now we plug those vectors into the cross product formula to find the normal:
 
nx = v1y*v2z - v1z*v2y;
ny = v1z*v2x - v1x*v2z;
nz = v1x*v2y - v1y*v2x;
 
There is our normal, defined by nx,ny,nz; although at this point it would be a good idea to normalize it because it may be very large or small depending on the size of the triangle; we already covered that though so I won’t repeat it here. If you want to see this in action, you can load the preset ‘Demonstration – Arbitrary Normal’ in your AVS Programming Guide directory. The preset creates a random triangle and shows its normal, you will notice that the line for the normal is based at the origin not on the triangle; this is because, if you remember, we are interested in using the normal to define a direction, we are not interested in the location. You should be able to read through the code and follow the steps we have just been through.
 
The final topic we are going to explore is the dot product, a formula that will tell us the angle between two vectors. We already know the direction of a face, but currently we only have it expressed as a vector; in a realistic programming context this isn’t very useful because we almost always want to know the angle between the normal of that face and something else. For example, if we want to know if a face is pointing away from the camera (so that we can avoid rendering it for speed) want we need is the angle between the face normal and a vector from the camera to the face.
 
The formula for the dot product of two vectors is:
 
Dot=(v1x*v2x+v1y*v2y+v1z*v1z)
 
In other words multiply each vector component together and sum the result, the value that you get as a result is the cosine of the angle between the vectors (multiplied by their lengths but that isn’t important for our needs). So in laymen’s terms the result is a value between -1 and 1 where 1 means they are facing each other, -1 means they are facing the same direction and 0 means they are at 90 degrees to each other (at least that’s all we really care about). This is another situation where normalizing the vectors is important, otherwise the result you get will not fall between -1 and 1.
 
Have you ever heard of Lambert's cosine law? Probably not, but it states that the apparent brightness of a face is proportional to the cosine of the angle between the normal of the face and the direction of the light. Gosh, that’s convenient isn’t it, so if we define a light as a vector (the way the light is pointing) and set the colour of our face to the dot product of the face normal and light vector, the face will be lit pretty accurately without any extra work on our part.
 
Let’s make a rotating square with some lighting to demonstrate this idea, make a new preset and add a superscope with the following code:
 
Init
Frame
n=1000;
linesize=2;
drawmode=2;
rotx=rotx+0.01;
roty=roty+0.02;
rotz=rotz+0.03;
crotx=cos(rotx);srotx=sin(rotx);croty=cos(roty);
sroty=sin(roty);crotz=cos(rotz);srotz=sin(rotz);
switch=0.5;
Beat
Point
switch=-switch;
y1=i-0.5;
x1=switch;
z1=0;
x2=x1*crotz-y1*srotz;
y2=x1*srotz+y1*crotz;
x3=x2*croty+z1*sroty;
z3=-x2*sroty+z1*croty;
y1=y2*crotx-z3*srotx;
z1=y2*srotx+z3*crotx;
z2=1/(z1+2);
x=x3*z2;
y=y1*z2;
 
There is a simple rotating square, now the first thing we need to add to do some lighting is calculate the normal, we already know how to do this, so add the following code to the end of the frame box:
 
//define the normal
nx=0;ny=0;nz=1;
//rotate the normal
x1=nx;y1=ny;z1=nz;
x2=x1*crotz-y1*srotz;
y2=x1*srotz+y1*crotz;
x3=x2*croty+z1*sroty;
z3=-x2*sroty+z1*croty;
y1=y2*crotx-z3*srotx;
z1=y2*srotx+z3*crotx;
 
So now we can access the rotated normal as a vector (x3,y1,z1), again we specified the normal to be z=1 however z=-1 would have been fine too. Next we need to define our light as a vector, we will define it and then normalize it; add the following code to the frame box:
 
//define the light
lx=1;
ly=0;
lz=0;
//normalise it
length=sqrt(sqr(lx)+sqr(ly)+sqr(lz));
lx=lx/length;
ly=ly/length;
lz=lz/length;
 
So this is our light, a vector that points along the positive x axis. Now you can hopefully see that we don’t really need to normalize this vector because the sum of its components is already 1. The reason we are putting the code in is so that you can fiddle with the values of the light vector, for example we could make the light point at a 45 degree angle between the x and y axes by setting lx and ly to 1, the normalization code would turn it into a unit vector. Notice that we did not rotate the light vector, this is because we want the light to be stationary and shine on the rotating face, if we rotated the light vector with the same rotation as the face (like we did for the normal) it would be as if the light was attached to the face, so the lighting would not change.
 
Now the final stage is to dot product the light vector with the normal of the face to find out the angle between them, so add the following code to the end of the frame box:
 
//dot product the light and the normal
dot=x3*lx+y1*ly+z1*lz;
 
This should be nothing too surprising, we are simply plugging the face normal and light vectors into the dot product formula presented earlier. So how do we use this value? Well we can simply assign the colour values of the superscope directly to that variable, since ‘dot’ will by 1 when facing the light and <0 when facing away from it. Add the following code to the end of the point box:
 
red=dot;
blue=dot;
green=dot;
 
All done! Notice how the face appears to be lit as it faces our virtual light, you can fiddle with the values to see the effect they have, setting the light to 0,0,1 (a vector pointing down the positive z axis) will have the effect of lighting in the direction of the camera which may help you to see the effect.
 
Well there is your introduction to lighting; here we have seen one type of simple lighting, the best analogy for the effect we have created is a ‘sun’, because our faces are lit uniformly depending on rotation. In other words the location of our face isn’t important, as if the light were very far away, but very bright. A more realistic lighting system would account for the distance between the light and the face, something you already have the ability to do because of what we have covered regarding vectors. One method for finding the distance between the light and the face would be:
 
* Define the light location (a)
* Define the face location (b)
* Subtract (a) from (b) to create a vector (c)
* Find the length of (c) using Pythagoras
 
That length value is the distance between the light and the face, you would use this value to modify the colour of your face. We won’t go into any more detail about this though, since it is up to you to experiment.
 
Part 11: Tips & Tricks
 
This section contains some useful coding tips and tricks that do not apply to any specific object in AVS, but are handy for polishing your presets and solving certain problems.
 
Sine Waves
 
Sinusoidal effects are very useful in AVS; sinusoidal motion is very aesthetically pleasing to the human eye and occurs regularly in nature. It is important to understand the basics of sin and cos to use them effectively in presets, the (basic) equation of a sine wave is:
        y=?sin(2?fx)
       
That is, amplitude times sin of 2 times pi times the frequency times x. So to produce a sine wave of amplitude 0.5 and frequency 2 we can make a superscope containing:
 
Point:
x=2*i-1;
y=0.5*sin(2*$pi*2*x);
 
Remember that the x axis in AVS spans from -1 to 1 so you will see 4 periods rather than 2 as you might expect. Experiment a little with sine waves and sinusoidal motion so that you are able to plan and implement the effect you want, rather than throwing sin’s and cos’s into code randomly.
 
Aspect ratio correction
 
To aspect ratio correct a preset, for example a DM or superscope, the simplest method is:
 
Frame:
asp=h/w;
Point/Pixel:
x=x*asp;
 
Where x is your final x after translation, rotation etc… However this relies on the AVS window being wider than it is tall, otherwise the data will probably spill off the edge of the screen. You can apply more complex functions to create an aspect ratio correction for any size window:
 
Point/Pixel:
x=x*if(above(w,h),h/w,1);
y=y*if(above(h,w),w/h,1);
 
Frame rate independent movement
 
To avoid your preset moving too fast on fast computers you can limit the movement of objects speed by basing it on time rather than the frame rate.
 
Frame:
passed=gettime(0)-last;
MyVar=MyVar+passed;
last=gettime(0);
 
Where your movement is based on MyVar. To have different elements moving at different speeds you can scale this value ‘passed’, for example multiplying it by a random scale changed on beat.
 
Inactive effect lists
 
Effect lists that fade in and out or have changing alphaout of any kind should be disabled once the alphaout becomes unnoticeably small, thus improving the frame rate:
 
Frame:
enabled=above(alpha,0.01);
alphaout=alpha;
 
Caching static data
 
Static data, for example a gradient background to a preset can be ‘cached’ into a buffer at the start of a preset to improve the frame rate. Simply put the effects to be cached into an effect list with a buffer save at the end, then use the following code in the effect list:
 
Init
hi=0;
wi=0;
Frame:
enabled=bor(1-equal(hi,h),1-equal(wi,w));
hi=h;
wi=w;
 
This will enable the preset when the preset starts and (more importantly) when the AVS window is resized (so that the data can be recalculated which is probably necessary). You can use a buffer save with ‘restore’ to get your saved data at any point.
 
Part 12: Advanced Exercises
 
It is expected that by this chapter you have gained a fairly solid understanding of AVS programming and are able to translate most of your ideas into code. After all, the reason for learning the AVS language is to allow you to better implement your ideas and reproduce what you envisage in your mind. One of the greatest skills any programmer needs is problem solving, given a problem you need to be able to come up with a creative and graceful solution, that is the real joy of programming.
 
This final chapter contains some exercises that you should find challenging, read the tips if you struggle, but try to work it out on your own first. Also there is usually more than one solution to a problem, so do not think your solution must match mine, provided it works. Hopefully this will encourage you to think creatively and develop your own approach to solving AVS problems. And when in doubt… use the expression help!
 
Exercises:
 
Create a dot superscope that produces a solid 10 pixel by 10 pixel square in the centre of the AVS window. The square should remain 10x10 regardless of the AVS window size.
        (Exercise 12A)
 
Create a 10,000 point superscope that displays the oscilloscope data, but only updates once a second.        (Exercise 12B)
 
Tips for Exercise 12A:
 
* First decide how to define 1 pixel, it will be related to the height and width of the AVS window.
* Try to produce something of a fixed width before you tackle 2 dimensions.
* Use the solid superscope base from chapter 2, but rearrange it to your needs
* An ideal solution will have n=100, does yours?
 
Tips for Exercise 12B:
 
* Break the problem into smaller problems… can you make a variable equal 1 when a second has elapsed?
* You need to use the megabuf to store the oscilloscope values
* What is the maximum limit for a loop (I’ll give you a clue, it’s below 10,000!)? If you can’t use one loop, maybe you can use two.
* Sample the oscilloscope data using getosc(), divide your loop counter by 10,000 for the band.
 
52
 
http://avs.chat.ru