Глава 33. Изображение и управление трехмерными объектами в трехмерном пространстве
Листинг 33.1. Код выше и в теле метода Form1_Load.
//Начало координат:
private const double x_focus = 0;
private const double y_focus = 0;
private const double z_focus = 0;
//Сферические координаты глаза наблюдателя (точки E):
private float r_Eye;
private float phi_Eye;
private float theta_Eye;
//Переменные и матрица (как массив) MatrixProjection:
//(во всех массивах нулевые индексы не используем):
private const double pi = Math.PI;
private int Tetrahedron;
private int Cube;
private int Octahedron;
private int Dodecahedron;
private int Icosahedron_first;
private int Icosahedron_last;
private float[,] MatrixProjection = new float[5, 5];
//Для параллельного проецирования объекта на экран
//(parallel projection) задаем константу:
private const int ParallelProjection = 0;
//Для перспективного проецирования объекта на экран
//(perspective projection) задаем константу:
private const int PerspectiveProjection = 1;
private void Form1_Load(object sender, EventArgs e)
{
//Задаем координаты глаза наблюдателя, например:
r_Eye = 4;
phi_Eye = (float)(0.05 * pi);
theta_Eye = (float)(0.3 * pi);
//Вызываем метод для перспективного проецирования,
//когда type_of_projection = PerspectiveProjection
//(для параллельного проецирования вместо
//PerspectiveProjection пишем ParallelProjection):
Projection(ref MatrixProjection, PerspectiveProjection,
r_Eye, phi_Eye, theta_Eye,
(float)x_focus, (float)y_focus, (float)z_focus, 0, 1, 0);
//Рассчитываем параметры геометрического тела:
СalculateParameters();
//Связываем элемент PictureBox1 с классом Bitmap:
pictureBox1.Image = new Bitmap(pictureBox1.Width,
pictureBox1.Height);
//Проектируем и в PictureBox рисуем выбранное нами тело:
Designing((Bitmap)pictureBox1.Image);
}
Чтобы мы могли управлять (например, вращать) объектами при помощи нажатия клавиш, желательно в панели Properties на вкладке Events выбрать событие ProcessCmdKey. Если в версии VS, которая имеется у читателя, отсутствует событие ProcessCmdKey или имя этого события скрыто, то необходимо полностью записать нижеследующий метод вместе с шаблоном (или скопировать весь метод из прилагаемого к книге диска).
Листинг 33.2. Метод ProcessCmdKey.
protected override bool ProcessCmdKey(
ref System.Windows.Forms.Message msg,
System.Windows.Forms.Keys keyData)
{
//Задаем угол поворота фигуры после нажатия клавиши:
const float delta_theta = (float)pi / 20; ;
//Рассчитываем новые координаты глаза наблюдателя:
if (keyData == System.Windows.Forms.Keys.Left)
theta_Eye = theta_Eye - delta_theta;
if (keyData == System.Windows.Forms.Keys.Right)
theta_Eye = theta_Eye + delta_theta;
if (keyData == System.Windows.Forms.Keys.Up)
phi_Eye = phi_Eye - delta_theta;
if (keyData == System.Windows.Forms.Keys.Down)
phi_Eye = phi_Eye + delta_theta;
//Проектируем выбранное нами геометрическое тело:
Projection(ref MatrixProjection, PerspectiveProjection,
r_Eye, phi_Eye, theta_Eye,
(float)x_focus, (float)y_focus, (float)z_focus,
0, 1, 0);
Designing((Bitmap)pictureBox1.Image);
//В элементе PictureBox перерисовываем объект:
pictureBox1.Refresh();
return true;
}
Ниже этого кода записываем следующие все методы.
Листинг 33.3. Методы для решения поставленной задачи.
//Проектируем и при помощи процедуры DrawSolid
//рисуем выбранное флажком CheckBox геом-е тело:
private void Designing(Bitmap bmp)
{
//Создаем объект g класса Graphics:
Graphics g;
//Связываем объект g с изображением bmp:
g = Graphics.FromImage(bmp);
//Задаем белый цвет типа Window
//для элемента управления PictureBox1:
g.Clear(SystemColors.Window);
//Высвобождаем ресурсы от графического объекта g:
g.Dispose();
//Преобразуем точки:
TransformAllDataFull(ref MatrixProjection);
//Проектируем и рисуем выбранное на CheckBox тело:
if (checkBox1.CheckState ==
System.Windows.Forms.CheckState.Checked)
{
DrawSolid(
bmp, Tetrahedron, Cube - 1,
System.Drawing.Color.Red, false);
}
if (checkBox2.CheckState ==
System.Windows.Forms.CheckState.Checked)
{
DrawSolid(bmp, Cube, Octahedron - 1,
System.Drawing.Color.Black, false);
}
if (checkBox3.CheckState ==
System.Windows.Forms.CheckState.Checked)
{
DrawSolid(bmp, Octahedron, Dodecahedron - 1,
System.Drawing.Color.Green, false);
}
if (checkBox4.CheckState ==
System.Windows.Forms.CheckState.Checked)
{
DrawSolid(bmp, Dodecahedron, Icosahedron_first - 1,
System.Drawing.Color.Blue, false);
}
if (checkBox5.CheckState ==
System.Windows.Forms.CheckState.Checked)
{
DrawSolid(bmp, Icosahedron_first, Icosahedron_last,
System.Drawing.Color.Orange, false);
}
if (checkBox6.CheckState ==
System.Windows.Forms.CheckState.Checked)
{
DrawSolid(bmp, 1, Tetrahedron - 1,
System.Drawing.Color.Salmon, false);
}
}
//Рассчитываем параметры геометрических тел и осей:
private void СalculateParameters()
{
float theta1; float theta2;
float s1; float s2; float c1; float c2;
float S; float R; float H; float A;
float B; float C; float D; float X;
float Y; float y2; float M; float N;
//Оси координат:
DesigningLine(0, 0, 0, 0.5f, 0, 0); //Ось x.
DesigningLine(0, 0, 0, 0, 0.5f, 0); //Ось y.
DesigningLine(0, 0, 0, 0, 0, 0.5f); //Ось z.
//Тетраэдр (Tetrahedron):
Tetrahedron = NumLines + 1;
S = (float)Math.Sqrt(6);
A = (float)(S / Math.Sqrt(3)); B = -A / 2;
C = (float)(A * Math.Sqrt(2) - 1); D = S / 2;
DesigningLine(0, C, 0, A, -1, 0);
DesigningLine(0, C, 0, B, -1, D);
DesigningLine(0, C, 0, B, -1, -D);
DesigningLine(B, -1, -D, B, -1, D);
DesigningLine(B, -1, D, A, -1, 0);
DesigningLine(A, -1, 0, B, -1, -D);
//Куб (Cube):
Cube = NumLines + 1;
DesigningLine(-1, -1, -1, -1, 1, -1);
DesigningLine(-1, 1, -1, 1, 1, -1);
DesigningLine(1, 1, -1, 1, -1, -1);
DesigningLine(1, -1, -1, -1, -1, -1);
DesigningLine(-1, -1, 1, -1, 1, 1);
DesigningLine(-1, 1, 1, 1, 1, 1);
DesigningLine(1, 1, 1, 1, -1, 1);
DesigningLine(1, -1, 1, -1, -1, 1);
DesigningLine(-1, -1, -1, -1, -1, 1);
DesigningLine(-1, 1, -1, -1, 1, 1);
DesigningLine(1, 1, -1, 1, 1, 1);
DesigningLine(1, -1, -1, 1, -1, 1);
//Октаэдр (Octahedron):
Octahedron = NumLines + 1;
DesigningLine(0, 1, 0, 1, 0, 0);
DesigningLine(0, 1, 0, -1, 0, 0);
DesigningLine(0, 1, 0, 0, 0, 1);
DesigningLine(0, 1, 0, 0, 0, -1);
DesigningLine(0, -1, 0, 1, 0, 0);
DesigningLine(0, -1, 0, -1, 0, 0);
DesigningLine(0, -1, 0, 0, 0, 1);
DesigningLine(0, -1, 0, 0, 0, -1);
DesigningLine(0, 0, 1, 1, 0, 0);
DesigningLine(0, 0, 1, -1, 0, 0);
DesigningLine(0, 0, -1, 1, 0, 0);
DesigningLine(0, 0, -1, -1, 0, 0);
//ДОдекаэдр (Dodecahedron):
Dodecahedron = NumLines + 1;
theta1 = (float)(pi * 0.4); theta2 = (float)(pi * 0.8);
s1 = (float)Math.Sin(theta1);
c1 = (float)Math.Cos(theta1);
s2 = (float)Math.Sin(theta2);
c2 = (float)Math.Cos(theta2);
M = 1 - (2 - 2 * c1 - 4 * s1 * s1) / (2 * c1 - 2);
N = (float)Math.Sqrt((2 - 2 * c1) - M * M) *
(1 + (1 - c2) / (c1 - c2)); R = 2 / N;
S = (float)(R * Math.Sqrt(2 - 2 * c1));
A = R * s1; B = R * s2; C = R * c1; D = R * c2;
H = R * (c1 - s1);
X = (R * R * (2 - 2 * c1) - 4 * A * A) /
(2 * C - 2 * R);
Y = (float)Math.Sqrt(S * S - (R - X) * (R - X));
y2 = Y * (1 - c2) / (c1 - c2);
DesigningLine(R, 1, 0, C, 1, A);
DesigningLine(C, 1, A, D, 1, B);
DesigningLine(D, 1, B, D, 1, -B);
DesigningLine(D, 1, -B, C, 1, -A);
DesigningLine(C, 1, -A, R, 1, 0);
DesigningLine(R, 1, 0, X, 1 - Y, 0);
DesigningLine(C, 1, A, X * c1, 1 - Y, X * s1);
DesigningLine(C, 1, -A, X * c1, 1 - Y, -X * s1);
DesigningLine(D, 1, B, X * c2, 1 - Y, X * s2);
DesigningLine(D, 1, -B, X * c2, 1 - Y, -X * s2);
DesigningLine(X, 1 - Y, 0, -X * c2, 1 - y2, -X * s2);
DesigningLine(X, 1 - Y, 0, -X * c2, 1 - y2, X * s2);
DesigningLine(X * c1, 1 - Y, X * s1,
-X * c2, 1 - y2, X * s2);
DesigningLine(X * c1, 1 - Y, X * s1,
-X * c1, 1 - y2, X * s1);
DesigningLine(X * c2, 1 - Y, X * s2,
-X * c1, 1 - y2, X * s1);
DesigningLine(X * c2, 1 - Y, X * s2, -X, 1 - y2, 0);
DesigningLine(X * c2, 1 - Y, -X * s2, -X, 1 - y2, 0);
DesigningLine(X * c2, 1 - Y, -X * s2,
-X * c1, 1 - y2, -X * s1);
DesigningLine(X * c1, 1 - Y, -X * s1,
-X * c1, 1 - y2, -X * s1);
DesigningLine(X * c1, 1 - Y, -X * s1,
-X * c2, 1 - y2, -X * s2);
DesigningLine(-R, -1, 0, -X, 1 - y2, 0);
DesigningLine(-C, -1, A, -X * c1, 1 - y2, X * s1);
DesigningLine(-D, -1, B, -X * c2, 1 - y2, X * s2);
DesigningLine(-D, -1, -B, -X * c2, 1 - y2, -X * s2);
DesigningLine(-C, -1, -A, -X * c1, 1 - y2, -X * s1);
DesigningLine(-R, -1, 0, -C, -1, A);
DesigningLine(-C, -1, A, -D, -1, B);
DesigningLine(-D, -1, B, -D, -1, -B);
DesigningLine(-D, -1, -B, -C, -1, -A);
DesigningLine(-C, -1, -A, -R, -1, 0);
//Икосаэдр (Icosahedron):
Icosahedron_first = NumLines + 1;
R = (float)(2f / (2f * Math.Sqrt(1 - 2f * c1) +
Math.Sqrt(3f / 4f * (2f - 2f * c1) -
2f * c2 - c2 * c2 - 1f)));
S = R * (float)Math.Sqrt(2 - 2 * c1);
H = 1 - (float)Math.Sqrt(S * S - R * R);
A = R * s1; B = R * s2; C = R * c1; D = R * c2;
DesigningLine(R, H, 0, C, H, A);
DesigningLine(C, H, A, D, H, B);
DesigningLine(D, H, B, D, H, -B);
DesigningLine(D, H, -B, C, H, -A);
DesigningLine(C, H, -A, R, H, 0);
DesigningLine(R, H, 0, 0, 1, 0);
DesigningLine(C, H, A, 0, 1, 0);
DesigningLine(D, H, B, 0, 1, 0);
DesigningLine(D, H, -B, 0, 1, 0);
DesigningLine(C, H, -A, 0, 1, 0);
DesigningLine(-R, -H, 0, -C, -H, A);
DesigningLine(-C, -H, A, -D, -H, B);
DesigningLine(-D, -H, B, -D, -H, -B);
DesigningLine(-D, -H, -B, -C, -H, -A);
DesigningLine(-C, -H, -A, -R, -H, 0);
DesigningLine(-R, -H, 0, 0, -1, 0);
DesigningLine(-C, -H, A, 0, -1, 0);
DesigningLine(-D, -H, B, 0, -1, 0);
DesigningLine(-D, -H, -B, 0, -1, 0);
DesigningLine(-C, -H, -A, 0, -1, 0);
DesigningLine(R, H, 0, -D, -H, B);
DesigningLine(R, H, 0, -D, -H, -B);
DesigningLine(C, H, A, -D, -H, B);
DesigningLine(C, H, A, -C, -H, A);
DesigningLine(D, H, B, -C, -H, A);
DesigningLine(D, H, B, -R, -H, 0);
DesigningLine(D, H, -B, -R, -H, 0);
DesigningLine(D, H, -B, -C, -H, -A);
DesigningLine(C, H, -A, -C, -H, -A);
DesigningLine(C, H, -A, -D, -H, -B);
Icosahedron_last = NumLines;
}
//Объявляем структуру Line и массивы этой структуры:
public struct Line
{
//Объявляем массивы для соединения точек (points):
public float[] fr_points;
public float[] to_points;
//Массивы для соединения преобразованных точек:
//(transformed (tr) points):
public float[] fr_tr_points;
public float[] to_tr_points;
//Создаем и инициализируем массивы, т.е.
//всем пяти элементам каждого массива присваиваем 0:
public void Initialize()
{
fr_points = new float[5];
to_points = new float[5];
fr_tr_points = new float[5];
to_tr_points = new float[5];
}
}
//Объявляем массив Lines структуры Line, оператором new
//создаем массив из 100 элементов и инициализируем его,
//т.е всем элементам этого массива присваиваем значение null:
public Line[] Lines = new Line[100];
//Объявляем и инициализируем переменную для индекса массива:
public int NumLines = 0;
//Проектируем линию между точками (x1,y1,z1),(x2,y2,z2):
public void DesigningLine(float x1, float y1, float z1,
float x2, float y2, float z2)
{
NumLines = NumLines + 1;
//Инициализируем и рассчитываем массив:
Lines[NumLines].Initialize();
Lines[NumLines].fr_points[1] = x1;
Lines[NumLines].fr_points[2] = y1;
Lines[NumLines].fr_points[3] = z1;
Lines[NumLines].fr_points[4] = 1;
Lines[NumLines].to_points[1] = x2;
Lines[NumLines].to_points[2] = y2;
Lines[NumLines].to_points[3] = z2;
Lines[NumLines].to_points[4] = 1;
}
//Применяем матрицу переноса (translation matrix)
//ко всем линиям, используя MatrixApplyFull.
//Преобразование не имеет 0, 0, 0, 1 в последнем столбце:
public void TransformAllDataFull(ref float[,] M)
{
TransformDataFull(ref M, 1, NumLines);
}
//Применяем матрицу переноса (translation matrix)
//ко всем выделенным линиям, используя MatrixApplyFull.
//Преобразование не имеет 0, 0, 0, 1 в последнем столбце:
public void TransformDataFull(ref float[,] M,
int line1, int line2)
{
for (int i = line1; i <= line2; i++)
{
MatrixApplyFull(ref Lines[i].fr_points, ref M,
ref Lines[i].fr_tr_points);
MatrixApplyFull(ref Lines[i].to_points, ref M,
ref Lines[i].to_tr_points);
}
}
//Рисуем выделенные преобразованные линии:
public void DrawSolid(Bitmap bmp,
int first_line, int last_line, Color color, bool clear)
{
float x1, y1, x2, y2;
Graphics g; Pen pen;
//Задаем толщину линии рисования, например, 2
//(цвет линии мы задали в процедуре Designing):
pen = new Pen(color, 2);
//Связываем объект g с изображением bmp:
g = Graphics.FromImage(bmp);
if (clear) g.Clear(System.Drawing.Color.Black);
//Рисуем линии:
for (int i = first_line; i <= last_line; i++)
{
x1 = Lines[i].fr_tr_points[1];
y1 = Lines[i].fr_tr_points[2];
x2 = Lines[i].to_tr_points[1];
y2 = Lines[i].to_tr_points[2];
//Нормализуем и рисуем многогранник:
g.DrawLine(pen,
(x1 * bmp.Width / 4) + bmp.Width / 2.0F,
bmp.Height / 2.0F - (y1 * bmp.Height / 4),
(x2 * bmp.Width / 4) + bmp.Width / 2.0F,
bmp.Height / 2.0F - (y2 * bmp.Height / 4));
}
//Высвобождаем ресурсы от объектов g и pen:
g.Dispose(); pen.Dispose();
}
//Строим единичную матрицу:
public void MatrixIdentity(ref float[,] M)
{
for (int i = 1; i <= 4; i++)
{
for (int j = 1; j <= 4; j++)
{
if (i == j) M[i, j] = 1;
else M[i, j] = 0;
}
}
}
//Строим матрицу преобразования (3-D transformation matrix)
//для перспективной проекции вдоль оси z на плоскость x,y
//с центром объекта (фокусом) в начале координат
//и c центром проецирования на расстоянии (0, 0, Distance):
public void MatrixPerspectiveXZ(ref float[,] M,
float Distance)
{
MatrixIdentity(ref M);
if (Distance != 0) M[3, 4] = -1 / Distance;
}
//Строим матрицу преобразования (3-D transformation matrix)
//для проецирования с координатами:
//центр проецирования (cx, cy, cz), фокус (fx, fy, fx),
//вектор от объекта до экрана UP <ux, yx, uz>,
//тип проецирования (type_of_projection):
//PerspectiveProjection или ParallelProjection:
public void MatrixTransformation(ref float[,] M,
int type_of_projection,
float Cx, float Cy, float Cz,
float Fx, float Fy, float Fz,
float ux, float uy, float uz)
{
float[,] M1 = new float[5, 5];
float[,] M2 = new float[5, 5];
float[,] M3 = new float[5, 5];
float[,] M4 = new float[5, 5];
float[,] M5 = new float[5, 5];
float[,] M12 = new float[5, 5];
float[,] M34 = new float[5, 5];
float[,] M1234 = new float[5, 5];
float sin1 = 0, cos1 = 0; float sin2 = 0, cos2 = 0;
float sin3, cos3; float A, B, C; float d1, d2, d3;
float[] up1 = new float[5]; float[] up2 = new float[5];
//Переносим фокус (центр объекта) в начало координат:
MatrixTranslate(ref M1, -Fx, -Fy, -Fz);
A = Cx - Fx; B = Cy - Fy; C = Cz - Fz;
d1 = (float)Math.Sqrt(A * A + C * C);
if (d1 != 0)
{
sin1 = -A / d1; cos1 = C / d1;
}
d2 = (float)Math.Sqrt(A * A + B * B + C * C);
if (d2 != 0)
{
sin2 = B / d2; cos2 = d1 / d2;
}
//Вращаем объект вокруг оси y, чтобы разместить
//центр проекции в y-z плоскости:
MatrixIdentity(ref M2);
//Если d1 = 0, тогда центр проекции
//уже находится на оси y и в y-z плоскости:
if (d1 != 0)
{
M2[1, 1] = cos1; M2[1, 3] = -sin1;
M2[3, 1] = sin1; M2[3, 3] = cos1;
}
//Вращаем вокруг оси x,
//чтобы разместить центр проекции на оси z:
MatrixIdentity(ref M3);
//Если d2 = 0, то центр проекции
//находится в начале координат.
//Это делает проекцию невозможной:
if (d2 != 0)
{
M3[2, 2] = cos2; M3[2, 3] = sin2;
M3[3, 2] = -sin2; M3[3, 3] = cos2;
}
//Вращаем вектор UP:
up1[1] = ux; up1[2] = uy; up1[3] = uz;
up1[4] = 1;
MatrixApply(ref up1, ref M2, ref up2);
MatrixApply(ref up2, ref M3, ref up1);
//Вращаем вокруг оси z, чтобы разместить
//вектор UP в y-z плоскости:
d3 = (float)Math.Sqrt(up1[1] * up1[1] +
up1[2] * up1[2]);
MatrixIdentity(ref M4);
//Если d3 = 0, то вектор UP равен нулю:
if (d3 != 0)
{
sin3 = up1[1] / d3; cos3 = up1[2] / d3;
M4[1, 1] = cos3; M4[1, 2] = sin3;
M4[2, 1] = -sin3; M4[2, 2] = cos3;
}
//Проецируем:
if (type_of_projection == PerspectiveProjection)
MatrixPerspectiveXZ(ref M5, d2);
else
MatrixIdentity(ref M5);
if (d2 != 0)
MatrixPerspectiveXZ(ref M5, d2);
else
MatrixIdentity(ref M5);
//Комбинируем преобразования:
m3MatMultiply(ref M12, ref M1, ref M2);
m3MatMultiply(ref M34, ref M3, ref M4);
m3MatMultiply(ref M1234, ref M12, ref M34);
if (type_of_projection == PerspectiveProjection)
m3MatMultiplyFull(ref M, ref M1234, ref M5);
else
m3MatMultiply(ref M, ref M1234, ref M5);
}
//Строим матрицу преобразования (3-D transformation matrix)
//для перспективного проецирования (perspective projection):
//центр проецирования (r, phi, theta),
//фокус (fx, fy, fx),
//вектор от объекта до экрана UP <ux, yx, uz>,
//тип проецирования (type_of_projection):
//PerspectiveProjection:
public void Projection(ref float[,] M,
int type_of_projection, float R,
float phi, float theta,
float Fx, float Fy, float Fz,
float ux, float uy, float uz)
{
float Cx, Cy, Cz, r2;
//Переходим к прямоугольным координатам:
Cy = R * (float)Math.Sin(phi);
r2 = R * (float)Math.Cos(phi);
Cx = r2 * (float)Math.Cos(theta);
Cz = r2 * (float)Math.Sin(theta);
MatrixTransformation(ref M, type_of_projection,
Cx, Cy, Cz, Fx, Fy, Fz, ux, uy, uz); //ref M
}
//Строим матрицу преобразования, чтобы получить
//отражение напротив плоскости, проходящей
//через (p1, p2, p3) с вектором нормали <n1, n2, n3>:
public void m3Reflect(ref float[,] M,
float p1, float p2, float p3,
float n1, float n2, float n3)
{
float[,] T = new float[5, 5]; //Перенос.
float[,] R1 = new float[5, 5]; //Вращение 1.
float[,] r2 = new float[5, 5]; //Вращение 2.
float[,] S = new float[5, 5]; //Отражение.
float[,] R2i = new float[5, 5]; //Не вращать 2.
float[,] R1i = new float[5, 5]; //Не вращать 1.
float[,] Ti = new float[5, 5]; //Не переносить.
float D, L;
float[,] M12 = new float[5, 5];
float[,] M34 = new float[5, 5];
float[,] M1234 = new float[5, 5];
float[,] M56 = new float[5, 5];
float[,] M567 = new float[5, 5];
//Переносим плоскость к началу координат:
MatrixTranslate(ref T, -p1, -p2, -p3);
MatrixTranslate(ref Ti, p1, p2, p3);
//Вращаем вокруг оси z,
//пока нормаль не будет в y-z плоскости:
MatrixIdentity(ref R1);
D = (float)Math.Sqrt(n1 * n1 + n2 * n2);
R1[1, 1] = n2 / D; R1[1, 2] = n1 / D;
R1[2, 1] = -R1[1, 2]; R1[2, 2] = R1[1, 1];
MatrixIdentity(ref R1i);
R1i[1, 1] = R1[1, 1]; R1i[1, 2] = -R1[1, 2];
R1i[2, 1] = -R1[2, 1]; R1i[2, 2] = R1[2, 2];
//Вращаем вокруг оси x, когда нормаль будет по оси y:
MatrixIdentity(ref r2);
L = (float)Math.Sqrt(n1 * n1 + n2 * n2 + n3 * n3);
r2[2, 2] = D / L; r2[2, 3] = -n3 / L;
r2[3, 2] = -r2[2, 3]; r2[3, 3] = r2[2, 2];
MatrixIdentity(ref R2i);
R2i[2, 2] = r2[2, 2]; R2i[2, 3] = -r2[2, 3];
R2i[3, 2] = -r2[3, 2]; R2i[3, 3] = r2[3, 3];
//Рисуем отражение объекта перпендикулярно x-z плоскости:
MatrixIdentity(ref S); S[2, 2] = -1;
//Комбинируем матрицы:
m3MatMultiply(ref M12, ref T, ref R1);
m3MatMultiply(ref M34, ref r2, ref S);
m3MatMultiply(ref M1234, ref M12, ref M34);
m3MatMultiply(ref M56, ref R2i, ref R1i);
m3MatMultiply(ref M567, ref M56, ref Ti);
m3MatMultiply(ref M, ref M1234, ref M567);
}
//Строим матрицу преобразования для поворота на угол theta
//вокруг линии, проходящей через (p1, p2, p3)
//в направлении <d1, d2, d3>.
//Угол theta откладывается против часовой стрелки,
//если мы смотрим вниз в направлении,
//противоположном направлению линии:
public void m3LineRotate(ref float[,] M,
float p1, float p2, float p3,
float d1, float d2, float d3, float theta)
{
float[,] T = new float[5, 5]; //Перенос.
float[,] R1 = new float[5, 5]; //Вращение 1.
float[,] r2 = new float[5, 5]; //Вращение 2.
float[,] Rot3 = new float[5, 5]; //Вращение.
float[,] R2i = new float[5, 5]; //Стоп вращению 2.
float[,] R1i = new float[5, 5]; //Стоп вращению 1.
float[,] Ti = new float[5, 5]; //Стоп переносу.
float D, L;
float[,] M12 = new float[5, 5];
float[,] M34 = new float[5, 5];
float[,] M1234 = new float[5, 5];
float[,] M56 = new float[5, 5];
float[,] M567 = new float[5, 5];
//Переносим плоскость к началу координат:
MatrixTranslate(ref T, -p1, -p2, -p3);
MatrixTranslate(ref Ti, p1, p2, p3);
//Вращаем вокруг оси z,
//пока линия не окажется в y-z плоскости:
MatrixIdentity(ref R1);
D = (float)Math.Sqrt(d1 * d1 + d2 * d2);
R1[1, 1] = d2 / D; R1[1, 2] = d1 / D;
R1[2, 1] = -R1[1, 2]; R1[2, 2] = R1[1, 1];
MatrixIdentity(ref R1i);
R1i[1, 1] = R1[1, 1]; R1i[1, 2] = -R1[1, 2];
R1i[2, 1] = -R1[2, 1]; R1i[2, 2] = R1[2, 2];
//Вращаем вокруг оси x, когда линия будет по оси y:
MatrixIdentity(ref r2);
L = (float)Math.Sqrt(d1 * d1 + d2 * d2 + d3 * d3);
r2[2, 2] = D / L; r2[2, 3] = -d3 / L;
r2[3, 2] = -r2[2, 3]; r2[3, 3] = r2[2, 2];
MatrixIdentity(ref R2i);
R2i[2, 2] = r2[2, 2]; R2i[2, 3] = -r2[2, 3];
R2i[3, 2] = -r2[3, 2]; R2i[3, 3] = r2[3, 3];
//Вращаем вокруг линии (оси y):
MatrixYRotate(ref Rot3, theta);
//Комбинируем матрицы:
m3MatMultiply(ref M12, ref T, ref R1);
m3MatMultiply(ref M34, ref r2, ref Rot3);
m3MatMultiply(ref M1234, ref M12, ref M34);
m3MatMultiply(ref M56, ref R2i, ref R1i);
m3MatMultiply(ref M567, ref M56, ref Ti);
m3MatMultiply(ref M, ref M1234, ref M567);
}
//Строим матрицу преобразования (3-D transformation matrix)
//для переноса на Tx, Ty, Tz:
public void MatrixTranslate(ref float[,] M,
float Tx, float Ty, float Tz)
{
MatrixIdentity(ref M);
M[4, 1] = Tx; M[4, 2] = Ty; M[4, 3] = Tz;
}
//Строим матрицу преобразования (3-D transformation matrix)
//для поворота вокруг оси y (угол - в радианах):
public void MatrixYRotate(ref float[,] M, float theta)
{
MatrixIdentity(ref M);
M[1, 1] = (float)Math.Cos(theta);
M[3, 3] = M[1, 1];
M[3, 1] = (float)Math.Sin(theta);
M[1, 3] = -M[3, 1];
}
//Применяем матрицу преобразования к точке,
//где матрица не может иметь 0, 0, 0, 1
//в последнем столбце. Нормализуем только
//x и y компоненты результата, чтобы сохранить z информацию:
public void MatrixApplyFull(ref float[] V, ref float[,] M, ref float[] Result)
{
int i, j; float value = 0;
Result = new float[5] { 0, 0, 0, 0, 0 };
for (i = 1; i <= 4; i++)
{
value = 0;
for (j = 1; j <= 4; j++)
{
value = value + V[j] * M[j, i];
}
Result[i] = value;
}
//Повторно нормализуем точку (value = Result[4]):
if (value != 0)
{
Result[1] = Result[1] / value;
Result[2] = Result[2] / value;
}
else
{
//Не преобразовываем z - составляющую.
//Если значение z больше, чем от центра проекции,
//эта точка будет удалена:
Result[3] = Single.MaxValue;
}
Result[4] = 1;
}
//Применяем матрицу преобразования к точке:
public void MatrixApply(ref float[] V,
ref float[,] M, ref float[] Result)
{
Result[1] = V[1] * M[1, 1] + V[2] * M[2, 1] +
V[3] * M[3, 1] + M[4, 1];
Result[2] = V[1] * M[1, 2] + V[2] * M[2, 2] +
V[3] * M[3, 2] + M[4, 2];
Result[3] = V[1] * M[1, 3] + V[2] * M[2, 3] +
V[3] * M[3, 3] + M[4, 3];
Result[4] = 1;
}
//Умножаем две матрицы. Матрицы
//не могут содержать 0, 0, 0, 1 в последних столбцах:
public void m3MatMultiplyFull(ref float[,] Result,
ref float[,] A, ref float[,] B)
{
int i, j, k; float value; Result = new float[5, 5];
for (i = 1; i <= 4; i++)
{
for (j = 1; j <= 4; j++)
{
value = 0;
for (k = 1; k <= 4; k++)
value = value + A[i, k] * B[k, j];
Result[i, j] = value;
}
}
}
//Умножаем две матрицы:
public void m3MatMultiply(ref float[,] Result,
ref float[,] A, ref float[,] B)
{
Result[1, 1] = A[1, 1] * B[1, 1] + A[1, 2] * B[2, 1]
+ A[1, 3] * B[3, 1];
Result[1, 2] = A[1, 1] * B[1, 2] + A[1, 2] * B[2, 2]
+ A[1, 3] * B[3, 2];
Result[1, 3] = A[1, 1] * B[1, 3] + A[1, 2] * B[2, 3]
+ A[1, 3] * B[3, 3];
Result[1, 4] = 0;
Result[2, 1] = A[2, 1] * B[1, 1] + A[2, 2] * B[2, 1]
+ A[2, 3] * B[3, 1];
Result[2, 2] = A[2, 1] * B[1, 2] + A[2, 2] * B[2, 2]
+ A[2, 3] * B[3, 2];
Result[2, 3] = A[2, 1] * B[1, 3] + A[2, 2] * B[2, 3]
+ A[2, 3] * B[3, 3];
Result[2, 4] = 0;
Result[3, 1] = A[3, 1] * B[1, 1] + A[3, 2] * B[2, 1]
+ A[3, 3] * B[3, 1];
Result[3, 2] = A[3, 1] * B[1, 2] + A[3, 2] * B[2, 2]
+ A[3, 3] * B[3, 2];
Result[3, 3] = A[3, 1] * B[1, 3] + A[3, 2] * B[2, 3]
+ A[3, 3] * B[3, 3];
Result[3, 4] = 0;
Result[4, 1] = A[4, 1] * B[1, 1] + A[4, 2] * B[2, 1]
+ A[4, 3] * B[3, 1] + B[4, 1];
Result[4, 2] = A[4, 1] * B[1, 2] + A[4, 2] * B[2, 2]
+ A[4, 3] * B[3, 2] + B[4, 2];
Result[4, 3] = A[4, 1] * B[1, 3] + A[4, 2] * B[2, 3]
+ A[4, 3] * B[3, 3] + B[4, 3];
Result[4, 4] = 1;
}
Листинг 33.4. Метод для печати изображения с элемента PictureBox.
private void printDocument1_PrintPage(object sender,
System.Drawing.Printing.PrintPageEventArgs e)
{
e.Graphics.DrawImage(pictureBox1.Image, 0, 0);
}