Это - первая часть из курса статей "3d обучение". Здесь
будут рассмотрены основные 3d преобразования и как их эффективней
реализовать в flash mx.
Содержание.
Введение.
1. Основы.
1.1 - координаты точки
1.2 - система координат
2. Преобразование поворота.
2.1 - поворот вокруг координатных осей относительно центра координат
2.2 - поворот вокруг координатных осей относительно произвольной точки
2.3 - поворот вокруг произвольной оси относительно центра координат
2.4 - поворот вокруг произвольной оси относительно произвольной точки
3. Преобразование масштабирования.
3.1 - масштабирование относительно центра координат
3.2 - масштабирование относительно произвольной точки
4. Преобразование сдвига.
4.1 - сдвиг начала координат
5. Проецирование.
5.1 - перспективное проецирование
6. Комбинирование преобразований.
6.1 - последовательное выполнение сдвигов
6.2 - последовательное выполнение поворотов
6.3 - последовательное выполнение масштабирований
7. Реализация в flash
Заключение.

1. Основы.
Предполагаемые обозначения в тексте
x, y, z - координаты точки
x', y', z' - новые координаты точки
xp, yp - проецированные координаты точки
dist - дистанция от точки наблюдения (позиции камеры) до центра координат
cnx, cny - координаты центра клипа
sin(A), cos(A), tan(A) - синус, косинус и тангенс угла A
rad = Math.PI/180 - коэффициет перевода градусов в радианы и обратно
1.1 Координаты точки.
Точка в 3d пространстве имеет три координаты (x,y,z) - это расстояние
от каждой из осей до самой точки. К примеру координаты (1, 2, 3) означают,
что точка расположена на расстоянии 1 от оси X, на расстоянии 2 от
оси Y и на расстоянии 3 от оси Z.
1.2 Система координат.
В нашем случае мы будем применять правую систему координат. Правая
система координат наиболее часто применяется в машинной графике.
определение: Система координат называется правой, если для совмещения
с положительной полуосью Y положительную полуось X требуется повернуть
на +90° при этом направление движения расположенного вдоль оси
Z и поворачивающегося против часовой стрелки правого винта и положительной
полуоси Z совпадают.
2. Преобразование поворота.
Преобразование поворота играет важнейшую роль в 3d движениях. Без
него невозможны плавные движения облёта, вращения и др. основополагающие
движения камеры.
2.1 Поворот вокруг координатных осей относительно центра координат.
Самым лёгким поворотом является поворот вокруг координатной оси.
Формулы поворота вокруг координатных осей на угол A
- вокруг оси X
x' = x;
y' = y*cos(A)-z*sin(A);
z' = y*sin(A)+z*cos(A);
- вокруг оси Y
x' = x*cos(A)+z*sin(A);
y' = y;
z' = -x*sin(A)+z*cos(A);
- вокруг оси Z
x' = x*cos(A)-y*sin(A);
y' = x*sin(A)+y*cos(A);
z' = z;
2.2 Поворот вокруг координатных осей относительно произвольной точки.
Смысл поворота вокруг произвольной точки:
- делаем сдвиг начала координат в нужную точку
- производим поворот
- возвращаем центр координат обратно на место
Формулы поворота относительно точки M(x0,y0,z0) на угол A
- вокруг оси X
x' = x;
y' = y0+(y-y0)*cos(A)+(z0-z)*sin(A);
z' = z0+(y-y0)*sin(A)+(z-z0)*cos(A);
- вокруг оси Y
x' = x0+(x-x0)*cos(A)+(z-z0)*sin(A);
y' = y;
z' = z0+(x0-x)*sin(A)+(z-z0)*cos(A);
- вокруг оси Z
x' = x0+(x-x0)*cos(A)+(y0-y)*sin(A);
y' = y0+(x-x0)*sin(A)+(y-y0)*cos(A);
z' = z;
2.3 Поворот вокруг произвольной оси относительно центра координат.
Вращение вокруг произвольной оси немного сложнее. Его нужно непременно
знать и понимать.
Формулы поворота на угол A вокруг произвольной оси (alpha, beta, gamma)
temp = 1.0-cos(A);
x' = x*(alpha*temp*alpha + cos(A)) + y*(beta*temp*alpha - sin(A)*gamma)
+ z*(gamma*temp*alpha + sin(A)*beta);
y' = x*(alpha*temp*beta + sin(A)*gamma) + y*(beta*temp*beta + cos(A))
+ z*(gamma*temp*beta - sin(A)*alpha);
z' = x*(alpha*temp*gamma - sin(A)*beta) + y*(beta*temp*gamma + sin(A)*alpha)
+ z*(gamma*temp*gamma + cos(A));
2.4 Поворот вокруг произвольной оси относительно произвольной точки.
Смысл поворота относительно произвольной точки вокруг произвольной
оси:
- делаем сдвиг начала координат в нужную точку
- производим поворот вокруг произвольной оси
- возвращаем центр координат обратно на место
Формулы поворота на угол A вокруг произвольной оси (alpha, beta, gamma)
относительно точки M(x0, y0, z0)
temp = 1.0-cos(A);
x'
= x0 + (x-x0)*(alpha*temp*alpha + cos(A)) + (y-y0)*(beta*temp*alpha
- sin(A)*gamma) + (z-z0)*(gamma*temp*alpha + sin(A)*beta);
y'
= y0 + (x-x0)*(alpha*temp*beta + sin(A)*gamma) + (y-y0)*(beta*temp*beta
+ cos(A)) + (z-z0)*(gamma*temp*beta - sin(A)*alpha);
z'
= z0 + (x-x0)*(alpha*temp*gamma - sin(A)*beta) + (y-y0)*(beta*temp*gamma
+ sin(A)*alpha) + (z-z0)*(gamma*temp*gamma + cos(A));
3. Преобразование
масштабирования.
Преобразование масштабирования это - изменение размера, изменение
масштаба.
3.1 Масштабирование относительно центра координат.
Наипростейшим масштабированием является масштабирование относительно
центра координат. Это достаточно часто используемое преобразование.
Sx, Sy, Sz - коэффициенты масштабирования по осям X, Y и Z
Формула масштабирования:
x' = x*Sx;
y' = y*Sy;
z' = z*Sz;
3.2 Масштабирование относительно произвольной точки.
Данное преобразование нужно в основном для увеличения отдельных деталей
фигуры. Допустим вам нужно промасштабировать один кубик. Делаем сдвиг
центра координат в центр кубика, масштабируем все точки куба, и возвращаем
центр координат на место. Вуаля - кубик увеличился в размерах не сдвигаясь
со своего места.
Смысл масштабирования относительно произвольной точки:
- делаем сдвиг начала координат в нужную точку
- производим масштабирование
- возвращаем центр координат обратно на место
Формула масштабирования относительно произвольной точки M(x0,y0,z0):
x' = x0+(x-x0)*Sx;
y' = y0+(y-y0)*Sy;
z' = z0+(z-z0)*Sz;
4. Преобразование сдвига.
Название говорит само за себя - изменение позиции точки.
4.1 Сдвиг начала координат.
incX, incY, incZ - величина сдвига по осям X, Y и Z;
Формула преобразования сдвига:
x' = x+incX;
y' = y+incY;
z' = z+incZ;
5. Проецирование.
Прошу не путать. Все вычисления производятся исключительно с нормальными
координатами. Проецирование производится в самом конце, и оно не запоминается
и не заменяет координат точки. Оно лишь нужно для визуально более
реального изображения (получения перспективы) и перехода от 3d координат
к 2d (монитор-то 2d, а не 3d)!
5.1 Перспективное проецирование.
Формула перспективного проецирования:
rx = cnx + x*dist/(dist+z);
ry = cny - y*dist/(dist+z);
6. Комбинирование преобразований.
Последовательное выполнение нескольких преобразований можно представить
в виде единого преобразования. Это затрачивает меньше ресурсов машины.
6.1 Последовательное выполнение сдвигов.
Сдвиг аддитивен, т.е. последовательное выполнение сдвигов на расстояние
(T1.x, T1.y, T1.z) и (T2.x, T2.y, T2.z) эквивалентно одному сдвигу
на расстояние (T1.x + T2.x, T1.y + T2.y, T1.z + T2.z).
6.2 Последовательное выполнение поворотов.
Можно показать, что два последовательных поворота аддитивны. Например
повернём точку на угол 45° и после этого повернём на угол -67°.
Это эквивалентно повороту на угол (45° + (-67°)) = -22°.
6.3 Последовательное выполнение масштабирований.
Первое масштабирование с коэффициентами (Sx1, Sy1, Sz1) второе с коэффициентами
(Sx2, Sy2, Sz2). Следует ожидать, что суммарное масштабирование будет
мультипликативным. А именно последовательное выполнение этих масштабирований
даст результат, эквивалентный масштабированию (Sx1*Sx2, Sy1*Sy2, Sz1*Sz2).
7. Реализация в flash.
А теперь реализуем всё это в flash mx.
Создадим специальную функцию vertex для координат точки. Для этой
функции мы сделаем прототипы обработки точки - вращения, сдвига и
др. 3d преобразований с точкой.
vertex = function (x, y, z) {
this.x = x;
this.y = y;
this.z = z;
};
Функция vertex будет запоминать координаты точки. Это - родитель остальных
прототипов.
прототип поворота вокруг оси X
vertex.prototype.rotateX = function(sinX, cosX)
{
var xp = this.x;
var yp = this.y*cosX-this.z*sinX;
var zp = this.y*sinX+this.z*cosX;
this.x = xp;
this.y = yp;
this.z = zp;
};
прототип поворота вокруг оси Y
vertex.prototype.rotateY = function(sinY, cosY)
{
var xp = this.x*cosY+this.z*sinY;
var yp = this.y;
var zp = -this.x*sinY+this.z*cosY;
this.x = xp;
this.y = yp;
this.z = zp;
};
прототип поворота вокруг оси Z
vertex.prototype.rotateZ = function(sinZ, cosZ)
{
var xp = this.x*cosZ-this.y*sinZ;
var yp = this.x*sinZ+this.y*cosZ;
var zp = this.z;
this.x = xp;
this.y = yp;
this.z = zp;
};
прототип поворота вокруг точки a по оси X
vertex.prototype.rotateX_around_point = function(sinX,
cosX, a) {
var xp = this.x;
var yp = a.y+(this.y-a.y)*cosX-(this.z-a.z)*sinX;
var zp = a.z+(this.y-a.y)*sinX+(this.z-a.z)*cosX;
this.x = xp;
this.y = yp;
this.z = zp;
};
прототип поворота вокруг точки a по оси Y
vertex.prototype.rotateY_around_point = function(sinY,
cosY, a) {
var xp = a.x+(this.x-a.x)*cosY+(this.z-a.z)*sinY;
var yp = this.y;
var zp = a.z-(this.x-a.x)*sinY+(this.z-a.z)*cosY;
this.x = xp;
this.y = yp;
this.z = zp;
};
прототип поворота вокруг точки a по оси Z
vertex.prototype.rotateZ_around_point = function(sinZ,
cosZ, a) {
var xp = a.x+(this.x-a.x)*cosZ-(this.y-a.y)*sinZ;
var yp = a.y+(this.x-a.x)*sinZ+(this.y-a.y)*cosZ;
var zp = this.z;
this.x = xp;
this.y = yp;
this.z = zp;
};
прототип поворота вокруг произвольной оси a относительно центра координат
vertex.prototype.rotate_axis = function(a, sin,
cos) {
var temp = 1.0-cos;
var t1 = a.x*temp;
var t2 = a.y*temp;
var t3 = a.z*temp;
var tsinx = sin*a.x;
var tsiny = sin*a.y;
var tsinz = sin*a.z;
var xp = this.x*(t1*a.x+cos)+this.y*(t2*a.x-tsinz)+this.z*(t3*a.x+tsiny);
var yp = this.x*(t1*a.y+tsinz)+this.y*(t2*a.y+cos)+this.z*(t3*a.y-tsinx);
var zp = this.x*(t1*a.z-tsiny)+this.y*(t2*a.z+tsinx)+this.z*(t3*a.z+cos);
this.x = xp;
this.y = yp;
this.z = zp;
};
прототип поворота вокруг произвольной оси a относительно точки b
vertex.prototype.rotate_axis_around_point = function(a,
sin, cos, b) {
var temp = 1.0-cos;
var t1 = a.x*temp;
var t2 = a.y*temp;
var t3 = a.z*temp;
var tsinx = sin*a.x;
var tsiny = sin*a.y;
var tsinz = sin*a.z;
var tbx = this.x-b.x;
var tby = this.y-b.y;
var tbz = this.z-b.z;
var xp = b.x+tbx*(t1*a.x+cos)+tby*(t2*a.x-tsinz)+tbz*(t3*a.x+tsiny);
var yp = b.y+tby*(t1*a.y+tsinz)+tby*(t2*a.y+cos)+tbz*(t3*a.y-tsinx);
var zp = b.z+tbz*(t1*a.z-tsiny)+tby*(t2*a.z+tsinx)+tbz*(t3*a.z+cos);
this.x = xp;
this.y = yp;
this.z = zp;
};
прототип масштабирования, относительно центра координат
vertex.prototype.scale = function(sx, sy, sz) {
this.x *= sX;
this.y *= sY;
this.z *= sZ;
};
прототип масштабирования относительно точки a
vertex.prototype.scale_point = function(sx, sy,
sz, a) {
this.x = a.x+(this.x-a.x)*sX;
this.y = a.y+(this.y-a.y)*sY;
this.z = a.z+(this.z-a.z)*sZ;
};
прототип масштабирования относительно точки a
vertex.prototype.scale_point = function(sx, sy,
sz, a) {
this.x = a.x+(this.x-a.x)*sX;
this.y = a.y+(this.y-a.y)*sY;
this.z = a.z+(this.z-a.z)*sZ;
};
прототип масштабирования по оси X относительно центра координат
vertex.prototype.scaleX = function(sx) {
this.x *= sx;
};
прототип масштабирования по оси Y относительно центра координат
vertex.prototype.scaleY = function(sy) {
this.y *= sy;
};
прототип масштабирования по оси Z относительно центра координат
vertex.prototype.scaleZ = function(sz) {
this.z *= sz;
};
прототип преобразования сдвига
vertex.prototype.transpose = function(incX, incY,
incZ) {
this.x += incX;
this.y += incY;
this.z += incZ;
};
прототип преобразования сдвига по оси X
vertex.prototype.transposeX = function(incX) {
this.x += incX;
};
прототип преобразования сдвига по оси Y
vertex.prototype.transposeY = function(incY) {
this.y += incY;
};
прототип преобразования сдвига по оси Z
vertex.prototype.transposeZ = function(incZ) {
this.z += incZ;
};
прототип преобразования перспективы
vertex.prototype.perspective = function() {
var perRatio = (this.z/dist+1);
this.rx = cnx+this.x/perRatio;
this.ry = cny-this.y/perRatio;
};
дополнительная функции для облегчения заливки грани куба
face = function (c, d, a) {
// размерность грани
this.d = d;
// чётность грани
this.c = c;
// массив точек грани
this.a = a;
};
прототип закраски грани
face.prototype.draw = function() {
_root.lineStyle(0, 0x000000, 100);
_root.beginFill(0x999999, 50);
_root.moveTo(v[this.a[0]].rx, v[this.a[0]].ry);
for (var i = 1; i<=(this.d); i++) {
_root.lineTo(v[this.a[i]].rx, v[this.a[i]].ry, v[this.a[i+1]].rx,
v[this.a[i+1]].ry);
}
_root.endFill();
};
прототип заливки грани
face.prototype.fill = function() {
this.draw();
};
функция, выполняемая один раз, после загрузке клипа
_root.onLoad = function() {
// задаём координаты центра клипа
cnx = 250;
cny = 250;
// задаём дистанцию от точки наблюдения до центра координат
dist = 600;
// задаём размеры куба
var sx = 75;
var sy = 75;
var sz = 75;
// задам коэффициент перевода градусов в радианы
rad = Math.PI/180;
// инициализируем вершины куба
v = new Array();
v[0] = new vertex(sx, -sy, sz);
v[1] = new vertex(sx, sy, sz);
v[2] = new vertex(sx, sy, -sz);
v[3] = new vertex(sx, -sy, -sz);
v[4] = new vertex(-sx, -sy, -sz);
v[5] = new vertex(-sx, sy, -sz);
v[6] = new vertex(-sx, sy, sz);
v[7] = new vertex(-sx, -sy, sz);
// инициализируем грани куба
f = new Array();
f[0] = new face(1, 3, [0, 1, 2, 3]);
f[1] = new face(0, 3, [2, 3, 4, 5]);
f[2] = new face(1, 3, [4, 5, 6, 7]);
f[3] = new face(0, 3, [0, 1, 6, 7]);
f[4] = new face(1, 3, [0, 3, 4, 7]);
f[5] = new face(0, 3, [1, 2, 5, 6]);
// инициализируем произвольные оси вращения
axisX = new vertex(1.0, 0.0, 0.0);
axisY = new vertex(0.0, 1.0, 0.0);
axisZ = new vertex(0.0, 0.0, 1.0);
// задаём колличество вершин
amount = v.length - 1;
};
//
####################################################
// -- © 2002-2003 Grigory Ryabov / Nuran.
// -- http://www.ShockOne.com
// -- EASY 3d ENGINE ORIGINAL
// ####################################################
/*
Инструкция
--------- Поворот
осей:
------------
- поворот вокруг оси X: клавиши J и L
- поворот вокруг оси Y: клавиши I и K
- поворот вокруг оси Z: клавиши U и O
Преобразование
сдвига:
---------------------
- по оси X: клавиши A и D
- по оси Y: клавиши W и S
- по оси Z: клавиши Q и E
Масштабирование
относительно центра координат:
--------------------------------------------
- по оси X: клавиши 1 и 2
- по оси Y: клавиши 3 и 4
- по оси Z: клавиши 5 и 6
- по всем осям: клавиши 9 и 0
Вращение
вокруг произвольной точки:
---------------------------------
- по оси X: клавиши UP и DOWN
- по оси Y: клавиши LEFT и RIGHT
*/
функция, выполняемая каждый раз, при смене кадра
_root.onEnterFrame = function() {
// цикл перебора всех вершин и задание им преобразований
for (var i = 0; i<=amount; i++) {
if (Key.isDown(65)) {
// Клавиша A
v[i].transpose(-10, 0, 0);
}
if (Key.isDown(68)) {
// Клавиша D
v[i].transpose(10, 0, 0);
}
if (Key.isDown(87)) {
// Клавиша W
v[i].transpose(0, 10, 0);
}
if (Key.isDown(83)) {
// Клавиша S
v[i].transpose(0, -10, 0);
}
if (Key.isDown(81)) {
// Клавиша Q
v[i].transpose(0, 0, 10);
}
if (Key.isDown(69)) {
// Клавиша E
v[i].transpose(0, 0, -10);
}
if (Key.isDown(48)) {
// Клавиша 0
v[i].scale(0.9, 0.9, 0.9);
}
if (Key.isDown(57)) {
// Клавиша 9
v[i].scale(1.1, 1.1, 1.1);
}
if (Key.isDown(49)) {
// Клавиша 1
v[i].scale(0.9, 1, 1);
}
if (Key.isDown(50)) {
// Клавиша 2
v[i].scale(1.1, 1, 1);
}
if (Key.isDown(51)) {
// Клавиша 3
v[i].scale(1, 0.9, 1);
}
if (Key.isDown(52)) {
// Клавиша 4
v[i].scale(1, 1.1, 1);
}
if (Key.isDown(53)) {
// Клавиша 5
v[i].scale(1, 1, 0.9);
}
if (Key.isDown(54)) {
// Клавиша 6
v[i].scale(1, 1, 1.1);
}
if (Key.isDown(73)) {
// Клавиша I
var xa = 10*rad;
var sinX = Math.sin(xa);
var cosX = Math.cos(xa);
v[i].rotateX(sinX, cosX);
}
if (Key.isDown(75)) {
// Клавиша K
var xa = -10*rad;
var sinX = Math.sin(xa);
var cosX = Math.cos(xa);
v[i].rotateX(sinX, cosX);
}
if (Key.isDown(74)) {
// Клавиша J
var ya = 10*rad;
var sinY = Math.sin(ya);
var cosY = Math.cos(ya);
v[i].rotateY(sinY, cosY);
}
if (Key.isDown(76)) {
// Клавиша L
var ya = -10*rad;
var sinY = Math.sin(ya);
var cosY = Math.cos(ya);
v[i].rotateY(sinY, cosY);
}
if (Key.isDown(85)) {
// Клавиша V
var za = 10*rad;
var sinZ = Math.sin(za);
var cosZ = Math.cos(za);
v[i].rotateZ(sinZ, cosZ);
}
if (Key.isDown(79)) {
// Клавиша 0
var za = -10*rad;
var sinZ = Math.sin(za);
var cosZ = Math.cos(za);
v[i].rotateZ(sinZ, cosZ);
}
if (Key.isDown(38)) {
// Клавиша UP
var ya = 10*rad;
var sinY = Math.sin(ya);
var cosY = Math.cos(ya);
v[i].rotateX_around_point(sinY, cosY, new vertex(0, 0, 200));
}
if (Key.isDown(40)) {
// Клавиша DOWN
var ya = -10*rad;
var sinY = Math.sin(ya);
var cosY = Math.cos(ya);
v[i].rotateX_around_point(sinY, cosY, new vertex(0, 0, 200));
}
if (Key.isDown(37)) {
// Клавиша LEFT
var xa = 10*rad;
var sinX = Math.sin(xa);
var cosX = Math.cos(xa);
v[i].rotateY_around_point(sinX, cosX, new vertex(0, 0, 200));
}
if (Key.isDown(39)) {
// Клавиша RIGHT
var xa = -10*rad;
var sinX = Math.sin(xa);
var cosX = Math.cos(xa);
v[i].rotateY_around_point(sinX, cosX, new vertex(0, 0, 200));
}
// выполняем преобразование перспективы
v[i].perspective();
}
_root.clear();
// заливаем правую грань
f[0].fill();
// заливаем переднюю грань
f[1].fill();
// заливаем левую грань
f[2].fill();
// заливаем заднюю грань
f[3].fill();
// заливаем нижнюю грань
f[4].fill();
// заливаем верхнюю грань
f[5].fill();
};
stop();
Заключение.
С помошью этих формул можно производить любые 3d преобразования.
Конечно, в матричных вычислениях это намного удобнее, и выглядело
бы это всё лучше, но и ресурчов оно бы занимало намного больше.
В следующих статьях будет также: 3d движок на матрицах 4x4 с движениями
произвольной камеры, 3d освещение, 3d сортировка, 3d удаление невидимых
граней, 3d тень и так далее.
Удачи!
Всегда Ваш Григорий Рябов / Nuran.
Сделано специально для flasher.ru.
Продолжение следует...
Перепечатка данного материала строго запрещена!
|