Программа Flash изначально была предназначена для двухмерной графики (рисунки на плоскости), и в ней нет специальных средств для создания трехмерных эффектов. Поэтому достаточно сложно сделать качественную трехмерную анимацию накладывать текстуры (рисунки) на поверхности объектов, создавать детальные природные сцены.
Дело еще и в том, что построение трехмерных реалистичных картинок, таких, как в популярных играх, требует большого объема вычислений, с которыми не справляется Flash-проигрыватель.
Но кое-что все-таки сделать можно. Существует два подхода:
Второй способ требует довольно серьезного программирования и понимания математики трехмерного мира. Кроме того, сложные вычисления сильно нагружают процессор, и поэтому в анимации рекомендуется использовать не более 30 клипов, управляемых из программы.
Создайте новый документ и сохраните его в папке PRACTICE\15 под именем okno.fla. Включите инструмент , установите прозрачную границу и бежевый цвет заливки. Отключите режим рисования объектов (нужно, чтобы кнопка в нижней части панели инструментов не была нажата). Нарисуйте прямоугольник, изображающий одну половину рамы. |
Измените цвет заливки на любой другой и нарисуйте два прямоугольника, обозначающих место для стекол. Затем, выделив эти прямоугольники, удалите их. |
Скопируйте половину рамы, перетащив ее при нажатой клавише Alt, постройте отражение для копии (ModifyTransformFlip Horizontal) и соедините обе половинки так, чтобы заливки слились в одну. |
Выделите раму и выберите пункт меню CommandsSwift 3D Express. В левой части окна щелкните по строчке Sizing (размеры) и установите параметры Width=0.8 (ширина), Height=0.8 (высота) и Depth=2 (глубина). |
Таким образом, мы немного уменьшили ширину и высоту фигуры (до 80% исходного размера), и в 2 раза увеличили глубину объема.
В нижней части окна выберите вкладку Bevels (фаски) и перетащите вариант Beveled (прямая фаска) на раму в правой части экрана. |
В нижней части окна выберите вкладку Materials (материалы), а чуть ниже вкладку Wood (дерево). Схватите мышкой один из образцов и перетащите его прямо на раму в правой части окна. Затем перетащите тот же образец на внутреннюю поверхность рамы. |
Сделайте активной вкладку Lighting и установите понравившийся вам вариант освещений, перетащив его на изображение рамы. |
Перейдите на вкладку Animation (анимация), далее на вкладку Common Spins (общие вращения) и перетащите на раму вариант Horizontal Right (горизонтальное вращение вправо). |
Теперь стал активным блок управления анимацией под полем просмотра:
Кнопки (слева направо) предназначены для:
Справа от блока управления в поле Frames мы видим число кадров анимации (по умолчанию 19). Это значение можно изменять. Увеличение числа кадров делает анимацию более плавной, но увеличивает объем файла.
С помощью панели управления просмотрите анимацию в непрерывном режиме, а затем по кадрам. |
В верхней части окна перейдите на вкладку Render and Export to Flash (построить изображение и передать во Flash). Нажмите на кнопку Vector (векторные рисунки), выберите в окне слева строчку Fill options (настройка заливки) им установите в списке Fill Type (тип заливки) вариант Area Gradient Shading. |
Щелкните на кнопку Render Frames (построить изображение фреймов) и дождитесь, когда этот процесс закончится. |
Щелкните по кнопке Create Flash Movie Clip (создать отдельный клип). Разместите клип на поле так, как вам хочется, и проверьте работу фильма. |
Выделите клип и добавьте к нему код onClipEvent ( load ) { function rotate ( x ) { frame = _currentframe + x; if ( frame < 1 ) frame += _totalframes; if ( frame > _totalframes ) frame -= _totalframes; gotoAndStop ( frame ); } stop(); } onClipEvent ( keyDown ){ key = Key.getCode(); if ( key == Key.LEFT ) rotate ( 1 ); if ( key == Key.RIGHT ) rotate ( -1 ); }Проверьте работу клавиш-стрелок. |
Оси X и Y направлены так, как в обычной декартовой системе на плоскости. Ось Z идет перпендикулярно осям X и Y, она уходит от зрителя так, что точки с положительными значениями z-координаты находятся «за» экраном (плоскостью просмотра), а точки с отрицательными z-координатами перед экраном.
Z-координата нужна для того, чтобы
Создайте новый документ типа ActionScript File и сохраните его в папке PRACTICE\15 под именем
3D.fla. В этот файл добавьте код функции
function pt3D ( x, y, z ) { var p = {x:x, y:y, z:z}; return p; } |
Для упрощения можно не учитывать перспективу. Это значит, что видимые размеры удаленных предметов не будут изменяться. В этом случае z-координата нужна только для того, чтобы определить порядок рисования объектов (сначала дальние, затем ближние). Все точки, которые имеют одинаковые координаты x и y, проецируются в одну и ту же точку на экране.
Для преобразования пространственных координат точки в экранные координаты нужно учесть два момента:
X = k·x; Y = k·y; Z = k·z;Здесь и далее (x,y,z) обозначают исходные координаты точки, а (X,Y,Z) новые координаты, после преобразования.
X = x + a; Y = y + b; Z = z + c;
X = x·cos(φ) - y·sin(φ) Y = x·sin(φ) + y·cos(φ) Z = z |
X = (x-a)·cos(φ) - (y-b)·sin(φ) + a Y = (x-a)·sin(φ) + (y-b)·cos(φ) + b Z = z
Аналогичные формулы можно записать для вращения относительно оси X
X = x Y = (y-b)·cos(φ) - (z-c)·sin(φ) + b Z = (y-b)·sin(φ) + (z-c)·cos(φ) + cи относительно оси Y:
X = (z-c)·sin(φ) + (x-a)·cos(φ) + a Y = y Z = (z-c)·cos(φ) - (x-a)·siz(φ) + c
Добавьте в файл 3D.as код функции, которая выполняет поворот массива точек на заданные углы относительно
трех координатных осей:
function rotate3D ( points, angles, center ) { var g2R = Math.PI/180; var sx = Math.sin(angles.x*g2R); var cx = Math.cos(angles.x*g2R); var sy = Math.sin(angles.y*g2R); var cy = Math.cos(angles.y*g2R); var sz = Math.sin(angles.z*g2R); var cz = Math.cos(angles.z*g2R); var x,y,z, xy,xz, yx,yz, zx,zy, scale; var a = 0, b = 0, c = 0; if ( arguments.length > 2 ) { a = center.x; b = center.y; c = center.z; } for (i=0; i<points.length; i++) { x = points[i].x - a; y = points[i].y - b; z = points[i].z - c; // вращение вокруг оси x xy = cx*y - sx*z; xz = sx*y + cx*z; // вращение вокруг оси y yz = cy*xz - sy*x; yx = sy*xz + cy*x; // вращение вокруг оси z zx = cz*yx - sz*xy; zy = sz*yx + cz*xy; // сохранение новых координат points[i].x = zx + a; points[i].y = zy + b; points[i].z = yz + c; } } |
Параметр center можно не указывать, тогда вращение выполняется относительно начала координат.
В начале функции вводятся вспомогательные локальные переменные. Здесь g2R коэффициент для перевода угла из градусов в радианы, еще 6 переменных используются для предварительного вычисления синусов и косинусов этих углов (не нужно будет повторять расчеты для каждой точки!).
В переменные a, b и c записываются координаты центра вращения. По умолчанию все они равны нулю. Оператор
if ( arguments.length > 2 ) ...срабатывает тогда, когда количество аргументов, переданное функции, больше двух.
К аргументам функции можно обратиться с помощью массива arguments, длина которого равна их количеству. В данном случае arguments[0] это параметр points, arguments[1] это angles и т.д. |
Пусть объект имеет высоту h и координаты (x,y,z). Как следует из подобия треугольников на рисунке, изображение на экране будет иметь высоту
H = h·F / (F + z);где F расстояние от глаза до экрана, называемое фокусом. Таким образом, все размеры умножаются на коэффициент
p = F / (F + z);Кроме того, на этот же коэффициент нужно умножить x-координату и y-координату всех точек, которые находятся на данной глубине (имеют одинаковую z-координату). Таким образом, при учете перспективы меняется не только размер, но и видимое расположение объектов на экране.
Заметим, что при уменьшении фокуса эффект перспективы будет усиливаться, при увеличении ослабляться.
Добавьте в конец файла 3D.as функцию, которая строит массив видимых точек с учетом перспективы:
function perspective (points, focalLength) { var pt2D = []; var i, x, y, z, scale; for (i=0; i<points.length; i++) { scale = focalLength/(focalLength + points[i].z); x = points[i].x*scale; y = points[i].y*scale; z = points[i].z; pt2D[i] = {x:x, y:y, z:z, scale: scale, id: i }; } return pt2D; };Сохраните файл. |
Таким образом, мы создали файл, содержащий все необходимые нам функции для создания и преобразования координат точек в пространстве.
Здесь каждый шарик это клип (символ типа MovieClip), а в программе только меняются координаты и размер этих клипов (с учетом перспективы).
Откройте файл PRACTICE\15\ball.fla и добавьте к кадру 1 код
#include "3d.as" focalLength = 300; this.createEmptyMovieClip ( "scene", 1 ); scene._x = 275; scene._y = 200; vert = [pt3D(50, 50, -50), pt3D(50, -50, -50), pt3D(-50, -50, -50), pt3D(-50, 50, -50), pt3D(50, 50, 50), pt3D(50, -50, 50), pt3D(-50, -50, 50), pt3D(-50, 50, 50) ]; |
Для рисования создается отдельный пустой клип с именем scene, в котором будут размещаться все клипы-шарики. Он помещается в центр экрана.
Массив vert задает координаты вершин куба (от английского vertex вершина). Для создания объекта-точки используется функция pt3D, которая находится в файле 3D.as.
Первые 4 точки описывают ближнюю к нам грань (она имеет отрицательную z-координату), а последние 4 точки дальнюю.
Добавьте к кадру 1 код создания клипов-шариков: for (i=0; i<vert.length; i++) { name = "ball" + i; scene.attachMovie ( "ball", name, i ); if ( i > 3 ) { var c = new Color ( scene[name].back ); c.setRGB ( 0x9900 ); } } |
Имена клипов строятся в переменной name следующим образом: к слову ball добавляется номер точки в массиве vert. Клип с порядковым номером i пока располагается на глубине i, потом мы поместим клипы на разные глубины в соответствии с их z-координатами.
Для наглядности цвет «дальних» шариков (для которых i>3) изменяется на зеленый. Это легко сделать, потому что цвет шарика задается цветом его внутреннего клипа back. Поэтому мы создаем для него новый объект Color и изменяем его цвет с помощью метода setRGB.
Для выполнения второй мы должны выполнить так называемую Z-сортировку, то есть расставить в массиве точки в порядке их приближения, от самой дальней к самой ближней, по убыванию z-координаты. Это можно сделать с помощью метода sortOn, который сортирует массивы структур по заданному полю.
Добавьте в код кадра 1 строчки showFigure(); function showFigure() { var i, obj; ptScreen = perspective ( vert, focalLength ); ptScreen.sortOn ( "z", Array.DESCENDING + Array.NUMERIC ); for (i=0; i < ptScreen.length; i++){ obj = scene["ball"+ptScreen[i].id]; obj._x = ptScreen[i].x; obj._y = ptScreen[i].y; obj._xscale = obj._yscale = 100 * ptScreen[i].scale; obj.swapDepths(i+1); } } |
С помощью функции perspective (из файла 3D.as) строится массив ptScreen, содержащий экранные координаты точек. Кроме того, каждый его элемент содержит два дополнительных поля: scale (масштаб) и id (номер точки в исходном массиве).
В строчке
ptScreen.sortOn ( "z", Array.DESCENDING + Array.NUMERIC);вызывается метод sortOn, который сортирует массив ptScreen по полю z. Во втором параметре задаются два дополнительных режима
В принципе здесь можно было использовать и такой вариант сортировки
ind = ptScreen.sortOn ( "z", Array.DESCENDING + Array.NUMERIC + Array.RETURNINDEXEDARRAY);Параметр Array.RETURNINDEXEDARRAY говорит, что метод возвращает результат массив индексов после сортировки ind, а сам массив ptScreen остается неизменным. Однако в данном случае этот вариант не очень удобен. |
В строчке
obj.swapDepths ( i+1 );
определяются уровни шариков. Метод swapDepths
(поменять глубины), вызванный таким образом, переводит объект obj
на глубину i+1. При этом объект, который был расположен на
глубине i+1, переводится на ту глубину, где был объект obj
(они меняются глубинами). Это значит, что точки, расположенные
раньше в отсортированном массиве (имеющие большую z-координату)
будут иметь меньшую глубину, то есть будут «ниже» следующих
за ними точек.
Проверьте работу фильма, нажав клавиши Ctrl+Enter. Вы должны увидеть начальную расстановку шариков. |
Добавьте к кадру 1 код Key.addListener ( scene ); scene.onKeyDown = function () { var ax = 0, ay = 0, az = 0; switch ( Key.getCode() ) { case Key.LEFT: ay = -5; break; case Key.RIGHT: ay = 5; break; case Key.UP: ax = -5; break; case Key.DOWN: ax = 5; break; } rotate3D ( vert, pt3D(ax, ay, az) ); showFigure(); } |
Далее задается обработчик события keyDown (нажатие клавиши). Углы поворота относительно осей X, Y и Z записываются в переменные ax, ay и az соответственно. Нужный поворот выбирается в зависимости от кода нажатой клавиши, который определяется с помощью метода Key.getCode(). Затем вызывается функция rotate3D, определенная в файле 3D.as, и функция showFigure, которая изменяет рисунок на экране.
Создайте новый документ и сохраните его в папке PRACTICE\15 под именем pyramid.fla.
Добавьте в кадру 1 код
#include "3d.as" focalLength = 400; this.createEmptyMovieClip ( "scene", 1 ); scene._x = 275; scene._y = 200; |
В основании пирамиды лежит равносторонний треугольник, все стороны которого равны a, а все углы 60°. С помощью простых вычислений можно показать, что его высота определяется формулой
h = a·sqrt(2/3)где sqrt() обозначает квадратный корень. Мы расположим начало координат так, чтобы
вершина 0: x=a/2, y=h/3, z= a·sqrt(3)/6 вершина 1: x=0, y=h/3, z=-a·sqrt(3)/3 вершина 2: x=a/2, y=h/3, z= a·sqrt(3)/6 вершина 3: x=a/2, y=-2·h/3, z=0Они записываются в массив vert с помощью функции pt3D.
Добавьте к кадру 1 код a = 200; sq3 = Math.sqrt(3); h = a*Math.sqrt(2/3); b = h/3; t = 2*h/3; vert = [ pt3D(a/2, b, a*sq3/6), pt3D(0, b, -a*sq3/3), pt3D(-a/2, b, a*sq3/6), pt3D(0, -t, 0) ]; |
Добавьте к кадру 1 код faucet = [ [0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3] ]; color = [ 0xFFFFFF, 0xFF0000, 0xFF00, 0xFF ]; showFigure(); |
В массив color записаны цвета каждой из граней, вы можете выбрать их на свой вкус. Здесь грань 0 белого цвета, грань 1 красного, грань 2 зеленого и грань 3 синего.
Функция showFigure, которая вызывается в последней строчке нового блока, рисует фигуру. При этом важно сначала нарисовать удаленные грани, а потом уже ближние, то есть нужна Z-сортировка. Мы уже использовали Z-сортировку для точек, теперь нужно применить ее для граней, с первого взгляда эта задача нетривиальна. Однако можно свести решение к уже известному результату применить Z-сортировку для центральных точек граней, координаты которых находятся как среднее арифметическое координат ее вершин.
Лобавьте к кадру 1 код функции function showFigure() { var i, midScreen; ptScreen = perspective(vert, focalLength); midScreen = midPointsZ(); midScreen.sortOn ( "z", Array.DESCENDING + Array.NUMERIC ); scene.clear(); for (i=0; i<midScreen.length; i++) drawFaucet ( midScreen[i].id ); } |
Далее в строчке
midScreen = midPointsZ();строится массив, каждый элемент которого содержит два поля: z z-координата центральной точки (остальные ее координаты вообще не нужны), и id номер грани в исходном массиве (для того, чтобы найти ее после Z-сортировки).
С помощью метода sortOn центральные точки сортируются по убыванию z-координаты (от дальних к ближним) и в цикле строятся все грани. Функцию drawFaucet, которая выполняет рисование грани с заданным номером, мы напишем далее.
Добавьте код функции function midPointsZ() { var i, z; var mid = new Array(); for(i=0; i<faucet.length; i++) { z = (ptScreen[faucet[i][0]].z + ptScreen[faucet[i][1]].z + ptScreen[faucet[i][2]].z) / 3; mid[i] = {id:i, z:z}; } return mid; } |
Добавьте код функции для построения грани function drawFaucet ( id ) { var i1 = faucet[id][0]; var i2 = faucet[id][1]; var i3 = faucet[id][2]; with ( scene ) { beginFill( color[id], 60); moveTo(ptScreen[i1].x, ptScreen[i1].y); lineTo(ptScreen[i2].x, ptScreen[i2].y); lineTo(ptScreen[i3].x, ptScreen[i3].y); lineTo(ptScreen[i1].x, ptScreen[i1].y); endFill(); } } |
Обратите внимание, что обход контура выполняется в том же порядке, в котором перечислены точки в массиве faucet. Для треугольных граней это неважно, но для более сложных (например, четырехугольников) неправильный порядок точек приводит к неправильному изображению.
Стиль заливки определяется строчкой
beginFill( color[id], 60);
Это значит, что из массива color берем цвет нужной
грани, и устанавливаем прозрачность 60%.
Добавьте к кадру 1 код Key.addListener(scene); scene.onKeyDown = function() { ax = ay = az = 0; switch ( Key.getCode() ) { case Key.LEFT: ay = -5; break; case Key.RIGHT: ay = 5; break; case Key.UP: ax = -5; break; case Key.DOWN: ax = 5; break; } rotate3D ( vert, pt3D(ax, ay, az) ); showFigure(); }Запустите ролик и просмотрите результат. |
Создайте новый документ и постройте модель куба, управляемого стрелками. Помните, что
|
Для описания фигуры будем использовать структуру, показанную на рисунке.
Она содержит три поля (свойства):
Сначала напишем функцию moveBy, которая перемещает фигуру на вектор (a,b,c). Это значит, что к x-координатам всех вершин нужно добавить a, к y-координатам добавить b и к z-координатам добавить c. При этом начало координат перемещается в точку (a,b,c).
Для такого перемещения нужно сделать цикл по массиву вершин vert и для каждой из них выполнить код:
vert[i].x += a; vert[i].y += b; vert[i].z += c;Для удобства три координаты вектора перемещения записываются в одну структуру, которая может быть построена с помощью функции pt3D (ее код находится в файле 3D.as).
Создайте новый документ типа ActionScript File, запишите в него код функции перемещения:
function moveBy ( fig, way ) { var i; for (i=0; i<fig.vert.length; i++) { with ( fig.vert[i] ) { x += way.x; y += way.y; z += way.z; } } }Сохраните этот файл под именем figures3D.as в папке PRACTICE\15. |
Далее создадим функцию, которая строит прямоугольный параллелепипед (блок, «кирпич»). Так можно, например, моделировать дома.
Эта функция должна принимать в параметрах размеры блока, массив из 6 элементов, задающий цвета граней, и начальную точку. В качестве точки привязки выберем левый нижний угол ближней к нам грани. Если начальное положение не задано, эта точка помещается в начало координат.
Добавьте в файл figures3D.fla код функции function brick ( dx, dy, dz, color, place ) { var a = 0, b = 0, c = 0; if ( arguments.length > 4 ) { a = place.x; b = place.y; c = place.z; } var rect = new Object(); rect.vert = [ pt3D(0, 0, dz), pt3D(0, -dy, dz), pt3D(dx, -dy, dz), pt3D(dx, 0, dz), pt3D(0, 0, 0), pt3D(0, -dy, 0), pt3D(dx, -dy, 0), pt3D(dx, 0, 0) ]; rect.faucet = [ [0, 1, 2, 3], [2, 3, 7, 6], [5, 6, 7, 4], [1, 5, 4, 0], [1, 2, 6, 5], [0, 3, 7, 4] ]; rect.color = color; moveBy ( rect, pt3D(a,b,c) ); return rect; } |
Массив точек и вершин строится так же, как и ранее, для перемещения используется только что написанная функция moveBy.
Обратите внимание, что в приведенном варианте функции brick массив color не копируется, а просто в
поле rect.color записывается ссылка на него (адрес). Это означает
следующее: если мы создаем два блока, используя один и
тот же массив color, и потом изменим этот массив, то изменятся
цвета обоих блоков. Чтобы этого не произошло, можно создавать
массив заново и копировать значения, переданные через параметр:
rect.color = new Array(); for ( i=0; i<color.length; i++) rect.color[i] = color[i]; |
В этой программе мы будем работать с несколькими объектами, которые могут перекрывать друг друга. Чтобы учитывать это, нужно собрать все грани вместе, в одном объекте, и применить единую Z-сортировку. Следующая функция добавляет к объекту-фигуре fig новую фигуру newFig того же типа, которая передается как второй параметр. Фактически при этом сливаются их массивы vert, faucet и color, в результате строится один объект, описывающий все грани обоих исходных фигур.
Для слияния массивов используется метод concat (concatenation соединение, сцепка). Например, при выполнении
кода
a = [1, 2, 3];
b = [4, 5];
c = a.concat ( b );
создается новый массив c с элементами [1,2,3,4,5].
|
Добавьте в файл figures3D.fla код функции function addFigure ( fig, newFig ) { var nPrev = fig.vert.length; var i, k, n; fig.vert = fig.vert.concat ( newFig.vert ); for(i=0; i<newFig.faucet.length; i++) { n = newFig.faucet[i].length; for(k=0; k<n; k++) newFig.faucet[i][k] += nPrev; } fig.faucet = fig.faucet.concat ( newFig.faucet ); fig.color = fig.color.concat ( newFig.color ); } |
Например, если первая фигура включала 5 вершин, номера вершин второй фигуры в общем списке будут начинаться с 5. Следовательно, перед слиянием с массиве faucet фигуры newFig нужно увеличить номера всех вершин на 5. Вот как это сделано в функции:
var nPrev = fig.vert.length; for(i=0; i<newFig.faucet.length; i++) { n = newFig.faucet[i].length; for(k=0; k<n; k++) newFig.faucet[i][k] += nPrev; }Следующая функция, showFigures, предназначена для рисования всех граней в нужном порядке.
Добавьте в файл figures3D.fla код функции function showFigures ( fig ) { var i, midScreen; ptScreen = perspective(fig.vert, focalLength); midScreen = midPointsZ ( fig ); midScreen.sortOn ( "z", Array.DESCENDING + Array.NUMERIC ); scene.clear(); for (i=0; i<midScreen.length; i++) drawFaucet ( fig, midScreen[i] ); } |
Добавьте в файл figures3D.fla код функции function midPointsZ ( fig ) { var i, j, k, nPoints, sumZ; var mid = new Array(); for(i=0; i<fig.faucet.length; i++) { sumZ = 0; nPoints = fig.faucet[i].length; for(j=0; j<nPoints; j++) { k = fig.faucet[i][j]; sumZ += ptScreen[k].z; ); } sumZ /= nPoints; mid[i] = {id:i, z:sumZ}; } return mid; } |
Новая версия функции drawFaucet также работает для любого числа вершин, они перебираются в цикле.
Добавьте в файл figures3D.fla код функции function drawFaucet ( fig, midPoint ) { if ( midPoint.z < -focalLength ) return; var id = midPoint.id; with ( scene ) { beginFill( fig.color[id], 100); var k0 = fig.faucet[id][0]; moveTo(ptScreen[k0].x, ptScreen[k0].y); for (var i=1; i<fig.faucet[id].length; i++) { var k = fig.faucet[id][i]; lineTo(ptScreen[k].x, ptScreen[k].y); } lineTo(ptScreen[k0].x, ptScreen[k0].y); endFill(); } }Сохраните окончательный вариант библиотеки функций. |
if ( midPoint.z < -focalLength ) return;Мы считаем, что наблюдатель находится в точке с координатами (0,0,-focalLength), поэтому все грани, оказавшиеся позади него (имеющие меньшую z-координату, чем -focalLength) не прорисовываются, поскольку происходит выход из функции по оператору return.
Создайте новый Flash-документ и сохраните его в папке PRACTICE\15 под именем view.fla. Нарисуйте прямоугольник, занимающий всю сцену, и залейте его градиентом от синего к черному, как на образце. |
Добавьте к кадру 1 код #include "3d.as" #include "figures3d.as" focalLength = 800; this.createEmptyMovieClip ( "scene", 1 ); scene._x = 275; scene._y = 200; |
Добавьте к кадру 1 код color = [ 0xFFFFFF, 0xFF00, 0xFFFF00, 0xFF, 0xFF9900, 0xFF0000 ]; figAll = brick ( 100, 100, 100, color, pt3D(100,100,0) ); addFigure ( figAll, brick ( 100, 200, 100, color, pt3D(-200,100,0) ) ); showFigures ( figAll ); |
Информация о вершинах и гранях обоих объектов записывается в структуру figAll, которая выводится на экран с помощью функции showFigures.
Запустите ролик и посмотрите на начальное положение фигур. Проверьте, верно ли рисуются грани у объекта слева. |
Стандартная сортировка по z-координате центра грани работает только тогда, когда наблюдатель находится прямо перед объектом. |
Здесь через F обозначен фокус, в программе это переменная focalLength.
Откройте файл figures3D.as и измените алгоритм вычисления z-координаты, по которой выполняется
сортировка:
sumZ += Math.sqrt( ptScreen[k].x*ptScreen[k].x + ptScreen[k].y*ptScreen[k].y + (ptScreen[k].z+focalLength)*(ptScreen[k].z+focalLength) );Проверьте результат. |
При движении вперед и назад объекты становятся ближе или дальше, значит, изменяется z-координата. Если двигаемся вверх или вниз (при нажатой клавише Shift), для всех точек изменяется y-координата. А при поворотах все точки сцены вращаются относительно наблюдателя, то есть относительно точки (0,0,-focallength). Все это легко реализовать с помощью функций moveBy (перемещение) и rotate3D (вращение).
Добавьте к коду кадра 1 обработчик события enterFrame для клипа scene:
Key.addListener ( scene ); scene.onEnterFrame = function() { var dx=0, dy=0, dz=0; var ax=0, ay=0, az=0; if ( Key.isDown(Key.LEFT) ) ay = 1; break; if ( Key.isDown(Key.RIGHT) ) ay = -1; break; if ( Key.isDown(Key.UP) ) if ( Key.isDown(Key.SHIFT) ) dy = 10; else dz = -10; if ( Key.isDown(Key.DOWN) ) if ( Key.isDown(Key.SHIFT) ) dy = -10; else dz = 10; moveBy ( figAll, pt3D(dx, dy, dz) ); rotate3D ( figAll.vert, pt3D(ax,ay,az), pt3D(0,0,-focalLength) ); showFigures ( figAll ); }Сохраните файл и проверьте работу ролика. |
В этом разделе мы будем использовать параллельную проекцию (без учета перспективных искажений, которые существенно осложняют дело).
В этом примере текстуры используются на каждой грани куба, его можно вращать в разные стороны мышкой и клавишами-стрелками.
Щелкнув по кнопке с изображением маленького кубика можно установить его в начальное положение, которое соответствует особой изометрической проекции, которая не искажает пропорции. Длины всех ребер куба здесь одинаковы, угол между ребрами равен 120°.
Для построения изометрии кубик нужно сначала повернуть на 45° относительно оси Y, а затем на 30° относительно оси X:
rotate3D ( vert, pt3D(0, 45, 0) ); rotate3D ( vert, pt3D(30, 0, 0) );
К сожалению Flash пока не имеет встроенных функций для работы с текстурами, поэтому придется писать свои. Один из способов наложения текстуры использует вложенный клип. Представьте себе клип clip, внутри которого находится внутренний клип с именем inner прямоугольник, повернутый на 45° против часовой стрелки. У главного клипа мы будем изменять свойства _yscale (масштаб по оси y) и _rotation (угол поворота), а у вложенного только масштабы по обеим осям. Важно что точка регистрации (точка (0,0)) у главного и вложенного клипов совпадают с точкой p0 на рисунке.
Попробуйте с помощью кнопок-стрелок изменять форму прямоугольника.
Важно заметить что:
Здесь через p0 обозначена точка регистрации клипов, а через pH и pW угловые точки, определяющие высоту и ширину параллелограмма. Тангенсы углов α1 и α2 могут быть найдены из формул
а сами углы с помощью функции Math.atan2. Здесь через Δx обозначена разность x-координат точек, а через Δy разность их y-координат.
Тогда внутренний угол параллелограмма, обозначенный как 2α, вычисляется как разность
2·α = 180° - α1 - α2Этот угол нужно получить масштабированием исходного прямоугольника (на рисунке справа) только по оси Y. Обозначим коэффициент масштаба через k, он равен масштабному коэффициенту, который нужно применить для главного клипа.
При y-масштабировании все вертикальные размеры умножаются на k, а горизонтальные сохраняются, поэтому имеем
Теперь найдем масштабные коэффициенты kx и ky для вложенного клипа. Обозначим через w' длину ребра, соединяющего точки p0 и pW, а через w0 нужную ширину внутреннего клипа-прямоугольника. Учитывая масштабирование главного клипа, имеем
Аналогичные выражения можно записать и для высоты:
Создайте новый файл типа ActionScript File, запишите в него код функции сдвига
function skewClip ( clip, p0, pW, pH, w, h ) { function dX ( p1, p2 ) { return p1.x - p2.x; } function dY ( p1, p2 ) { return p1.y - p2.y; } function dist ( p1, p2 ) { var dx = dX(p1,p2), dy = dY(p1,p2); return Math.sqrt(dx*dx+dy*dy); } var a, a1, a2, da, s, scale; clip._x = p0.x; clip._y = p0.y; clip._xscale = 100; clip._rotation = 0; a1 = Math.atan2(dX(pW,p0), dY(p0,pW)); a2 = Math.atan2(dX(pH,p0), dY(pH,p0)); a = (Math.PI - a1 - a2) / 2; scale = Math.tan(a); clip._yscale = 100*scale; da = Math.PI/2 - a - a1; clip._rotation -= da*180/Math.PI; s = dist(p0,pW); q = Math.sin(a)/scale*Math.sqrt(2); clip.inner._xscale = 100*s*q/w; s = dist(p0,pH); clip.inner._yscale = 100*s*q/h; }Сохраните файл в папке PRACTICE\15 под именем skew.as. |
Откройте файл texture.fla из папки PRACTICE\15 и добавьте к кадру 1 код
#include "3d.as" #include "skew.as" this.createEmptyMovieClip ( "scene", 1 ); scene._x = 275; scene._y = 200; |
Добавьте к кадру 1 код
fig = new Object();
fig.vert = [ pt3D(-50,50,0), pt3D(-50,-50,0),
pt3D(50,-50,0), pt3D(50,50,0) ];
fig.faucet = [ [0, 1, 2, 3] ];
fig.bitmap = [ 1 ];
|
Важно, что в массиве faucet первый номер вершины соответствует точке pH, второй точке p0, а третий точке pW (см. рисунок выше). Этот порядок будет использоваться при наложении текстуры.
Все рисунки записаны в библиотеку в виде клипов pic1, pic2, ..., pic6.
Присвойте клипам-рисункам pic1...pic6 такие же кодовые имена для использования в программе (правая кнопка мыши, пункт Linkage в контекстном меню). |
Добавьте к кадру 1 код scene.createEmptyMovieClip ( "faucet0", 1 ); scene.faucet0.attachMovie ( "pic"+fig.bitmap[0], "inner", 1); scene.faucet0.inner._rotation = -45; |
Добавьте к кадру 1 код w0 = 100; h0 = 100; rotate3D ( fig.vert, pt3D(0,45,45) ); showFigureBmp(fig); function showFigureBmp ( fig ) { var v = fig.faucet[0]; var pH = fig.vert[v[0]]; var p0 = fig.vert[v[1]]; var pW = fig.vert[v[2]]; skewClip ( scene.faucet0, p0, pW, pH, w0, h0 ); } |
Добавьте к кадру 1 код обработчика scene.onEnterFrame = function () { rotate3D ( fig.vert, pt3D(2,0,2) ); showFigureBmp(fig); }Сохраните файл и проверьте работу клипа |
Возникает вопрос: «Как наложить текстуру на фигуру другой формы (треугольник, трапецию и т.п.). Простейший способ использовать маску, то есть накладывать текстуру на прямоугольник, но ненужную часть отсекать с помощью маски.
Сохраните файл под именем cube_texture.fla и доработайте его так, чтобы на экране получился вращающийся куб, все 6 граней которого залиты текстурами. |
Рисование из программы | ... |
© 2007 К. Поляков