Алгоритм и проект системы
Выше мы условно приняли, что если в приложении фазы движения объектов спроектированы и выводятся на экран без использования графических файлов, то такое движение называется анимацией (или эффектом анимации).
Как уже отмечалось, например, изменение во времени внешнего вида формы и какого-либо элемента управления, к примеру, кнопки, на форме мы считаем анимацией. Анимация во многих случаях требуется в учебе и на практике, чтобы показать какую-нибудь конструкцию в работе (во времени) или показать какой-либо процесс, например, изготовления изделия (детали, вещи) в динамике, т.е. по мере поэтапного изготовления изделия во времени. Это позволяет не только наглядно увидеть происходящее, но и выявить ошибки (недостатки) проектирования с целью последующей доработки этого процесса до наиболее рационального (оптимального).
Следовательно, методика разработки анимации заключается в написании программы на основе использования стандартных функций из библиотеки классов какой-либо платформы визуального программирования и на основе математических зависимостей (без рисования последовательных фаз изменения объектов и без сохранения этих фаз в виде графических файлов).
Для разработки анимации в среде Visual C# имеется несколько вариантов применения элементов управления и программ. Например, один из вариантов использует элемент управления StatusBar, внутри которого располагают панели, на которых размещают уже готовые анимационные рисунки из класса StatusBar. Другой вариант основан на применении класса Matrix (пространства имен System.Drawing. Drawing2D), который (при помощи своих методов) производит преобразования (перемещение, поворот, растяжение и сжатие) какой-либо геометрической фигуры. Все варианты, естественно, используют компонент Timer, при помощи которого задаются интервалы времени изменения положения объекта.
Мы закончили главу с методикой проектирования первой формы с оглавлением нашей персональной (или корпоративной) системы анимации.
Теперь приступаем к разработке раздела, согласно оглавлению системы, для решения конкретных анимационных задач на следующих формах.
Алгоритм оглавления и проектирование формы
Продолжим разрабатывать методологию интеграции комплекса Visual C# (из платформы Visual Studio 2005) с другими платформами и комплексами на примере интеграции с очень мощной и широко применяемой системой управления базами данных (СУБД) Microsoft Access (из платформы Office 10/XP или 11/2003). Будем создавать нашу локальную систему управления объектами Access. Мы применим готовые методы Access для решения некоторых широко распространенных практических задач по работе с базами данных, чтобы в дальнейшем по этой методологии мы могли постепенно дополнять нашу персональную систему другими возможностями из этой СУБД и иных известных систем и комплексов.
Как обычно, в нашем новом приложении (или локальной системе из нашей глобальной вычислительной системы) ввод исходных данных и вывод результатов проектирования будет осуществляться при помощи форм. На первой (будем называть ее также главной) формы мы разместим оглавление локальной системы. Напомним, что оглавление – это составная часть системы, содержащая перечень заголовков (для разделов и других структурных единиц); после выбора пользователем любого заголовка должна выводиться форма с соответствующим разделом системы. Оглавление оформим при помощи переключателей. Каждый переключатель соответствует одному разделу, который располагается на одной или нескольких формах.
Алгоритм такого оглавления формулируем так же (как и ранее):
после запуска нашей системы (в режиме выполнения) выходит главная форма, на которой включен верхний нулевой переключатель (холостой, не связанный ни с каким разделом); щелкаем переключатель, напротив которого записан заголовок интересующего нас раздела; появляется форма, на которой мы можем начать решение задачи по тематике данного раздела.
Для создания проекта системы управления объектами Access поступаем так.
1. В VS 2005 щелкаем кнопку или значок New Project (или File, New, Project).
2. В панели New Project в окне Project Types выбираем Visual C# Projects, а в окне Templates выделяем шаблон Windows Application; в окне Name печатаем имя проекта, например, Visual + Access. Таким образом, имя проекта “Visual + Access” соответствует поставленной в данной части книги задаче создания в комплексе Visual C# локальной системы управления базами данных при помощи объектной модели Access.
3. В панели New Project щелкаем OK. В ответ VC# создает проект приложения и выводит форму Form1 в режиме проектирования.
Анимация в виде циклического изменения непрозрачности панели
В разрабатываемой нами вычислительной системе можно будет одновременно вызвать несколько различных форм, которые могут заслонять друг друга. Чтобы за первой формой была видна другая форма (или несколько других форм), первую форму можно сделать полупрозрачной в такой мере прозрачности, чтобы и на первой форме были видны результаты расчетов, и чтобы за полупрозрачной первой формой были видны другие формы с другими результатами расчетов. Поэтому на данной форме разработаем циклическое изменение непрозрачности цвета этой формы.
Напомним, что степень непрозрачности формы устанавливается в панели Properties (с заголовком Form1) в строке Opacity и по умолчанию имеет 100-процентную непрозрачность (при значении, равном 1). Если значение свойства Opacity равно 0, то форма полностью прозрачна.
Алгоритм данной анимации формулируем так:
в режиме выполнения форма появляется с заданным нами значением свойства непрозрачности Opacity, например, 30%;
далее значение свойства непрозрачности Opacity увеличивается пошагово (например, с шагом 0.1) до максимального значения 1 (с заданным нами выше в панели Properties интервалом времени Interval для компонента Timer1);
на каждом шаге значение свойства непрозрачности Opacity выводится в заголовок формы, и подается звуковой сигнал;
после достижения максимального значения 1, свойству непрозрачности Opacity снова задается исходное значение, например, 0.3, и весь цикл увеличения непрозрачности формы повторяется;
на любом шаге мы должны иметь возможность остановить увеличение непрозрачности формы (остановить анимацию), например, щелчком по форме.
Для программной реализации этого алгоритма сначала в панели Properties с заголовком Form1 в строке свойства Opacity изменяем заданное по умолчанию значение 100% на исходное значение, например, на 30% и нажимаем клавишу Enter.
Затем дважды щелкаем значок timer1 (ниже формы в режиме проектирования). Появляется файл Form1.cs с шаблоном, в который записываем код:
//Увеличиваем непрозрачность на шаг 0.1:
this.Opacity = this.Opacity + 0.1;
//При достижении или превышении
//полной непрозрачности панели, равной 1,
//возвращаемся к исходной непрозрачности 0.3:
if (this.Opacity >= 1)
this.Opacity = 0.3;
//Выводим значение непрозрачности
//в заголовок формы:
this.Text = "Opacity: " + this.Opacity;
В этом коде в строке (this.Opacity = 0.3;) мы можем устанавливать различные значения исходной непрозрачности.
Задавая в панели Properties с заголовком Timer1 различные значения свойству Interval (а выше мы задали 1000 миллисекунд или 1 секунду), можно уменьшить частоту изменения непрозрачности, чтобы мы успели присмотреться и остановить анимацию при приятном для нашего глаза значении непрозрачности формы.
Методика приостановки и возобновления анимации после щелчка по дополнительной кнопке на форме, а также подачи звукового сигнала приведена выше.
Анимация в виде циклического изменения цвета формы
На данной форме, например, с целью выделить ее из многих последующих панелей, разработаем циклическое изменение цвета формы.
Напомним, что цвет формы устанавливается в панели Properties (с заголовком Form5) в свойстве BackColor и по умолчанию имеет значение Control.
Алгоритм данной анимации формулируем так:
после появления формы ее цвет начинает циклически изменяться, например, с красного на зеленый и обратно, с заданным нами выше в панели Properties интервалом времени для компонента Timer1.
Для программной реализации этого алгоритма дважды щелкаем значок timer1 (ниже формы в режиме проектирования). Появляется файл с шаблоном, который после записи нашего кода принимает следующий следующий вид.
//Объявляем булеву переменную myColor со значением false:
bool myColor = false;
private void timer1_Tick(object sender, EventArgs e)
{
//Вводим анимацию:
if (myColor == false)
{
//Задаем чередование красного (Red)
//и зеленого (Green) цветов формы:
this.BackColor = System.Drawing.Color.Red;
//Изменяем значение myColor на противоположное:
myColor = true;
}
else
{
//Задаем зеленый цвет (Green):
this.BackColor = System.Drawing.Color.Green;
//Изменяем значение myColor на противоположное:
myColor = false;
}
}
В этом коде мы можем устанавливать чередование двух других цветов из списка, который появляется, когда при написании кода мы ставим точку после имени структуры Color.
Задавая в панели Properties с заголовком timer1 различные значения свойству Interval (а выше мы задали 1000 миллисекунд или 1 секунду), можно изменять частоту мигания цветов формы, чтобы это мигание было приятным для наших глаз. Методика приостановки и возобновления анимации дана выше.
Алгоритмы и оглавление графической системы
Листинг 23.1. Метод для выполнения анимации.
//Объявляем булеву переменную myColor со значением false:
bool myColor = false;
private void timer1_Tick(object sender, EventArgs e)
{
//Вводим анимацию:
if (myColor == false)
{
//Выводим красный цвет переключателя:
this.radioButton2.BackColor = Color.Red;
//Изменяем значение myColor на противоположное:
myColor = true;
}
else
{
//Выводим белый цвет переключателя:
this.radioButton2.BackColor = Color.White;
//Изменяем значение myColor на противоположное:
myColor = false;
}
}
В этом коде мы можем устанавливать чередование двух других цветов из списка, который появляется, когда при написании кода мы ставим точку после имени структуры Color. Аналогично можно также дописать код, чтобы сделать анимационными сразу несколько переключателей. Задавая в панели Properties для компонента Timer различные значения свойству Interval (а выше мы задали 1000 миллисекунд или 1 секунду), можно изменять частоту чередования цветов переключателей. Методика приостановки и возобновления анимации приведена выше.
Проверяем, как на данном этапе проектирования действует оглавление системы. Для этого строим (Build, Build Solution) и запускаем программу на выполнение (Debug, Start Without Debugging).
Анимация текста в консольном приложении
Листинг 63.1. Модуль Module1.cs после записи нашего кода.
using System;
//Подключаем пространство имен Timers:
using System.Timers;
namespace Animated_text_on_console
{
/// <summary>
/// Summary description for Class1.
/// </summary>
class Class1
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
// TODO: Add code to start application here
//
//Вводим таймер, который генерирует событие
//через каждый Interval времени, равный
//3000 миллисекундам или 3 секундам:
System.Timers.Timer myTimer =
new System.Timers.Timer(3000);
myTimer.Elapsed+=
new ElapsedEventHandler(OnTimedEvent);
//Чтобы таймер совершил событие
//только один раз и остановился,
//в следующей строке убираем символы //:
//myTimer.AutoReset = false;
//Запускаем в работу таймер:
myTimer.Enabled = true;
//Выводим сообщение,
//как остановить анимацию:
Console.WriteLine(
"Чтобы остановить анимацию,
нажмите клавиши c + Enter.");
//Организовываем цикл работы таймера
//при помощи оператора while,
//который выключает таймер
//после нажатия клавиш c + Enter:
while(Console.Read()!='c');
}
//Эта процедура выводит анимационное сообщение
//через каждый Interval времени работы таймера:
private static void OnTimedEvent(object source,
ElapsedEventArgs e)
{
//Анимационное сообщение:
Console.WriteLine(
"Введите исходные данные
и нажмите клавишу Enter:");
}
}
}
Аналогично можно разработать программы для циклического вывода на консоль любой строки текста как одного содержания, так и различного содержания. Таким образом, мы закончили раздел по разработке системы из анимационных программ на языке Visual C#. Напомним, что этот же раздел по разработке системы из анимационных программ на другом главном (в мире программирования) языке Visual Basic даны в нашей предыдущей книге [8].
Архитектура доступа к данным ADO.NET
Листинг 21.1. Присоединяем таблицу “Customers” к набору данных без связей.
private void button1_Click(object sender, EventArgs e)
{
DataSet myDataSet = new DataSet();
oleDbConnection1.Open();
oleDbDataAdapter1.Fill(myDataSet, "Customers");
oleDbDataAdapter2.Fill(myDataSet, "Orders");
oleDbDataAdapter3.Fill(myDataSet, "OrderDetails");
oleDbConnection1.Close();
dataGrid1.DataSource = myDataSet;
dataGrid1.DataMember = "Customers";
this.Text = "The table Customers is bound to DataSet"
"with no defined relationships";
}
Аналогично дважды щелкаем вторую кнопку с надписью Bind the table “Customers” to DataSet with relationships (Присоединить таблицу “Customers” к набору данных со связями). Появляется файл Form1.cs с шаблоном, который после записи нашего кода принимает следующий вид.
Листинг 21.2. Присоединяем таблицу “Customers” к набору данных со связями.
private void button2_Click(object sender, EventArgs e)
{
DataSet myDataSet = new DataSet();
oleDbConnection1.Open();
oleDbDataAdapter1.Fill(myDataSet, "Customers");
oleDbDataAdapter2.Fill(myDataSet, "Orders");
oleDbDataAdapter3.Fill(myDataSet, "OrderDetails");
oleDbConnection1.Close();
myDataSet.Relations.Add("CustomerOrders",
myDataSet.Tables.
Item[S"Customers"].Columns.Item[S"customerID"],
myDataSet.Tables.Item[S"Orders"].
Columns.Item[S"customerID"]);
myDataSet.Relations.Add("OrderOrderDetails",
myDataSet.Tables.
Item[S"Orders"].Columns.Item["OrderID"],
myDataSet.Tables.Item[S"OrderDetails"].
Columns.Item[S"OrderID"]);
dataGrid1.DataSource = myDataSet;
dataGrid1.DataMember = "Customers";
this.Text = " The table Customers is bound"
"to DataSet with defined relationships";
}
Аналогично дважды щелкаем третью кнопку с надписью Bind the table “Orders” to DataSet with no relationships (Присоединить таблицу “Orders” к набору данных без связей).
Появляется файл Form1.cs с шаблоном соответствующей функции, которая после записи нашего кода принимает следующий видо.
Листинг 21.3. Присоединяем таблицу “Orders” к набору данных без связей.
private void button3_Click(object sender, EventArgs e)
{
DataSet* myDataSet = new DataSet();
oleDbConnection1.Open();
oleDbDataAdapter1.Fill(myDataSet, "Customers");
oleDbDataAdapter2.Fill(myDataSet, "Orders");
oleDbDataAdapter3.Fill(myDataSet, "OrderDetails");
oleDbConnection1.Close();
dataGrid1.DataSource = myDataSet;
dataGrid1.DataMember = "Orders";
this.Text = " The table Orders is bound"
"to DataSet with no defined relationships";
}
Арифметические вычисления
Теперь в файл Form3.cs необходимо написать нашу часть кода для выполнения первой арифметической операции (сложения) после щелчка первой кнопки со знаком равенства “=” на Form3; дважды щелкаем эту кнопку. Появляется файл Form3.cs с шаблоном, в который записываем:
double add1, add2, add3;
add1 = Convert.ToDouble(textBox1.Text);
add2 = Convert.ToDouble(textBox2.Text);
add3 = add1 + add2;
textBox3.Text = add3.ToString();
Аналогично дважды щелкаем вторую кнопку. Появляется файл Form3.cs с шаблоном, в который записываем такой же код, только вместо сложения записываем вычитание двух чисел и следующие номера окон textBox:
double sub1, sub2, sub3;
sub1 = Convert.ToDouble(textBox4.Text);
sub2 = Convert.ToDouble(textBox5.Text);
sub3 = sub1 - sub2;
textBox6.Text = sub3.ToString();
Аналогично дважды щелкаем третью кнопку. Появляется файл Form3.cs с шаблоном, в который записываем такой же код, только вместо вычитания записываем умножение двух чисел и следующие номера окон textBox:
double mul1, mul2, mul3;
mul1 = Convert.ToDouble(textBox7.Text);
mul2 = Convert.ToDouble(textBox8.Text);
mul3 = mul1 * mul2;
textBox9.Text = mul3.ToString();
Аналогично дважды щелкаем последнюю четвертую кнопку. Появляется файл Form3.cs с шаблоном, в который записываем такой же код, только вместо умножения записываем деление двух чисел и следующие номера окон textBox:
double div1, div2, div3;
div1 = Convert.ToDouble(textBox10.Text);
div2 = Convert.ToDouble(textBox11.Text);
div3 = div1 / div2;
textBox12.Text = div3.ToString();
График линейной функции
Листинг 24.1. Наш код в шаблоне метода для кнопки Graph.
double a_Form3, b_Form3, x_min_Form3, x_max_Form3;
a_Form3 = Convert.ToDouble(textBox1.Text);
b_Form3 = Convert.ToDouble(textBox2.Text);
x_min_Form3 = Convert.ToDouble(textBox3.Text);
x_max_Form3 = Convert.ToDouble(textBox4.Text);
Form5 myForm5 = new Form5();
myForm5.a = a_Form3;
myForm5.b = b_Form3;
myForm5.x_min = x_min_Form3;
myForm5.x_max = x_max_Form3;
myForm5.Show();
Для программной реализации этого алгоритма дважды щелкаем значок timer1 (ниже формы в режиме проектирования). Появляется файл с шаблоном, в который записываем наш следующий код:
//Объявляем булеву переменную myColor со значением false:
bool myColor = false;
private void timer1_Tick(object sender, EventArgs e)
{
//Вводим анимацию:
if (myColor == false)
{
//Выводим красный цвет переключателя:
this.radioButton2.BackColor =
System.Drawing.Color.Red;
//Изменяем значение myColor на противоположное:
myColor = true;
}
else
{
//Выводим белый цвет переключателя:
this.radioButton2.BackColor =
System.Drawing.Color.White;
//Изменяем значение myColor на противоположное:
myColor = false;
}
}
Листинг 24.2. Первая часть кода (выше шаблона pictureBox1_Paint).
//Параметры графика функции y = a*x + b
//в виде глобальных переменных:
public float a, b, x_min, x_max;
float Function_of_graph(float x)
{
float Function;
//Метод y = f(x), график которой будем строить:
Function = a * x + b;
return Function;
}
Листинг 24.3. Вторая часть кода (выше шаблона pictureBox1_Paint).
//Число точек графика:
public int Npoints = 100;
//Величины (в пикселах) O_x_pix и O_y_pix
//для параллельного переноса
//осей "x" и "y" новой системы координат (по сравнению
//со старой системой в верхнем левом углу рамки PictureBox):
public float O_x_pix = 500;
public float O_y_pix = 350;
//Масштабы по осям "x" и "y" (M_x и M_y) для перехода
//от действительных значений к пикселам
//и построения графика в пикселах:
public float M_x = 450;
public float M_y = 300;
Теперь внутрь этого (приведенного выше) шаблона для процедуры pictureBox1_Paint записываем основную часть нашего кода для вывода графика на экран монитора. Перед каждым логическим блоком кода мы даем подробный комментарий, чтобы читатель мог изучить это важный код и мог грамотно внести изменения (в случае необходимости).
Листинг 24.4. Главный код построения графика функции на экране монитора.
//Шаг по оси абсцисс "x" между точками графика:
float step_x = (x_max-x_min)/Npoints;
//Наибольшее абсолютное значение x_max_abs
//из двух концов заданного нами числового интервала
//x_min и x_max:
float x_max_abs = Math.Abs(x_max);
if (x_max_abs < Math.Abs(x_min)) x_max_abs = Math.Abs(x_min);
//Промежуточные локальные переменные:
float x_0, y_0, x_1, y_1, x_0_pix, y_0_pix, x_1_pix, y_1_pix;
//Расчет минимального y_min и максимального y_max
//действительных значений функции:
float y_min, y_max;
//Присваиваем y_min, y_max значение y_0
//для нулевой точки (i=0):
x_0 = x_min; y_0 = Function_of_graph(x_0);
y_min = y_0; y_max = y_0; int i;
//Организовываем цикл по всем точкам, начиная с i=1:
for (i=1; i<=(Npoints-1); i++)
{
x_1 = x_min + i * step_x;
y_1 = Function_of_graph(x_1);
//Расчет минимального и максимального значений функции:
if (y_min > y_1) y_min = y_1;
if (y_max < y_1) y_max = y_1;
}
//Т.к. в последней точке i = Npoints
//значение x_1 = x_min + Npoints * step_x
//может отличаться от заданного значения x_max
//(из-за накапливания погрешности в цикле), то проверяем,
//может быть y_min или y_max находится в последней
//точке при точном задании нами значения x_max:
x_1 = x_max; y_1 = Function_of_graph(x_1);
//Проверка минимального и максимального
//значений функции в последней точке:
if (y_min > y_1) y_min = y_1;
if (y_max < y_1) y_max = y_1;
//Наибольшее абсолютное значение функции y_max_abs
//из двух значений y_min и y_max:
float y_max_abs = Math.Abs(y_max);
if (y_max_abs < Math.Abs(y_min)) y_max_abs = Math.Abs(y_min);
//Строим сетку координат:
//Сначала строим ось абсцисс "x" от x = -1 до x = 1:
// Задаем абсциссу последней точки оси абсцисс "x"
//при x = 1:
float x_point_end, x_point_end_pix; x_point_end = 1;
x_point_end_pix = x_point_end * M_x + O_x_pix;
//Выбираем зеленое перо толщиной 2:
Pen greenPen_x = new Pen(Color.Green, 2);
//Задаем координаты двух граничных точек оси:
PointF point1 = new PointF(-1 * M_x + O_x_pix, O_y_pix);
PointF point2 = new PointF(x_point_end_pix, O_y_pix);
//Строим линию через две заданные граничные точки:
e.Graphics.DrawLine(greenPen_x, point1, point2);
//Строим горизонтальные линии сетки координат
//(кроме оси "x"):
//Ширина (размах) графика по оси ординат "y":
float span_y = y_max - y_min;
//Число шагов по всей высоте сетки (по оси "y"):
int N_step_grid_y = 20;
//Шаг сетки в направлении оси "y"
//(высота всей сетки равна 2 единицам):
float step_grid_y, step_grid_y_pix;
//Преобразование типов переменных:
step_grid_y = (float) 2 / N_step_grid_y;
step_grid_y_pix = step_grid_y * M_y;
//Выбираем красное перо толщиной 1:
Pen redPen = new Pen(Color.Red, 1);
//Строим сетку от нулевой линии в одну сторону (вниз):
int j_y; float y1, y1_pix;
for (j_y = 1; j_y<=(N_step_grid_y/2); j_y++)
{
y1 = j_y * step_grid_y;
y1_pix = O_y_pix + j_y * step_grid_y_pix;
//Задаем координаты двух граничных точек линии сетки:
PointF point3 = new PointF(-1 * M_x + O_x_pix, y1_pix);
PointF point4 = new PointF(x_point_end_pix, y1_pix);
//Строим линию через две заданные граничные точки:
e.Graphics.DrawLine(redPen, point3, point4);
}
// Строим сетку от нулевой линии в другую сторону (вверх):
for (j_y = 1; j_y<=(N_step_grid_y / 2); j_y++)
{
y1_pix = O_y_pix - j_y * step_grid_y * M_y;
//Задаем координаты двух граничных точек линии сетки:
PointF point5 = new PointF(-1 * M_x + O_x_pix, y1_pix);
PointF point6 = new PointF(x_point_end_pix, y1_pix);
//Строим прямую линию через две заданные точки:
e.Graphics.DrawLine(redPen, point5, point6);
}
//Строим ось ординат "y" от y= -1 до y = 1:
//Задаем ординату последней точки оси ординат "y" при y = 1:
float y_point_end, y_point_end_pix; y_point_end = 1;
y_point_end_pix = y_point_end * M_y + O_y_pix;
//Выбираем зеленое перо толщиной 2:
Pen greenPen_y = new Pen(Color.Green, 2);
//Задаем координаты двух граничных точек оси:
PointF point7 = new PointF(O_x_pix, -1 * M_y + O_y_pix);
PointF point8 = new PointF(O_x_pix, y_point_end_pix);
//Строим линию через две заданные граничные точки:
e.Graphics.DrawLine(greenPen_y, point7, point8);
//Строим вертикальные линии сетки координат (кроме оси "y"):
//Ширина (размах) графика по оси абсцисс "x":
float span_x = x_max - x_min;
//Число шагов по всей ширине сетки по обе стороны от оси y:
int N_step_grid_x = 20;
//Шаг сетки в направлении оси "x"
//(ширина всей сетки равна 2 единицам):
float step_grid_x = 0.1F, step_grid_x_pix;
step_grid_x_pix = step_grid_x * M_x;
//Выбираем красное перо толщиной 1:
Pen redPen_y = new Pen(Color.Red, 1);
//Строим сетку от нулевой линии в одну сторону (вправо):
int j_x; float x1, x1_pix;
for (j_x = 1; j_x<=(N_step_grid_x / 2); j_x++)
{
x1 = j_x * step_grid_x;
x1_pix = O_x_pix + j_x * step_grid_x_pix;
//Задаем координаты двух граничных точек линии сетки:
PointF point9 = new PointF(x1_pix, -1 * M_y + O_y_pix);
PointF point10 = new PointF(x1_pix, y_point_end_pix);
//Строим линию через две заданные граничные точки:
e.Graphics.DrawLine(greenPen_y, point9, point10);
}
// Строим сетку от нулевой линии в другую сторону (влево):
for (j_x = 1; j_x<=(N_step_grid_x / 2); j_x++)
{
x1 = j_x * step_grid_x;
x1_pix = O_x_pix - j_x * step_grid_x_pix;
//Задаем координаты двух граничных точек линии сетки:
PointF point11 = new PointF(x1_pix, -1 * M_y +
O_y_pix);
PointF point12 = new PointF(x1_pix, y_point_end_pix);
//Строим прямую линию через две заданные точки:
e.Graphics.DrawLine(greenPen_y, point11, point12);
}
//Записываем числа по осям координат:
//Объявляем локальные переменные:
int n; float p1 = 1, p2; string msg;
//Записываем числа по оси "O","+y":
for (n = 0; n<=9; n++)
{
p2 = p1 - n * 0.1F;
msg = "" + p2.ToString() + "";
e.Graphics.DrawString(msg, this.Font, Brushes.Blue,
O_x_pix - 35, O_y_pix - 323 + n * step_grid_y_pix);
}
//Записываем числа по оси "O","-y":
p1 = 0;
for (n = 1; n<=10; n++)
{
p2 = p1 - n * 0.1F;
msg = "" + p2.ToString() + "";
e.Graphics.DrawString(msg, this.Font, Brushes.Blue,
O_x_pix - 40, O_y_pix - 23 + n * step_grid_y_pix);
}
//Записываем числа по оси "O","+x":
p1 = 0;
for (n = 0; n<=10; n++)
{
p2 = p1 + n * 0.1F;
msg = "" + p2.ToString() + "";
e.Graphics.DrawString(msg, this.Font, Brushes.Blue,
O_x_pix - 0 + n * step_grid_x_pix, O_y_pix - 0);
}
//Записываем числа по оси "O","-x":
p1 = 0;
for (n = 1; n<=10; n++)
{
p2 = p1 - n * 0.1F;
msg = "" + p2.ToString() + "";
e.Graphics.DrawString(msg, this.Font, Brushes.Blue,
O_x_pix - 39 - n * step_grid_x_pix, O_y_pix - 0);
}
//Записываем обозначение оси y' = y / y_max:
msg = "y ' = y / | y_max | ;";
e.Graphics.DrawString(msg, this.Font,
Brushes.Blue, O_x_pix - 5, O_y_pix - 333);
//Записываем значение | y_max |:
msg = "| y_max | = " + y_max_abs.ToString() + ";";
e.Graphics.DrawString(msg, this.Font,
Brushes.Blue, O_x_pix + 170, O_y_pix - 333);
//Записываем значение y_max:
msg = "y_max = " + y_max.ToString() + ";";
e.Graphics.DrawString(msg, this.Font,
Brushes.Blue, O_x_pix + 380, O_y_pix - 333);
//Записываем значение y_min:
msg = "y_min = " + y_min.ToString() + "";
e.Graphics.DrawString(msg, this.Font,
Brushes.Blue, O_x_pix + 455, O_y_pix - 300);
//Записываем обозначение оси x' = x / | x_max |:
msg = "x ' = x / | x_max |";
e.Graphics.DrawString(msg, this.Font,
Brushes.Blue, O_x_pix + 455, O_y_pix - 30);
//Записываем значение | x_max |:
msg = "| x_max | = " + x_max_abs.ToString() + "";
e.Graphics.DrawString(msg, this.Font,
Brushes.Blue, O_x_pix + 455, O_y_pix + 40);
//Записываем значение x_max:
msg = "x_max = " + x_max.ToString() + "";
e.Graphics.DrawString(msg, this.Font,
Brushes.Blue, O_x_pix + 455, O_y_pix + 90);
//Записываем значение x_min:
msg = "x_min = " + x_min.ToString() + "";
e.Graphics.DrawString(msg, this.Font,
Brushes.Blue, O_x_pix + 455, O_y_pix + 140);
//Построение графика функции y = f(x):
//Координаты нулевой (i=0) точки,
//с которой строится график:
x_0 = x_min; x_0_pix = x_0 / x_max_abs * M_x + O_x_pix;
//Рассчитываем "y" и вводим знак минус,
//чтобы положительное значение "y"
//отложилось вверх по оси "y" (а не вниз):
y_0 = -(Function_of_graph(x_0));
y_0_pix = y_0 / y_max_abs * M_y + O_y_pix;
//Выбираем черное перо толщиной 4:
Pen blackPen = new Pen(Color.Black, 4);
// Организовываем цикл по всем точкам, начиная с i = 1:
for (i = 1; i<=Npoints; i++)
{
//Рассчитываем абсциссу "x" данной i-й точки:
x_1 = x_min + i * step_x;
//Рассчитываем ординату "y" этой i-й точки
// и вводим знак минус, чтобы положительные значения "y"
//откладывались вверх (а не вниз), и чтобы
//строить график традиционно снизу-вверх по оси "y":
y_1 = -(Function_of_graph(x_1));
//Переходим к относительным величинам и пикселам:
x_1_pix = x_1 / x_max_abs * M_x + O_x_pix;
y_1_pix = y_1 / y_max_abs * M_y + O_y_pix;
//Строим отрезок линии графика y = f(x)
//между двумя известными точками:
e.Graphics.DrawLine(blackPen, x_0_pix, y_0_pix,
x_1_pix, y_1_pix);
//Присваиваем предыдущей (i-1)-й точке
//координаты данной i-й точки:
x_0_pix = x_1_pix; y_0_pix = y_1_pix;
//Задаем следующую (i+1)-ю точку:
}
Отметим еще раз, что наши подробные комментарии внутри кода позволят читателю на этом примере понять и изучить основные конструкции и синтаксис языка C#. А также грамотно внести изменения в код, если читатель пожелает модернизировать внешний вид графика для учета своих конкретных требований.
Листинг 24.5. Код для выполнения анимации.
//Объявляем булеву переменную myText со значением false:
bool myText = false;
private void timer1_Tick(object sender, EventArgs e)
{
//Вводим анимацию:
if (myText == false)
{
//Выводим текст:
this.label1.Text =
"Graph 'Straight Line' of function y = a*x + b:";
//Изменяем значение myText на противоположное:
myText = true;
}
else
{
//Удаляем текст:
this.label1.Text = "";
//Изменяем значение myText на противоположное:
myText = false;
}
}
Графики типичных функций
Листинг 25.1. Метод обработчика щелчка по кнопке Graph.
private void button5_Click(object sender, EventArgs e)
{
float a, b, c, x_min, x_max;
a = Convert.ToSingle(textBox1.Text);
b = Convert.ToSingle(textBox2.Text);
c = Convert.ToSingle(textBox3.Text);
x_min = Convert.ToSingle(textBox4.Text);
x_max = Convert.ToSingle(textBox5.Text);
Form9 myForm9 = new Form9();
myForm9.a = a;
myForm9.b = b;
myForm9.c = c;
myForm9.x_min = x_min;
myForm9.x_max = x_max;
myForm9.Show();
}
Листинг 25.2. Метод для выполнения анимации.
//Объявляем булеву переменную myColor со значением false:
bool myColor = false;
private void timer1_Tick(object sender, EventArgs e)
{
//Вводим анимацию:
if (myColor == false)
{
//Выводим черный цвет текста
//для всех элементов внутри рамки:
this.groupBox1.ForeColor =
System.Drawing.Color.Black;
//Изменяем значение myColor на противоположное:
myColor = true;
}
else
{
//Выводим белый цвет текста
//для всех элементов внутри рамки:
this.groupBox1.ForeColor =
System.Drawing.Color.White;
//Изменяем значение myColor на противоположное:
myColor = false;
}
}
Листинг 25.3. Метод для выполнения анимации.
//Объявляем булеву переменную myColor со значением false:
bool myColor = false;
private void timer1_Tick(object sender, EventArgs e)
{
//Вводим анимацию:
if (myColor == false)
{
//Выводим красный цвет панели pictureBox1:
this.pictureBox1.BackColor = Color.Red;
//Изменяем значение myColor на противоположное:
myColor = true;
}
else
{
//Выводим зеленый цвет панели pictureBox1:
this.pictureBox1.BackColor = Color.Green;
//Изменяем значение myColor на противоположное:
myColor = false;
}
}
Листинг 25.4. Метод для кнопки Graph.
private void button5_Click(object sender, EventArgs e)
{
float a, b, c, d, x_min, x_max;
a = Convert.ToSingle(textBox1.Text);
b = Convert.ToSingle(textBox2.Text);
c = Convert.ToSingle(textBox3.Text);
d = Convert.ToSingle(textBox4.Text);
x_min = Convert.ToSingle(textBox5.Text);
x_max = Convert.ToSingle(textBox6.Text);
Form13 myForm13 = new Form13();
myForm13.a = a;
myForm13.b = b;
myForm13.c = c;
myForm13.d = d;
myForm13.x_min = x_min;
myForm13.x_max = x_max;
myForm13.Show();
}
Листинг 25.5. Метод для выполнения анимации.
//Объявляем булеву переменную myColor со значением false:
bool myColor = false;
private void timer1_Tick(object sender, EventArgs e)
{
//Вводим анимацию:
if (myColor == false)
{
//Выводим желтый цвет поля текста:
this.label1.BackColor =
System.Drawing.Color.Yellow;
//Изменяем значение myColor на противоположное:
myColor = true;
}
else
{
//Выводим белый цвет поля текста:
this.label1.BackColor =
System.Drawing.Color.White;
//Изменяем значение myColor на противоположное:
myColor = false;
}
}
Листинг 25.6. Метод для выполнения анимации.
//Объявляем булеву переменную myColor со значением false:
bool myColor = false;
private void timer1_Tick(object sender, EventArgs e)
{
//Вводим анимацию:
if (myColor == false)
{
//Выводим черный цвет текста:
this.pictureBox1.ForeColor = Color.Black;
//Изменяем значение myColor на противоположное:
myColor = true;
}
else
{
//Выводим белый цвет текста:
this.pictureBox1.ForeColor = Color.White;
//Изменяем значение myColor на противоположное:
myColor = false;
}
}
Интеграция Visual C# с браузером Internet Explorer
Листинг 72.1. Метод для открытия Internet Explorer.
private void button1_Click(object sender, EventArgs
e)
{
//Открываем Internet Explorer на домашней странице:
Process.Start("IExplore.exe");
}
Теперь в файл Form3.cs необходимо написать нашу часть кода для открытия в браузере папки Favorites (Избранное) при помощи второй основной кнопки “Показать в браузере содержание папки Favorites – Избранное (Display the contents of the favorites folder in the browser)” на данной Form3 (рис. 72.2). Дважды щелкаем эту кнопку в режиме редактирования. Появляется файл Form3.cs с шаблоном, который после записи нашего кода принимает следующий вид.
Листинг 72.2. Метод для открытия папки Favorites (Избранное).
private void button2_Click(object sender, EventArgs
e)
{
//Объявляем переменную пути к папке Favorites (Избранное)
//и записываем путь к этой папке:
String myFavoritesPath = Environment.GetFolderPath(
Environment.SpecialFolder.Favorites);
//Показываем в браузере содержание папки Favorites:
Process.Start(myFavoritesPath);
}
Листинг 72.3. Метод для открытия Internet Explorer и вывода сайта.
private void button1_Click(object sender, EventArgs
e)
{
//Открываем Internet Explorer на заданном сайте:
Process.Start("IExplore.exe", "www.company1.ru");
}
Теперь в файл Form5.cs необходимо написать нашу часть кода для открытия Internet Explorer и вывода сайта, адрес которого мы записали на второй основной кнопке (Start “www.company2.ru”) на Form5 (рис. 72.5). Дважды щелкаем эту кнопку в режиме редактирования. Появляется файл Form5.cs с шаблоном, который после записи нашего кода принимает такой вид.
Листинг 72.4. Метод для открытия домашней страницы.
private void button1_Click(object sender, EventArgs
e)
{
//Открываем Internet Explorer на заданном сайте:
Process.Start("IExplore.exe", "www.company2.ru");
}
Листинг 72.5. Метод для открытия Internet Explorer и вывода файла.
private void button1_Click(object sender, EventArgs
e)
{
//Открываем в Internet Explorer заданный файл:
Process.Start("IExplore.exe", @"D:\myPath\myFile.html");
}
Теперь в файл Form7. cs необходимо написать нашу часть кода для открытия Internet Explorer и вывода файла, путь к которому (на нашем компьютере) мы записали на второй основной кнопке (Start the file "myFile.asp") на данной Form7 (рис. 72.7). Дважды щелкаем эту кнопку в режиме редактирования. Появляется файл Form7.cs с шаблоном, который после записи нашего кода принимает такой вид.
Листинг 72.6. Метод для открытия Internet Explorer и вывода файла.
private void button2_Click(object sender, EventArgs
e)
{
//Открываем в Internet Explorer заданный файл:
Process.Start("IExplore.exe", @"D:\myPath\myFile.asp");
}
Проверяем в работе новую форму. Для этого строим программу и запускаем ее на выполнение обычным образом: Build, Build Solution; Debug, Start Without Debugging. В ответ Visual C# выполняет программу и на рабочий стол выводит первую форму Form1 (с оглавлением нашей системы) в режиме выполнения. После щелчка переключателя 3 (или нажатия клавиш Alt+3, или кнопки Next>> на предыдущей Form5) появляется Form7, показанная выше на рис. 72.7.
После щелчка первой основной кнопки (Start the file "myFile.html") на экране появляется браузер Internet Explorer с этим файлом. После щелчка второй основной кнопки (Start the file "myFile.asp") на экране появляется этот файл. Аналогично на форме можно разместить много кнопок (или переключателей, или других элементов управления) с именами файлов и выводить эти файлы. Далее можно продолжить выполнять любые операции, поддерживаемые интерфейсом IE.
Мы закончили главы книги по методологии интеграции (взаимодействия) комплекса Visual C# (из платформы Visual Studio 2005) с программными продуктами Windows Explorer, Notepad, WordPad, Calculator, PowerPoint, FrontPage и Internet Explorer корпорации Microsoft для решения широко распространенных на практике задач. И теперь по этой методологии мы можем постепенно дополнять эти задачи другими возможностями интеграции Visual C# с этими программными продуктами.
Использование в Visual C# программ из Excel
Листинг 14.1. Код функции для компонента Timer.
//Объявляем булеву переменную myVisible со значением false:
bool myVisible = false;
private void timer1_Tick(object sender, EventArgs e)
{
//Программируем анимацию:
if (myVisible == false)
{
//Делаем невидимым элемент управления:
this.button1.Visible = False;
//Изменяем значение myVisible на противоположное:
myVisible = true;
}
else
{
//Делаем видимым элемент управления:
this.button1.Visible = True;
//Изменяем значение myVisible на противоположное:
myVisible = false;
}
}
Задавая в панели Properties с заголовком timer1 различные значения свойству Interval (а выше мы задали 1000 миллисекунд или 1 секунду), можно изменять частоту исчезновения элемента управления, например, кнопки. Методика приостановки и возобновления анимации (с примерами) дана выше.
Теперь приступаем к разработке разделов, согласно оглавлению системы, для решения конкретных расчетных задач на следующих формах (и со следующими эффектами анимации).
Изображение и управление
Листинг 34.1. Код выше и в теле процедуры Form1_Load.
'Начало координат:
Private Const x_focus As Double = 0
Private Const y_focus As Double = 0
Private Const z_focus As Double = 0
'Сферические координаты точки E (глаза наблюдателя Eye):
Private r_Eye As Single
Private phi_Eye As Single
Private theta_Eye As Single
'Объявляем матрицу (как массив) и переменные
'(во всех массивах нулевые индексы не используем):
Private Const pi As Double = Math.PI
Private MatrixProjection(4, 4) As Single
Private Tetrahedron As Integer
Private Cube As Integer
Private Octahedron As Integer
Private Dodecahedron As Integer
Private Icosahedron_first As Integer
Private Icosahedron_last As Integer
'Для параллельного проецирования объекта на экран
'(parallel projection) задаем константу:
Private Const ParallelProjection As Integer = 0
'Для перспективного проецирования объекта на экран
'(perspective projection)задаем константу:
Private Const PerspectiveProjection As Integer = 1
Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As EventArgs) Handles MyBase.Load
'Задаем координаты глаза наблюдателя, например:
r_Eye = 4 : phi_Eye = 0.05 * pi : theta_Eye = 0.3 * pi
'Вызываем процедуру для перспективного проецирования,
'когда type_of_projection = PerspectiveProjection
'(для параллельного проецирования вместо
'PerspectiveProjection пишем ParallelProjection):
Projection(MatrixProjection, PerspectiveProjection, _
r_Eye, phi_Eye, theta_Eye, _
x_focus, y_focus, z_focus, 0, 1, 0)
'Рассчитываем параметры геометрического тела:
СalculateParameters()
'Связываем элемент PictureBox1 с классом Bitmap:
PictureBox1.Image = New Bitmap(PictureBox1.Width, _
PictureBox1.Height)
'Проектируем и в PictureBox1 рисуем выбранное нами тело:
Designing(DirectCast(PictureBox1.Image, Bitmap))
End Sub
Чтобы мы могли управлять (например, вращать) объектами при помощи нажатия клавиш, в окне Class Name выбираем (Overrides), а в окне Method Name выбираем ProcessCmdKey. Появляется файл Form1.vb с шаблоном (метода ProcessCmdKey), который после записи нашего кода принимает следующий вид. Отметим, что если в версии VS, которая имеется у читателя, отсутствует метод ProcessCmdKey, то необходимо полностью записать нижеследующий метод вместе с шаблоном (или скопировать весь метод из прилагаемого к книге диска).
Листинг 34.2. Метод ProcessCmdKey.
Protected Overrides Function ProcessCmdKey( _
ByRef msg As System.Windows.Forms.Message, _
ByVal keyData As System.Windows.Forms.Keys) As Boolean
'Задаем угол поворота фигуры после нажатия клавиши:
Const delta_theta As Single = pi / 20
Select Case keyData
Case System.Windows.Forms.Keys.Left
theta_Eye = theta_Eye - delta_theta
Case System.Windows.Forms.Keys.Right
theta_Eye = theta_Eye + delta_theta
Case System.Windows.Forms.Keys.Up
phi_Eye = phi_Eye - delta_theta
Case System.Windows.Forms.Keys.Down
phi_Eye = phi_Eye + delta_theta
Case Else
Return MyBase.ProcessCmdKey(msg, keyData)
End Select
Projection(MatrixProjection, PerspectiveProjection, _
r_Eye, phi_Eye, theta_Eye, _
x_focus, y_focus, z_focus, 0, 1, 0)
'В элементе PictureBox1 перерисовываем объект:
Designing(DirectCast(PictureBox1.Image, Bitmap))
PictureBox1.Refresh()
Return True
End Function
Ниже этого кода записываем следующие все процедуры и функции.
Листинг 34.3. Процедуры и функции.
'Проектируем и при помощи процедуры DrawSolid
'рисуем выбранное флажком CheckBox геометрическое тело:
Private Sub Designing(ByVal bmp As Bitmap)
'Создаем объект g класса Graphics:
Dim g As Graphics
'Связываем объект g с изображением bmp:
g = Graphics.FromImage(bmp)
' Задаем белый цвет типа Window
'для элемента управления PictureBox1:
g.Clear(SystemColors.Window)
'Высвобождаем ресурсы от графического объекта g:
g.Dispose()
'Преобразуем точки:
TransformAllDataFull(MatrixProjection)
'Проектируем и рисуем выбранное на CheckBox тело:
If CheckBox1.CheckState = _
System.Windows.Forms.CheckState.Checked Then
DrawSolid(bmp, Tetrahedron, Cube - 1, _
System.Drawing.Color.Red, False)
End If
If CheckBox2.CheckState = _
System.Windows.Forms.CheckState.Checked Then _
DrawSolid(bmp, Cube, Octahedron - 1, _
System.Drawing.Color.Black, False)
If CheckBox3.CheckState = _
System.Windows.Forms.CheckState.Checked Then _
DrawSolid(bmp, Octahedron, Dodecahedron - 1, _
System.Drawing.Color.Green, False)
If CheckBox4.CheckState = _
System.Windows.Forms.CheckState.Checked Then _
DrawSolid(bmp, Dodecahedron, Icosahedron_first - 1, _
System.Drawing.Color.Blue, False)
If CheckBox5.CheckState = _
System.Windows.Forms.CheckState.Checked Then _
DrawSolid(bmp, Icosahedron_first, Icosahedron_last, _
System.Drawing.Color.Orange, False)
If CheckBox6.CheckState = _
System.Windows.Forms.CheckState.Checked Then _
DrawSolid(bmp, 1, Tetrahedron - 1, _
System.Drawing.Color.Salmon, False)
End Sub
'Рассчитываем параметры геометрических тел и осей:
Private Sub СalculateParameters()
Dim theta1 As Single : Dim theta2 As Single
Dim s1 As Single : Dim s2 As Single
Dim c1 As Single : Dim c2 As Single
Dim S As Single : Dim R As Single
Dim H As Single : Dim A As Single
Dim B As Single : Dim C As Single
Dim D As Single : Dim X As Single
Dim Y As Single : Dim y2 As Single
Dim M As Single : Dim N As Single
'Оси координат:
DesigningLine(0, 0, 0, 0.5, 0, 0) 'Ось x.
DesigningLine(0, 0, 0, 0, 0.5, 0) 'Ось y.
DesigningLine(0, 0, 0, 0, 0, 0.5) 'Ось z.
'Тетраэдр (Tetrahedron):
Tetrahedron = NumLines + 1
S = CSng(Sqrt(6))
A = S / CSng(Sqrt(3))
B = -A / 2
C = A * CSng(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 = pi * 0.4 : theta2 = pi * 0.8
s1 = CSng(Sin(theta1))
c1 = CSng(Cos(theta1))
s2 = CSng(Sin(theta2))
c2 = CSng(Cos(theta2))
M = 1 - (2 - 2 * c1 - 4 * s1 * s1) / (2 * c1 - 2)
N = CSng(Sqrt((2 - 2 * c1) - M * M)) * _
(1 + (1 - c2) / (c1 - c2)) : R = 2 / N
S = R * CSng(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 = CSng(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 = 2 / (2 * CSng(Sqrt(1 - 2 * c1)) + _
CSng(Sqrt(3 / 4 * (2 - 2 * c1) - _
2 * c2 - c2 * c2 - 1)))
S = R * CSng(Sqrt(2 - 2 * c1))
H = 1 - CSng(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
End Sub
Public Structure Line
'Массивы для соединения точек (points):
<VBFixedArray(4)> Dim fr_points() As Single
<VBFixedArray(4)> Dim to_points() As Single
' Массивы для соединения преобразованных точек
'(transformed points):
<VBFixedArray(4)> Dim fr_tr_points() As Single
<VBFixedArray(4)> Dim to_tr_points() As Single
Public Sub Initialize()
ReDim fr_points(4) : ReDim to_points(4)
ReDim fr_tr_points(4) : ReDim to_tr_points(4)
End Sub
End Structure
'Объявляем массив Lines структуры Line (создавать массив
'из изменяемого количества элементов и инициализировать его 'при помощи оператора ReDim мы будем ниже):
Public Lines() As Line
'Объявляем и инициализируем переменную для индекса массива:
Public NumLines As Integer
'Проектируем линию между точками (x1,y1,z1),(x2,y2,z2):
Public Sub DesigningLine(ByVal x1 As Single, _
ByVal y1 As Single, ByVal z1 As Single, ByVal x2 As Single, _
ByVal y2 As Single, ByVal z2 As Single)
NumLines = NumLines + 1
'Создаем массив Lines структуры Line из изменяемого
'количества элементов NumLines, инициализируем его
'оператором ReDim и при помощи ключевого слова Preserve
'сохраняем предыдущие данные массива:
ReDim Preserve Lines(NumLines)
'Инициализируем и рассчитываем массивы:
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
End Sub
'Применяем матрицу переноса (translation matrix)
'ко всем линиям, используя MatrixApplyFull.
' Преобразование не имеет 0, 0, 0, 1 в последнем столбце:
Public Sub TransformAllDataFull(ByRef M(,) As Single)
TransformDataFull(M, 1, NumLines)
End Sub
'Применяем матрицу переноса (translation matrix)
'ко всем выделенным линиям, используя MatrixApplyFull.
'Преобразование не имеет 0, 0, 0, 1 в последнем столбце:
Public Sub TransformDataFull(ByRef M(,) As Single, _
ByVal line1 As Integer, ByVal line2 As Integer)
Dim i As Integer
For i = line1 To line2
MatrixApplyFull(Lines(i).fr_points, M, _
Lines(i).fr_tr_points)
MatrixApplyFull(Lines(i).to_points, M, _
Lines(i).to_tr_points)
Next i
End Sub
'Вводим перем-ю N_Graphics для номера многих геом-х изобр-й.
'Номер первого изображения равен 1:
Dim N_Graphics As Integer = 1
'Рисуем выделенные преобразованные линии и экпорт-м в файлы:
Public Sub DrawSolid(ByVal bmp As Bitmap, _
ByVal first_line As Integer, ByVal last_line As Integer, _
ByVal color As Color, ByVal clear As Boolean)
Dim k As Integer
Dim x1 As Single : Dim y1 As Single
Dim x2 As Single : Dim y2 As Single
Dim g As Graphics : Dim pen As Pen
'Задаем толщину линии рисования, например, 2
'(цвет линии мы задали в процедуре Designing):
pen = New Pen(color, 2)
'Связываем объект g с изображением bmp:
g = Graphics.FromImage(bmp)
If clear Then g.Clear(System.Drawing.Color.Black)
'Объявляем индексы элементов всех массивов:
Dim i, j As Integer
'Если этот метод DrawSolid вызван второй раз
'для рисования второго изображения и N_Graphics = 2,
'то обходим 1-й массив для первого изобр-я до метки M2:
If N_Graphics = 2 Then GoTo M2
'Программируем первый массив для первого изображения:
'Задаем границы индексов первого массива myArrayVB(i, j)
Dim N_x As Integer = 200
Dim N_y As Integer = 1
'Объявляем массив myArrayVB(i, j) переменных типа Single,
'когда i = 0,1,2,3,...,N_x; j = 0,1,2,3,...,N_y:
Dim myArrayVB(N_x, N_y) As Single 'Автомат-ки обнуляется.
'Значение первой границы массива myArrayVB:
Dim N_1_myArrayVB As Integer
'Рассчитываем элементы массива myArrayVB(i, j)
'для рисования линий первого геом-го изображения:
i = -1 'Задаем до цикла.
For k = first_line To last_line
x1 = Lines(k).fr_tr_points(1)
y1 = Lines(k).fr_tr_points(2)
x2 = Lines(k).to_tr_points(1)
y2 = Lines(k).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) _
')
'Масштабируем значения координат:
x1 = (x1 * bmp.Width / 4) + bmp.Width / 2.0F
y1 = bmp.Height / 2.0F - (y1 * bmp.Height / 4)
x2 = (x2 * bmp.Width / 4) + bmp.Width / 2.0F
y2 = bmp.Height / 2.0F - (y2 * bmp.Height / 4)
'Записываем координаты точек в массив:
i = i + 2
myArrayVB(i, 0) = x1
myArrayVB(i, 1) = y1
myArrayVB(i + 1, 0) = x2
myArrayVB(i + 1, 1) = y2
N_1_myArrayVB = i + 1 'Значение границы массива.
Next
'Начало N_first_line и конец N_last_line цикла
'при рисовании из массива myArrayVB:
Dim N_first_line, N_last_line As Integer
N_first_line = first_line
N_last_line = last_line
'Передаем значения начала N_first_line
'и конца цикла N_last_line в элементы массива
'myArrayVB(0, 0) и myArrayVB(0, 1):
myArrayVB(0, 0) = N_first_line
myArrayVB(0, 1) = N_last_line
' Рисуем при помощи массива координат myArrayVB(200, 1).
i = -1
For k = N_first_line To N_last_line
i = i + 2
x1 = myArrayVB(i, 0)
y1 = myArrayVB(i, 1)
x2 = myArrayVB(i + 1, 0)
y2 = myArrayVB(i + 1, 1)
g.DrawLine(pen, x1, y1, x2, y2)
Next
'Записываем массив координат myArrayVB(200, 1) в файл.
'Создаем объект sw класса StreamWriter для записи
'в файл по адресу D:\MyDocs\MyTest3D_Graphics.txt.
If N_Graphics = 1 Then
Dim sw As StreamWriter = _
New StreamWriter("D:\MyDocs\MyTest3D_Graphics.txt")
'Каждый элемент массива myArrayVB(i, j) записываем в файл
'в виде отдельной строки при помощи процедуры WriteLine:
For i = 0 To N_x
For j = 0 To N_y
sw.WriteLine(myArrayVB(i, j))
Next
Next
sw.Close()
End If
M2:
'Если этот метод DrawSolid вызван первый раз
'для рисования первого изображения и N_Graphics = 1,
'то обходим 2-й массив для 2-го изобр-я до метки M_End:
If N_Graphics = 1 Then GoTo M_End
'Программируем второй массив для второго изображения.
'Задаем границы индексов 2-го массива myArrayVB_2(i, j):
Dim N_x_2 As Integer = 200
Dim N_y_2 As Integer = 1
'Задаем массив myArrayVB_2(i, j) переменных типа Single,
'когда i = 0,1,2,3,...,N_x; j = 0,1,2,3,...,N_y:
Dim myArrayVB_2(N_x_2, N_y_2) As Single
'Значение первой границы массива myArrayVB_2:
Dim N_1_myArrayVB_2 As Integer
'Рассчитываем элементы массива myArrayVB_2(i, j)
'для рисования линий второго геом-го изображения:
i = -1 'Задаем до цикла.
For k = first_line To last_line
x1 = Lines(k).fr_tr_points(1)
y1 = Lines(k).fr_tr_points(2)
x2 = Lines(k).to_tr_points(1)
y2 = Lines(k).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) _
')
'Масштабируем значения координат:
x1 = (x1 * bmp.Width / 4) + bmp.Width / 2.0F
y1 = bmp.Height / 2.0F - (y1 * bmp.Height / 4)
x2 = (x2 * bmp.Width / 4) + bmp.Width / 2.0F
y2 = bmp.Height / 2.0F - (y2 * bmp.Height / 4)
'Записываем координаты точек в массив:
i = i + 2
myArrayVB_2(i, 0) = x1
myArrayVB_2(i, 1) = y1
myArrayVB_2(i + 1, 0) = x2
myArrayVB_2(i + 1, 1) = y2
N_1_myArrayVB_2 = i + 1 'Значение границы массива.
Next
'Начало N_first_line_2 и конец N_last_line_2 цикла
'при рисовании из массива myArrayVB_2:
Dim N_first_line_2, N_last_line_2 As Integer
N_first_line_2 = first_line
N_last_line_2 = last_line
'Передаем значения начала N_first_line_2
'и конца цикла N_last_line_2 в элементы массива
'myArrayVB_2(0, 0) и myArrayVB_2(0, 1):
myArrayVB_2(0, 0) = N_first_line_2
myArrayVB_2(0, 1) = N_last_line_2
' Рисуем при помощи массива координат myArrayVB_2(200, 1):
i = -1
For k = N_first_line_2 To N_last_line_2
i = i + 2
x1 = myArrayVB_2(i, 0)
y1 = myArrayVB_2(i, 1)
x2 = myArrayVB_2(i + 1, 0)
y2 = myArrayVB_2(i + 1, 1)
g.DrawLine(pen, x1, y1, x2, y2)
Next
'Записываем массив координат myArrayVB_2(200, 1) в файл.
'Создаем объект sw_2 класса StreamWriter для записи
'в файл по адресу D:\MyDocs\MyTest3D_Graphics_2.txt.
'Файл автоматически "опустошается":
Dim sw_2 As StreamWriter = _
New StreamWriter("D:\MyDocs\MyTest3D_Graphics_2.txt")
'Каждый элемент массива myArrayVB_2(i, j) запис-м в файл
' в виде отдельной строки при помощи процедуры WriteLine:
For i = 0 To N_x_2
For j = 0 To N_y_2
sw_2.WriteLine(myArrayVB_2(i, j))
Next
Next
sw_2.Close()
'Высвобождаем ресурсы от объектов g и pen:
g.Dispose() : pen.Dispose()
M_End:
'Если эта метод DrawSolid вызвана еще раз
'для рисования следующего изображения,
'то увеличиваем номер изображения N_Graphics на 1:
N_Graphics = N_Graphics + 1
End Sub
'Строим единичную матрицу:
Public Sub MatrixIdentity(ByRef M(,) As Single)
Dim i As Integer : Dim j As Integer
For i = 1 To 4
For j = 1 To 4
If i = j Then
M(i, j) = 1
Else
M(i, j) = 0
End If
Next
Next
End Sub
'Строим матрицу преобразования (3-D transformation matrix)
'для перспективной проекции вдоль оси z на плоскость x,y
'с центром объекта (фокусом) в начале координат
'и c центром проецирования на расстоянии (0, 0, Distance):
Public Sub MatrixPerspectiveXZ(ByRef M(,) As Single, _
ByVal Distance As Single)
MatrixIdentity(M)
If Distance <> 0 Then M(3, 4) = -1 / Distance
End Sub
'Строим матрицу преобразования (3-D transformation matrix)
'для проецирования с координатами:
'центр проецирования (cx, cy, cz),
'фокус (fx, fy, fx),
'вектор от объекта до экрана UP <ux, yx, uz>,
'тип проецирования (type_of_projection):
'PerspectiveProjection или ParallelProjection:
Public Sub MatrixTransformation(ByRef M(,) As Single, _
ByVal type_of_projection As Integer, _
ByVal Cx As Single, _
ByVal Cy As Single, ByVal Cz As Single, _
ByVal Fx As Single, ByVal Fy As Single, _
ByVal Fz As Single, ByVal ux As Single, _
ByVal uy As Single, ByVal uz As Single)
Static M1(4, 4) As Single : Static M2(4, 4) As Single
Static M3(4, 4) As Single : Static M4(4, 4) As Single
Static M5(4, 4) As Single : Static M12(4, 4) As Single
Static M34(4, 4) As Single
Static M1234(4, 4) As Single
Dim sin1 As Single : Dim cos1 As Single
Dim sin2 As Single : Dim cos2 As Single
Dim sin3 As Single : Dim cos3 As Single
Dim A As Single : Dim B As Single
Dim C As Single : Dim d1 As Single
Dim d2 As Single : Dim d3 As Single
Dim up1(4) As Single : Dim up2(4) As Single
'Переносим фокус (центр объекта) в начало координат:
MatrixTranslate(M1, -Fx, -Fy, -Fz)
A = Cx - Fx : B = Cy - Fy : C = Cz - Fz
d1 = CSng(Sqrt(A * A + C * C))
If d1 <> 0 Then
sin1 = -A / d1 : cos1 = C / d1
End If
d2 = CSng(Sqrt(A * A + B * B + C * C))
If d2 <> 0 Then
sin2 = B / d2 : cos2 = d1 / d2
End If
'Вращаем объект вокруг оси y, чтобы разместить
'центр проекции в y-z плоскости:
MatrixIdentity(M2)
'Если d1 = 0, тогда центр проекции
'уже находится на оси y и в y-z плоскости:
If d1 <> 0 Then
M2(1, 1) = cos1 : M2(1, 3) = -sin1
M2(3, 1) = sin1 : M2(3, 3) = cos1
End If
'Вращаем вокруг оси x,
'чтобы разместить центр проекции на оси Z.
MatrixIdentity(M3)
'Если d2 = 0, то центр проекции
'находится в начале координат.
'Это делает проекцию невозможной.
If d2 <> 0 Then
M3(2, 2) = cos2 : M3(2, 3) = sin2
M3(3, 2) = -sin2 : M3(3, 3) = cos2
End If
'Вращаем вектор UP:
up1(1) = ux : up1(2) = uy : up1(3) = uz
up1(4) = 1 : MatrixApply(up1, M2, up2)
MatrixApply(up2, M3, up1)
' Rotate around the Z axis to put the UP
' vector in the Y-Z plane.
'Вращаем вокруг оси z, чтобы разместить
'вектор UP в y-z плоскости:
d3 = CSng(Sqrt(up1(1) * up1(1) + _
up1(2) * up1(2)))
MatrixIdentity(M4)
'Если d3 = 0, то вектор UP равен нулю:
If d3 <> 0 Then
sin3 = up1(1) / d3 : cos3 = up1(2) / d3
M4(1, 1) = cos3 : M4(1, 2) = sin3
M4(2, 1) = -sin3 : M4(2, 2) = cos3
End If
'Проецируем:
If type_of_projection = _
PerspectiveProjection And d2 <> 0 Then
MatrixPerspectiveXZ(M5, d2)
Else
MatrixIdentity(M5)
End If
'Комбинируем преобразования:
m3MatMultiply(M12, M1, M2)
m3MatMultiply(M34, M3, M4)
m3MatMultiply(M1234, M12, M34)
If type_of_projection = PerspectiveProjection Then
m3MatMultiplyFull(M, M1234, M5)
Else
m3MatMultiply(M, M1234, M5)
End If
End Sub
'Строим матрицу преобразования (3-D transformation matrix)
'для перспективного проецирования (perspective projection):
'центр проецирования (r, phi, theta),
'фокус (fx, fy, fx),
'вектор от объекта до экрана UP <ux, yx, uz>,
'тип проецирования (type_of_projection):
'PerspectiveProjection:
Public Sub Projection(ByRef M(,) As Single, _
ByVal type_of_projection As Integer, ByVal R As Single, _
ByVal phi As Single, ByVal theta As Single, _
ByVal Fx As Single, ByVal Fy As Single, ByVal Fz As Single, _
ByVal ux As Single, ByVal uy As Single, ByVal uz As Single)
Dim Cx As Single : Dim Cy As Single
Dim Cz As Single : Dim r2 As Single
'Переходим к прямоугольным координатам:
Cy = R * CSng(Sin(phi))
r2 = R * CSng(Cos(phi))
Cx = r2 * CSng(Cos(theta))
Cz = r2 * CSng(Sin(theta))
MatrixTransformation(M, type_of_projection, _
Cx, Cy, Cz, Fx, Fy, Fz, ux, uy, uz)
End Sub
' Строим матрицу преобразования, чтобы получить
'отражение напротив плоскости, проходящей
'через (p1, p2, p3) с вектором нормали <n1, n2, n3>:
Public Sub m3Reflect(ByRef M(,) As Single, _
ByVal p1 As Single, ByVal p2 As Single, _
ByVal p3 As Single, ByVal n1 As Single, _
ByVal n2 As Single, ByVal n3 As Single)
Dim T(4, 4) As Single 'Перенос.
Dim R1(4, 4) As Single 'Вращение 1.
Dim r2(4, 4) As Single 'Вращение 2.
Dim S(4, 4) As Single 'Отражение.
Dim R2i(4, 4) As Single 'Не вращать 2.
Dim R1i(4, 4) As Single 'Не вращать 1.
Dim Ti(4, 4) As Single 'Не переносить.
Dim D As Single : Dim L As Single
Dim M12(4, 4) As Single : Dim M34(4, 4) As Single
Dim M1234(4, 4) As Single
Dim M56(4, 4) As Single : Dim M567(4, 4) As Single
'Переносим плоскость к началу координат:
MatrixTranslate(T, -p1, -p2, -p3)
MatrixTranslate(Ti, p1, p2, p3)
'Вращаем вокруг оси z,
'пока нормаль не будет в y-z плоскости:
MatrixIdentity(R1)
D = CSng(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(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(r2)
L = CSng(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(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(S)
S(2, 2) = -1
'Комбинируем матрицы:
m3MatMultiply(M12, T, R1) : m3MatMultiply(M34, r2, S)
m3MatMultiply(M1234, M12, M34)
m3MatMultiply(M56, R2i, R1i)
m3MatMultiply(M567, M56, Ti)
m3MatMultiply(M, M1234, M567)
End Sub
' Строим матрицу преобразования для поворота на угол theta
'вокруг линии, проходящей через (p1, p2, p3)
'в направлении <d1, d2, d3>.
'Угол theta откладывается против часовой стрелки,
'если мы смотрим вниз в направлении,
'противоположном направлению линии:
Public Sub m3LineRotate(ByRef M(,) As Single, _
ByVal p1 As Single, ByVal p2 As Single, ByVal p3 As Single, _
ByVal d1 As Single, ByVal d2 As Single, ByVal d3 As Single, _
ByVal theta As Single)
Dim T(4, 4) As Single 'Перенос.
Dim R1(4, 4) As Single 'Вращение 1.
Dim r2(4, 4) As Single 'Вращение 2.
Dim Rot3(4, 4) As Single 'Вращение.
Dim R2i(4, 4) As Single 'Стоп вращению 2.
Dim R1i(4, 4) As Single 'Стоп вращению 1.
Dim Ti(4, 4) As Single 'Стоп переносу.
Dim D As Single : Dim L As Single
Dim M12(4, 4) As Single : Dim M34(4, 4) As Single
Dim M1234(4, 4) As Single
Dim M56(4, 4) As Single : Dim M567(4, 4) As Single
'Переносим плоскость к началу координат:
MatrixTranslate(T, -p1, -p2, -p3)
MatrixTranslate(Ti, p1, p2, p3)
'Вращаем вокруг оси z,
'пока линия не окажется в y-z плоскости:
MatrixIdentity(R1)
D = CSng(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(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(r2)
L = CSng(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(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(Rot3, theta)
'Комбинируем матрицы:
m3MatMultiply(M12, T, R1)
m3MatMultiply(M34, r2, Rot3)
m3MatMultiply(M1234, M12, M34)
m3MatMultiply(M56, R2i, R1i)
m3MatMultiply(M567, M56, Ti)
m3MatMultiply(M, M1234, M567)
End Sub
'Строим матрицу преобразования (3-D transformation matrix)
' для переноса на Tx, Ty, Tz:
Public Sub MatrixTranslate(ByRef M(,) As Single, _
ByVal Tx As Single, ByVal Ty As Single, ByVal Tz As Single)
MatrixIdentity(M)
M(4, 1) = Tx : M(4, 2) = Ty : M(4, 3) = Tz
End Sub
'Строим матрицу преобразования (3-D transformation matrix)
'для поворота вокруг оси y (угол - в радианах):
Public Sub MatrixYRotate(ByRef M(,) As Single, _
ByVal theta As Single)
MatrixIdentity(M)
M(1, 1) = CSng(Cos(theta))
M(3, 3) = M(1, 1)
M(3, 1) = CSng(Sin(theta))
M(1, 3) = -M(3, 1)
End Sub
'Применяем матрицу преобразования к точке,
'где матрица не может иметь 0, 0, 0, 1
'в последнем столбце. Нормализуем только
'x и y компоненты результата, чтобы сохранить z информацию:
Public Sub MatrixApplyFull(ByRef V() As Single, _
ByRef M(,) As Single, ByRef Result() As Single)
Dim i As Integer : Dim j As Integer
Dim value As Single
For i = 1 To 4
value = 0
For j = 1 To 4
value = value + V(j) * M(j, i)
Next j
Result(i) = value
Next i
'Повторно нормализуем точку (value = Result(4)):
If value <> 0 Then
Result(1) = Result(1) / value
Result(2) = Result(2) / value
'Не преобразовываем z-составляющую:
Else
'Если значение z больше, чем от центра проекции,
'эта точка будет удалена:
Result(3) = Single.MaxValue
End If
Result(4) = 1
End Sub
'Применяем матрицу преобразования к точке:
Public Sub MatrixApply(ByRef V() As Single, _
ByRef M(,) As Single, ByRef Result() As Single)
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
End Sub
'Умножаем две матрицы. Матрицы
'не могут содержать 0, 0, 0, 1 в последних столбцах:
Public Sub m3MatMultiplyFull(ByRef Result(,) As Single, _
ByRef A(,) As Single, ByRef B(,) As Single)
Dim i As Integer : Dim j As Integer
Dim k As Integer : Dim value As Single
For i = 1 To 4
For j = 1 To 4
value = 0
For k = 1 To 4
value = value + A(i, k) * B(k, j)
Next k
Result(i, j) = value
Next
Next
End Sub
'Умножаем две матрицы:
Public Sub m3MatMultiply(ByRef Result(,) As Single, _
ByRef A(,) As Single, ByRef B(,) As Single)
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
End Sub
Листинг 34.4. Метод для печати изображения с элемента PictureBox.
Private Sub PrintDocument1_PrintPage( _
ByVal sender As System.Object, _
ByVal e As System.Drawing.Printing.PrintPageEventArgs) _
Handles PrintDocument1.PrintPage
e.Graphics.DrawImage(PictureBox1.Image, 0, 0)
End Sub
Изображение и управление поверхностями в трехмерном пространстве
Листинг 37.1. Код выше и в теле метода Form1_Load.
//Вводим функцию для изображаемой поверхности z = f(x, y):
double f1;
double f(double x, double y)
{
//Параметры поверхности z = f(x, y)для эллипсоида:
double a = 1.03;
double b = 1.02;
double c = 1.01;
//Уравнение поверхности z = f(x, y) в виде эллипсоида:
f1 = Math.Sqrt(c * c * (200 - x * x / (a * a) –
y * y / (b * b)));
return f1;
}
//Точка наблюдения:
myClassPoint3D myEye = new myClassPoint3D();
//Концы числового интервала области задания поверхности:
const int x_max = 20;
const int y_max = 20;
const int x_min = -10;
const int y_min = -10;
//Массив узловых точек:
myClassPoint3D[,] Points = new myClassPoint3D[x_max + 1, y_max + 1];
//Точки на осях координат:
myClassPoint3D[] Axes = new myClassPoint3D[4];
//Загрузка данных и их инициализация:
private void Form1_Load(object sender, EventArgs e)
{
//Задаем координаты точки наблюдения:
myEye = new myClassPoint3D(40, 20, 20);
//Объявляем координаты точки:
double x, y, z; int i, j;
//В каждом (i,j)-м узле плоскости x,y
//рассчитываем координату z точки пов-ти z = f(x, y):
for (i = 0; i <= x_max; i++)
{
x = i + x_min;
for (j = 0; j <= y_max; j++)
{
y = j + y_min;
//Координата z точек пов-ти z = f(x, y):
z = f(x, y);
Points[i, j] = new myClassPoint3D(x, y, z);
}
}
//Инициализация осей (axes) координат:
Axes[0] = new myClassPoint3D(0, 0, 0);
Axes[1] = new myClassPoint3D(30, 0, 0);
Axes[2] = new myClassPoint3D(0, 30, 0);
Axes[3] = new myClassPoint3D(0, 0, 30);
}
Чтобы мы могли рисовать заданную поверхность и оси координат внутри графического элемента PictureBox, в панели Properties (для этого элемента) на вкладке Events дважды щелкаем по имени события Paint. Появляется файл Form1.cs с шаблоном (метода pictureBox1_Paint), который после записи кода принимает такой вид.
Листинг 37.2. Методо pictureBox1_Paint для рисования поверхности.
private void pictureBox1_Paint(object sender,
PaintEventArgs e)
{
// Масштабируем все графические объекты на PictureBox1.
//Коэффициенты масштабирования:
float M_1 = 29; float M_2 = 31;
e.Graphics.ScaleTransform(pictureBox1.Width / M_1,
-pictureBox1.Height / M_2, MatrixOrder.Append);
float M_3 = 2; float M_4 = 2;
e.Graphics.TranslateTransform(pictureBox1.Width / M_3,
pictureBox1.Height / M_4, MatrixOrder.Append);
//Задавая M_1, M_2, M_3, M_4 другие значения,
//мы будем смещать поверхность по отношению к осям x,y,z.
//Матрица преобразования (transformation matrix):
myClassMatrix3D tr = CalculateTransformation();
//Применяем матрицу преобразования к точкам:
for (int x = 0; x <= x_max; x++)
for (int y = 0; y <= y_max; y++)
Points[x, y].Transformation(tr);
//Объявляем индексы массива myArrayVCsharp[i, j]:
int i, j;
//Задаем границы индексов массива myArrayVCsharp[i, j]:
int N_x = 2000; int N_y = 1;
//Задаем массив myArrayVCsharp[i, j]переменных Single,
//когда i = 0,1,2,3,...,N_x; j = 0,1,2,3,...,N_y:
float[,] myArrayVCsharp = new float[N_x + 1, N_y + 1];
//Первая, вторая и третья границы массива, разделяющие
//линии поверхности, параллельные xz, yz, и оси:
int N_1_myArrayCsharp = 0, N_2_myArrayCsharp = 0,
N_3_myArrayCsharp = 0;
//Рассчитываем элементы массива myArrayVCsharp[i, j]
//для рисования линий поверхности,
//параллельных плоскости координат xz:
float x1, y1, x2, y2;
Pen myPen = new Pen(Color.Black, 0);
i = -2;//Задаем до циклов.
for (int x = 0; x <= x_max; x++)
{
x2 =
Convert.ToSingle(Points[x, 0].trans_coord[0]);
y2 =
Convert.ToSingle(Points[x, 0].trans_coord[1]);
for (int y = 1; y <= y_max; y++)
{
x1 = x2; y1 = y2;
x2 =
Convert.ToSingle(Points[x, y].trans_coord[0]);
y2 =
Convert.ToSingle(Points[x, y].trans_coord[1]);
//Можно рисовать линии поверхности и здесь:
//e.Graphics.DrawLine(myPen, x1, y1, x2, y2);
//Записываем в массив координат:
i = i + 2;
myArrayVCsharp[i, 0] = x1;
myArrayVCsharp[i, 1] = y1;
myArrayVCsharp[i + 1, 0] = x2;
myArrayVCsharp[i + 1, 1] = y2;
N_1_myArrayCsharp = i + 1; //1-я гран. в массиве.
}
}
//Рассчитываем элементы массива myArrayVCsharp[i, j]
//для рисования линий поверхности,
//параллельных плоскости координат yz:
i = N_1_myArrayCsharp - 1;
for (int y = 0; y <= y_max; y++)
{
x2 =
Convert.ToSingle(Points[0, y].trans_coord[0]);
y2 =
Convert.ToSingle(Points[0, y].trans_coord[1]);
for (int x = 1; x <= x_max; x++)
{
x1 = x2; y1 = y2;
x2 =
Convert.ToSingle(Points[x, y].trans_coord[0]);
y2 =
Convert.ToSingle(Points[x, y].trans_coord[1]);
//Можно рисовать линии поверхности и здесь:
//e.Graphics.DrawLine(myPen,x1,y1, x2, y2);
//Записываем в массив координат:
i = i + 2;
myArrayVCsharp[i, 0] = x1;
myArrayVCsharp[i, 1] = y1;
myArrayVCsharp[i + 1, 0] = x2;
myArrayVCsharp[i + 1, 1] = y2;
N_2_myArrayCsharp = i + 1; //2-я гран. в массиве.
}
}
//Преобразовываем оси(axes):
for (int k = 0; k <= 3; k++)
Axes[k].Transformation(tr);
// Рассчитываем элементы массива для рисования осей:
Pen myPenAxes = new Pen(Color.Red, 0);
i = N_2_myArrayCsharp - 1;
x1 = Convert.ToSingle(Axes[0].trans_coord[0]);
y1 = Convert.ToSingle(Axes[0].trans_coord[1]);
for (int k = 1; k <= 3; k++)
{
x2 = Convert.ToSingle(Axes[k].trans_coord[0]);
y2 = Convert.ToSingle(Axes[k].trans_coord[1]);
// Можно рисовать оси координат и здесь:
//e.Graphics.DrawLine(myPenAxes, x1, y1, x2, y2);
//Записываем в массив координат:
i = i + 2;
myArrayVCsharp[i, 0] = x1;
myArrayVCsharp[i, 1] = y1;
myArrayVCsharp[i + 1, 0] = x2;
myArrayVCsharp[i + 1, 1] = y2;
N_3_myArrayCsharp = i + 1; //Число чисел в массиве.
}
//Рисуем при помощи массива
//координат myArrayVCsharp[2000, 1].
//Рисуем линии, параллельные плоскости xz:
i = -2;
for (int x = 0; x <= x_max; x++)
{
for (int y = 1; y <= y_max; y++)
{
i = i + 2;
x1 = myArrayVCsharp[i, 0];
y1 = myArrayVCsharp[i, 1];
x2 = myArrayVCsharp[i + 1, 0];
y2 = myArrayVCsharp[i + 1, 1];
e.Graphics.DrawLine(myPen, x1, y1, x2, y2);
}
}
//Рисуем линии, параллельные плоскости yz:
i = N_1_myArrayCsharp - 1;
for (int y = 0; y <= y_max; y++)
{
for (int x = 1; x <= x_max; x++)
{
i = i + 2;
x1 = myArrayVCsharp[i, 0];
y1 = myArrayVCsharp[i, 1];
x2 = myArrayVCsharp[i + 1, 0];
y2 = myArrayVCsharp[i + 1, 1];
e.Graphics.DrawLine(myPen, x1, y1, x2, y2);
}
}
//Рисуем три оси координат:
i = N_2_myArrayCsharp - 1;
Pen myPen2 = new Pen(Color.Red, 0);
for (int k = 1; k <= 3; k++)
{
i = i + 2;
x1 = myArrayVCsharp[i, 0];
y1 = myArrayVCsharp[i, 1];
x2 = myArrayVCsharp[i + 1, 0];
y2 = myArrayVCsharp[i + 1, 1];
e.Graphics.DrawLine(myPen2, x1, y1, x2, y2);
}
//Записываем массив myArrayVCsharp[2000, 2] в файл.
// Создаем объект sw класса StreamWriter
//для записи в файл по адресу D:\MyDocs\MyTest.txt.
//Файл автоматически “опустошается”:
StreamWriter sw =
new StreamWriter("D:\\MyDocs\\MyTest.txt");
//Каждый элемент myArrayVCsharp[i, j] записываем в файл
//в виде отдельной строки при помощи процедуры WriteLine:
for (i = 0; i <= N_x; i++)
for (j = 0; j <= N_y; j++)
sw.WriteLine(myArrayVCsharp[i, j]);
sw.Close();
}//Конец метода pictureBox1_Paint.
Далее, чтобы мы могли управлять (например, вращать) объектами при помощи нажатия клавиш, в панели Properties (для элемента PictureBox) на вкладке Events мы должны дважды щелкнуть по имени события ProcessCmdKey, если это имя там имеется. Если же такого имени там нет, то мы полностью переписываем (или копируем с прилагаемого диска) нижеследующий метод.
Листинг 37.3. Метод ProcessCmdKey для вращения поверхности.
protected override bool ProcessCmdKey(
ref System.Windows.Forms.Message msg,
System.Windows.Forms.Keys keyData)
{
//Задаем шаг поворота поверхности:
const double delta_theta = Math.PI / 32;
const double delta_phi = Math.PI / 16;
//Вычисляем сферич-е коорд-ты (spherical coordinates)
//точки наблюдения:
double theta = Math.Atan2(myEye.orig_coord[1],
myEye.orig_coord[0]);
double r1 = Math.Sqrt(myEye.orig_coord[0] *
myEye.orig_coord[0]
+ myEye.orig_coord[1] * myEye.orig_coord[1]);
double r2 = Math.Sqrt(myEye.orig_coord[0] *
myEye.orig_coord[0]
+ myEye.orig_coord[1] * myEye.orig_coord[1] +
myEye.orig_coord[2] * myEye.orig_coord[2]);
double phi = Math.Atan2(myEye.orig_coord[2], r1);
//Корректируем углы phi и theta:
switch (keyData)
{
case Keys.Left:
theta = theta - delta_theta;
break;
case Keys.Up:
phi = phi - delta_phi;
if (phi < -Math.PI / 2) phi = -Math.PI / 2;
break;
case Keys.Right:
theta = theta + delta_theta;
break;
case Keys.Down:
phi = phi + delta_phi;
if (phi > Math.PI / 2) phi = Math.PI / 2;
break;
}
//Изменяем координаты точки наблюдения:
myEye.orig_coord[0] = r1 * Math.Cos(theta);
myEye.orig_coord[1] = r1 * Math.Sin(theta);
myEye.orig_coord[2] = r2 * Math.Sin(phi);
//Перерисовываем изображение внутри PictureBox1:
pictureBox1.Invalidate();
return true;
}
Ниже записываем следующий метод.
Листинг 37.4. Метод CalculateTransformation.
// Метод для вычисления матрицы преобразования
//для текущей точки наблюдения:
myClassMatrix3D CalculateTransformation()
{
//Поворачиваем вокруг оси z,
//чтобы точка наблюдения оказалась в плоскости y-z:
myClassMatrix3D transformation1 =
myClassMatrix3D.GetZRotPointToYZ(myEye);
//Поворачиваем вокруг оси x,
//чтобы точка наблюдения оказалась на оси z:
myClassMatrix3D transformation2 =
myClassMatrix3D.GetXRotPointToZ(myEye);
//Проецируем по оси z, игнорируя координату z.
//Для этого умножаем матрицы преобразования:
return (transformation1.TimesMatrix(transformation2));
}
Ниже этого автоматически сгенерированного класса Form1:
public partial class Form1 : System.Windows.Forms.Form
{
}
вводим два новых класса с методами преобразования систем координат.
Листинг 37.5. Два новых класса.
//Вводим класс с методами преобразования систем координат
//в трехмерном пространстве:
public class myClassPoint3D
{
//Массив из 4-х элементов для первоначальных координат
//(original coordinates); нулевой индекс используем:
public double[] orig_coord = new double[4];
//Массив для преобразованных координат:
public double[] trans_coord = new double[4];
//Создаем неинициализирующий конструктор этого класса:
public myClassPoint3D()
{
}
//Создаем инициализирующий конструктор:
public myClassPoint3D( double x, double y, double z)
{
orig_coord[0] = x;
orig_coord[1] = y;
orig_coord[2] = z;
double myScale;
myScale = 1; //Масштаб.
orig_coord[3] = myScale;
}
//Матрица преобразования (transformation matrix):
bool normalize = true;
public void Transformation(myClassMatrix3D matrix)
{
double value = 0;
myClassPoint3D result = new myClassPoint3D();
int i, j;
for (i = 0; i <= 3; i++)
{
value = 0;
for (j = 0; j <= 3; j++)
value = value + orig_coord[j] * matrix.M[j, i];
trans_coord[i] = value;
}
//Повторно нормализуем точку:
if (normalize == true)
{
//После выхода из цикла value = trans_coord[4]:
trans_coord[0] = trans_coord[0] / value;
trans_coord[1] = trans_coord[1] / value;
trans_coord[2] = trans_coord[2] / value;
trans_coord[3] = 1;
}
}
} //Конец класса public class myClassPoint3D.
//Вводим класс с методами преобразования координат точки
//в трехмерном пространстве:
public class myClassMatrix3D
{
//Матрица (matrix) в виде массива размера 4x4:
public double[,] M = new double[4, 4];
//Создаем неинициализирующий конструктор этого класса:
public myClassMatrix3D()
{
}
//Создаем конструктор, инициализирующий матрицу:
public myClassMatrix3D(
double m00, double m01, double m02, double m03,
double m10, double m11, double m12, double m13,
double m20, double m21, double m22, double m23,
double m30, double m31, double m32, double m33)
{
M[0, 0] = m00; M[0, 1] = m01;
M[0, 2] = m02; M[0, 3] = m03;
M[1, 0] = m10; M[1, 1] = m11;
M[1, 2] = m12; M[1, 3] = m13;
M[2, 0] = m20; M[2, 1] = m21;
M[2, 2] = m22; M[2, 3] = m23;
M[3, 0] = m30; M[3, 1] = m31;
M[3, 2] = m32; M[3, 3] = m33;
}
//Метод для умножения матрицы на матрицу справа:
public myClassMatrix3D TimesMatrix(
myClassMatrix3D right_matrix)
{
myClassMatrix3D result = new myClassMatrix3D();
double value; int i, j, k;
for (i = 0; i <= 3; i++)
{
for (j = 0; j <= 3; j++)
{
value = 0;
for (k = 0; k <= 3; k++)
{
value = value + M[i, k] *
right_matrix.M[k, j];
}
result.M[i, j] = value;
}
}
return result;
}
//Метод для поворота вокруг оси z
//до точки в y-z плоскости:
public static myClassMatrix3D GetZRotPointToYZ(
myClassPoint3D pt)
{
double R = Math.Sqrt(
pt.orig_coord[0] * pt.orig_coord[0] +
pt.orig_coord[1] * pt.orig_coord[1]);
double stheta = pt.orig_coord[0] / R;
double ctheta = pt.orig_coord[1] / R;
return new myClassMatrix3D(
ctheta, stheta, 0, 0,
-stheta, ctheta, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
}
//Метод для поворота вокруг оси x до точки на оси z:
public static myClassMatrix3D
GetXRotPointToZ(myClassPoint3D pt)
{
double R1 = Math.Sqrt(
pt.orig_coord[0] * pt.orig_coord[0] +
pt.orig_coord[1] * pt.orig_coord[1]);
double R2 = Math.Sqrt(
pt.orig_coord[0] * pt.orig_coord[0] +
pt.orig_coord[1] * pt.orig_coord[1] +
pt.orig_coord[2] * pt.orig_coord[2]);
double sphi = -R1 / R1;
double cphi = -pt.orig_coord[2] / R2;
return new myClassMatrix3D(
1, 0, 0, 0,
0, cphi, sphi, 0,
0, -sphi, cphi, 0,
0, 0, 0, 1);
}
} //Конец класса class myClassMatrix3D.
Изображение и управление трехмерными объектами в трехмерном пространстве
Листинг 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);
}
Изображение кругов на воде с использованием двойной буферизации
Листинг 62.1. Шаблон метода Form1_Load с кодом.
//Массив окружностей:
myClass[] Circles = new myClass[100];
//В структуре Point объявляем объект myPoint:
Point myPoint;
//Генератор случайных чисел:
Random myRandow;
int c, j;
private void myMethod()
{
Circles[c] = new myClass(myPoint);
c = c + 1;
if (c >= Circles.Length) c = 0;
}
private void Form1_Load(object sender, EventArgs e)
{
//Используем двойную буферизацию:
this.SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint, true);
myRandow = new Random();
myMethod();
}
В панели Properties для элемента управления PictureBox на вкладке Events дважды щелкаем по имени события Paint и в шаблон записываем следующий код.
Листинг 62.2. Шаблон метода pictureBox1_Paint с кодом.
private void pictureBox1_Paint(object sender,
PaintEventArgs e)
{
Color myColor = Color.Black;
float f;
e.Graphics.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.HighQuality;
int k;
for (k = 0; k <= c - 1; k++)
{
f = 0.05f * Circles[k].value;
e.Graphics.DrawEllipse(new Pen(myColor, f),
Circles[k].pos.X - Circles[k].value,
Convert.ToInt32(Circles[k].pos.Y - Circles[k].value / 4),
Circles[k].value * 2,
Convert.ToInt32(Circles[k].value / 2));
Circles[k].value = Circles[k].value + 10;
if (Circles[k].value > 100) Circles[k].Reset();
}
}
Чтобы подключить к работе таймер, дважды щелкаем значок timer1 (ниже формы в режиме проектирования). Появляется файл Form1.cs с шаблоном, который после записи нашего кода принимает такой вид.
Листинг 62.3. Метод для компонента Timer1.
private void timer1_Tick(object sender, EventArgs e)
{
myMethod();
Refresh();
j = j + 1;
if (j > myRandow.Next(30))
{
j = 0;
myPoint.X = myRandow.Next(pictureBox1.Width);
myPoint.Y = myRandow.Next(pictureBox1.Height);
c = 0;
}
}
Листинг 62.4. Дополнительный класс.
class myClass
{
public Point pos = Point.Empty;
public int value = 0;
public myClass(Point pos)
{
Reset();
this.pos = pos;
}
public void Reset()
{
pos = Point.Empty;
value = 0;
}
}
Изображение линий уровня поверхности на основе нового синтаксиса Visual C++
Листинг 42.1. Код для рисования изображения.
//Концы числового интервала области задания поверхности:
private: static const float x_min = -1.5;
private: static const float x_max = 1.5;
private: static const float y_min = -1.5;
private: static const float y_max = 1.5;
private:
System::Void pictureBox1_Paint(System::Object^ sender,
System::Windows::Forms::PaintEventArgs^ e)
{
//Масштабируем объекты класса Graphics на pictureBox1.
//Коэффициенты масштабирования:
float M_1 = 2 * (x_max - x_min);
float M_2 = 2 * (y_max - y_min);
e->Graphics->ScaleTransform(
Convert::ToSingle(pictureBox1->Width / M_1),
Convert::ToSingle(pictureBox1->Height / M_2));
float M_3 = 2 * (-x_min); float M_4 = 2 * (-y_min);
e->Graphics->TranslateTransform(M_3, M_4,
MatrixOrder::Prepend);
//Задавая M_1, M_2, M_3, M_4 другие значения,
//мы будем смещать линии уровня на pictureBox1.
//Объявляем индексы элементов всех массивов:
int i, j, k;
//Задаем границы индексов массива myArrayVC(i, j):
int N_x = 20001; int N_y = 2;
//Объявляем массив myArrayVC[i, j] пер-х типа float,
//когда i = 0,1,2,3,...,(N_x - 1);
// j = 0,1,2,3,...,(N_y - 1):
array<float,2>^ myArrayVC =
gcnew array<float,2>(N_x, N_y);
//Для считывания из файла
//по адресу D:\\MyDocs\\MyTest_LevelCurves.txt
//координат изображения в массив myArrayVC(20001, 2)
//создаем объект sr класса StreamReader:
String^ path = "D:\\MyDocs\\MyTest_LevelCurves.txt";
StreamReader^ sr = gcnew StreamReader(path);
//Считываем из файла MyTest_LevelCurves.txt
//координаты изображения в массив myArrayVC(20001, 2)
//при помощи метода ReadLine:
for (i = 0; i <= N_x - 1; i++)
for (j = 0; j <= N_y - 1; j++)
myArrayVC[i, j] = Convert::ToSingle(sr->ReadLine());
sr->Close();
//Рисуем изображение по координатам из массива.
//Объявляем координаты двух точек:
float x1, y1, x2, y2;
// Будем рисовать пером myPen толщиной 0:
Pen^ myPen = gcnew Pen(Color::Black, 0);
//Объявляем переменные для начала N_first_line
//и конца N_last_line цикла при рисовании:
int N_first_line, N_last_line;
//Передаем переменным для начала и конца цикла
//значения первых двух элементов массива myArrayVC:
N_first_line = Convert::ToInt32(myArrayVC[0, 0]);
N_last_line = Convert::ToInt32(myArrayVC[0, 1]);
//Рисуем изображение, начиная с точки (1,0; 1,1):
i = -1;
for (k = N_first_line; k <= N_last_line; k++)
{
i = i + 2;
x1 = myArrayVC[i, 0];
y1 = myArrayVC[i, 1];
x2 = myArrayVC[i + 1, 0];
y2 = myArrayVC[i + 1, 1];
e->Graphics->DrawLine(myPen, x1, y1, x2, y2);
}
}
Аналогично по этой методологии мы можем разработать другие приложения для построения и управления различными пространственными изображениями.
Изображение объектов
Imports System.Math 'Для математических функций.
Imports System.Drawing 'Для рисования изображений.
Imports System.Drawing.Drawing2D 'Для рисования.
6. В тело этого класса Class1 записываем, для примера, следующую произвольную функцию для расчета массива с координатами точек графика.
Листинг 43.1. Метод для расчета массива с координатами точек графика.
Public Class Class1
Public Function myFunction1() As Object
'Объявляем индексы элементов массива myArrayVB(i, j):
Dim i, j As Integer
'Задаем границы индексов массива myArrayVB(i, j):
Dim N_x As Integer = 2000
Dim N_y As Integer = 1
'Массив переменных Single в виде myArrayVB(i, j),
'когда i = 0,1,2,3,...,N_x; j = 0,1,2,3,...,N_y:
Dim myArrayVB(N_x, N_y) As Single
'Задаем произвольное значение 10 в двойном цикле:
For i = 0 To N_x
For j = 0 To N_y
myArrayVB(i, j) = 10
Next
Next
'Произвольные координаты 4-х точек ломаной линии:
myArrayVB(0, 0) = 100
myArrayVB(0, 1) = 50
myArrayVB(1, 0) = 200
myArrayVB(1, 1) = 75
myArrayVB(2, 0) = 300
myArrayVB(2, 1) = 150
myArrayVB(3, 0) = 400
myArrayVB(3, 1) = 300
Return myArrayVB
End Function
End Class
Листинг 43.2. Процедура PictureBox1_Paint для рисования.
Private Sub PictureBox1_Paint(ByVal sender As Object, _
ByVal e As System.Windows.Forms.PaintEventArgs) _
Handles PictureBox1.Paint
'Объявляем объект myObject класса Class1 компонента VB:
Dim myObject As New ComponentVB.Class1
'Объявляем индексы элементов массива myArrayVC(i, j):
Dim i, j As Integer
'Задаем границы индексов массива myArrayVC(i, j):
Dim N_x As Integer = 2000
Dim N_y As Integer = 1
'Объявляем массив myArrayVC(i, j) переменных типа float,
'когда i = 0,1,2,3,...,(N_x - 1);
' j = 0,1,2,3,...,(N_y - 1):
Dim myArrayVB_Client(N_x, N_y) As Single
'Экспортируем массив myArrayVB(i, j) компонента VB
'в массив myArrayVB_Client(i, j) клиента VB:
For i = 0 To N_x
For j = 0 To N_y
myArrayVB_Client(i, j) = myObject.myFunction1(i, j)
Next
Next
'Рисуем изображение из массива.
'Объявляем координаты двух граничных точек:
Dim x1, y1, x2, y2 As Single
'Будем рисовать пером myPen толщиной 3:
Dim myPen As New Pen(Color.Black, 3)
'Рисуем линию, заданную в массиве myArrayVB_Client(i, j):
x1 = myArrayVB_Client(0, 0)
y1 = myArrayVB_Client(0, 1)
x2 = myArrayVB_Client(1, 0)
y2 = myArrayVB_Client(1, 1)
e.Graphics.DrawLine(myPen, x1, y1, x2, y2)
x1 = x2
y1 = y2
x2 = myArrayVB_Client(2, 0)
y2 = myArrayVB_Client(2, 1)
e.Graphics.DrawLine(myPen, x1, y1, x2, y2)
x1 = x2
y1 = y2
x2 = myArrayVB_Client(3, 0)
y2 = myArrayVB_Client(3, 1)
e.Graphics.DrawLine(myPen, x1, y1, x2, y2)
End Sub
Листинг 43.3. Метод pictureBox1_Paint для рисования.
private void pictureBox1_Paint(object sender,
PaintEventArgs e)
{
//Объявляем объект myObject кл. Class1 компонента VB:
ComponentVB.Class1 myObject =
new ComponentVB.Class1();
//Объявляем индексы элементов массива myArrayVC(i, j):
int i, j;
//Задаем границы индексов массива myArrayVC(i, j):
int N_x = 2001; int N_y = 2;
//Объявляем массив myArrayVC[i, j] перем-х типа float,
//когда i = 0,1,2,3,...,(N_x - 1);
// j = 0,1,2,3,...,(N_y - 1):
float[,] myArrayVC = new float[N_x, N_y];
//Объявляем вспомогательный массив myArray класса Array
//и связываем его с функцией myFunction1, которая
//возвращает массив myArrayVB(i, j) компонента VB:
Array myArray = (Array)myObject.myFunction1();
//Экспортируем массив myArrayVB(i, j) компонента VB
//в массив myArrayVC[i, j] клиента VC:
for (i = 0; i <= N_x - 1; i++)
for (j = 0; j <= N_y - 1; j++)
myArrayVC[i, j] =
Convert.ToSingle(myArray.GetValue(i,j));
//Рисуем изображение из массива.
//Объявляем координаты двух граничных точек:
float x1, y1, x2, y2;
// Будем рисовать пером myPen толщиной 3:
Pen myPen = new Pen(Color.Black, 3);
//Рисуем линию, заданную в массиве myArrayVC[i, j]:
x1 = myArrayVC[0, 0];
y1 = myArrayVC[0, 1];
x2 = myArrayVC[1, 0];
y2 = myArrayVC[1, 1];
e.Graphics.DrawLine(myPen, x1, y1, x2, y2);
x1 = x2; y1 = y2;
x2 = myArrayVC[2, 0]; y2 = myArrayVC[2, 1];
e.Graphics.DrawLine(myPen, x1, y1, x2, y2);
x1 = x2; y1 = y2;
x2 = myArrayVC[3, 0]; y2 = myArrayVC[3, 1];
e.Graphics.DrawLine(myPen, x1, y1, x2, y2);
}
Листинг 43.4.
Метод pictureBox1_Paint для рисования.
private:
System::Void pictureBox1_Paint(System::Object^ sender,
System::Windows::Forms::PaintEventArgs^ e)
{
//Объявляем объект myObject класса Class1 компонента VB:
ComponentVB::Class1^ myObject =
gcnew ComponentVB::Class1();
//Объявляем индексы элементов массива myArrayVC(i, j):
int i, j;
//Задаем границы индексов массива myArrayVC(i, j):
int N_x = 2001; int N_y = 2;
//Объявляем массив myArrayVC[i, j] переменных типа float,
//когда i = 0,1,2,3,...,(N_x - 1);
// j = 0,1,2,3,...,(N_y - 1):
array<float,2>^ myArrayVC =
gcnew array<float,2>(N_x, N_y);
//Объявляем вспомогательный массив myArray класса Array
//и связываем его с функцией myFunction1, которая
//возвращает массив myArrayVB[i, j] компонента VB:
Array^ myArray = (Array^)myObject->myFunction1();
//Экспортируем массив myArrayVB(i, j) компонента VB
//в массив myArrayVC[i, j] клиента VC:
for (i = 0 ; i <= N_x - 1; i++)
for (j = 0 ; j <= N_y - 1; j++)
myArrayVC[i, j] =
Convert::ToSingle(myArray->GetValue(i,j));
//Рисуем изображение из массива.
//Объявляем координаты двух граничных точек:
float x1, y1, x2, y2;
//Будем рисовать пером myPen толщиной 3:
Pen^ myPen = gcnew Pen(Color::Black, 3);
//Рисуем линию, заданную в массиве myArrayVC[i, j]:
x1 = myArrayVC[0, 0];
y1 = myArrayVC[0, 1];
x2 = myArrayVC[1, 0];
y2 = myArrayVC[1, 1];
e->Graphics->DrawLine(myPen, x1, y1, x2, y2);
x1 = x2; y1 = y2;
x2 = myArrayVC[2, 0]; y2 = myArrayVC[2, 1];
e->Graphics->DrawLine(myPen, x1, y1, x2, y2);
x1 = x2; y1 = y2;
x2 = myArrayVC[3, 0]; y2 = myArrayVC[3, 1];
e->Graphics->DrawLine(myPen, x1, y1, x2, y2);
}
Изображение объектов в трехмерном пространстве на основе нового синтаксиса Visual C++
Листинг 36.1. Метод для рисования геометрических изображений.
private: System::Void pictureBox1_Paint(
System::Object^ sender,
System::Windows::Forms::PaintEventArgs^ e)
{
//Объявляем индексы элементов всех массивов:
int i, j, k;
//Задаем границы индексов 1-го массива myArrayVC(i, j):
int N_x = 201; int N_y = 2;
//Объявляем 1-й массив myArrayVC[i, j] переменных float,
//когда i = 0,1,2,3,...,(N_x - 1);
// j = 0,1,2,3,...,(N_y - 1):
array<float,2>^ myArrayVC =
gcnew array<float,2>(N_x, N_y);
//Для считывания из файла
//по адресу D:\\MyDocs\\MyTest3D_Graphics.txt
//координат изображения в массив myArrayVC(201, 2)
//создаем объект sr класса StreamReader:
StreamReader^ sr = gcnew StreamReader(
"D:\\MyDocs\\MyTest3D_Graphics.txt");
//Считываем из файла MyTest3D_Graphics.txt
//координаты изображения в массив myArrayVC(201, 2)
//при помощи метода ReadLine:
for (i = 0; i <= N_x - 1; i++)
for (j = 0; j <= N_y - 1; j++)
myArrayVC[i, j] =
Convert::ToSingle(sr->ReadLine());
sr->Close();
//Рисуем первое изображение по координатам из массива.
//Объявляем координаты двух точек:
float x1, y1, x2, y2;
//Будем рисовать пером myPen толщиной 3 пикселя:
Pen^ myPen = gcnew Pen(Color::Black, 3);
//Объявляем переменные для начала N_first_line
//и конца N_last_line цикла при рисовании:
int N_first_line, N_last_line;
//Передаем переменным для начала и конца цикла
//значения первых двух элементов массива myArrayVC:
N_first_line = Convert::ToInt32(myArrayVC[0, 0]);
N_last_line = Convert::ToInt32(myArrayVC[0, 1]);
//Рисуем изображение, начиная с точки (1,0; 1,1):
i = -1;
for (k = N_first_line; k <= N_last_line; k++)
{
i = i + 2;
x1 = myArrayVC[i, 0];
y1 = myArrayVC[i, 1];
x2 = myArrayVC[i + 1, 0];
y2 = myArrayVC[i + 1, 1];
e->Graphics->DrawLine(myPen, x1, y1, x2, y2);
}
//Задаем границы индексов 2-го массива myArrayVC_2(i, j):
int N_x_2 = 201; int N_y_2 = 2;
//Объявляем 2-й массив myArrayVC_2(i, j) перем-х float,
//когда i = 0,1,2,3,...,(N_x_2 - 1);
// j = 0,1,2,3,...,(N_y_2 - 1):
array<float,2>^ myArrayVC_2 =
gcnew array<float,2>(N_x_2, N_y_2);
//Для считывания из файла
//по адресу D:\\MyDocs\\MyTest3D_Graphics_2.txt
//координат изображения в массив myArrayVC_2(201, 2)
// создаем объект sr_2 класса StreamReader:
StreamReader^ sr_2 = gcnew StreamReader(
"D:\\MyDocs\\MyTest3D_Graphics_2.txt");
//Считываем из файла MyTest3D_Graphics_2.txt
//координаты изображения в массив myArrayVC_2(201, 2)
//при помощи метода ReadLine:
for (i = 0; i <= N_x - 1; i++)
for (j = 0; j <= N_y - 1; j++)
myArrayVC_2[i, j] =
Convert::ToSingle(sr_2->ReadLine());
sr_2->Close();
//Рисуем второе изображение по координатам из массива.
//Будем рисовать пером myPen толщиной 1:
Pen^ myPen_2 = gcnew Pen(Color::Black, 1);
//Объявляем переменные для начала N_first_line
//и конца N_last_line цикла при рисовании:
int N_first_line_2, N_last_line_2;
//Передаем переменным для начала и конца цикла
//значения первых двух элементов массива myArrayVC:
N_first_line_2 = Convert::ToInt32(myArrayVC_2[0, 0]);
N_last_line_2 = Convert::ToInt32(myArrayVC_2[0, 1]);
//Рисуем изображение, начиная с точки (1,0; 1,1):
i = -1;
for (k = N_first_line_2; k <= N_last_line_2; k++)
{
i = i + 2;
x1 = myArrayVC_2[i, 0]; y1 = myArrayVC_2[i, 1];
x2 = myArrayVC_2[i + 1, 0];y2 = myArrayVC_2[i + 1, 1];
e->Graphics->DrawLine(myPen_2, x1, y1, x2, y2);
}
}
Аналогично по этой методологии мы можем разработать другие приложения для построения различных пространственных изображений.
Изображение поверхностей
Листинг 39.1. Метод для рисования поверхности.
//Концы числового интервала области задания поверхно-сти:
private: static const int x_max = 20;
private: static const int y_max = 20;
private: static const int x_min = -10;
private: static const int y_min = -10;
private: System::Void pictureBox1_Paint(
System::Object^ sender,
System::Windows::Forms::PaintEventArgs^ e)
{
//Масштабируем объекты класса Graphics на pictureBox1.
//Коэффициенты масштабирования:
float M_1 = 31; float M_2 = 29;
e->Graphics->ScaleTransform(
Convert::ToSingle(pictureBox1->Width / M_1),
Convert::ToSingle(-pictureBox1->Height / M_2),
MatrixOrder::Append);
float M_3 = 1.9f;
float M_4 = 1.7f;
e->Graphics->TranslateTransform(
Convert::ToSingle(pictureBox1->Width / M_3),
Convert::ToSingle(pictureBox1->Height / M_4),
MatrixOrder::Append);
//Задавая M_1, M_2, M_3, M_4 другие значения,
//мы будем смещать пов-ть по отношению к осям x,y,z.
//Объявляем индексы элементов массива myArrayVC(i, j):
int i, j;
//Значение первой, второй и третьей границ индекса "i":
int N_1_myArray, N_2_myArray, N_3_myArray;
//Задаем границы индексов массива myArrayVC(i, j):
int N_x = 2001;
int N_y = 2;
//Объявляем массив myArrayVC(i, j) переменных float,
//когда i = 0,1,2,3,...,(N_x - 1);
// j = 0,1,2,3,...,(N_y - 1):
array<float,2>^ myArrayVC =
gcnew array<float,2>(N_x, N_y);
//Для считывания из файла D:\\MyDocs\\MyTest.txt
//координат изображения в массив myArrayVC[2001, 2]
//создаем объект sr класса StreamReader:
String^ path = "D:\\MyDocs\\MyTest.txt";
StreamReader^ sr =
gcnew StreamReader(path);
//Считываем из файла MyTest.txt координаты изображения
//в массив myArrayVC(2001, 2) при помощи ReadLine:
for (i = 0; i <= N_x - 1; i++)
for (j = 0; j <= N_y - 1; j++)
myArrayVC[i, j] =
Convert::ToSingle(sr->ReadLine());
sr->Close();
//Рисуем поверхность z=f(x,y) из массива.
//Объявляем координаты двух точек:
float x1, y1, x2, y2;
// Будем рисовать пером myPen толщиной 0:
Pen^ myPen = gcnew Pen(Color::Black, 0);
//Рисуем линии поверхности, параллельные плоскости xz:
i = -2;
for (int x = 0; x <= x_max; x++)
{
for (int y = 1; y <= y_max; y++)
{
i = i + 2;
x1 = myArrayVC[i, 0];
y1 = myArrayVC[i, 1];
x2 = myArrayVC[i + 1, 0];
y2 = myArrayVC[i + 1, 1];
e->Graphics->DrawLine(myPen, x1, y1, x2, y2);
}
}
N_1_myArray = i + 1; //Первая граница массива.
//Рисуем линии поверхности, параллельные плоскости yz:
i = N_1_myArray - 1;
for (int y = 0; y <= y_max; y++)
{
for (int x = 1; x <= x_max; x++)
{
i = i + 2;
x1 = myArrayVC[i, 0];
y1 = myArrayVC[i, 1];
x2 = myArrayVC[i + 1, 0];
y2 = myArrayVC[i + 1, 1];
e->Graphics->DrawLine(myPen, x1, y1, x2, y2);
}
}
N_2_myArray = i + 1; //Вторая граница массива.
//Рисуем оси координат:
Pen^ myPen2 = gcnew Pen(Color::Red, 0);
i = N_2_myArray - 1;
for (int k = 1; k <= 3; k++)
{
i = i + 2;
x1 = myArrayVC[i, 0];
y1 = myArrayVC[i, 1];
x2 = myArrayVC[i + 1, 0];
y2 = myArrayVC[i + 1, 1];
e->Graphics->DrawLine(myPen2, x1, y1, x2, y2);
N_3_myArray = i + 1; //Третья граница массива.
}
}
Интеграция Visual C# с Visual Basic, Visual C++ 2005 и другими языками состоит в том, что в одном из проектов на Visual C#, Visual Basic, Visual C++ 2005 или другом языке координаты геометрических изображений сначала записываются в файлы на жестком диске компьютера (например, в текстовый файл D:\MyDocs\MyTest.txt), а затем в другом проекте на любом языке эти координаты считываются из файлов в массивы с последующим построением геометрических изображений по данным этих массивов.
Аналогично по этой методологии мы можем разработать другие приложения для построения различных пространственных изображений.
Консольное приложение для расчета массы изделий
Листинг 76.1. Проект Part 3. Файл Program.cs.
#region Using directives
using System;
using System.Collections.Generic;
using System.Text;
#endregion
namespace ConsoleApplication1
{
class Program
{
//Мы начинаем записывать код:
double d, H, s; double pi = Math.PI;
double rho=7850; //: for steel.
double A, V, m, D;
double Area() //Площадь поверхности детали:
{
double A1, A2;
A1=Area1(); A2=Area2(); A=A1+A2;
return A;
}
double Volume() //Объем детали:
{
A=Area(); V=A*s; return V;
}
double mass() //Масса детали:
{
Console.Write("Part diameter, mm, d =");
d = Convert.ToDouble(Console.ReadLine());
Console.Write("Part heigth, mm, H =");
H = Convert.ToDouble(Console.ReadLine());
Console.Write("Part thickness, mm, s =");
s = Convert.ToDouble(Console.ReadLine());
A=Area();
V=Volume(); //mm*mm*mm
m=V*rho*1.0e-9; // kg
Console.WriteLine("");//Пробельная строка.
//Вывод площади:
Console.Write("Part area, mm*mm, A =");
Console.WriteLine(A);
//Вывод объема:
Console.Write("Part volume, mm*mm*mm, V =");
Console.WriteLine(V);
//Вывод массы:
Console.Write("Part mass for steel,kg,m=");
Console.WriteLine(m);
return m;
}
double DBlank() //Диаметр плоской заготовки:
{
A=Area();
D=Math.Sqrt(4*A/pi);
//Вывод диаметра заготовки:
Console.Write("Blank diameter, mm, D =");
Console.WriteLine(D);
Console.WriteLine("");//Пробельная строка.
return D;
}
double Area1() //Площадь круга:
{
double A1; double Rn; Rn=H;
A1=pi*(d-2*Rn)*(d-2*Rn)/4;
return A1; //mm*mm
}
double Area2() //Площадь тора:
{
double A2; double Rn; Rn=H;
A2=pi*(Rn-s/2)*(2*(Rn-s/2)+pi*(d-2*Rn)/2);
return A2; //mm*mm
}
//Мы закончили написание кода.
static void Main(string[] args)
{
//Мы начинаем записывать код:
//Создать объект-деталь Part3:
Program Part3 = new Program();
//На объекте Part3 рассчитать площадь:
Part3.Area();
//На объекте Part3 рассчитать объем:
Part3.Volume();
// На объекте Part3 рассчитать массу:
Part3.mass();
//На объекте Part3 рассчитать заготовку:
Part3.DBlank();
//Мы закончили написание кода.
}
}
}
Краткие сведения о графических возможностях Visual Studio
Вторая особенность заключается в том, что график строится не в действительных значениях переменных для функции y = f (x), которые рассчитает программа, а в пикселях. Напомним, что пиксель – это отдельная точка графического изображения, наименьшая адресуемая единица. Число пикселей, появляющихся на экране, различно для различных мониторов, и для конкретного монитора может быть установлено или изменено пользователем (в определенных пределах). Обозначения некоторых пикселей показаны на рис. 4.5.
Когда мы хотим использовать GDI+, чтобы провести линию, прямоугольник или кривую, мы должны задать некоторую ключевую информацию о графическом объекте, который будет выведен. Например, мы можем определить линию, задавая две точки, или прямоугольник, задавая толщину линии, высоту и ширину. GDI+ работает вместе с драйвером монитора, чтобы определить, какие пиксели должны быть включены для отображения заданного графического объекта. На рис. 4.6 показаны пиксели, которые включены для отображения прямой линии AB от точки A(4; 2) до точки B(12; 8).
Поэтому мы должны подобрать масштабы (выполнить масштабирование) для перевода действительных значений переменных в пиксели с целью увеличения (или уменьшения) размеров графика таким образом, чтобы он был виден на экране.
В справке Visual Studio подробно описаны классы GDI+ и даны простые примеры программ для реализации их графических возможностей.
В данной части книги мы дадим примеры программ средней сложности для практического применения графики при помощи некоторых основных классов.
Сложные примеры мы разработаем далее (идя от простого к сложному).
Начнем строить фигуры средней сложности при помощи GDI+ и печатать эти фигуры на принтере.
Математическое моделирование и графика для типичных техпроцессов
В предыдущих главах мы разработали методы кодирования геометрии заданного изделия (детали), расчета формы и размеров заготовки, ввода механических свойств материала заготовки и математического моделирования обработки заготовки, из которой мы планируем производить изделие (деталь). И теперь на базе такой информации мы можем приступить к созданию приложения нашей вычислительной системы для математического моделирования (для проведения вычислительного эксперимента с использованием компьютерной графики) сложного технологического процесса изготовления одной из типичных деталей на производстве. Выше в качестве таких типичных и очень распространенных в быту и технике деталей были выбраны детали типа стакана, которые изготавливаются одним из основных способов листовой штамповки, а именно, вытяжкой в инструментальном штампе. Поэтому на примере этих деталей мы и опишем методику моделирования процесса их изготовления.
Проектирование технологических процессов первой и последующих операций вытяжки в штампах осесимметричных, коробчатых и сложных деталей из плоской или пространственной листовой заготовки включает следующие основные стадии: ввод исходных данных в вычислительную систему; определение конфигурации и размеров плоской заготовки; теоретическое исследование и расчет возможности вытяжки заданной по чертежу детали из плоской заготовки за одну операцию без разрушения; теоретическое исследование и расчет количества операций и размеров полуфабрикатов после каждой последующей операции (если за одну операцию вытяжка невозможна); определение силы и работы деформирования на каждой операции для выбора прессового оборудования и проектирования штампов.
Ввод в вычислительную систему и расчет исходных данных для проектирования технологического процесса осуществляется при помощи чертежа детали на бумаге или мониторе компьютера. Толщина заготовки принимается равной толщине детали, форма пуансона соответствует внутренней полости готовой детали или полуфабриката для последующих операций вытяжки, радиус скругленной кромки пуансона равен радиусу у дна детали или полуфабриката, радиус скругленной кромки матрицы равен радиусу у фланца детали или, если у детали нет фланца, радиусу, рассчитанному по справочной литературе в области листовой штамповки.
Методика исследования заключается в следующем:
- весь процесс формоизменения заготовки разбивается на большое число элементарных этапов и задается малое приращение хода пуансона (или матрицы);
- с учетом напряженно-деформированного состояния (НДС) и изменения толщины заготовки рассчитывается объем заготовки, который на данном этапе должен быть втянут из фланца для образования стенки детали;
Аналогично по данной методике мы можем проектировать технологию изготовления другой осесимметричной (в частном случае, цилиндрической) детали из заготовки любой конфигурации и из любого материала.
Математическое моделирование обработки заготовки при помощи аналитических решений
Определив форму и размеры заготовки по описанной ранее методике (например, в первом приближении), приступаем к математическому моделированию обработки этой заготовки. Как мы уже отмечали, существует много способов обработки заготовки. Теоретическое исследование (математическое моделирование) и расчет практически важных (технологических) параметров обработки заготовки многими способами, как правило, основаны на применении теории упругости и теории пластичности. Даже обработка материала резанием исследуется при помощи теории пластичности. А вычислительные эксперименты по таким способам, как прокатка, прессование, ковка и штамповка проводятся только на базе теорий упругости и пластичности, которые мы также будем применять в данной книге.
Для теоретического исследования (математического моделирования) обработки заготовки сначала необходимо принять определенные допущения и выбрать математическую (расчетную) модель заготовки, которая: с одной стороны, должна в наибольшей мере соответствовать реальным условиям обработки заготовки на производстве; а с другой стороны, эта модель должна позволить рассчитать с достаточной точностью практически важные параметры процесса с минимальным временем работы компьютера.
Затем для выбранной расчетной модели необходимо рассчитать деформации и напряжения, которые возникают в заготовке при ее обработке. И только после этого можно рассчитать практически важные параметры технологического процесса обработки заготовки, например такие, как сила воздействия инструмента на заготовку (эта сила является интегральной характеристикой напряжений, возникающих в зоне контакта инструмента и заготовки).
В качестве конкретного примера рассматриваем обработку тонкой плоской металлической заготовки типа кольца (изготовленного, например, резкой листового проката в штампе или на лазерной установке) с размерами (рис. 79.1): наружный диаметр кольца D (радиус R); иногда мы будем применять также обозначение наружного диаметра в виде
(радиус ); внутренний диаметр кольца (радиус ); толщина кольца s.Обработка заготовки заключается в том, что внутренняя поверхность кольца радиуса
равномерно перемещается к оси кольца на заданную величину (рис. 79.1). Такая обработка может осуществляться в промышленности, например, с целью: упрочнения (нагартовки) материала на внутреннем контуре для повышения его прочностных характеристик; обжима другой детали типа оси, которая вставляется внутрь данного кольца. В общем случае способ обработки заготовки учитывается в граничных условиях при математической постановке задачи исследования.При исследовании обработки заготовки аналитические решения с помощью теорий упругости и пластичности могут быть получены лишь для небольшого числа простых задач; такие примеры были приведены в предыдущей главе. При решении сложных задач (например, упруго-пластических задач с учетом анизотропии и неоднородности свойств заготовки) применяют численные методы, в основном, метод конечных элементов – МКЭ (finite element method - FEM), который включает шаги:
1. Задание исходных размеров и механических свойств заготовки.
2. Разбиение заготовки на конечные элементы, назначение и расчет координат узлов, в которых необходимо определить перемещения.
3. Определение зависимостей между силами и перемещениями в узлах элемента, то есть, вычисление локальных матриц жесткости и вектора узловых сил для каждого конечного элемента.
4. Составление полной системы линейных алгебраических уравнений равновесия, то есть, сборка одной глобальной матрицы жесткости и глобального вектора узловых сил для всех конечных элементов.
5. Изменение системы уравнений с учетом граничных условий в перемещениях и силах.
6. Решение полученной системы уравнений.
7. Определение компонентов напряженно-деформированного состояния (НДС) заготовки.
8. Расчет практически важных параметров технологического процесса обработки заготовки.
Разбиваем срединную поверхность плоской заготовки (например, то же кольцо из предыдущей главы) на треугольные конечные элементы (рис. 80.1, 80.2, 80.3).
Теперь применим аналитические и численные решения задач для кольца и пластины, полученные нами в предыдущих главах, при математическом моделировании сложного процесса изготовления одной из типичных деталей на производстве.
Методика анимации градиентного заполнения текста цветом
Листинг 60.1. Объявление и инициализация глобальных переменных.
//Задаем ширину и высоту прямоугольника,
//внутри которого будет размещаться текст:
int width_of_rectangle = 700;
int height_of_rectangle = 300;
//Верхняя горизонтальная линия прямоугольника отстоит
//от оси 'x' на расстоянии boundary_of_rectangle_у:
int boundary_of_rectangle_у = 15;
//Левая вертикальная линия прямоугольника отстоит
//от оси 'y' на расстоянии boundary_of_rectangle_x:
int boundary_of_rectangle_x = 15;
//Объявляем целочисленные переменные
//и задаем значения:
int Shift_of_Gradient = 10;
int Step_of_Gradient = 5;
Дважды щелкаем по диалоговой панели Form1 в режиме проектирования и в появившийся шаблон записываем наш код, после чего метод принимает такой вид.
Листинг 60.2. Метод Form1_Load.
private void Form1_Load(object sender, EventArgs e)
{
//Создаем объект myGraphics класса Graphics
//и стираем (Clear) другие изображения:
Graphics myGraphics = CreateGraphics();
myGraphics.Clear(BackColor);
}
Чтобы подключить к работе таймер, дважды щелкаем значок для компонента Timer (ниже формы в режиме проектирования). Появляется файл Form1.cs с шаблоном, который после записи нашего кода принимает следующий вид.
Листинг 60.3. Метод для компонента Timer.
private void timer1_Tick(object sender, EventArgs e)
{
//Создаем объект myGraphics класса Graphics:
Graphics myGraphics = CreateGraphics();
//Рисуем четыре граничные линии прямоугольника,
//внутри которого будет размещаться текст:
myGraphics.DrawLine(new Pen(Color.Red, 3),
boundary_of_rectangle_x, boundary_of_rectangle_у,
width_of_rectangle, boundary_of_rectangle_у);
myGraphics.DrawLine(new Pen(Color.Red, 3),
boundary_of_rectangle_x, boundary_of_rectangle_у,
boundary_of_rectangle_x, height_of_rectangle);
myGraphics.DrawLine(new Pen(Color.Red, 3),
boundary_of_rectangle_x, height_of_rectangle,
width_of_rectangle, height_of_rectangle);
myGraphics.DrawLine(new Pen(Color.Red, 3),
width_of_rectangle, boundary_of_rectangle_у,
width_of_rectangle, height_of_rectangle);
// Задаем тип шрифта, размер текста и стиль
//шрифта:
Font drawFont = new Font("Times New Roman", 100,
FontStyle.Bold, GraphicsUnit.Point);
//Записываем текст, цвет которого будет изменяться:
string drawText = "Visual";
//Задаем меру строки текста
//при помощи метода MeasureString:
SizeF SizeF_of_Text = new
SizeF(myGraphics.MeasureString(drawText, drawFont));
//Задаем точку, от которой
//будет рисоваться текст,
//сцентрированный внутри прямоугольника:
PointF Start_of_drawPointF =
new PointF(Convert.ToSingle((width_of_rectangle
- SizeF_of_Text.Width) / 2),
Convert.ToSingle((height_of_rectangle
- SizeF_of_Text.Height) / 2));
//Задаем координаты начальной и конечной точек
//градиентной заливки текста; изменяя эти координаты,
//мы изменяем и анимационный эффект:
PointF Start_of_Gradient_of_drawPointF =
new PointF(0, 0);
PointF End_of_Gradient_of_drawPointF =
new PointF(Shift_of_Gradient, 200);
//Создаем объект drawBrush класса LinearGradientBrush
//и задаем его параметры
//для градиентной заливки текста:
LinearGradientBrush drawBrush = new
LinearGradientBrush(Start_of_Gradient_of_drawPointF,
End_of_Gradient_of_drawPointF, Color.Red, BackColor);
//Рисуем текст в центре прямоугольника:
myGraphics.DrawString(drawText, drawFont,
drawBrush, Start_of_drawPointF);
//Высвобождаем ресурсы, выделенные объекту
//myGraphics:
myGraphics.Dispose();
//Сдвигаем градиент заливки текста
//на шаг Shift_of_Gradient;
// заново начинаем цикл градиентной заливки текста,
//когда величина Shift_of_Gradient
//достигнет заданного значения:
Shift_of_Gradient = Shift_of_Gradient +
Step_of_Gradient;
if (Shift_of_Gradient == 500)
{
Step_of_Gradient = -5;
}
else if (Shift_of_Gradient == -50)
{
Step_of_Gradient = 5;
}
}
В этом коде главным является вызов перегруженного метода DrawString (класса Graphics) вида: Overloads Public Sub DrawString(String, Font, Brush, PointF).
Чтобы установить значение свойства Interval компонента Timer в зависимости от свойства Value ползунка элемента управления TrackBar, дважды щелкаем элемент управления TrackBar в режиме проектирования. Появляется файл Form1.cs с шаблоном, который после записи нашего кода принимает следующий вид.
Листинг 60.4. Метод для элемента управления TrackBar.
private void trackBar1_Scroll(object sender,
EventArgs e)
{
timer1.Enabled = true;
timer1.Interval = trackBar1.Value;
}
Согласно разработанной выше методике, чтобы иметь возможность приостановить (и запустить вновь) процесс анимации при помощи кнопки Stop Animation, дважды щелкаем эту кнопку в режиме проектирования (рис. 60.1). Появляется файл Form1.cs с шаблоном, выше которого объявляем переменную OffOn и присваиваем ей значение false:
//Объявляем булеву переменную OffOn
//и задаем ей значение, например, false:
bool OffOn = false;
После записи нашего кода шаблон принимает вид следующего метода.
Листинг 60.5. Метод для кнопки Stop Animation.
private void button1_Click(object sender, EventArgs e)
{
//Задаем чередование остановки и возобновления анимации
//после каждого щелчка кнопки button:
if (OffOn == false)
{
//Приостанавливаем анимацию:
timer1.Enabled = false;
//Изменяем значение OffOn на противоположное:
OffOn = true;
}
else
{
//Возобновляем анимацию:
timer1.Enabled = true;
//Изменяем значение OffOn на противоположное:
OffOn = false;
}
}
Листинг 60.6. Метод для компонента PrintDocument.
private void printDocument1_PrintPage(object sender,
System.Drawing.Printing.PrintPageEventArgs e)
{
//Создаем объект myGraphics класса Graphics:
Graphics myGraphics = CreateGraphics();
//Связываем объект myGraphics с текущим принтером:
myGraphics = e.Graphics;
//Рисуем четыре граничные линии прямоугольника,
//внутри которого будет размещаться текст:
myGraphics.DrawLine(new Pen(Color.Red, 3),
boundary_of_rectangle_x, boundary_of_rectangle_у,
width_of_rectangle, boundary_of_rectangle_у);
myGraphics.DrawLine(new Pen(Color.Red, 3),
boundary_of_rectangle_x, boundary_of_rectangle_у,
boundary_of_rectangle_x, height_of_rectangle);
myGraphics.DrawLine(new Pen(Color.Red, 3),
boundary_of_rectangle_x, height_of_rectangle,
width_of_rectangle, height_of_rectangle);
myGraphics.DrawLine(new Pen(Color.Red, 3),
width_of_rectangle, boundary_of_rectangle_у,
width_of_rectangle, height_of_rectangle);
// Задаем тип шрифта, размер текста и стиль
//шрифта:
Font drawFont = new Font("Times New Roman", 100,
FontStyle.Bold, GraphicsUnit.Point);
//Записываем текст, цвет которого будет изменяться:
string drawText = "Visual";
//Задаем меру строки текста
//при помощи метода MeasureString:
SizeF SizeF_of_Text = new
SizeF(myGraphics.MeasureString(drawText, drawFont));
//Задаем точку, от которой
//будет рисоваться текст,
//сцентрированный внутри прямоугольника:
PointF Start_of_drawPointF =
new PointF(Convert.ToSingle((width_of_rectangle
- SizeF_of_Text.Width) / 2),
Convert.ToSingle((height_of_rectangle
- SizeF_of_Text.Height) / 2));
//Задаем координаты начальной и конечной точек
// градиентной заливки текста; изменяя эти координаты,
//мы изменяем и анимационный эффект:
PointF Start_of_Gradient_of_drawPointF =
new PointF(0, 0);
PointF End_of_Gradient_of_drawPointF =
new PointF(Shift_of_Gradient, 200);
//Создаем объект drawBrush класса LinearGradientBrush
//и задаем его параметры
//для градиентной заливки текста:
LinearGradientBrush drawBrush = new
LinearGradientBrush(Start_of_Gradient_of_drawPointF,
End_of_Gradient_of_drawPointF, Color.Red, BackColor);
//Рисуем текст в центре прямоугольника:
myGraphics.DrawString(drawText, drawFont,
drawBrush, Start_of_drawPointF);
//Высвобождаем ресурсы, выделенные объекту
//myGraphics:
myGraphics.Dispose();
}
Теперь дважды щелкаем кнопку Print (рис. 60.1). Открывается файл Form1.cs с шаблоном, в который записываем код из предыдущей главы. В режиме выполнения (можно, но не обязательно это делать, остановить градиентную заливку текста в интересующий нас момент при помощи кнопки Stop Animation) после щелчка кнопки Print появляется стандартная панель Print, на которой мы оставляем по умолчанию принтер и параметры печати. После щелчка кнопки OK на панели Print принтер печатает прямоугольник и анимационный текст в том положении, который был на экране монитора в момент щелчка кнопки Print. Если печатается не весь прямоугольник с текстом, то в панели Print вместо включенного по умолчанию переключателя Портрет (Portrait)
выбираем переключатель Ландшафт (Landscape), так как Form1 вытянута слева – направо, рис. 60.2.
Щелкая кнопку Print в различные моменты изменения (анимации) изображения, мы получим на принтере распечатки различных (во времени) положений анимационного текста внутри прямоугольника.
Таким образом, по разработанной в данной главе методике можно спроектировать анимацию на экране монитора и печать текущего положения анимации на принтере для любого текста внутри любого элемента управления для приложений различных типов.
Методика изображения летающих предметов
Листинг 59.1. Объявление объекта и глобальных переменных.
//Задаем ширину и высоту прямоугольника,
//внутри которого будет летать мяч:
int width_of_rectangle = 1000;
int height_of_rectangle = 500;
//Верхняя горизонтальная линия прямоугольника отстоит
//от оси 'x' на расстоянии boundary_of_rectangle_у:
int boundary_of_rectangle_у = 15;
//Левая вертикальная линия прямоугольника отстоит
//от оси 'y' на расстоянии boundary_of_rectangle_x:
int boundary_of_rectangle_x = 15;
//Размер мяча size_of_ball, как часть (доля)
//от размеров прямоугольника:
int size_of_ball = 20;
//Величина перемещения мяча size_of_move_of_ball,
//как часть (доля) от размеров мяча:
int size_of_move_of_ball = 5;
//Создаем объект myBitmap класса Bitmap:
private Bitmap myBitmap;
//Объявляем целочисленные переменные:
int position_of_ball_x, position_of_ball_y,
radius_of_ball_x, radius_of_ball_y,
move_of_ball_x, move_of_ball_y,
width_of_bitmap_of_ball, height_of_bitmap_of_ball,
width_of_margin_of_bitmap, height_of_margin_of_bitmap;
Дважды щелкаем по диалоговой панели Form1 в режиме проектирования и в появившийся шаблон записываем следующий наш код.
Листинг 59.2. Метод Form1_Load.
private void Form1_Load(object sender, EventArgs e)
{
//Создаем объект myGraphics класса Graphics
//и стираем другие изображения:
Graphics myGraphics = CreateGraphics();
myGraphics.Clear(BackColor);
//Задаем радиус мяча как дробь (часть)
//от ширины или высоты прямоугольника,
//в зависимости от того, какая дробь меньше:
double radius_of_ball =
Math.Min(width_of_rectangle / myGraphics.DpiX,
height_of_rectangle / myGraphics.DpiY) / size_of_ball;
//Задаем ширину и высоту мяча в DPI (ТОЧКИ НА ДЮЙМ)
//для единиц разрешения изображения
//по горизонтали и вертикали, что идентично значениям
//в направлении осей 'x' и 'y':
radius_of_ball_x = Convert.ToInt32(radius_of_ball *
myGraphics.DpiX);
radius_of_ball_y = Convert.ToInt32(radius_of_ball *
myGraphics.DpiY);
//Высвобождаем ресурсы, выделенные объекту myGraphics:
myGraphics.Dispose();
// Задаем шаг перемещения мяча или в 1 пиксель,
//или как часть (дробь, доля) от размера шара,
//в зависимости от того, какая величина больше.
//Это означает, что на каждом шаге перемещение мяча
//пропорционально его размеру,
//а размер мяча, в свою очередь,
//пропорционален размеру прямоугольника.
//Таким образом, при прочих равных условиях,
//когда размеры прямоугольника уменьшаются,
//перемещение мяча замедляется,
//и когда увеличиваются, перемещение мяча убыстряется:
move_of_ball_x = Convert.ToInt32(Math.Max(1,
radius_of_ball_x / size_of_move_of_ball));
move_of_ball_y = Convert.ToInt32(Math.Max(1,
radius_of_ball_y / size_of_move_of_ball));
//Значение перемещения мяча также определяет
//поле изображения вокруг мяча.
//На каждом шаге перемещение мяча
//равно перемещению поля изображения,
//что позволяет стирать предыдущее изображение мяча
//перед каждым последующим изображением мяча
//без мерцания:
width_of_margin_of_bitmap = move_of_ball_x;
height_of_margin_of_bitmap = move_of_ball_y;
//Определяем фактический размер изображения,
//где нарисован мяч,
//прибавляя поля к размерам мяча:
width_of_bitmap_of_ball = 2 * (radius_of_ball_x +
width_of_margin_of_bitmap);
height_of_bitmap_of_ball = 2 * (radius_of_ball_y +
height_of_margin_of_bitmap);
//Создаем новый рисунок мяча
//соответствующей ширины и высоты:
myBitmap = new Bitmap(width_of_bitmap_of_ball,
height_of_bitmap_of_ball);
//Получаем объект класса Graphics
//для изображения мяча,
//удаляем существующий мяч и рисуем новый мяч.
// Задаем черный цвет Black для мяча,
//чтобы он был лучше виден в книге:
myGraphics = Graphics.FromImage(myBitmap);
myGraphics.Clear(BackColor);
myGraphics.FillEllipse(Brushes.Black, new
Rectangle(move_of_ball_x,
move_of_ball_y, 2 * radius_of_ball_x, 2 *
radius_of_ball_y));
//Высвобождаем ресурсы, выделенные объекту myGraphics:
myGraphics.Dispose();
//Задаем расположение мяча в центре прямоугольника:
position_of_ball_x =
Convert.ToInt32(width_of_rectangle / 2);
position_of_ball_y =
Convert.ToInt32(height_of_rectangle / 2);
}
Чтобы подключить к работе таймер, дважды щелкаем значок для компонента Timer (ниже формы в режиме проектирования). Появляется файл Form1.cs с шаблоном, который после записи нашего кода принимает следующий вид.
Листинг 59.3. Метод для компонента Timer.
private void timer1_Tick(object sender, EventArgs e)
{
//Создаем объект myGraphics класса Graphics:
Graphics myGraphics = CreateGraphics();
//Рисуем четыре граничные линии прямоугольника,
//от которых будет отскакивать летающий мяч:
myGraphics.DrawLine(new Pen(Color.Red, 3),
boundary_of_rectangle_x, boundary_of_rectangle_у,
width_of_rectangle, boundary_of_rectangle_у);
myGraphics.DrawLine(new Pen(Color.Red, 3),
boundary_of_rectangle_x, boundary_of_rectangle_у,
boundary_of_rectangle_x, height_of_rectangle);
myGraphics.DrawLine(new Pen(Color.Red, 3),
boundary_of_rectangle_x, height_of_rectangle,
width_of_rectangle, height_of_rectangle);
myGraphics.DrawLine(new Pen(Color.Red, 3),
width_of_rectangle, boundary_of_rectangle_у,
width_of_rectangle, height_of_rectangle);
// Рисуем изображение мяча на диалоговой панели Form1
//при помощи метода DrawImage:
myGraphics.DrawImage(myBitmap,
Convert.ToInt32(position_of_ball_x -
width_of_bitmap_of_ball / 2),
Convert.ToInt32(position_of_ball_y -
height_of_bitmap_of_ball / 2),
width_of_bitmap_of_ball, height_of_bitmap_of_ball);
//Высвобождаем ресурсы, выделенные объекту myGraphics:
myGraphics.Dispose();
//После изображения мяча с текущими координатами
//увеличиваем координаты мяча
//на шаг перемещения в направлении осей 'x' и 'y':
position_of_ball_x = position_of_ball_x +
move_of_ball_x;
position_of_ball_y = position_of_ball_y +
move_of_ball_y;
//Изменяем направление перемещения мяча по оси 'x',
//когда мяч ударяется о какую-либо из двух
//граничных вертикальных линий прямоугольника:
if (position_of_ball_x + radius_of_ball_x >=
width_of_rectangle)
{
move_of_ball_x = -move_of_ball_x;
//В момент удара подаем звуковой сигнал Beep:
Microsoft.VisualBasic.Interaction.Beep();
}
if (position_of_ball_x - radius_of_ball_x <=
boundary_of_rectangle_x)
{
move_of_ball_x = -move_of_ball_x;
Microsoft.VisualBasic.Interaction.Beep();
}
//Изменяем направление перемещения мяча по оси 'y',
//когда мяч ударяется о какую-либо из двух
//граничных горизонтальных линий прямоугольника:
if (position_of_ball_y + radius_of_ball_y >=
height_of_rectangle)
{
move_of_ball_y = -move_of_ball_y;
Microsoft.VisualBasic.Interaction.Beep();
}
if (position_of_ball_y - radius_of_ball_y <=
boundary_of_rectangle_у)
{
move_of_ball_y = -move_of_ball_y;
Microsoft.VisualBasic.Interaction.Beep();
}
}
В этом коде главным является вызов перегруженного метода DrawImage, который в VC# имеет много видов перегрузки. Здесь использован вид перегрузки номер 13 с сигнатурой: Overloads Public Sub DrawImage(Image, Integer, Integer, Integer, Integer). Этот метод мы уже объясняли в предыдущей главе.
Чтобы установить значение свойства Interval компонента Timer в зависимости от свойства Value ползунка элемента управления TrackBar, дважды щелкаем элемент управления TrackBar в режиме проектирования. Появляется файл Form1.cs с шаблоном, который после записи нашего кода принимает следующий вид.
Листинг 59.4. Метод для элемента управления TrackBar.
private void trackBar1_Scroll(object sender, EventArgs e)
{
timer1.Enabled = true;
timer1.Interval = trackBar1.Value;
}
Согласно разработанной выше методике, чтобы иметь возможность приостановить (и запустить вновь) процесс анимации на любом рисунке при помощи кнопки Stop Animation, дважды щелкаем эту кнопку в режиме проектирования (рис. 59.1). Появляется файл Form1.cs с шаблоном, выше которого объявляем переменную OffOn и присваиваем ей значение false:
//Объявляем булеву переменную OffOn
//и задаем ей значение, например, false:
bool OffOn = false;
После записи нашего кода шаблон принимает вид следующего метода.
Листинг 59.5. Метод для кнопки Stop Animation.
private void button6_Click(object sender, EventArgs e)
{
//Задаем чередование остановки и возобновления анимации
//после каждого щелчка кнопки button2:
if (OffOn == false)
{
//Приостанавливаем анимацию:
timer1.Enabled = false;
//Изменяем значение OffOn на противоположное:
OffOn = true;
}
else
{
//Возобновляем анимацию:
timer1.Enabled = true;
//Изменяем значение OffOn на противоположное:
OffOn = false;
}
}
Как и выше, наши подробные комментарии на всех листингах помогут читателю грамотно внести изменения в код (если читатель пожелает модернизировать анимацию для учета собственных требований).
Листинг 59.6. Метод для компонента PrintDocument.
private void printDocument1_PrintPage(object sender,
System.Drawing.Printing.PrintPageEventArgs e)
{
//Создаем объект myGraphics класса Graphics:
Graphics myGraphics = CreateGraphics();
//Связываем объект myGraphics с текущим принтером:
myGraphics = e.Graphics;
//Рисуем четыре граничные линии прямоугольника,
//от которых будет отскакивать летающий мяч:
myGraphics.DrawLine(new Pen(Color.Red, 3),
boundary_of_rectangle_x, boundary_of_rectangle_у,
width_of_rectangle, boundary_of_rectangle_у);
myGraphics.DrawLine(new Pen(Color.Red, 3),
boundary_of_rectangle_x, boundary_of_rectangle_у,
boundary_of_rectangle_x, height_of_rectangle);
myGraphics.DrawLine(new Pen(Color.Red, 3),
boundary_of_rectangle_x, height_of_rectangle,
width_of_rectangle, height_of_rectangle);
myGraphics.DrawLine(new Pen(Color.Red, 3),
width_of_rectangle, boundary_of_rectangle_у,
width_of_rectangle, height_of_rectangle);
//Рисуем изображение мяча на диалоговой панели Form1
//при помощи метода DrawImage:
myGraphics.DrawImage(myBitmap,
Convert.ToInt32(position_of_ball_x -
width_of_bitmap_of_ball / 2),
Convert.ToInt32(position_of_ball_y -
height_of_bitmap_of_ball / 2),
width_of_bitmap_of_ball,
height_of_bitmap_of_ball);
//Высвобождаем ресурсы, выделенные объекту myGraphics:
myGraphics.Dispose();
}
Теперь дважды щелкаем кнопку Print (рис. 59.1). Открывается файл Form1.cs с шаблоном, который после записи нашего кода принимает следующий вид.
Листинг 59.7. Метод для кнопки Print.
private void button3_Click(object sender, EventArgs e)
{
//Передаем объекту PrintDialog1 информацию об объекте
//PrintDocument1 при помощи свойства Document:
printDialog1.Document = printDocument1;
//Выводим стандартную панель Print при помощи метода
//ShowDialog для задания параметров печати:
if (printDialog1.ShowDialog() == DialogResult.OK)
printDocument1.Print();
}
Таким образом, по разработанной в данной главе методике можно спроектировать анимацию на экране монитора и печать текущего положения анимации на принтере для любого предмета (в виде мяча, шара, пули и т.п.), летающего в открытом или замкнутом пространстве.
Методика проектирования неподвижных и подвижных плоских фигур
Листинг 44.1. Код для вывода формы и рисования на ней графики.
using (Form1 myForm1 = new Form1())
{
if (!myForm1.InitializeDirectX())
{
MessageBox.Show("Ошибка при инициализации DirectX.");
return;
}
//Показываем форму Form1:
myForm1.Show();
//Рисуем графику на форме Form1:
while (myForm1.Created)
{
myForm1.myRendering();
Application.DoEvents();
}
}
Для закрытия Form1 после щелчка кнопки Cancel дважды щелкаем эту кнопку. Появляется файл Form1.cs с шаблоном, в который записываем (Close()).
Для вывода справочной Form2 (после щелчка кнопки Help на Form1) дважды щелкаем эту кнопку. Появляется файл Form1.cs с шаблоном, в который записываем:
Form2 myForm2 = new Form2(); myForm2.Show(); (44.1)
Для ввода в проект новой формы в меню Project выбираем Add Windows Form, в панели Add New Item в окне Templates выделяем Windows Form, в окне Name оставляем имя Form2 и щелкаем кнопку Add (или Open для иной версии VS). Появляется Form2, в которую записываем (если нужно) справочную информацию. Вывод следующей формы (если в этом есть необходимость) после щелчка кнопки Next>> (или предыдущей формы после щелчка кнопки <<Back) осуществляется аналогично при помощи кода (44.1) с номером соответствующей формы.
Теперь в любом месте файла Form1.cs (например, ниже предыдущих методов для обработки щелчков по кнопкам) записываем следующие методы для связывания формы Form1 с Direct3D.
Листинг 44.2. Методы для визуализации преобразований.
//Объявляем и инициализируем глобальную переменную
//для устройства myDevice класса Device:
Device myDevice = null;
//Устанавливаем параметры Direct3D:
public bool InitializeDirectX()
{
try
{
PresentParameters myPresentParameters =
new PresentParameters();
myPresentParameters.Windowed = true;
myPresentParameters.SwapEffect = SwapEffect.Discard;
myDevice = new Device(0, DeviceType.Hardware,
Открываем файл Form1.cs (например, по схеме: File, Open, File) и выше пространства имен с именем нашего проекта (namespace Visual_DirectX_n5) записываем директивы для подключения пространств имен:
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Direct3D = Microsoft.DirectX.Direct3D;
Листинг 45.1. Методы для визуализации преобразованных вершин фигуры.
//Глобальные переменные.
//Объявляем устройство для визуализации вершин:
Device myDevice = null;
VertexBuffer myVertexBuffer = null;
PresentParameters myPresentParameters =
new PresentParameters();
bool myPause = false;
//Задаем параметры DirectX:
public bool InitializeDirectX()
{
try
{
myPresentParameters.Windowed = true;
myPresentParameters.SwapEffect =
SwapEffect.Discard;
myPresentParameters.EnableAutoDepthStencil = true;
myPresentParameters.AutoDepthStencilFormat =
DepthFormat.D16;
//Создаем устройство для визуализации:
myDevice = new Device(0, DeviceType.Hardware, this,
CreateFlags.SoftwareVertexProcessing,
myPresentParameters);
myDevice.DeviceReset +=
new System.EventHandler(this.OnResetDevice);
this.OnCreateDevice(myDevice, null);
this.OnResetDevice(myDevice, null);
myPause = false;
return true;
}
catch (DirectXException)
{
//Перехвачена ошибка инициализации DirectX:
return false;
}
}
//Создаем буфер вершин фигуры:
public void OnCreateDevice(object sender, EventArgs e)
{
Device myDev = (Device)sender;
myVertexBuffer = new VertexBuffer(
typeof(CustomVertex.PositionNormal), 100, myDev,
Usage.WriteOnly, CustomVertex.PositionNormal.Format,
Pool.Default);
myVertexBuffer.Created += new System.EventHandler(
this.OnCreateVertexBuffer);
this.OnCreateVertexBuffer(myVertexBuffer, null);
}
//Задаем параметры устройству:
this, CreateFlags.SoftwareVertexProcessing,
myPresentParameters);
return true;
}
catch (DirectXException)
{
return false;
}
}
//Метод для визуализации преобразований и построения графики:
public void myRendering()
{
if (myDevice == null)
return;
//Очищаем и заливаем белым цветом устр-во в виде Form1:
myDevice.Clear(ClearFlags.Target,
System.Drawing.Color.White, 1.0f, 0);
//Начинаем сцену:
myDevice.BeginScene();
//Заканчиваем сцену:
myDevice.EndScene();
myDevice.Present();
}
// Чтобы закрыть Form1 после нажатия клавиши Esc:
protected override void OnKeyPress(
System.Windows.Forms.KeyPressEventArgs e)
{
if ((int)(byte)e.KeyChar ==
(int)System.Windows.Forms.Keys.Escape)
this.Close();
}
Листинг 44.3. Методы для визуализации преобразованных вершин фигуры.
//Объявляем и инициализируем глобальные переменные:
Device myDevice = null;
VertexBuffer myVertexBuffer = null;
//Устанавливаем параметры Direct3D:
public bool InitializeDirectX()
{
try
{
PresentParameters myPresentParameters =
new PresentParameters();
myPresentParameters.Windowed = true;
myPresentParameters.SwapEffect = SwapEffect.Discard;
myDevice = new Device(0, DeviceType.Hardware, this,
CreateFlags.SoftwareVertexProcessing,
myPresentParameters);
this.OnCreateDevice(myDevice, null);
return true;
}
catch (DirectXException)
{
return false;
}
}
//Создаем массив вершин фигуры:
public void OnCreateDevice(object sender, EventArgs e)
{
//Создаем буфер для 3-x вершин треугольника:
myVertexBuffer = new VertexBuffer(
typeof(CustomVertex.TransformedColored), 3,
myDevice, 0, CustomVertex.TransformedColored.Format,
Pool.Default);
myVertexBuffer.Created += new System.EventHandler(
this.OnCreateVertexBuffer);
this.OnCreateVertexBuffer(myVertexBuffer, null);
}
//Задаем параметры вершин:
public void OnCreateVertexBuffer(object sender, EventArgs e)
{
GraphicsStream myGraphicsStream = myVertexBuffer.Lock(0, 0, 0);
CustomVertex.TransformedColored[] Vertex =
new CustomVertex.TransformedColored[3];
//Вершина 0:
Vertex[0].X = 150; Vertex[0].Y = 50; Vertex[0].Z = 0.5f;
Vertex[0].Rhw = 1;
Vertex[0].Color = System.Drawing.Color.Aqua.ToArgb();
//Вершина 1:
Vertex[1].X = 250; Vertex[1].Y = 300; Vertex[1].Z = 0.5f;
Vertex[1].Rhw = 1;
Vertex[1].Color = System.Drawing.Color.Black.ToArgb();
//Вершина 2:
Vertex[2].X = 50; Vertex[2].Y = 300; Vertex[2].Z = 0.5f;
Vertex[2].Rhw = 1;
Vertex[2].Color = System.Drawing.Color.LightPink.ToArgb();
myGraphicsStream.Write(Vertex);
myVertexBuffer.Unlock();
}
//Метод для начала и окончания визуализации
//преобразованных вершин:
public void myRendering()
{
if (myDevice == null)
return;
//Задаем белый цвет (Color.White) форме Form1:
myDevice.Clear(ClearFlags.Target,
System.Drawing.Color.White, 1.0f, 0);
//Начинаем сцену:
myDevice.BeginScene();
myDevice.SetStreamSource(0, myVertexBuffer, 0);
myDevice.VertexFormat = CustomVertex.TransformedColored.Format;
myDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);
//Заканчиваем сцену:
myDevice.EndScene();
myDevice.Present();
}
Теперь в файле Form1.cs (или Program.cs) находим главный метод Main, комментируем весь имеющийся в этом методе автоматически сгенерированный код и записываем код со следующего листинга (для вывода формы Form1 и рисования на ней графики).
Листинг 44.4. Код для вывода формы и рисования на ней графики.
using (Form1 myForm1 = new Form1())
{
if (!myForm1.InitializeDirectX())
{
MessageBox.Show("Ошибка при инициализации DirectX.");
return;
}
//Показываем форму Form1:
myForm1.Show();
//Рисуем графику на форме Form1:
while (myForm1.Created)
{
myForm1.myRendering();
Application.DoEvents();
}
}
Листинг 44.5. Метод для фотографирования клиентской области формы.
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern long BitBlt(IntPtr hdcDest,
int nXDest, int nYDest, int nWidth, int nHeight,
IntPtr hdcSrc, int nXSrc, int nYSrc, int dwRop);
private Bitmap myMemoryImage;
private void myCaptureScreen()
{
Graphics myGraphics = this.CreateGraphics();
Size s = this.Size;
myMemoryImage = new Bitmap(s.Width, s.Height,
myGraphics);
Graphics myMemoryGraphics =
Graphics.FromImage(myMemoryImage);
IntPtr dc0 = myGraphics.GetHdc();
IntPtr dc1 = myMemoryGraphics.GetHdc();
BitBlt(dc1, 0, 0, this.ClientRectangle.Width,
this.ClientRectangle.Height,
dc0, 0, 0, 13369376);
myGraphics.ReleaseHdc(dc0);
myMemoryGraphics.ReleaseHdc(dc1);
}
Если мы забыли разместить компоненты PrintDocument и PrintDialog, то размещаем их сейчас и дважды щелкаем по значку для компонента PrintDocument. Открывается файл Form1.cs с шаблоном, который после записи одной строки нашего кода (для рисования в памяти сфотографированного выше изображения) имеет такой вид.
Листинг 44.6. Код для рисования изображения в памяти компьютера.
private void printDocument1_PrintPage(object sender,
System.Drawing.Printing.PrintPageEventArgs e)
{
e.Graphics.DrawImage(myMemoryImage, 0, 0);
}
Теперь дважды щелкаем по кнопке Print (рис. 44.7) в режиме проектирования. Открывается файл Form1.cs с автоматически сгенерированным шаблоном обработчика щелчка по кнопке, и этот шаблон после записи нашего кода принимает такой вид.
Листинг 44.7. Код для печати изображения на принтере.
private void button1_Click(object sender, EventArgs e)
{
//Вызываем метод для захвата изображения:
myCaptureScreen();
// Передаем объекту printDialog1 информацию об объекте
//printDocument1 при помощи свойства Document:
printDialog1.Document = printDocument1;
//Выводим стандартную панель Print при помощи метода
//ShowDialog() для задания параметров печати
//и после щелчка OK на панели Print печатаем документ
//при помощи метода Print():
if (printDialog1.ShowDialog() == DialogResult.OK)
printDocument1.Print();
}
Последнюю строку кода можно записать также в более полном (и более понятном, но более длинном) виде:
System.Windows.Forms.DialogResult result =
printDialog1.ShowDialog();
if (result == DialogResult.OK)
printDocument1.Print();
Открываем файл Form1.cs (например, по схеме: File, Open, File) и выше пространства имен с именем нашего проекта (namespace Visual_DirectX_n3) записываем директивы для подключения этих же двух пространств имен:
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
Коды для обработки щелчков по всем кнопкам на форме Form1 (рис. 44.8), а также для клавиши Esc приведены в предыдущем параграфе. Теперь в любом месте файла Form1.cs (например, ниже предыдущих методов для обработки щелчков по кнопкам) записываем следующие методы для выполнения преобразований вершин треугольника и прямоугольника и визуализации этих преобразований.
Листинг 44.8. Методы для визуализации преобразованных вершин фигур.
//Объявляем и инициализируем глобальные переменные.
//Общее устройство для всех фигур:
Device myDevice = null;
//Объявляем буфер вершин для треугольника:
VertexBuffer myVertexBuffer1 = null;
//Объявляем буфер вершин для прямоугольника:
VertexBuffer myVertexBuffer2 = null;
//Устанавливаем параметры Direct3D:
public bool InitializeDirectX()
{
try
{
//Для треугольника:
PresentParameters myPresentParameters1 =
new PresentParameters();
myPresentParameters1.Windowed = true;
myPresentParameters1.SwapEffect = SwapEffect.Discard;
myDevice = new Device(0, DeviceType.Hardware, this,
CreateFlags.SoftwareVertexProcessing,
myPresentParameters1);
this.OnCreateDevice(myDevice, null);
//Для прямоугольника:
PresentParameters myPresentParameters2 =
new PresentParameters();
myPresentParameters2.Windowed = true;
myPresentParameters2.SwapEffect = SwapEffect.Discard;
myDevice = new Device(0, DeviceType.Hardware, this,
CreateFlags.SoftwareVertexProcessing,
myPresentParameters2);
this.OnCreateDevice(myDevice, null);
return true;
}
catch (DirectXException)
{
return false;
}
}
//Метод для начала и окончания визуализации
//преобразованных вершин:
public void myRendering()
{
if (myDevice == null)
return;
myDevice.Clear(ClearFlags.Target,
System.Drawing.Color.White, 1.0f, 0);
myDevice.VertexFormat =
CustomVertex.TransformedColored.Format;
//Начинаем сцену:
myDevice.BeginScene();
//Для треугольника:
myDevice.SetStreamSource(0, myVertexBuffer1, 0);
myDevice.DrawPrimitives(PrimitiveType.TriangleList,0, 1);
//Для прямоугольника:
myDevice.SetStreamSource(0, myVertexBuffer2, 0);
myDevice.DrawPrimitives(PrimitiveType.TriangleStrip,0,2);
//Заканчиваем сцену:
myDevice.EndScene();
myDevice.Present();
}
//Создаем устройство и два буфера для вершин фигур:
public void OnCreateDevice(object sender, EventArgs e)
{
Device myDev = (Device)sender;
//Создаем буфер для вершин треугольника:
myVertexBuffer1 = new VertexBuffer(
typeof(CustomVertex.TransformedColored), 3,
myDev, 0, CustomVertex.TransformedColored.Format,
Pool.Default);
myVertexBuffer1.Created +=
new System.EventHandler(this.OnCreateVertexBuffer1);
this.OnCreateVertexBuffer1(myVertexBuffer1, null);
// Создаем буфер для вершин четырехугольника:
myVertexBuffer2 = new VertexBuffer(
typeof(CustomVertex.TransformedColored), 4,
myDev, 0, CustomVertex.TransformedColored.Format,
Pool.Default);
myVertexBuffer2.Created += new System.EventHandler(
this.OnCreateVertexBuffer2);
this.OnCreateVertexBuffer2(myVertexBuffer2, null);
}
//Задаем параметры вершин треугольника:
public void OnCreateVertexBuffer1(object sender, EventArgs e)
{
VertexBuffer myVB1 = (VertexBuffer)sender;
GraphicsStream myGraphicsStream1 = myVB1.Lock(0, 0, 0);
CustomVertex.TransformedColored[] Vertex1 =
new CustomVertex.TransformedColored[3];
//Вершина 0:
Vertex1[0].X = 150; Vertex1[0].Y = 50; Vertex1[0].Z=0.5f;
Vertex1[0].Rhw = 1;
Vertex1[0].Color = System.Drawing.Color.Aqua.ToArgb();
//Вершина 1:
Vertex1[1].X = 250; Vertex1[1].Y =300; Vertex1[1].Z=0.5f;
Vertex1[1].Rhw = 1;
Vertex1[1].Color = System.Drawing.Color.Black.ToArgb();
//Вершина 2:
Vertex1[2].X = 50; Vertex1[2].Y = 300; Vertex1[2].Z=0.5f;
Vertex1[2].Rhw = 1;
Vertex1[2].Color =
System.Drawing.Color.LightPink.ToArgb();
myGraphicsStream1.Write(Vertex1);
myVB1.Unlock();
}
//Задаем параметры вершин прямоугольника:
public void OnCreateVertexBuffer2(object sender, EventArgs EvArgs)
{
VertexBuffer myVB2 = (VertexBuffer)sender;
GraphicsStream myGraphicsStream2 = myVB2.Lock(0, 0, 0);
CustomVertex.TransformedColored[] Vertex2 =
new CustomVertex.TransformedColored[4];
//Вершина 0:
Vertex2[0].X = 300.0f; Vertex2[0].Y = 300.0f;
Vertex2[0].Z = 0.5f; Vertex2[0].Rhw = 1;
Vertex2[0].Color = System.Drawing.Color.Black.ToArgb();
//Вершина 1:
Vertex2[1].X = 300.0f; Vertex2[1].Y = 50.0f;
Vertex2[1].Z = 0.5f; Vertex2[1].Rhw = 1;
Vertex2[1].Color = System.Drawing.Color.White.ToArgb();
//Вершина 2:
Vertex2[2].X = 500.0f; Vertex2[2].Y = 300.0f;
Vertex2[2].Z = 0.5f; Vertex2[2].Rhw = 1;
Vertex2[2].Color = System.Drawing.Color.Blue.ToArgb();
//Вершина 3:
Vertex2[3].X = 500.0f; Vertex2[3].Y = 50.0f;
Vertex2[3].Z = 0.5f; Vertex2[3].Rhw = 1;
Vertex2[3].Color = System.Drawing.Color.Green.ToArgb();
myGraphicsStream2.Write(Vertex2);
myVB2.Unlock();
}
Листинг 44.9. Методы для визуализации преобразованных вершин фигуры.
//Глобальные переменные:
Device myDevice = null; // Our rendering device
VertexBuffer myVertexBuffer = null;
PresentParameters myPresentParameters =
new PresentParameters();
bool myPause = false;
//Задаем параметры DirectX:
public bool InitializeDirectX()
{
try
{
myPresentParameters.Windowed = true;
myPresentParameters.SwapEffect = SwapEffect.Discard;
myDevice = new Device(0, DeviceType.Hardware, this,
CreateFlags.SoftwareVertexProcessing,
myPresentParameters);
myDevice.DeviceReset +=
new System.EventHandler(this.OnResetDevice);
this.OnCreateDevice(myDevice, null);
this.OnResetDevice(myDevice, null);
myPause = false;
return true;
}
catch (DirectXException)
{
return false;
}
}
//Создаем буфер вершин для прямоугольника:
public void OnCreateDevice(object sender, EventArgs e)
{
Device myDev = (Device)sender;
//Задаем 4 вершины прямоугольника:
myVertexBuffer = new VertexBuffer(
typeof(CustomVertex.PositionColored), 4, myDev, 0,
CustomVertex.PositionColored.Format, Pool.Default);
//Создаем геом- е данные при обработке события Created:
myVertexBuffer.Created += new System.EventHandler(
this.OnCreateVertexBuffer);
this.OnCreateVertexBuffer(myVertexBuffer, null);
}
//Настраиваем параметры Direct3D:
public void OnResetDevice(object sender, EventArgs e)
{
Device myDev = (Device)sender;
// Выключаем режим CullMode, чтобы мы видели
//переднюю и заднюю поверхность фигуры:
myDev.RenderState.CullMode = Cull.None;
//Выключаем освещение Direct3D, так как мы задали
//наши собственные цвета в вершинах:
myDev.RenderState.Lighting = false;
}
//Создаем буфер вершин фигуры:
public void OnCreateVertexBuffer(object sender, EventArgs e)
{
//Для 4-х вершин прямоугольника:
VertexBuffer myVB = (VertexBuffer)sender;
CustomVertex.PositionColored[] Vertex = (
CustomVertex.PositionColored[])myVB.Lock(0, 0);
//Вершина 0:
Vertex[0].X = -1.0f; Vertex[0].Y = -1.0f;
Vertex[0].Z = 0.0f;
Vertex[0].Color = System.Drawing.Color.Black.ToArgb();
//Вершина 1:
Vertex[1].X = -1.0f; Vertex[1].Y = 1.0f;
Vertex[1].Z = 0.0f;
Vertex[1].Color =
System.Drawing.Color.MediumOrchid.ToArgb();
//Вершина 2:
Vertex[2].X = 1.0f; Vertex[2].Y = -1.0f;
Vertex[2].Z = 0.0f;
Vertex[2].Color = System.Drawing.Color.Black.ToArgb();
//Вершина 3:
Vertex[3].X = 1.0f; Vertex[3].Y = 1.0f;
Vertex[3].Z = 0.0f;
Vertex[3].Color = System.Drawing.Color.Cornsilk.ToArgb();
myVB.Unlock();
}
//Выполняем визуализацию преобразованных вершин:
public void myRendering()
{
if (myDevice == null) return;
if (myPause) return;
//Очищаем и заливаем форму Form1 белым цветом:
myDevice.Clear(ClearFlags.Target,
System.Drawing.Color.White, 1.0f, 0);
//Начинаем сцену:
myDevice.BeginScene();
//Используем матрицы для выполнения преобразований:
SetupMatrices();
myDevice.SetStreamSource(0, myVertexBuffer, 0);
myDevice.VertexFormat =
CustomVertex.PositionColored.Format;
//Для прямоугольника:
myDevice.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2);
//Заканчиваем сцену:
myDevice.EndScene();
myDevice.Present();
}
//Включаем таймер и выполняем матричные преобразования:
private void SetupMatrices()
{
//Используем структуру матриц Matrix,
// чтобы вращать фигуру вокруг оси y.
//Один оборот на 2*PI радиан фигура совершит
//за 1000 мс (1 секунду):
int iTime = Environment.TickCount % 1000;
float fAngle = iTime * (2.0f * (float)Math.PI) / 1000.0f;
myDevice.Transform.World = Matrix.RotationY(fAngle);
//Задаем координаты глаза наблюдателя
//в матрице вида (view matrix):
myDevice.Transform.View = Matrix.LookAtLH(
new Vector3(0.0f, 3.0f, -5.0f),
new Vector3(0.0f, 0.0f, 0.0f),
new Vector3(0.0f, 1.0f, 0.0f));
//При помощи матрицы проецирования (projection matrix)
//выполняем перспективные преобразования:
myDevice.Transform.Projection = Matrix.PerspectiveFovLH(
(float)Math.PI / 4, 1.0f, 1.0f, 100.0f);
}
//Останавливаем вращение фигуры
//во время изменения размеров формы Form1:
protected override void OnResize(System.EventArgs e)
{
myPause = ((this.WindowState ==
FormWindowState.Minimized) || !this.Visible);
}
Теперь в файле Form1.cs (или Program.cs) находим главный метод Main, комментируем весь имеющийся в этом методе автоматически сгенерированный код и записываем код с листинга предыдущего параграфа (для вывода формы Form1 и рисования на ней графики).
public void OnResetDevice(object sender, EventArgs e)
{
Device myDev = (Device)sender;
//Выключаем режим CullMode, чтобы видеть
//внутреннюю и наружную поверхности фигуры:
myDevice.RenderState.CullMode = Cull.None;
//Включаем Z - буфер:
myDevice.RenderState.ZBufferEnable = true;
//Делаем доступным освещение:
myDevice.RenderState.Lighting = true;
}
//Строим фигуру в буфере вершин:
public void OnCreateVertexBuffer(object sender, EventArgs e)
{
VertexBuffer myVB = (VertexBuffer)sender;
//В структуре PositionNormal
// создаем массив из 100 пользовательских вершин:
CustomVertex.PositionNormal[] Vertex =
(CustomVertex.PositionNormal[])myVB.Lock(0, 0);
for (int i = 0; i < 50; i++)
{
//Заполняем вершины данными:
float theta = (float)(2 * Math.PI * i) / 49;
//Вращающийся конус с одной неподвижной вершиной.
//Рассчитываем координаты вершин:
Vertex[2 * i].Position =
new Vector3((float)Math.Sin(theta), -1,
(float)Math.Cos(theta));
//Рассчитываем нормали в вершинах:
Vertex[2 * i + 1].Normal =
new Vector3((float)Math.Sin(theta), -1,
(float)Math.Cos(theta));
}
//Открываем буфер вершин:
myVB.Unlock();
}
//Включаем таймер и выполняем матричные преобразования:
private void SetupMatrices()
{
//Используем глобальную матрицу (world matrix),
//чтобы вращать фигуру вокруг оси y:
myDevice.Transform.World = Matrix.RotationAxis(
new Vector3(
(float)Math.Cos(Environment.TickCount / 250.0f), 1,
(float)Math.Sin(Environment.TickCount / 250.0f)),
Environment.TickCount / 3000.0f);
//Задаем координаты глаза наблюдателя
//в матрице вида (view matrix):
myDevice.Transform.View = Matrix.LookAtLH(
new Vector3(0.0f, 3.0f, -5.0f),
new Vector3(0.0f, 0.0f, 0.0f),
new Vector3(0.0f, 1.0f, 0.0f));
//При помощи матрицы проецирования (projection matrix)
//выполняем перспективные преобразования:
myDevice.Transform.Projection =
Matrix.PerspectiveFovLH(
(float)Math.PI / 4.0f, 1.0f, 1.0f, 100.0f);
}
// Определяем освещение фигуры цветом формата ARGB:
private void SetupLights()
{
//Устанавливаем материал и его цвет.
//Можно одновременно использовать только один материал:
Material myMaterial = new Material();
Color myColor = Color.White;
myMaterial.Diffuse = myColor;
myMaterial.Ambient = myColor;
myDevice.Material = myMaterial;
//Устанавливаем белое освещение
//с изменяющимся направлением:
myDevice.Lights[0].Type = LightType.Directional;
myDevice.Lights[0].Diffuse = Color.DarkTurquoise;
myDevice.Lights[0].Direction = new Vector3(
(float)Math.Cos(Environment.TickCount / 250.0f), 1.0f,
(float)Math.Sin(Environment.TickCount / 250.0f));
//Включаем освещение:
myDevice.Lights[0].Enabled = true;
//Включаем немного отраженного (Ambient)
//равномерно рассеянного света:
myDevice.RenderState.Ambient = Color.FromArgb(0x202020);
}
//Выполняем визуализацию преобразованных вершин:
public void myRendering()
{
if (myPause) return;
//Очищаем и заливаем форму Form1 белым цветом:
myDevice.Clear(ClearFlags.Target | ClearFlags.ZBuffer,
Color.White, 1.0f, 0);
//Начинаем сцену:
myDevice.BeginScene();
//Устанавливаем освещение и матерал:
SetupLights();
//Задаем матрицы (world, view, projection):
SetupMatrices();
myDevice.SetStreamSource(0, myVertexBuffer, 0);
myDevice.VertexFormat =
CustomVertex.PositionNormal.Format;
//Рисуем фигуру:
myDevice.DrawPrimitives(
PrimitiveType.TriangleStrip, 0, (4 * 25) - 2);
//Заканчиваем сцену:
myDevice.EndScene();
//Обновляем экран:
myDevice.Present();
}
//Останавливаем вращение фигуры
//во время изменения размеров формы Form1:
protected override void OnResize(System.EventArgs e)
{
myPause = ((this.WindowState ==
FormWindowState.Minimized) || !this.Visible);
}
// Закрываем форму Form1 после нажатия клавиши Esc:
protected override void OnKeyPress(KeyPressEventArgs e)
{
if ((int)(byte)e.KeyChar == (int)Keys.Escape)
this.Close();
}
Листинг 45.2. Код для вывода формы и рисования на ней графики.
using (Form1 myForm1 = new Form1())
{
if (!myForm1.InitializeDirectX())
{
MessageBox.Show("Ошибка при инициализации DirectX.");
return;
}
//Показываем форму Form1:
myForm1.Show();
//Рисуем графику на форме Form1:
while (myForm1.Created)
{
myForm1.myRendering();
Application.DoEvents();
}
}
Открываем файл Form1.cs (например, по схеме: File, Open, File) и выше пространства имен с именем нашего проекта (namespace Visual_DirectX_n6) записываем директивы для подключения пространств имен:
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Direct3D = Microsoft.DirectX.Direct3D;
Коды для обработки щелчков по всем кнопкам на форме Form1 (рис. 45.4) приведены выше в этой части книги.
Теперь в любом месте файла Form1.cs (например, ниже предыдущих методов для обработки щелчков по кнопкам) записываем следующие методы для выполнения матричных преобразований вершин фигуры (с текстуры) и визуализации этих преобразований в динамике.
Листинг 45.3. Методы для визуализации преобразованных вершин фигуры.
//Глобальные переменные.
//Объявляем устройство для визуализации вершин:
Device myDevice = null;
VertexBuffer myVertexBuffer = null;
//Объявляем и инициализируем объект myTexture
//класса Texture:
Texture myTexture = null;
PresentParameters myPresentParameters =
new PresentParameters();
bool myPause = false;
//Задаем параметры DirectX:
public bool InitializeDirectX()
{
try
{
myPresentParameters.Windowed = true;
myPresentParameters.SwapEffect = SwapEffect.Discard;
myPresentParameters.EnableAutoDepthStencil = true;
myPresentParameters.AutoDepthStencilFormat =
DepthFormat.D16;
//Создаем устройство для визуализации:
myDevice = new Device(0, DeviceType.Hardware, this,
CreateFlags.SoftwareVertexProcessing,
myPresentParameters);
myDevice.DeviceReset +=
new System.EventHandler(this.OnResetDevice);
this.OnCreateDevice(myDevice, null);
this.OnResetDevice(myDevice, null);
myPause = false;
return true;
}
catch (DirectXException)
{
//Перехвачена ошибка инициализации DirectX:
return false;
}
}
//Создаем буфер вершин фигуры:
public void OnCreateDevice(object sender, EventArgs e)
{
Device myDev = (Device)sender;
myVertexBuffer = new VertexBuffer(
typeof(CustomVertex.PositionNormalTextured), 100,
myDev, Usage.WriteOnly,
CustomVertex.PositionNormalTextured.Format,
Pool.Default);
myVertexBuffer.Created +=
new System.EventHandler(this.OnCreateVertexBuffer);
this.OnCreateVertexBuffer(myVertexBuffer, null);
}
//Задаем параметры устройству:
public void OnResetDevice(object sender, EventArgs e)
{
Device myDev = (Device)sender;
//Выключаем режим CullMode, чтобы видеть
//внутреннюю и наружную поверхности фигуры:
myDev.RenderState.CullMode = Cull.None;
//Выключаем трехмерное освещение:
myDev.RenderState.Lighting = false;
//Включаем Z - буфер (ZBuffer):
myDev.RenderState.ZBufferEnable = true;
//Создаем нашу текстуру.
// Загружаем на поверхность фигуры наш рисунок Texture_1
//в виде текстуры:
myTexture = TextureLoader.FromFile(myDev,
Application.StartupPath + @"\..\..\Texture_1.bmp");
}
//Строим фигуру в буфере вершин:
public void OnCreateVertexBuffer(object sender, EventArgs e)
{
VertexBuffer myVB = (VertexBuffer)sender;
//В структуре PositionNormalTextured
// создаем массив из 100 пользовательских вершин:
CustomVertex.PositionNormalTextured[] Vertex =
(CustomVertex.PositionNormalTextured[])myVB.Lock(0, 0); // Lock the buffer (which will return our structs)
for (int i = 0; i < 50; i++)
{
//Заполняем вершины данными:
float theta = (float)(2 * Math.PI * i) / 49;
//Рассчитываем нормали в вершинах:
Vertex[2 * i].Normal = new Vector3((float)Math.Sin(theta), 0, (float)Math.Cos(theta));
//Добавляем в вершину v-компоненту текстуры:
Vertex[2 * i].Tv = 1.0f;
//Рассчитываем координаты вершин:
Vertex[2 * i + 1].Position = new Vector3((float)Math.Sin(theta), 1, (float)Math.Cos(theta));
//Добавляем в вершину u-компоненту текстуры:
Vertex[2 * i + 1].Tu = ((float)i) / (50 - 1);
}
//Открываем буфер вершин:
myVB.Unlock();
}
//Включаем таймер и выполняем матричные преобразования:
private void SetupMatrices()
{
//Используем глобальную матрицу (world matrix),
//чтобы вращать фигуру вокруг оси y.
myDevice.Transform.World = Matrix.RotationAxis(
new Vector3(
(float)Math.Cos(Environment.TickCount / 250.0f), 1,
(float)Math.Sin(Environment.TickCount / 250.0f)),
Environment.TickCount / 1000.0f);
//Задаем координаты глаза наблюдателя
//в матрице вида (view matrix):
myDevice.Transform.View = Matrix.LookAtLH(
new Vector3(0.0f, 3.0f, -5.0f),
new Vector3(0.0f, 0.0f, 0.0f),
new Vector3(0.0f, 1.0f, 0.0f));
//При помощи матрицы проецирования (projection matrix)
//выполняем перспективные преобразования:
myDevice.Transform.Projection =
Matrix.PerspectiveFovLH(
(float)Math.PI / 4.0f, 1.0f, 1.0f, 100.0f);
}
//Выполняем визуализацию преобразованных вершин:
public void myRendering()
{
if (myPause) return;
//Очищаем и заливаем форму Form1 белым цветом:
myDevice.Clear(ClearFlags.Target | ClearFlags.ZBuffer,
Color.White, 1.0f, 0);
//Начинаем сцену:
myDevice.BeginScene();
//Задаем матрицы (world, view, projection):
SetupMatrices();
//Устанавливаем нашу текстуру:
myDevice.SetTexture(0, myTexture);
myDevice.TextureState[0].ColorOperation =
TextureOperation.Modulate;
myDevice.TextureState[0].ColorArgument1 =
TextureArgument.TextureColor;
myDevice.TextureState[0].ColorArgument2 =
TextureArgument.Diffuse;
myDevice.TextureState[0].AlphaOperation =
TextureOperation.Disable;
//Рисуем фигуру:
myDevice.SetStreamSource(0, myVertexBuffer, 0);
myDevice.VertexFormat =
CustomVertex.PositionNormalTextured.Format;
myDevice.DrawPrimitives(
PrimitiveType.TriangleStrip, 0, (4 * 25) - 2);
//Заканчиваем сцену:
myDevice.EndScene();
//Обновляем экран:
myDevice.Present();
}
//Останавливаем вращение фигуры
// во время изменения размеров формы Form1:
protected override void OnResize(System.EventArgs e)
{
myPause = ((this.WindowState ==
FormWindowState.Minimized) || !this.Visible);
}
//Закрываем форму Form1 после нажатия клавиши Esc:
protected override void OnKeyPress(KeyPressEventArgs e)
{
if ((int)(byte)e.KeyChar == (int)Keys.Escape)
this.Close();
}
Теперь в файле Form1.cs (или Program.cs) находим главный метод Main, комментируем весь имеющийся в этом методе автоматически сгенерированный код и записываем код со следующего листинга.
Листинг 45.4. Код для вывода формы и рисования на ней графики.
using (Form1 myForm1 = new Form1())
{
if (!myForm1.InitializeDirectX())
{
MessageBox.Show("Ошибка при инициализации DirectX.");
return;
}
//Показываем форму Form1:
myForm1.Show();
//Рисуем графику на форме Form1:
while (myForm1.Created)
{
myForm1.myRendering();
Application.DoEvents();
}
}
По этой методологии можно в проектах Visual C# при помощи DirectX проектировать разнообразные анимированные объемные изображения.
Методика разработки мультипликации
Теперь в файл Form1.cs необходимо написать нашу часть кода для выполнения мультипликации. Сначала в любом месте внутри класса Form1 (например, ниже свернутого блока кода) объявляем массив и глобальные переменные:
//Объявляем массив из 6 изображений:
Image[] myArrayImages = new Image[6];
//Объявляем и инициализируем целочисленные переменные:
int j = 0; int k = 1;
Дважды щелкаем по Form1 в режиме проектирования (или Properties, Events, Load) и в шаблон записываем такой наш код.
Листинг 65.1. Метод Form1_Load для загрузки рисунков.
private void Form1_Load(object sender, EventArgs e)
{
//Из графических файлов 6 рисунков формата (.bmp)
//заполняем массив myArrayImages[6]:
myArrayImages[0] = Image.FromFile("E:/MyDocs/" +
"Анимация/Рисунки анимации/DeepDrawing_step0.bmp");
myArrayImages[1] = Image.FromFile("E:/MyDocs//" +
"Анимация/Рисунки анимации/DeepDrawing_step1.bmp");
myArrayImages[2] = Image.FromFile("E:/MyDocs/" +
"Анимация/Рисунки анимации/DeepDrawing_step2.bmp");
myArrayImages[3] = Image.FromFile("E:/MyDocs/" +
"Анимация/Рисунки анимации/DeepDrawing_step3.bmp");
myArrayImages[4] = Image.FromFile("E:/MyDocs/" +
"Анимация/Рисунки анимации/DeepDrawing_step4.bmp");
myArrayImages[5] = Image.FromFile("E:/MyDocs/" +
"Анимация/Рисунки анимации/DeepDrawing_step5.bmp");
}
Напомним, что метод Form1_Load выполняется одновременно с появлением (загрузкой – Load) Form1 на экране монитора. При этом рисунки мультипликации из любой папки загружаются в массив рисунков myArrayImages[N] с любым (заданным нами) количеством элементов N. Отметим, что в этом листинге полный путь расположения файла каждого рисунка формата (.bmp), начиная от диска E, записан на двух строках по правилу переноса на C#.
Аналогично в приложение загружаются другие рисунки в другие массивы рисунков, если мы хотим чередовать мультипликацию из серии одних рисунков сериями других рисунков.
Чтобы подключить к работе таймер, дважды щелкаем значок timer1 (ниже формы в режиме проектирования). Появляется файл Form1.cs с шаблоном, который после записи нашего кода принимает следующий вид.
Листинг 65.2. Метод для мультипликации изображений.
private void timer1_Tick(object sender, EventArgs e)
{
//Создаем объект myGraphics класса Graphics:
Graphics myGraphics = CreateGraphics();
//Вызываем метод DrawImage, используя перегрузку №8:
myGraphics.DrawImage(myArrayImages[j], -10, -10,
myArrayImages[j].Width, myArrayImages[j].Height);
//Высвобождаем ресурсы, выделенные объекту myGraphics:
myGraphics.Dispose();
//Организовываем цикл для шести рисунков:
j = j + k;
if (j == 5)
{
//От последнего рисунка переходим к первому:
k = -5;
}
else if (j == 0)
{
//Задаем первый рисунок для начала цикла:
k = 1;
}
}
В этом коде главным является вызов перегруженного метода DrawImage, который в VC# имеет много видов перегрузки. Здесь использована перегрузка с сигнатурой Graphics.DrawImage Method(Image, Int32, Int32, Int32, Int32).
Напомним, что система координат связана с Form1, а начало координат (0, 0) расположено в верхнем левом углу этой Form1. Координаты (-10, -10) в коде являются координатами верхнего левого угла j – го рисунка (j = 0, 1, 2, 3, 4, 5). Изменяя эти координаты, мы можем удобнее расположить рисунок на Form1. Последние два параметра в методе DrawImage определяют ширину и высоту рисунка на форме Form1.
Учитывая важность данного кода для использования на практике, приведем его второй вариант. А именно, вместо вывода рисунков при помощи метода DrawImage (точнее, вместо приведенной на листинге строки кода с этим методом), мультипликацию можно выводить в рамку рисунка pictureBox1 (предварительно расположив этот элемент PictureBox на форме) при помощи кода:
//Центрируем каждый рисунок внутри рамки pictureBox1:
pictureBox1.SizeMode =
PictureBoxSizeMode.CenterImage;
//Выводим j-е рисунки с интервалом Interval:
pictureBox1.Image = myArrayImages[j];
Чтобы установить значение свойства Interval компонента Timer в зависимости от свойства Value ползунка элемента управления TrackBar, дважды щелкаем элемент управления TrackBar в режиме проектирования. Появляется файл Form1.cs с шаблоном, который после записи нашего кода принимает вид следующей процедуры:
Листинг 65.3. Метод для элемента управления TrackBar.
private void trackBar1_Scroll(object sender,
EventArgs e)
{
//Устанавливаем свойство Enabled таймера, равным True:
timer1.Enabled = true;
//Устанавливаем значение свойства Interval таймера
//в зависимости от перемещения ползунка Value:
timer1.Interval = trackBar1.Value;
}
Согласно разработанной выше методике, чтобы иметь возможность приостановить (и запустить вновь) процесс анимации на любом рисунке при помощи кнопки Stop Animation, дважды щелкаем эту кнопку в режиме проектирования (рис. 65.8). Появляется файл Form1.cs с шаблоном, который после записи нашего кода имеет такой вид.
Листинг 65.4. Обработчик щелчка кнопки Stop Animation.
//Объявляем булеву переменную OffOn
//и задаем ей значение, например, false:
bool OffOn = false;
private void button6_Click(object sender, EventArgs e)
{
//Задаем чередование остановки и возобновления анимации
//после каждого щелчка кнопки button:
if (OffOn == false)
{
//Приостанавливаем анимацию:
timer1.Enabled = false;
//Изменяем значение OffOn на противоположное:
OffOn = true;
}
else
{
//Возобновляем анимацию:
timer1.Enabled = true;
//Изменяем значение OffOn на противоположное:
OffOn = false;
}
}
Листинг 65.5. Метод для компонента PrintDocument.
private void printDocument1_PrintPage(object sender,
System.Drawing.Printing.PrintPageEventArgs e)
{
//Чтобы печатать тот j-й рисунок, который видим:
j = j - 1;
//Выводим j- й рисунок, который видим на экране,
//при помощи метода DrawImage, используя перегрузку 8:
e.Graphics.DrawImage(myArrayImages[j], -10, -10,
myArrayImages[j].Width,
myArrayImages[j].Height);
}
Теперь дважды щелкаем кнопку Print (рис. 65.8). Открывается файл Form1.cs с шаблоном, который после записи нашего кода принимает такой вид.
Листинг 65.6. Обработчик щелчка кнопки Print.
private void button3_Click(object sender, EventArgs e)
{
//Передаем объекту PrintDialog1 информацию об объекте
//PrintDocument1 при помощи свойства Document:
printDialog1.Document = printDocument1;
//Выводим стандартную панель Print при помощи метода
//ShowDialog для задания параметров печати:
if (printDialog1.ShowDialog() == DialogResult.OK)
printDocument1.Print();
}
Проверяем в действии созданную нами программу для печати на принтере любого рисунка мультипликации (на примере изготовления стакана). Для этого получаем на экране монитора (по описанной выше методике) разработанную мультипликацию, останавливаем мультипликацию на интересующем нас рисунке (при помощи кнопки Stop Animation) и щелкаем кнопку Print.
Щелкая кнопки Stop Animation и Print в различные моменты изменения (мультипликации) рисунка, мы получим на принтере распечатки различных (во времени) положений рисунка.
Таким образом, по разработанной в данной главе методике можно спроектировать мультипликацию на экране монитора и печать текущего положения мультипликации на принтере для любого технологического процесса изготовления детали (изделия, вещи), любого рисунка, чертежа или схемы.
Методика разработки приложений для выполнения расчетов с эффектами анимации
Листинг 2.1. Наш основной код с тремя переменными.
double a, b, c;
a = Convert.ToDouble(textBox1.Text);
b = Convert.ToDouble(textBox2.Text);
c = a + b;
textBox3.Text = c.ToString();
Второй вариант – с двумя переменными:
double a, b;
a = Convert.ToDouble(textBox1.Text);
b = Convert.ToDouble(textBox2.Text);
textBox3.Text = (a + b).ToString();
Третий вариант – с одной переменной:
double a;
a = Convert.ToDouble(textBox1.Text);
textBox3.Text = (a +
Convert.ToDouble(textBox2.Text)).ToString();
Четвертый вариант – без использования переменных:
textBox3.Text = (Convert.ToDouble(textBox1.Text) +
Convert.ToDouble(textBox2.Text)).ToString();
Листинг 2.2. Метод для включения таймера и задания интервала времени.
private void InitializeTimer()
{
//Включаем таймер:
timer1.Enabled = true;
//Генерируем событие Tick через каждый интервал времени,
//равный значению свойства Interval:
timer1.Interval = 500;
}
Листинг 2.3. Код для создания анимации. Вариант 1.
//Объявляем булеву переменную myText и присваиваем ей false:
bool myText = false;
private void timer1_Tick(object sender, EventArgs e)
{
//Задаем чередование "Калькулятор(Calculator)" и
//"Калькулятор с анимацией (Calculator with animation)":
if (myText == false)
{
this.Text = "Калькулятор(Calculator)";
//Изменяем значение myText на противоположное:
myText = true;
}
else
{
this.Text = "Калькулятор с анимацией " +
"(Calculator with animation)";
//Изменяем значение myText на противоположное:
myText = false;
}
}
Листинг 2.4. Код для создания анимации. Вариант 2.
//Объявляем счетчик (counter) числа интервалов
//и задаем его начальное значение:
private int counter = 0;
//Объявляем и задаем число интервалов N_Interval,
//после которого один текст сменяется другим:
private int N_Interval = 3;
private void timer1_Tick(object sender, EventArgs e)
{
//Проверяем, значение счетчика counter
// равно или еще нет числу интервалов N_Interval,
//после которого один текст сменяется другим:
if (counter >= N_Interval)
{
//Если значение счетчика накопилось и равно
//N_Interval, то выводим другой текст:
this.Text = "Калькулятор(Calculator)";
//Значение счетчика counter снова обнуляем:
counter = 0;
}
else
{
//Если значение счетчика еще не накопилось
//и не равно N_Interval,
//то выводим первый текст:
this.Text = "Калькулятор с анимацией " +
"(Calculator with animation)";
//Значение счетчика увеличиваем на 1:
counter = counter + 1;
}
}
Листинг 2.5. Код для создания анимации. Вариант 3.
//Объявляем счетчик (counter) числа интервалов
//и задаем его начальное значение:
private int counter = 0;
//Объявляем и задаем число интервалов N_Interval,
//после которого один текст сменяется другим:
private int N_Interval = 3;
//Объявляем и обнуляем счетчик i_Interval_Stop,
//который считает число интервалов
//до остановки анимации:
private int i_Interval_Stop = 0;
//Объявляем и задаем число интервалов N_Interval_Stop,
//по достижении которого анимация прекращается:
private int N_Interval_Stop = 10;
private void timer1_Tick(object sender, EventArgs e)
{
//Значение счетчика i_Interval_Stop,
//который считает число интервалов
//до остановки анимации, увеличиваем на 1:
i_Interval_Stop = i_Interval_Stop + 1;
//Проверяем число интервалов i_Interval_Stop,
//по достижении которого анимация прекращается:
if (i_Interval_Stop >= N_Interval_Stop)
timer1.Enabled = false;
//Проверяем, значение счетчика counter
// равно или еще нет числу интервалов N_Interval,
//после которого один текст сменяется другим:
if (counter >= N_Interval)
{
//Если значение счетчика накопилось и равно
//N_Interval, то выводим другой текст:
this.Text = "Калькулятор (Calculator)";
//Значение счетчика counter снова обнуляем:
counter = 0;
}
else
{
//Если значение счетчика еще не накопилось
//и не равно N_Interval,
//то выводим первый текст:
this.Text = "Калькулятор с анимацией " +
"(Calculator with animation)";
//Значение счетчика увеличиваем на 1:
counter = counter + 1;
}
}
Листинг 2.6. Строка кода, останавливающая анимацию.
timer1.Enabled = false;
Недостаток записи только этой одной строки кода заключается в том, что после остановки анимации мы не сможем запустить ее вновь.
Чтобы возобновить анимацию, мы должны в обработчик события записать другую строку кода:
Листинг 2.7. Строка кода, возобновляющая анимацию.
timer1.Enabled = true;
Листинг 2.8. Код для приостановки и возобновления анимации.
//Объявляем булеву переменную OffOn и задаем ей false:
bool OffOn = false;
private void button2_Click(object sender, EventArgs e)
{
//Задаем чередование остановки и возобновления анимации
//после каждого щелчка кнопки button2:
if (OffOn == false)
{
//Останавливаем анимацию:
timer1.Enabled = false;
//Изменяем значение OffOn на противоположное:
OffOn = true;
}
else
{
//Возобновляем анимацию:
timer1.Enabled = true;
//Изменяем значение OffOn на противоположное:
OffOn = false;
}
}
Далее в шаблоне метода организовываем цикл по переменной i:
i = i + 1;
if (i <= N_Beep)
Microsoft.VisualBasic.Interaction.Beep();
Методика разработки приложений на нескольких формах
Листинг 3.1.
Метод Button1_Click с нашим кодом для первой формы.
private void Button1_Click(object sender, EventArgs e)
{
double A, B;
A = Convert.ToDouble(textBox1.Text);
B = Convert.ToDouble(textBox2.Text);
Form2 myForm2 = new Form2();
myForm2.C = A;
myForm2.D = B;
myForm2.Show();
}
Листинг 3.2.
Строка и метод Button1_Click с нашим кодом для Form2.
public double C, D;
private void Button2_Click(object sender, EventArgs e)
{
double F, G;
F = C;
G = D;
textBox1.Text = F.ToString();
textBox2.Text = G.ToString();
textBox3.Text = (F * G).ToString();
}
Листинг 3.3. Код для бегущего слева – направо заголовка.
//Объявляем и обнуляем глобальную переменную:
int i = 0;
private void timer1_Tick(object sender, EventArgs e)
{
//Записываем текст заголовка в переменную myString:
string myString = "Калькулятор2_2 (Calculator2_2) ";
//Справа - налево появляется i-я буква заголовка:
this.Text = myString.Substring(0,i);
//Организовываем цикл вывода следующей i-й буквы:
i = i + 1;
if (i == myString.Length)
i = 1;
}
Листинг 3.4. Код для бегущего справа – налево заголовка.
//Объявляем глобальную переменную myString
//и записываем в нее текст заголовка:
public static string myString =
"Калькулятор2_2 (Calculator2_2). Форма2 (Form2) ";
//Объявляем глобальную переменную i
//и приравниваем ее значение числу знаков заголовка:
int i = myString.Length;
private void timer1_Tick(object sender, EventArgs e)
{
//Слева - направо удаляется одна i-я буква заголовка:
this.Text = myString.Substring(0,i);
//Организовываем цикл удаления
//следующей i-й буквы заголовка:
i = i - 1;
if (i == -1)
i = myString.Length;
}
Методика создания компьютерных игр и мультфильмов
Листинг 49.1. Объявление члена интерфейса и глобальных переменных.
//Объявляем персонаж Джина (Genie) и путь к его файлу:
static AgentObjects.IAgentCtlCharacterEx myGenie;
static String DATAPATH_1 = "Genie.acs";
//Объявляем член интерфейса Agent:
private AgentObjects.Agent myAgentController;
//Глобальная переменная для любого текста персонажа:
static String mySpeech;
//Объявляем общую для персонажей объектную переменную:
Object myObject = null;
Теперь необходимо написать нашу часть кода в шаблон главного метода Form1_Load, чтобы после загрузки Form1 на ней автоматически появился Джин, произносил тексты, перелетал на другое место за заданное нами время (например, з 15000 миллисекунд или 15 секунд) и выполнял анимации нашего мультфильма (пояснения даны в программе). Для этого дважды щелкаем по Form1 в режиме проектирования (или в панели Properties с заголовком Form1 на вкладке Events дважды щелкаем по имени события Load). Появляется файл Form1.cs с шаблоном (метода Form1_Load), который после записи нашего кода (согласно постановке задачи для персонажа Genie) принимает следующий вид.
Листинг 49.2. Загрузка персонажа, его речей и анимаций.
private void Form1_Load(object sender, EventArgs e)
{
//Загружаем персонаж в элемент управления axAgent1:
axAgent1.Characters.Load("Genie", DATAPATH_1);
myGenie = axAgent1.Characters.Character("Genie");
//Чтобы он выполнял голосовые команды на англ. яз.:
myGenie.LanguageID = 0x409;
//Чтобы персонаж произносил речь через динамики,
//задаем ему следующие свойства:
myAgentController = new AgentObjects.Agent();
myAgentController.Connected = true;
myAgentController.Characters.Load(
"genie", "genie.acs");
myGenie =
myAgentController.Characters.Character("genie");
//От начала координат в вехнем левом углу Form1
//задаем координаты "x, y" _
//места расположения персонажа:
myGenie.MoveTo(
Convert.ToInt16(this.Location.X + 250),
Convert.ToInt16(this.Location.Y + 100), 1000);
//Показываем персонаж:
myGenie.Show(0);
//Персонаж выполняет анимацию Announce:
myGenie.Play("Announce");
//Произносит записанную нами речь:
mySpeech =
"Здравствуйте, меня зовут Джин. " +
" Спасибо за Ваше внимание ко мне.";
myGenie.Speak(mySpeech, myObject);
//Выполняет анимацию GestureRight:
myGenie.Play("GestureRight");
//Произносит речь:
mySpeech = "Попросите меня сказать " +
"текст из этого окна...";
myGenie.Speak(mySpeech, myObject);
//Перемещается к переключателям с именами анимаций
//за заданное нами время, равное,
//например, 15000 мс (или 15 с.):
myGenie.MoveTo(
Convert.ToInt16(this.Location.X + 540),
Convert.ToInt16(this.Location.Y + 300), 15000);
//Выполняет анимацию GestureRight:
myGenie.Play("GestureRight");
//Произносит записанную нами речь:
mySpeech = "... или выберите, пожалуйста, " +
"вид моей анимации..";
myGenie.Speak(mySpeech, myObject);
}
Теперь, следуя алгоритму в первом параграфе, мы записываем такой код, чтобы:
если мы щелкнем кнопку на форме, но ничего не запишем в окно TextBox, то персонаж попросит нас записать текст в это окно;
когда мы запишем в окно TextBox текст на русском языке и щелкнем кнопку на форме, то персонаж произнесет по-русски записанный нами текст.
Дважды щелкаем эту кнопку в режиме редактирования (или в панели Properties с заголовком button1 на вкладке Events дважды щелкаем по имени события Click).
Появляется файл Form1.cs с шаблоном (метода button1_Click), который после записи нашего кода принимает следующий вид.
Листинг 49.3. Метод для кнопки.
private void button1_Click(object sender, EventArgs e)
{
// Персонаж произносит текст из окна TextBox
//при условии:
//Если окно TextBox не заполнено:
if (textBox1.TextLength == 0)
{
mySpeech =
"Запишите, пожалуйста, в окно слова, " +
"которые я должен произнести.";
myGenie.Speak(mySpeech, myObject);
}
else
//иначе, если окно TextBox заполнено,
//персонаж произносит слова из этого TextBox:
{
mySpeech = textBox1.Text;
myGenie.Speak(mySpeech, myObject);
}
}
Записываем код, чтобы после нашего щелчка мышью (или после нажатий двух клавиш, например, для первого переключателя: Alt+1) по первому переключателю с именем анимации Announce, персонаж:
произносил речь;
показывал выбранную нами анимацию.
Для этого дважды щелкаем первый переключатель в режиме редактирования (или в панели Properties с заголовком radioButton1 на вкладке Events дважды щелкаем по имени события CheckedChanged).
Появляется файл Form1.cs с шаблоном (метода radioButton1_CheckedChanged), который после записи нашего кода принимает такой вид.
Листинг 49.4. Метод для переключателя.
private void radioButton1_CheckedChanged(
object sender, EventArgs e)
{
//Останавливаем все предыдущие действия персонажа:
myGenie.StopAll(myObject);
//Перед анимацией персонаж произносит фразу:
mySpeech = "Охотно выполняю эту анимацию.";
myGenie.Speak(mySpeech, myObject);
//Показываем выбранную нами анимацию персонажа:
myGenie.Play("Announce");
//Показываем анимацию персонажа в покое:
myGenie.Play("RestPose");
}
Аналогично последовательно дважды щелкаем по каждому последующему переключателю в режиме редактирования и в каждый шаблон метода записываем такой же (как на предыдущем листинге) код, только строку с именем предыдущей анимации заменяем на соответствующую строку из следующих девяти строк (по числу оставшихся переключателей) с именами новой анимации:
myGenie.Play("Congratulate");
myGenie.Play("Congratulate_2");
myGenie.Play("DontRecognize");
myGenie.Play("Explain");
myGenie.Play("GetAttention");
myGenie.Play("Suggest");
myGenie.Play("Surprised");
myGenie.Play("Wave");
myGenie.Play("Write");
Аналогично можно записать код для решения любой подобной задачи согласно разработанному нами алгоритму.
Методика создания вычислительной системы
Листинг 9.1. Код для мигания названия переключателя.
//Объявляем булеву переменную myText со значением false:
bool myText = false;
private void timer1_Tick(object sender, EventArgs e)
{
//Вводим анимацию:
if (myText == false)
{
//Выводим название переключателя:
this.radioButton3.Text = "&3. Calculator";
//Изменяем значение myText на противоположное:
myText = true;
}
else
{
//Удаляем название переключателя:
this.radioButton3.Text = "";
//Изменяем значение myText на противоположное:
myText = false;
}
}
В этом коде в строке (radioButton3->Text = "";) между кавычками мы можем записать другой текст, который будет чередоваться с первым названием.
Аналогично можно также дописать код, чтобы сделать анимационными сразу несколько переключателей. Задавая в панели Properties с заголовком Timer1 различные значения свойству Interval (а выше мы задали 1000 миллисекунд или 1 секунду), можно изменять частоту мигания, чтобы это мигание было, например, приятным для наших глаз. Методика приостановки и возобновления анимации после щелчков по кнопке или по самой формы уже была приведена выше.
Методика управления цветом изображения
Листинг 67.1. Метод для вывода двух прямоугольников, заполненных цветом.
private void pictureBox1_Paint(object sender,
PaintEventArgs e)
{
//Создаем изображение первого прямоугольника,
//заполненного серым цветом:
Bitmap myBitmap = new Bitmap(150, 150,
PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(myBitmap);
g.FillRectangle(new SolidBrush(Color.FromArgb(255,
128, 128, 128)), new Rectangle(0, 0, 150, 150));
myBitmap.Save("Rectangle1.jpg");
//Открываем файл изображения и
//рисуем это первое изображение на экране:
Image myImage =
Image.FromFile("Rectangle1.jpg");
e.Graphics.DrawImage(myImage, 20, 20);
//Создаем и инициализируем
//матрицу цвета myColorMatrix класса ColorMatrix:
ColorMatrix myColorMatrix = new ColorMatrix();
myColorMatrix.Matrix00 = 1.7F; // Red
myColorMatrix.Matrix11 = 1; // Green
myColorMatrix.Matrix22 = 1; // Blue
myColorMatrix.Matrix33 = 1; // alpha
myColorMatrix.Matrix44 = 1; // w
//Создаем объект myImageAttributes
//класса ImageAttributes:
ImageAttributes myImageAttributes =
new ImageAttributes();
//Устанавливаем матрицу цвета myColorMatrix
//в качестве текущей цветовой модели:
myImageAttributes.SetColorMatrix(myColorMatrix);
//Создаем второй прямоугольник:
Rectangle myRectangle2 = new Rectangle(200, 20,
200, 200);
//Рисуем на экране второй прямоугольник,
//заполненный при помощи матрицы цвета myColorMatrix:
e.Graphics.DrawImage(myImage, myRectangle2, 0, 0,
200, 200, GraphicsUnit.Pixel, myImageAttributes);
}
В самом верху этого файла Form1.cs подключаем нужное пространство имен:
using System.Drawing.Imaging;
После обычного построения и выполнения программы (Ctrl+F5) появляется форма Form1 с двумя прямоугольниками, из которых первый (слева) заполнен серым цветом, а второй прямоугольник заполнен красным цветом заданной нами (в строке myColorMatrix.Matrix00 = 1.7F;) величины 1.7 (рис. 67.1).
Листинг 67.2. Объявление и инициализация исходных данных.
//Объявляем и инициализируем исходные данные:
static float Alpha = 0.1F;
float Step_of_Color = 0.2F;
float [,] myArray = {{1, 0, 0, 0, 0},
{0, 1, 0, 0, 0},
{0, 0, 1, 0, 0},
{0, 0, 0, Alpha, 0},
{0, 0, 0, 0, 1}};
ColorMatrix myColorMatrix = new ColorMatrix();
ImageAttributes myImageAttributes = new ImageAttributes();
Rectangle myRectangle = new Rectangle();
//Присваиваем двум объектам myImage1 и myImage2
//изображения двух обложек книг
//при помощи метода FromFile
//и пути расположения файлов с изображениями:
Image myImage1 =
Image.FromFile("D:/MyDocs/Cover in site/
Cover on VS .NET.jpg");
Image myImage2 =
Image.FromFile("D:/MyDocs/Cover in site/
Cover on VB .NET.jpg");
//Создаем объект myRandom класса Random
//для генерирования случайных чисел:
Random myRandom = new Random();
Листинг 67.3. Метод Form1_Load для Form1.
private void Form1_Load(object sender, EventArgs e)
{
//Вводим прямоугольник myRectangle с размерами,
//равными размерам элемента PictureBox:
myRectangle = new Rectangle(pictureBox1.Location.X,
pictureBox1.Location.Y,
this.pictureBox1.Width, this.pictureBox1.Height);
//Передаем начальные значения
//диагональных элементов матрицы myArray
//элементам матрицы цвета myColorMatrix:
myColorMatrix.Matrix00 = myArray[0, 0];
myColorMatrix.Matrix11 = myArray[1, 1];
myColorMatrix.Matrix22 = myArray[2, 2];
myColorMatrix.Matrix33 = myArray[3, 3];
myColorMatrix.Matrix44 = myArray[4, 4];
}
Чтобы мультипликационное изображение появилось внутри элемента управления PictureBox, в панели Properties (для этого элемента) на вкладке Events дважды щелкаем по имени события Paint и в появившийся шаблон записываем наш код, после чего метод принимает такой вид.
Листинг 67.4. Код для элемента управления PictureBox.
private void pictureBox1_Paint(object sender,
PaintEventArgs e)
{
// Рисуем на экране первое изображение myImage1
//внутри прямоугольника myRectangle:
e.Graphics.DrawImage(myImage1, myRectangle);
//Устанавливаем матрицу цвета myColorMatrix
//в качестве текущей цветовой модели:
myImageAttributes.SetColorMatrix(myColorMatrix);
//Рисуем на экране второе изображение myImage2:
e.Graphics.DrawImage(myImage2, myRectangle,
pictureBox1.Location.X, pictureBox1.Location.Y,
myImage2.Width, myImage2.Height,
GraphicsUnit.Pixel, myImageAttributes);
}
Чтобы подключить к работе таймер, дважды щелкаем значок для компонента Timer (ниже формы в режиме проектирования). Появляется файл Form1.cs с шаблоном, который после записи нашего кода принимает следующий вид.
Листинг 67.5. Метод для компонента Timer.
private void timer1_Tick(object sender, EventArgs e)
{
//Задаем значения элементов
//матрицы цвета myColorMatrix класса ColorMatrix
//при помощи объекта myRandom
//генератора случайных чисел
//и метода NextDouble, которая возвращает
//случайное число между 0,0 и 1,0:
if (Alpha < 0 )
if (Alpha > 1)
{
Step_of_Color = Step_of_Color * (-1);
myColorMatrix.Matrix01 =
Convert.ToSingle(myRandom.NextDouble());
myColorMatrix.Matrix12 =
Convert.ToSingle(myRandom.NextDouble());
myColorMatrix.Matrix23 =
Convert.ToSingle(myRandom.NextDouble());
}
//Увеличиваем элемент матрицы цвета на один шаг:
Alpha = Alpha + Step_of_Color;
myColorMatrix.Matrix33 = Alpha;
//Перерисовываем экран:
this.Refresh();
}
Чтобы установить значение свойства Interval компонента Timer в зависимости от свойства Value ползунка элемента управления TrackBar, дважды щелкаем элемент управления TrackBar в режиме проектирования. Появляется файл Form1.cs с шаблоном, который после записи нашего кода принимает следующий вид.
Листинг 67.6. Метод для элемента управления TrackBar.
private void trackBar1_Scroll(object sender, EventArgs e)
{
timer1.Enabled = true;
timer1.Interval = trackBar1.Value;
}
Согласно разработанной выше методике, чтобы иметь возможность приостановить (и запустить вновь) процесс анимации на любом рисунке при помощи кнопки Stop Animation, дважды щелкаем эту кнопку в режиме проектирования (рис. 67.2). Появляется файл Form1.cs с шаблоном, который после записи нашего кода принимает следующий вид.
Листинг 67.7. Метод для кнопки Stop Animation.
bool OffOn = false;
private void button6_Click(object sender, EventArgs e)
{
if (OffOn == false)
{
//Приостанавливаем анимацию:
timer1.Enabled = false;
OffOn = true;
}
else
{
//Возобновляем анимацию:
timer1.Enabled = true;
OffOn = false;
}
}
Листинг 67.8. Метод для компонента PrintDocument.
private void printDocument1_PrintPage(object sender,
System.Drawing.Printing.PrintPageEventArgs e)
{
//Печатаем первое изображение myImage1
//внутри прямоугольника myRectangle:
e.Graphics.DrawImage(myImage1, myRectangle);
}
Теперь дважды щелкаем кнопку Print (рис. 67.1). Открывается файл Form1.cs с шаблоном, который после записи нашего кода принимает следующий вид.
Листинг 67.9. Метод для кнопки Print.
private void button3_Click(object sender, EventArgs e)
{
//Передаем объекту PrintDialog1 информацию об объекте
//PrintDocument1 при помощи свойства Document:
printDialog1.Document = printDocument1;
//Выводим стандартную панель Print при помощи метода
//ShowDialog для задания параметров печати:
if (printDialog1.ShowDialog() == DialogResult.OK)
printDocument1.Print();
}
Методика управления мультипликационными персонажами
Листинг 47.1. Объявление членов интерфейса и глобальных переменных.
//Объявляем персонаж Джина (Genie) и путь к его файлу:
static AgentObjects.IAgentCtlCharacterEx myGenie;
static String DATAPATH_1 = "Genie.acs";
//Объявляем персонаж Мага (Merlin) и путь к его файлу:
static AgentObjects.IAgentCtlCharacterEx myMerlin;
static String DATAPATH_2 = "Merlin.acs";
//Объявляем персонаж Попугая (Peedy) и путь к его файлу:
static AgentObjects.IAgentCtlCharacterEx myPeedy;
static String DATAPATH_3 = "Peedy.acs";
//Объявляем персонаж Робота (Robby) и путь к его файлу:
static AgentObjects.IAgentCtlCharacterEx myRobby;
static String DATAPATH_4 = "Robby.acs";
//Объявляем член интерфейса Agent:
private AgentObjects.Agent myAgentController;
//Глобальная переменная для любого текста персонажа:
static String mySpeech;
А в шаблон (метода Form1_Load) записываем наш код (согласно постановке задачи для всех персонажей), и метод Form1_Load принимает такой вид.
Листинг 47.2. Загрузка персонажей в элементы управления.
private void Form1_Load(object sender, EventArgs e)
{
//Загружаем персонаж в элемент управления axAgent1:
axAgent1.Characters.Load("Genie", DATAPATH_1);
myGenie = axAgent1.Characters.Character("Genie");
//Чтобы он выполнял голосовые команды на англ. яз.:
myGenie.LanguageID = 0x409;
//Записываем текст речи персонажа в окно TextBox:
textBox1.Text = "Здравствуйте. Меня зовут Джин.";
//Загружаем персонаж в элемент управления axAgent2:
axAgent2.Characters.Load("Merlin", DATAPATH_2);
myMerlin = axAgent2.Characters.Character("Merlin");
//Чтобы он выполнял голосовые команды на англ. яз.:
myMerlin.LanguageID = 0x409;
//Записываем текст речи персонажа в окно TextBox:
textBox2.Text = "Здравствуйте. Меня зовут Маг.";
//Загружаем персонаж в элемент управления axAgent3:
axAgent3.Characters.Load("Peedy", DATAPATH_3);
myPeedy = axAgent3.Characters.Character("Peedy");
// Чтобы он выполнял голосовые команды на англ. яз.:
myPeedy.LanguageID = 0x409;
//Записываем текст речи персонажа в окно TextBox:
textBox3.Text = "Здравствуйте. Меня зовут Попугай.";
//Загружаем персонаж в элемент управления axAgent4:
axAgent4.Characters.Load("Robby", DATAPATH_4);
myRobby = axAgent4.Characters.Character("Robby");
//Чтобы он выполнял голосовые команды на англ. яз.:
myRobby.LanguageID = 0x409;
//Записываем текст речи персонажа в окно TextBox:
textBox4.Text = "Здравствуйте. Меня зовут Робот.";
//Для всех четырех персонажей добавляем
//нашу голосовую команду, как пользователей,
//например, команду "MoveToMouse" -
//переместиться на место курсора мыши:
mySpeech = "MoveToMouse";
myGenie.Commands.Add(
mySpeech, mySpeech, mySpeech, true, true);
myMerlin.Commands.Add(
mySpeech, mySpeech, mySpeech, true, true);
myPeedy.Commands.Add(
mySpeech, mySpeech, mySpeech, true, true);
myRobby.Commands.Add(
mySpeech, mySpeech, mySpeech, true, true);
//Задаем, например, Джину выполнение еще команды,
//для примера, голосовой команды "Merlin":
mySpeech = "Merlin";
myGenie.Commands.Add(
mySpeech, mySpeech, mySpeech, true, true);
//Задаем, например, Магу выполнение еще команды,
//для примера, голосовой команды "Peedy":
mySpeech = "Peedy";
myMerlin.Commands.Add(
mySpeech, mySpeech, mySpeech, true, true);
//Задаем, например, Попугаю выполнение еще команды,
//для примера, голосовой команды "Robby":
mySpeech = "Robby";
myPeedy.Commands.Add(
mySpeech, mySpeech, mySpeech, true, true);
//Задаем, например, Роботу выполнение еще команды,
//для примера, голосовой команды "Genie":
mySpeech = "Genie";
myRobby.Commands.Add(
mySpeech, mySpeech, mySpeech, true, true);
}
Чтобы записать нашу часть кода для показа на экране монитора первого персонажа Genie (Джин) при помощи первой кнопки “Показать Джина” на Form1 (рис. 47.5), дважды щелкаем эту кнопку в режиме редактирования (или в панели Properties выбираем заголовок button1 и на вкладке Events дважды щелкаем по имени события Click). Появляется файл Form1.h с шаблоном (метода button1_Click), который после записи нашего кода (согласно постановке задачи для первого персонажа) принимает следующий вид.
Листинг 47.3. Метод для кнопки “Показать Джина”.
//Объявляем общую для персонажей объектную переменную:
Object myObject = null;
private void button1_Click(object sender, EventArgs e)
{
//От начала координат в вехнем левом углу Form1
//задаем координаты "x,y" места расположения персонажа:
myGenie.MoveTo(
Convert.ToInt16(this.Location.X + 400),
Convert.ToInt16(this.Location.Y - 130), 1000);
//Показать персонаж в заданном месте:
myGenie.Show(0);
//Чтобы персонаж произносил речь через динамики,
//задаем ему следующие свойства:
myAgentController = new AgentObjects.Agent();
myAgentController.Connected = true;
myAgentController.Characters.Load(
"genie", "genie.acs");
myGenie =
myAgentController.Characters.Character("genie");
//Персонаж произносит текст из окна TextBox
//и показывает этот текст в виде подсказки:
myGenie.Speak(textBox1.Text, myObject);
}
Отметим, что в этом коде и далее число 1000 означает время (в миллисекундах) выполнения персонажем нашей команды в коде; это время, естественно, мы можем изменять.
Чтобы удалить персонаж с экрана монитора при помощи второй кнопки “Скрыть Джина ” на Form1 (рис. 47.5), дважды щелкаем эту кнопку в режиме редактирования. Появляется файл Form1.h с шаблоном (метода button2_Click), который после записи нашего кода принимает такой вид.
Листинг 47.4. Метод для кнопки “Скрыть Джина”.
private void button2_Click(object sender, EventArgs e)
{
//Скрыть персонаж:
myGenie.Hide(0);
}
Аналогично, чтобы записать нашу часть кода для показа на экране монитора второго персонажа Merlin (Маг) при помощи кнопки “Показать Мага” на Form1 (рис. 47.5), дважды щелкаем эту кнопку в режиме редактирования. Появляется файл Form1.h с шаблоном (функции button3_Click), который после записи нашего кода принимает следующий вид.
Листинг 47.5. Метод для кнопки “Показать Мага”.
private void button3_Click(object sender, EventArgs e)
{
//От начала координат в вехнем левом углу Form1
//задаем координаты "x,y" места расположения персонажа:
myMerlin.MoveTo(
Convert.ToInt16(this.Location.X + 400),
Convert.ToInt16(this.Location.Y - 130), 1000);
//Показать персонаж в заданном месте:
myMerlin.Show(0);
//Чтобы персонаж произносил речь через динамики,
//задаем ему следующие свойства:
myAgentController = new AgentObjects.Agent();
myAgentController.Connected = true;
myAgentController.Characters.Load(
"merlin", "merlin.acs");
myMerlin =
myAgentController.Characters.Character("merlin");
//Персонаж произносит текст из окна TextBox
//и показывает этот текст в виде подсказки:
myMerlin.Speak(textBox2.Text, myObject);
}
Чтобы удалить персонаж с экрана монитора при помощи кнопки “Скрыть Мага” на Form1 (рис. 47.5), дважды щелкаем эту кнопку в режиме редактирования. Появляется файл Form1.h с шаблоном (функции button4_Click), который после записи нашего кода принимает такой вид.
Листинг 47.6. Метод для кнопки “Скрыть Мага”.
private void button4_Click(object sender, EventArgs e)
{
//Скрыть персонаж:
myMerlin.Hide(0);
}
Аналогично, чтобы записать нашу часть кода для показа на экране монитора третьего персонажа Peedy (Попугай) при помощи кнопки “Показать Попугая” на Form1 (рис. 47.5), дважды щелкаем эту кнопку в режиме редактирования. Появляется файл Form1.h с шаблоном (функции button5_Click), который после записи нашего кода принимает такой вид.
Листинг 47.7. Метод для кнопки “Показать Попугая”.
private void button5_Click(object sender, EventArgs e)
{
//От начала координат в вехнем левом углу Form1
//задаем координаты "x,y" места расположения персонажа:
myPeedy.MoveTo(
Convert.ToInt16(this.Location.X + 400),
Convert.ToInt16(this.Location.Y - 130), 1000);
//Показать персонаж в заданном месте:
myPeedy.Show(0);
// Чтобы персонаж произносил речь через динамики,
//задаем ему следующие свойства:
myAgentController = new AgentObjects.Agent();
myAgentController.Connected = true;
myAgentController.Characters.Load(
"peedy", "peedy.acs");
myPeedy =
myAgentController.Characters.Character("peedy");
//Персонаж произносит текст из окна TextBox
//и показывает этот текст в виде подсказки:
myPeedy.Speak(textBox3.Text, myObject);
}
Чтобы удалить персонаж с экрана монитора при помощи кнопки “Скрыть Попугая ” на Form1 (рис. 47.5), дважды щелкаем эту кнопку в режиме редактирования. Появляется файл Form1.h с шаблоном (метода button6_Click), который после записи нашего кода принимает такой вид.
Листинг 47.8. Метод для кнопки “Скрыть Попугая”.
private void button6_Click(object sender, EventArgs e)
{
//Скрыть персонаж:
myPeedy.Hide(0);
}
Аналогично, чтобы записать нашу часть кода для показа на экране монитора последнего четвертого персонажа Robby (Робот) при помощи кнопки “Показать Робота” на Form1 (рис. 47.5), дважды щелкаем эту кнопку в режиме редактирования. Появляется файл Form1.h с шаблоном (метода button7_Click), который после записи нашего кода принимает такой вид.
Листинг 47.9. Метод для кнопки “Показать Робота”.
private void button7_Click(object sender, EventArgs e)
{
//От начала координат в вехнем левом углу Form1
//задаем координаты "x,y" места расположения персонажа:
myRobby.MoveTo(
Convert.ToInt16(this.Location.X + 400),
Convert.ToInt16(this.Location.Y - 130), 1000);
//Показать персонаж в заданном месте:
myRobby.Show(0);
// Чтобы персонаж произносил речь через динамики,
//задаем ему следующие свойства:
myAgentController = new AgentObjects.Agent();
myAgentController.Connected = true;
myAgentController.Characters.Load(
"robby", "robby.acs");
myRobby =
myAgentController.Characters.Character("robby");
//Персонаж произносит текст из окна TextBox
//и показывает этот текст в виде подсказки:
myRobby.Speak(textBox4.Text, myObject);
}
Чтобы удалить персонаж с экрана монитора при помощи кнопки “Скрыть Робота ” на Form1 (рис. 47.5), дважды щелкаем эту кнопку в режиме редактирования. Появляется файл Form1.h с шаблоном (метода button8_Click), который после записи нашего кода принимает такой вид.
Листинг 47.10. Метод для кнопки “Скрыть Робота”.
private void button2_Click(object sender, EventArgs e)
{
//Скрыть персонаж:
myRobby.Hide(0);
}
Теперь, следуя алгоритму в первом параграфе, мы записываем такой код, чтобы после нашего щелчка мышью по любому персонажу, этот персонаж:
1) выполнял анимацию, например, Confused (Смущенный);
2) произносил текст, например, такой: Есть проблемы?;
3) выполнял заключительную анимацию, например, в виде расслабленной позы отдыха RestPose. Для этого в панели Properties выбираем заголовок axAgent1 и на вкладке Events дважды щелкаем по имени события ClickEvent (рис. 47.10).
Появляется файл Form1.cs с шаблоном (метода axAgent1_ClickEvent), который после записи нашего кода (согласно алгоритму) принимает вид листинга 47.11. Аналогично в панели Properties последовательно выбираем заголовки для других элементов управления (axAgent2, axAgent3, axAgent4) и на вкладке Events дважды щелкаем по имени события ClickEvent. Появляется файл Form1.cs с шаблонами, которые после записи нашего кода принимают вид листингов 47.12, 47.13, 47.14.
Листинг 47.11. Метод, обрабатывающий щелчок по персонажу Genie.
private void axAgent1_ClickEvent(object sender,
AxAgentObjects._AgentEvents_ClickEvent e)
{
//Персонаж выполняет анимацию Confused:
myGenie.Play("Confused");
//Персонаж произносит текст:
mySpeech = "Есть проблемы?";
myGenie.Speak(mySpeech, myObject);
//Персонаж выполняет анимацию RestPose:
myGenie.Play("RestPose");
}
Листинг 47.12. Метод, обрабатывающий щелчок по персонажу Merlin.
private void axAgent2_ClickEvent(object sender,
AxAgentObjects._AgentEvents_ClickEvent e)
{
//Персонаж выполняет анимацию Confused:
myMerlin.Play("Confused");
//Персонаж произносит текст:
mySpeech = "Имеются проблемы?";
myMerlin.Speak(mySpeech, myObject);
//Персонаж выполняет анимацию RestPose:
myMerlin.Play("RestPose");
}
Листинг 47.13. Метод, обрабатывающий щелчок по персонажу Peedy.
private void axAgent3_ClickEvent(object sender,
AxAgentObjects._AgentEvents_ClickEvent e)
{
//Персонаж выполняет анимацию Confused:
myPeedy.Play("Confused");
//Персонаж произносит текст:
mySpeech = "Попка умный и хочет есть.";
myPeedy.Speak(mySpeech, myObject);
//Персонаж выполняет анимацию RestPose:
myPeedy.Play("RestPose");
}
Листинг 47.14. Метод, обрабатывающий щелчок по персонажу Robby.
private void axAgent4_ClickEvent(object sender,
AxAgentObjects._AgentEvents_ClickEvent e)
{
//Персонаж выполняет анимацию Confused:
myGenie.Play("Confused");
//Персонаж произносит текст:
mySpeech = "Есть проблемы?";
myGenie.Speak(mySpeech, myObject);
//Персонаж выполняет анимацию RestPose:
myGenie.Play("RestPose");
}
Листинг 47.15. Метод, чтобы Джин выполнял наши голосовые команды.
//Объявляем глобальный объект myCommand интерфейса
static AgentObjects.IAgentCtlUserInput myCommand;
private void axAgent1_Command(object sender,
AxAgentObjects._AgentEvents_CommandEvent e)
{
//Связываем объект myCommand с голосом пользователя:
myCommand =
(AgentObjects.IAgentCtlUserInput)(e.userInput);
//После команды голосом "MoveToMouse"
// персонаж перемещается на место курсора мыши:
if (myCommand.Voice == "MoveToMouse")
{
myGenie.MoveTo(
Convert.ToInt16(Cursor.Position.X),
Convert.ToInt16(Cursor.Position.Y), 1000);
}
//После команды голосом "Merlin"
//прежний персонаж скрывается, а новый появляется:
if (myCommand.Voice == "Merlin")
{
//Скрыть прежний персонаж:
myGenie.Hide(0);
//От начала координат в верхнем левом углу Form1
//задаем координаты "x,y" места нового персонажа:
myMerlin.MoveTo(
Convert.ToInt16(this.Location.X + 400),
Convert.ToInt16(this.Location.Y - 130), 1000);
//Показать новый персонаж в заданном месте:
myMerlin.Show(0);
//Чтобы персонаж произносил речь через динамики,
//задаем ему следующие свойства:
myAgentController = new AgentObjects.Agent();
myAgentController.Connected = true;
myAgentController.Characters.Load(
"merlin", "merlin.acs");
myMerlin =
myAgentController.Characters.Character("merlin");
//Новый персонаж произносит речь:
mySpeech = "Я - Маг. Есть проблемы?";
myMerlin.Speak(mySpeech, myObject);
}
}
Поясним этот код. После нашей голосовой команды Hide текущий персонаж скрывается (без написания дополнительного кода в данном листинге). Но если мы хотим, чтобы после произнесения нами одного из заданных по умолчанию имен персонажей (Genie, Merlin, Peedy, Robby), этот озвученный новый персонаж не просто появился в верхнем левом углу экрана, а появился в заданном нами месте и после этого произнес речь, мы должны записать это в коде, что мы и сделали в этом листинге.
Аналогично (для следующего персонажа) в панели Properties выбираем заголовок следующего элемента управления axAgent2 и на вкладке Events дважды щелкаем по имени события Command.
Появляется файл Form1.h с шаблоном (метода axAgent1_Command), который после записи нашего кода (согласно алгоритму) принимает следующий вид.
Листинг 47.16. Метод, чтобы Маг выполнял наши голосовые команды.
private void axAgent2_Command(object sender,
AxAgentObjects._AgentEvents_CommandEvent e)
{
//Связываем объект myCommand с голосом пользователя:
myCommand =
(AgentObjects.IAgentCtlUserInput)(e.userInput);
//После команды голосом "MoveToMouse"
//персонаж перемещается на место курсора мыши:
if (myCommand.Voice == "MoveToMouse")
{
myMerlin.MoveTo(
Convert.ToInt16(Cursor.Position.X),
Convert.ToInt16(Cursor.Position.Y), 1000);
}
//После команды голосом "Peedy"
//прежний персонаж скрывается, а новый появляется:
if (myCommand.Voice == "Peedy")
{
//Скрыть прежний персонаж:
myMerlin.Hide(0);
//От начала координат в верхнем левом углу Form1
//задаем координаты "x,y" места нового персонажа:
myPeedy.MoveTo(
Convert.ToInt16(this.Location.X + 400),
Convert.ToInt16(this.Location.Y - 130), 1000);
//Показать новый персонаж в заданном месте:
myPeedy.Show(0);
//Чтобы персонаж произносил речь через динамики,
//задаем ему следующие свойства:
myAgentController = new AgentObjects.Agent();
myAgentController.Connected = true;
myAgentController.Characters.Load(
"peedy", "peedy.acs");
myPeedy =
myAgentController.Characters.Character("peedy");
//Новый персонаж произносит речь:
mySpeech = "Я - Попугай. Есть проблемы?";
myPeedy.Speak(mySpeech, myObject);
}
}
Аналогично (для следующего персонажа) в панели Properties выбираем заголовок следующего элемента управления axAgent3 и на вкладке Events дважды щелкаем по имени события Command. Появляется файл Form1.cs с шаблоном (метода axAgent3_Command), который после записи нашего кода (согласно алгоритму) принимает следующий вид.
Листинг 47.17. Метод, чтобы Попугай выполнял наши голосовые команды.
private void axAgent3_Command(object sender,
AxAgentObjects._AgentEvents_CommandEvent e)
{
//Связываем объект myCommand с голосом пользователя:
myCommand =
(AgentObjects.IAgentCtlUserInput)(e.userInput);
//После команды голосом "MoveToMouse"
//персонаж перемещается на место курсора мыши:
if (myCommand.Voice == "MoveToMouse")
{
myPeedy.MoveTo(
Convert.ToInt16(Cursor.Position.X),
Convert.ToInt16(Cursor.Position.Y), 1000);
}
//После команды голосом "Robby"
//прежний персонаж скрывается, а новый появляется:
if (myCommand.Voice == "Robby")
{
//Скрыть прежний персонаж:
myPeedy.Hide(0);
//От начала координат в верхнем левом углу Form1
//задаем координаты "x,y" места нового персонажа:
myRobby.MoveTo(
Convert.ToInt16(this.Location.X + 400),
Convert.ToInt16(this.Location.Y - 130), 1000);
//Показать новый персонаж в заданном месте:
myRobby.Show(0);
//Чтобы персонаж произносил речь через динамики,
//задаем ему следующие свойства:
myAgentController = new AgentObjects.Agent();
myAgentController.Connected = true;
myAgentController.Characters.Load(
"robby", "robby.acs");
myRobby =
myAgentController.Characters.Character("robby");
//Новый персонаж произносит речь:
mySpeech = "Я - Робот. Есть проблемы?";
myRobby.Speak(mySpeech, myObject);
}
}
Аналогично (для следующего персонажа) в панели Properties выбираем заголовок следующего элемента управления axAgent4 и на вкладке Events дважды щелкаем по имени события Command. Появляется файл Form1.cs с шаблоном (метода axAgent4_Command), который после записи нашего кода (согласно алгоритму) принимает следующий вид.
Листинг 47.18. Метод, чтобы Робот выполнял наши голосовые команды.
private void axAgent4_Command(object sender,
AxAgentObjects._AgentEvents_CommandEvent e)
{
//Связываем объект myCommand с голосом пользователя:
myCommand =
(AgentObjects.IAgentCtlUserInput)(e.userInput);
//После команды голосом "MoveToMouse"
//персонаж перемещается на место курсора мыши:
if (myCommand.Voice == "MoveToMouse")
{
myRobby.MoveTo(
Convert.ToInt16(Cursor.Position.X),
Convert.ToInt16(Cursor.Position.Y), 1000);
}
//После команды голосом "Genie"
//прежний персонаж скрывается, а новый появляется:
if (myCommand.Voice == "Genie")
{
//Скрыть прежний персонаж:
myRobby.Hide(0);
//От начала координат в верхнем левом углу Form1
//задаем координаты "x,y" места нового персонажа:
myGenie.MoveTo(
Convert.ToInt16(this.Location.X + 400),
Convert.ToInt16(this.Location.Y - 130), 1000);
//Показать новый персонаж в заданном месте:
myGenie.Show(0);
//Чтобы персонаж произносил речь через динамики,
//задаем ему следующие свойства:
myAgentController = new AgentObjects.Agent();
myAgentController.Connected = true;
myAgentController.Characters.Load(
"genie", "genie.acs");
myGenie =
myAgentController.Characters.Character("genie");
//Новый персонаж произносит речь:
mySpeech = "Я - Джин. Есть проблемы?";
myGenie.Speak(mySpeech, myObject);
}
}
Отметим, что в программе речь персонажа мы можем записать не только на одной строке кода, но и на многих строках, как выше мы уже делали, например, при помощи такого кода:
mySpeech = "Попка умный " +
"и хочет есть.";
Аналогично мы можем записать код для решения любой подобной задачи согласно разработанному нами алгоритму.
Cтроим программу и запускаем на выполнение обычным образом: Build, Build Solution; Debug, Start Without Debugging. В ответ Visual C# выполняет программу и на рабочий стол выводит форму Form1 в режиме выполнения.
После щелчка первой кнопки “Показать Джина” появляется первый персонаж Genie (Джин) и произносит (через динамики компьютера) приветствующий нас текст (“Здравствуйте. Меня зовут Джин”), который мы записали выше в код программы.
В заключении этой главы дадим некоторые рекомендации по устранению ошибок, когда в режиме выполнения мы видим персонаж, но после нажатия клавиши Scroll Lock мы не видим никакой реакции персонажа на наше нажатие.
Методика задания, конвертирования и поочередной замены изображений
Листинг 64.1. Метод с нашим кодом для вывода фонового изображения.
private void Form1_Load(object sender, EventArgs e)
{
this.BackgroundImage =
Image.FromFile("D:\\MyDocs\\MyDocs F\\Fig1.jpg");
}
Интересна другая запись нашей строки кода:
this.BackgroundImage =
Image.FromFile(@"D:\MyDocs\MyDocs F\Fig1.jpg");
После старта проекта (Build, Build Solution; Debug, Start Without Debugging) появляется форма с фоновым изображением в виде файла Fig1.jpg (рис. 64.6).
Листинг 64.2. Обработчик щелчка кнопки для вывода фонового изображения.
private void button1_Click(object sender, EventArgs e)
{
this.groupBox1.BackgroundImage =
Image.FromFile("D:\\MyDocs\\MyDocs F\\Fig5.jpg");
}
Теперь после старта проекта и щелчка кнопки предыдущее фоновое изображение Fig1.jpg (листинг 64.1) заменится новым изображением Fig5.jpg (листинг 64.2), показанным на рис. 64.7.
Аналогично можно записать наш код в любой другой метод, например, в обработчик щелчка по форме или по любому другому элементу управления.
Листинг 64.3.
Конвертирование, сохранение и вывод изображения.
private void Form1_Load(object sender, EventArgs e)
{
//Загружаем Fig2_1.jpg в объект myImage класса Image:
Image myImage =
Image.FromFile(@"D:\MyDocs\MyDocs F\Fig2_1.jpg");
//Конвертируем Fig2_1.jpg в (.gif) и сохраняем Save:
myImage.Save(@"D:\MyDocs\MyDocs F\Fig2_1.gif",
System.Drawing.Imaging.ImageFormat.Gif);
//Выводим изображение как фоновое для формы:
this.BackgroundImage =
Image.FromFile(@"D:\MyDocs\MyDocs F\Fig2_1.gif");
}
Аналогично для конвертирования изображения Fig2_1.gif из формата (.gif), например, в формат (.png) используется код:
Image myImage =
Image.FromFile(@"D:\MyDocs\MyDocs F\Fig2_1.gif");
myImage.Save(@"D:\MyDocs\MyDocs F\Fig2_1.png",
System.Drawing.Imaging.ImageFormat.Png);
Аналогично конвертируются изображения из одного формата в другой из перечня поддерживаемых форматов на рис. 64.8.
После старта проекта мы увидим форму с фоновым изображением в другом формате (рис. 64.9).
Теперь в файл Form1.cs необходимо написать нашу часть кода. Сначала в любом месте внутри класса Form1 (например, ниже свернутого блока кода) объявляем массив изображений класса Image и глобальные переменные:
//Массив из 8 изображений (индекс 0 не используем):
Image[] myArrayImages = new Image[9];
//Объявляем и инициализируем целочисленные переменные:
int j = 0;
int k = 1;
Дважды щелкаем по Form1 в режиме проектирования (или в панели Properties на вкладке Events дважды щелкаем по имени события Load) и в появившийся шаблон записываем наш код, после чего метод принимает такой вид.
Листинг 64.4. Метод Form1_Load для загрузки изображений.
private void Form1_Load(object sender, EventArgs e)
{
//Из графических файлов 8 рисунков формата (.jpg)
//заполняем массив myArrayImages[9]:
myArrayImages[1] =
Image.FromFile("D:\\MyDocs\\MyDocs F\\Fig1.jpg");
myArrayImages[2] =
Image.FromFile("D:\\MyDocs\\MyDocs F\\Fig2.jpg");
myArrayImages[3] =
Image.FromFile("D:\\MyDocs\\MyDocs F\\Fig3.jpg");
myArrayImages[4] =
Image.FromFile("D:\\MyDocs\\MyDocs F\\Fig4.jpg");
myArrayImages[5] =
Image.FromFile("D:\\MyDocs\\MyDocs F\\Fig5.jpg");
myArrayImages[6] =
Image.FromFile("D:\\MyDocs\\MyDocs F\\Fig6.jpg");
myArrayImages[7] =
Image.FromFile("D:\\MyDocs\\MyDocs F\\Fig7.jpg");
myArrayImages[8] =
Image.FromFile("D:\\MyDocs\\MyDocs F\\Fig8.jpg");
}
Аналогично в приложение загружаются другие массивы, если мы хотим использовать чередование серии одних изображений сериями других. Чтобы подключить к работе таймер, дважды щелкаем значок timer1 (ниже формы в режиме проектирования). Появляется файл Form1.cs с шаблоном, который после записи нашего кода принимает следующий вид.
Листинг 64.5. Метод для компонента Timer1.
private void timer1_Tick(object sender, EventArgs e)
{
//Выводим изображение в качестве фонового PictureBox:
pictureBox1.BackgroundImage = myArrayImages[j];
//Организовываем цикл для восьми рисунков:
j = j + k;
if (j == 8)
{
//От последнего рисунка переходим к первому:
k = -8;
}
else if (j == 0)
{
// Задаем первый рисунок для начала цикла:
k = 1;
}
}
Запускаем проект: Build, Build Solution; Debug, Start Without Debugging.
В ответ Visual C# выполняет программу и на рабочий стол выводит Form1 в режиме выполнения (рис. 64.9). Внутри элемента pictureBox осуществляется поочередная замена одного рисунка следующим за интервал времени в 1000 миллисекунд, который мы установили с помощью свойства Interval в панели Properties для компонента Timer (Таймер). Частота смены изображений регулируется за счет изменения значения свойства Interval. Вместо элемента pictureBox в качестве окна мы можем использовать другой элемент, например, Panel или GroupBox, или какой-либо еще.
Таким образом, по разработанной в данной главе методике можно спроектировать на экране циклическую замену любых изображений, заданных в виде графических файлов.
В следующей главе мы перейдем к созданию более сложных приложений типа мультипликаций.
Методология проектирования техпроцессов изготовления изделий коробчатой формы
В данной главе разработаем методологию проектирования (при помощи математического моделирования) однооперационной и многооперационной штамповки-вытяжки коробчатых деталей различной геометрии из листовых материалов (металлов и неметаллов). Напомним, что детали коробчатой формы широко применяются в быту (металлическая посуда, консервные банки, кухонные мойки и т.п.) и в машинах (разнообразные кожуха, кузовные детали и т.п.).
Чтобы спроектировать процесс вытяжки коробки, мы должны задать на первой форме системы [1] следующие четыре типа исходных данных. 1. Размеры коробки AxBxRxH (A – длина, B – ширина, R – радиус углового закругления по наружной поверхности коробки, H –высота). 2. При помощи переключателя задаем (или не задаем) обрезку неровного края стенки коробки после вытяжки; если мы задали обрезку, то программа рассчитывает высоту полуфабриката после вытяжки (до обрезки) с учетом технологического припуска dH: Ha=H+dH. 3. Размеры листа, из которого будет изготавливаться коробка. 4. Шесть механических свойств листа после его стандартного испытания на растяжение: коэффициент поперечной упругой деформации
, модуль упругости E, предел текучести , предел прочности , относительное равномерное удлинение и относительное равномерное сужение . Если мы хотим учесть анизотропию листа, то необходимо иметь дополнительные данные о механических свойствах листа, которые описаны в предыдущей главе. После ввода исходных данных система рассчитывает в первом приближении размеры квадратной заготовки для получения коробки заданной высоты (с учетом припуска на обрезку) и выполняет математическое моделирование вытяжки коробки из этой заготовки. При моделировании форма и размеры заготовки уточняются. Типичная (для промышленности) схема вытяжки коробки и результаты моделирования и графики приведены на рис. 83.1.Для математического моделирования вытяжки осесимметричной, коробчатой или сложной детали из исходной заготовки применяем авторскую теорию вытяжки Жаркова, которая заключается в следующем: 1) весь процесс формоизменения заготовки разбиваем на большое число элементарных этапов и на каждом этапе задаем малое приращение хода пуансона (или матрицы); 2) при помощи метода конечных элементов – МКЭ (finite element method - FEM [1]) вычисляем компоненты напряженно-деформированного состояния (НДС) заготовки и практически важные параметры процесса с учетом упрочнения, анизотропии, изменения толщины заготовки, сил трения и других факторов; 3) анализируем опасность складкообразования и разрушения заготовки; 4) рассчитываем новую измененную форму заготовки после данного приращения хода пуансона; 5) задаем следующее приращение хода пуансона и повторяем все описанные выше стадии моделирования.
Методология учета анизотропии
Продолжаем разрабатывать методологию создания системы компьютерного проектирования (СИСКОМПР или CAD/CAM system) технологических процессов изготовления типичных деталей в промышленности (например, при производстве деталей из листового материала) на основе платформы визуального программирования (которая включает методы компьютерной графики). В предыдущих главах мы разработали методику проектирования процессов обработки заготовок на примере вытяжки листовой заготовки. Проектирование процесса выполнено при помощи математического моделирования обработки изотропной заготовки на основе аналитического и численного решения основных уравнений (моделирующих процесс обработки). Однако изотропный материал на практике применяется редко. Большинство материалов (металлов и неметаллов) обладает анизотропией, а именно, различием механических и других свойств. У наиболее распространенных горячекатаных и холоднокатаных металлических листовых материалов анизотропия проявляется различием механических свойств в различных направлениях в плоскости прокатки и по толщине листа (вследствие особенностей технологии прокатки и отжига). Анизотропия имеет кристаллографическую и текстурную природу, которая описана в литературе по физике металлов. В данной главе мы разработаем методику проектирования процессов с учетом анизотропии исходного материала на примере вытяжки детали из заготовки (любой конфигурации), которая вырезана из этого материала. Как и выше, проектирование выполним при помощи математического моделирования процесса на основе метода конечных элементов.
Для математического моделирования обработки заготовки с учетом ее анизотропии необходимо провести испытания материала и определить его механические свойства. Тип и количество этих свойств зависит от теории, которую мы будем использовать при моделировании. Принимаем, что осями координат x,y,z являются главные оси анизотропии: ось x размещаем вдоль базового направления; ось y перпендикулярна оси x; оси x и y лежат в плоскости листа; ось z перпендикулярна плоскости листа. Анизотропные листовые материалы, как правило, обладают симметрией механических свойств. Мы будем рассматривать материалы, у которых имеются три взаимно перпендикулярные плоскости симметрии механических свойств; эти плоскости проходят через оси координат x,y,z.
Непрерывный график-осциллограмма
Листинг 30.1. Переменные и метод для построения графика.
//Объявляем и инициализируем переменные:
float x = 0;
float y0 = 49;
float y1 = 0;
float k = 10;
float b = -5;
//Перемещение графика dx за каждый интервал времени::
private const float dx = 2;
//Определяем значение функции y=f(x):
private float f()
{
//Генератор в виде метода NextDouble возвращает
//случайное число типа double, которое присваиваем x:
Random myRandom = new Random();
x = Convert.ToSingle(myRandom.NextDouble());
//Вычисляем новое значение функции y=f(x):
y1 = y0 + k * x + b;
//Задаем интервал для значений функции y=f(x):
if (y1 > 98.9) y1 = 98.9f;
if (y1 < 0.999) y1 = 0.999f;
return y1;
}
Листинг 30.2. Метод Form1_Load.
private void Form1_Load(object sender, EventArgs e)
{
//Для рисования графика создаем объект myGraphics,
//связываем его с PictureBox1 и масштабируем:
Bitmap myBitmap = new Bitmap(pictureBox1.Width,
pictureBox1.Height);
Graphics myGraphics = Graphics.FromImage(myBitmap);
myGraphics.ScaleTransform(1, -100.0f /
pictureBox1.Height);
myGraphics.TranslateTransform(0, -100);
//Проектируем координатные горизонтальные линии:
Pen myPen = new Pen(Color.Blue, 1);
int i;
for (i = 10; i <= 100; i+=10)
myGraphics.DrawLine(myPen,
0, i, pictureBox1.Width, i);
//Показываем координатные горизонтальные линии
//на панели PictureBox1:
pictureBox1.Image = myBitmap;
//Освобождаем ресурсы, занятые объектом myGraphics:
myGraphics.Dispose();
}
Для программной реализации алгоритма из первого параграфа дважды щелкаем значок Timer (ниже формы в режиме проектирования). Появляется файл Form1.cs с шаблоном, который после записи нашего кода принимает следующий вид.
Листинг 30.3. Метод Timer1_Tick.
private void timer1_Tick(object sender, EventArgs e)
{
//Перемещаем прежний график влево:
Bitmap myBitmap = new Bitmap(pictureBox1.Width,
pictureBox1.Height);
Graphics g = Graphics.FromImage(myBitmap);
g.DrawImage(pictureBox1.Image, -dx, 0);
//Проектируем координатные горизонтальные линии:
g.ScaleTransform(1, -100.0F / pictureBox1.Height);
g.TranslateTransform(0, -100);
Pen myPen = new Pen(Color.Blue, 1);
int i;
for (i = 10; i <= 100; i+=10)
g.DrawLine(myPen, pictureBox1.Width - dx, i,
pictureBox1.Width, i);
// На каждом интервале времени
//получаем новое значение функции f():
y1 = f();
//Справа рисуем новую часть графика:
Pen myPen2 = new Pen(Color.Black, 1);
g.DrawLine(myPen2,
pictureBox1.Width - 1 - dx, y0,
pictureBox1.Width - 1, y1);
y0 = y1;
//Показываем новую часть графика
//на панели PictureBox1:
pictureBox1.Image = myBitmap;
}
Оглавление приложения по интеграции Visual C# с Windows Media Player
Продолжаем разрабатывать методологию интеграции Visual C# с другими платформами на примере интеграции с очень популярным и широко применяемым универсальным проигрывателем звуковых, видео и мультимедийных файлов Microsoft Windows Media Player. Мы разработаем схему применения методов VC# для воспроизведения основных файлов, чтобы в дальнейшем по этой схеме мы могли постепенно дополнять нашу систему другими возможностями этой интеграции.
Чтобы воспроизводить указанные файлы, на компьютере должны быть установлены соответствующие техническое (“железо”) и программное обеспечения. Под техническим обеспечением понимается звуковая плата, кабели и колонки (и/или наушники). В качестве программного обеспечения известно много проигрывателей, но в данной книге мы рассмотрим наиболее известный из них, а именно, Microsoft Windows Media Player следующих версий:
Windows Media Player 6.4 for Win95 and Win NT4
Windows Media 7.1 for Win98, Win2000 and Win Me
Windows Media 8 Series for Win98 SE, ME and 2000
Windows Media 8 Series for Windows XP
Windows Media Player 9 Series for Win98 SE, ME and 2000
Windows Media Player 9 Series for Windows XP
Все эти версии, а также WMP 10 можно загрузить по адресу:
http://microsoft.com/windows/windowsmedia/download/
Напомним, что проигрыватель Windows Media используется, чтобы слушать или просматривать оперативные новости, спортивные передачи, делать обзор аудио и видео файлов на Web-сайте, “посещать” концерт или семинар, воспроизводить клипы из кинофильма. К слову, в Windows Media Player понятие “клип” означает индивидуальный звуковой, видео или мультимедийный файл. Такие особенности делают Windows Media Player наиболее всесторонним, простым и эффективным проигрывателем мультимедиа: высококачественное воспроизведение широкого диапазона типов файлов; легкая адаптация (приспособление), чтобы выполнить наши пожелания (предпочтения); широкие и разнообразные возможности для работы с мультимедийными файлами; быстрый доступ к содержанию файлов. По указанному выше адресу можно узнать, что последняя версия Windows Media Player 9 Series – это первый универсальный проигрыватель с функцией музыкального автомата, а также можно узнать многое другое. Мы закончили главу с методикой проектирования первой формы с оглавлением нашей системы. Теперь приступаем к разработке разделов, согласно оглавлению системы, для решения конкретных задач на следующих формах.
Оглавление приложения по использованию методов Word
Листинг 16.1. Метод для выполнения анимации.
//Объявляем булеву переменную myColor со значением false:
bool myColor = false;
private void timer1_Tick(object sender, EventArgs e)
{
//Программируем анимацию:
if (myColor == false)
{
//Выводим красный цвет текста внутри переключателя:
radioButton2.ForeColor = Color.Red;
//Изменяем значение myColor на противоположное:
myColor = true;
}
else
{
//Выводим белый цвет текста внутри переключателя:
radioButton2.ForeColor = Color.White;
//Изменяем значение myColor на противоположное:
myColor = false;
}
}
В этом коде мы можем установить чередование двух других цветов из списка, который появляется, когда мы запишем символы после имени структуры Color. Аналогично можно также дописать код, чтобы сделать анимационными тексты сразу внутри нескольких переключателей. Задавая в панели Properties для компонента Timer различные значения свойству Interval (а выше мы задали 1000 миллисекунд или 1 секунду), можно изменять частоту чередования цветов текста внутри переключателя. Методика приостановки и возобновления анимации приведена выше.
Проверяем, как на данном этапе проектирования действует оглавление системы. Для этого строим (Build, Build Solution) и запускаем программу на выполнение (Debug, Start Without Debugging). Появляется первая панель в режиме выполнения (рис. 16.1), на которой включен первый переключатель и видна анимация в виде чередования двух цветов (красного и белого) текста внутри второго переключателя.
Оптимизация раскроя материалов
В качестве следующего приложения для системы компьютерного проектирования, например, технологических процессов (СИСКОМПРТП или Computer-Aided Design/Computer-Aided Manufacture systems, CAD/CAM systems) опишем методы расчета оптимального раскроя материала на заготовки (из которых планируется изготавливать заданные детали). Рассмотрим широко распространенные (типичные) варианты раскроя листа (из металлов и неметаллов) на полосы, которые могут быть применены на практике в различных производствах. Известны два основных варианта разрезки листа на полосы: продольный (рис. 84.1) и поперечный (рис. 84.2).
Выбор типа раскроя осуществляется при помощи следующего экономического анализа возможных вариантов раскроя материала (с целью определения оптимального). Первый этап расчета – это расчет количества полос при продольной и поперечной разрезке. Сначала рассматриваем продольную разрезку листа (рис. 84.1). Рассчитываем количество полос
при продольной разрезке листа:, (84.1)
где B – ширина полосы (формулы для расчета B приведены в нашей книге [1]).
Зная число заготовок
, получаемых из полосы при однорядном или двухрядном раскроях (формулы для расчета приведены в [1]), определяем общее число заготовок, получаемых из листа: . (84.2)Ширина полосы-отхода
(на рис. 84.1 эта полоса заштрихована) равна . (84.3)Как правило, полоса-отход используется для изготовления других, например, более мелких деталей. Теперь изучим поперечную разрезку листа (рис. 84.2).
Количество полос
при поперечной разрезке листа рассчитывается так: . (84.4)Ширина полосы-отхода
(на рис. 84.2 эта полоса заштрихована) равна . (84.5)Открытие Access
Листинг 19.1. Объектная переменная и метод для открытия Access.
//Объявляем объектную переменную objectAccess
//и, тем самым, запускаем систему Access,
//которая по умолчанию невидим:
Access.Application objectAccess =
new Access.ApplicationClass();
private void button6_Click(object sender, EventArgs e)
{
//Делаем интерфейс Access видимым:
objectAccess.Visible = true;
}
Открытие Windows Explorer, Notepad, WordPad, Calculator, PowerPoint и FrontPage
Листинг 71.1. Метод для открытия проводника Windows Explorer.
private void button1_Click(object sender, EventArgs
e)
{
Process.Start("Explorer.exe");
}
Напомним, что перед написанием любой программы в начале файла Form1.cs сначала подключаем пространство имен Diagnostics при помощи кода:
using System.Diagnostics;
Теперь в файл Form1.cs необходимо написать нашу часть кода для открытия блокнота Notepad при помощи кнопки (рис. 71.5). Дважды щелкаем эту кнопку в режиме редактирования. Появляется файл Form1.cs с шаблоном (метода button1_Click), который после записи нашего кода принимает следующий вид.
Листинг 71.2. Метод для открытия блокнота Notepad.
private void button1_Click(object sender, EventArgs
e)
{
Process.Start("Notepad.exe");
}
В режиме выполнения (Build, Build Solution; Debug, Start Without Debugging) после щелчка кнопки на экране монитора появляется блокнот Notepad.
Аналогично для открытия редактора WordPad в данный проект вводим новую кнопку (или создаем новый проект с кнопкой) и в шаблон метода для обработки щелчка этой кнопки записываем код:
Process.Start("WordPad.exe"); или Process.Start("WordPad");
Аналогично для открытия калькулятора Microsoft Calculator в данный проект вводим новую кнопку (или создаем новый проект с кнопкой) и в шаблон метода для обработки щелчка этой кнопки записываем код:
Process.Start("Calc.exe"); или Process.Start("Calc");
Мы можем одновременно открыть эти три приложения при помощи одной кнопки, если в шаблон метода для обработки щелчка этой кнопки записать:
Process.Start("Notepad");
Process.Start("WordPad");
Process.Start("Calc");
Далее по этой методике (при помощи класса Process и метода Start) мы можем открывать другие программы и постепенно дополнять нашу систему другими возможностями такой интеграции.
Теперь в файл Form1.cs необходимо написать нашу часть кода для открытия PowerPoint при помощи кнопки (рис. 71.6). Дважды щелкаем эту кнопку в режиме редактирования. Появляется файл Form1.cs с шаблоном (метода button1_Click), который после записи нашего кода принимает следующий вид.
Листинг 71.3. Метод для открытия PowerPoint.
private void button1_Click(object sender, EventArgs
e)
{
// Объявляем объектную переменную и, тем самым, запускаем
//программу PowerPoint, которая по умолчанию невидима:
PowerPoint.ApplicationClass objectPowerPoint =
new PowerPoint.ApplicationClass();
//Делаем программу PowerPoint видимой:
objectPowerPoint.Visible =
Office.Core.MsoTriState.msoCTrue;
}
Листинг 71.4. Метод для открытия FrontPage.
private void button1_Click(object sender, EventArgs
e)
{
//Запускаем FrontPage:
Process.Start("FrontPg.exe");
}
Напомним, что везде в скобках имя и расширение исполняемого файла мы можем записать как строчными, так и прописными буквами. В начале файла Form1.cs подключаем пространство имен: using System.Diagnostics;
В режиме выполнения (Build, Build Solution; Debug, Start Without Debugging) после щелчка кнопки (рис. 71.10) на экране появляется FrontPage (рис. 71.11).
Мы можем проектировать новый сайт или открыть существующий наш сайт, внести в него изменения (например, из приложения Visual C#) и при помощи команды File, Publish Web отправить на сервер провайдера (предоставляющего нам услуги хостинга), чтобы наш новый вариант сайта заменил предыдущий и был виден в Интернете.
Построение графика по
Листинг 51.1. Объявление членов интерфейса и глобальных переменных.
//Объявляем персонаж Джина (Genie) и путь к его файлу:
static AgentObjects.IAgentCtlCharacterEx myGenie;
static String DATAPATH_1 = "Genie.acs";
//Объявляем персонаж Мага (Merlin) и путь к его файлу:
static AgentObjects.IAgentCtlCharacterEx myMerlin;
static String DATAPATH_2 = "Merlin.acs";
//Объявляем персонаж Попугая (Peedy) и путь к его файлу:
static AgentObjects.IAgentCtlCharacterEx myPeedy;
static String DATAPATH_3 = "Peedy.acs";
//Объявляем персонаж Робота (Robby) и путь к его файлу:
static AgentObjects.IAgentCtlCharacterEx myRobby;
static String DATAPATH_4 = "Robby.acs";
//Объявляем член интерфейса Agent:
private AgentObjects.Agent myAgentController;
//Глобальная переменная для любого текста персонажа:
static String mySpeech;
//Объявляем общую для персонажей объектную переменную:
Object myObject = null;
А в шаблон (метода Form1_Load) записываем наш код (согласно постановке задачи для всех персонажей), и метод Form1_Load принимает такой вид.
Листинг 51.2. Загрузка персонажей в элементы управления.
private void Form1_Load(object sender, EventArgs e)
{
//Загружаем персонаж в элемент управления axAgent1:
axAgent1.Characters.Load("Genie", DATAPATH_1);
myGenie = axAgent1.Characters.Character("Genie");
//Чтобы он выполнял голосовые команды на англ. яз.:
myGenie.LanguageID = 0x409;
//Загружаем персонаж в элемент управления axAgent2:
axAgent2.Characters.Load("Merlin", DATAPATH_2);
myMerlin = axAgent2.Characters.Character("Merlin");
//Чтобы он выполнял голосовые команды на англ. яз.:
myMerlin.LanguageID = 0x409;
//Загружаем персонаж в элемент управления axAgent3:
axAgent3.Characters.Load("Peedy", DATAPATH_3);
myPeedy = axAgent3.Characters.Character("Peedy");
// Чтобы он выполнял голосовые команды на англ. яз.:
myPeedy.LanguageID = 0x409;
//Загружаем персонаж в элемент управления axAgent4:
axAgent4.Characters.Load("Robby", DATAPATH_4);
myRobby = axAgent4.Characters.Character("Robby");
//От начала координат в вехнем левом углу Form1
//задаем координаты "x, y" персонажа:
myGenie.MoveTo(
Convert.ToInt16(this.Location.X + 0),
Convert.ToInt16(this.Location.Y - 130), 1000);
//Показываем персонаж в заданном месте:
myGenie.Show(0);
//Чтобы персонажи произносили речи через динамики,
//задаем им следующие свойства:
myAgentController = new AgentObjects.Agent();
myAgentController.Connected = true;
myAgentController.Characters.Load(
"genie", "genie.acs");
myGenie =
myAgentController.Characters.Character("genie");
myAgentController.Characters.Load(
"merlin", "merlin.acs");
myMerlin =
myAgentController.Characters.Character("merlin");
myAgentController.Characters.Load(
"peedy", "peedy.acs");
myPeedy =
myAgentController.Characters.Character("peedy");
myAgentController.Characters.Load(
"robby", "robby.acs");
myRobby =
myAgentController.Characters.Character("robby");
//Первый персонаж произносит текст mySpeech
//и показывает этот текст в виде подсказки:
mySpeech = "Сидите за монитором прямо, " +
"как пианист.";
myGenie.Speak(mySpeech, myObject);
}
Чтобы следующий персонаж, например, Маг (Merlin) появился через заданное нами время, щелкаем по значку для первого компонента Timer и в панели Properties для этого компонента в свойстве Interval записываем данное время, например, 5000 миллисекунд (или 5 секунд) и запускаем таймер (в свойстве Enabled выбираем True).
Теперь дважды щелкаем по значку для этого компонента Timer (или в панели Properties на вкладке Events дважды щелкаем по имени единственного там события Tick). Появляется файл Form1.cs с шаблоном (метода timer1_Tick), который после записи нашего кода принимает следующий вид.
Листинг 51.3. Метод для вызова персонажа через Interval времени.
private void timer1_Tick(object sender, EventArgs e)
{
//Показываем следующий персонаж рядом:
myMerlin.MoveTo(
Convert.ToInt16(this.Location.X + 200),
Convert.ToInt16(this.Location.Y - 130), 1000);
myMerlin.Show(0);
//Текущий персонаж произносит текст mySpeech
//и показывает этот текст в виде подсказки:
mySpeech = "Держите глаза от монитора " +
"на расстоянии вытянутой руки.";
myMerlin.Speak(mySpeech, myObject);
//Останавливаем генерирование события Tick:
timer1.Enabled = false;
}
Чтобы следующий персонаж, например, Попугай (Peedy) появился через заданное нами время, щелкаем по значку для второго компонента Timer и в панели Properties для этого компонента в свойстве Interval записываем данное время, например, 10000 миллисекунд (или 10 секунд) и запускаем таймер (в свойстве Enabled выбираем True). Теперь дважды щелкаем по значку для этого компонента Timer (или в панели Properties на вкладке Events дважды щелкаем по имени единственного там события Tick). Появляется файл Form1.cs с шаблоном (метода timer2_Tick), который после записи нашего кода принимает следующий вид.
Листинг 51.4. Метод для вызова персонажа через Interval времени.
private void timer2_Tick(object sender, EventArgs e)
{
//Показываем следующий персонаж рядом:
myPeedy.MoveTo(
Convert.ToInt16(this.Location.X + 400),
Convert.ToInt16(this.Location.Y - 130), 1000);
myPeedy.Show(0);
//Текущий персонаж произносит текст mySpeech
//и показывает этот текст в виде подсказки:
mySpeech = "Поднимите монитор, чтобы глаза были " +
"на уровне верхней кромки экрана.";
myPeedy.Speak(mySpeech, myObject);
//Останавливаем генерирование события Tick:
timer2.Enabled = false;
}
Чтобы следующий персонаж, например, Робот (Robby) появился через заданное нами время, щелкаем по значку для третьего компонента Timer и в панели Properties для этого компонента в свойстве Interval записываем данное время, например, 15000 миллисекунд (или 15 секунд) и запускаем таймер (в свойстве Enabled выбираем True). Теперь дважды щелкаем по значку для этого компонента Timer (или в панели Properties на вкладке Events дважды щелкаем по имени единственного там события Tick). Появляется файл Form1.cs с шаблоном (метода timer3_Tick), который после записи нашего кода принимает следующий вид.
Листинг 51.5. Метод для вызова персонажа через Interval времени.
private void timer3_Tick(object sender, EventArgs e)
{
//Показываем следующий персонаж рядом:
myRobby.MoveTo(
Convert.ToInt16(this.Location.X + 600),
Convert.ToInt16(this.Location.Y - 130), 1000);
myRobby.Show(0);
//Текущий персонаж произносит текст mySpeech
//и показывает этот текст в виде подсказки:
mySpeech = "Работайте на клавиатуре всеми пальцами.";
myRobby.Speak(mySpeech, myObject);
//Останавливаем генерирование события Tick:
timer3.Enabled = false;
}
Мы записали код для вывода на экран всех четырех персонажей. Чтобы один из этих персонажей, например, Джин (Genie) периодически (с заданным нами интервалом времени в компоненте Timer) повторял инструкцию пользователю (например, о том, как строить график на данной форме), щелкаем по значку для четвертого компонента Timer и в панели Properties для этого компонента в свойстве Interval записываем данный интервал времени, например, 18000 миллисекунд (или 18 секунд) и запускаем таймер (в свойстве Enabled выбираем True). Теперь дважды щелкаем по значку для этого компонента Timer (или в панели Properties на вкладке Events дважды щелкаем по имени единственного там события Tick). Появляется файл Form1.cs с шаблоном (метода timer4_Tick), который после записи нашего кода принимает следующий вид.
Листинг 51.6. Метод для произнесения инструкции через Interval времени.
private void timer4_Tick(object sender, EventArgs e)
{
//Текущий персонаж произносит текст mySpeech
//и показывает этот текст в виде подсказки:
mySpeech = "Запишите координаты точек целыми числами, " +
"щелкните верхнюю кнопку, и Вы увидите график. " +
"Чтобы стереть график, щелкните нижнюю кнопку.";
myGenie.Speak(mySpeech, myObject);
}
Мы видим, что в последнем методе, в отличие от трех предыдущих методов, мы не останавливаем генерирование события Tick (тем самым, не выключаем таймер) и даем возможность персонажу говорить до тех пор, пока мы не щелкнем по нему правой кнопкой и не выберем команду Hide (Скрыть).
Чтобы при помощи кнопки “1. Построить график” появился график функциональной зависимости y = f(x), дважды щелкаем или эту кнопку в режиме проектирования (рис. 51.1), или имя события Click в панели Properties на вкладке Events. Появляется шаблон, который после записи нашего кода принимает такой вид.
Листинг 51.7. Метод для обработки щелчка кнопки с целью рисования графика.
private void button1_Click(object sender, EventArgs e)
{
//Объявляем локальные переменные:
float x1, y1, x2, y2, x3, y3, x4, y4;
float x_O, y_O, x_end, y_end;
//Координаты начальной точки O:
x_O = 20; y_O = 20;
//Считываем координаты точек из окон:
x1 = Convert.ToSingle(textBox1.Text) + x_O;
y1 = -Convert.ToSingle(textBox2.Text) +
pictureBox1.Height - y_O;
x2 = Convert.ToSingle(textBox3.Text) + x_O;
y2 = -Convert.ToSingle(textBox4.Text) +
pictureBox1.Height - y_O;
x3 = Convert.ToSingle(textBox5.Text) + x_O;
y3 = -Convert.ToSingle(textBox6.Text) +
pictureBox1.Height - y_O;
x4 = Convert.ToSingle(textBox7.Text) + x_O;
y4 = -Convert.ToSingle(textBox8.Text) +
pictureBox1.Height - y_O;
//Создаем графический объект g класса Graphics:
Graphics g;
//Связываем графический объект g класса Graphics
//с элементом управления pictureBox1:
g = pictureBox1.CreateGraphics();
//Создаем перо для осей координат:
Pen myPenAxes = new Pen(Color.Black, 1);
//Строим ось координат x:
x_end = pictureBox1.Width - y_O;
g.DrawLine(myPenAxes,
x_O, pictureBox1.Height - y_O,
x_end, pictureBox1.Height - y_O);
//Строим ось координат y:
y_end = y_O;
g.DrawLine(myPenAxes,
x_O, pictureBox1.Height - y_O,
x_O, y_O);
//Создаем перо для графика:
Pen myPen = new Pen(Color.Black, 3);
//Строим график:
g.DrawLine(myPen, x1, y1, x2, y2);
g.DrawLine(myPen, x2, y2, x3, y3);
g.DrawLine(myPen, x3, y3, x4, y4);
}
Чтобы при помощи кнопки “2. Удалить график” график функциональной зависимости y = f(x) был стерт, дважды щелкаем или эту кнопку в режиме проектирования (рис. 51.1), или имя события Click в панели Properties на вкладке Events. Появляется шаблон, который после записи нашего кода принимает такой вид.
Листинг 51.8. Метод для обработки щелчка по кнопке.
private void button2_Click(object sender, EventArgs e)
{
//Создаем графический объект g класса Graphics:
Graphics g;
//Связываем графический объект g класса Graphics
//с элементом управления pictureBox1:
g = pictureBox1.CreateGraphics();
//Очистить pictureBox1, заполнив белым цветом White:
g.Clear(Color.White);
}
Мы закончили часть книги, в которой разработали методологию написания программ на Visual С# с целью создания сложных интерактивных компьютерных игр, мультфильмов, Интернет-магазинов и других практических приложений на основе мультимедийной программной технологии (платформы) Microsoft Agent версии 2.0.
В данной последней главе мы не только разработали новую графическую подсистему для визуализации результатов эксперимента (опыта), но и дополнили эту подсистему мультипликационными персонажами.
По этой методологии можно создавать разнообразные мультимедиа и игры, а также дополнять графические системы и другие приложения мультипликационными персонажами.
Пример Интернет-магазина на основе Microsoft Agent
На основе программ Visual C#, разработанных нами в предыдущих главах, теперь мы можем проектировать сложные интерактивные компьютерные игры, мультфильмы, Интернет-магазины и другие практические приложения.
Такое сложное приложение мы рассмотрим на примере реального интернет-магазина под названием “Peedy’s Pizza Palace”, в котором уже известный нам Попугай (Peedy) продает пиццу. Этот пример разработан самой корпорацией Microsoft на базе своей же мультимедийной программной технологии (платформы) Microsoft Agent версии 2.0.
Данный магазин создан по сценарию (постановке), который состоит из следующих основных эпизодов.
Эпизод 1. После загрузки браузера Internet Explorer в его окне автоматически появляются интерфейс магазина и его продавец в виде Попугая (Peedy).
Эпизод 2. Попугай объясняет нам, как пользоваться данным Интернет-магазином, и предлагает сделать выбор вида пиццы и дополнительной начинки.
Эпизод 3. При помощи элементов управления или голосовых команд мы выбираем какой-либо вид пиццы и дополнительной начинки и даем команду сделать нам расчет.
Эпизод 4. Попугай делает расчет, сообщает нам стоимость пиццы, перелетает на новое место и сообщает, что готов принять от нас новый заказ.
Ключевые понятия мультимедиа мы дали в предыдущих главах, поэтому здесь сразу перейдем к описанию работы магазина согласно эпизодам.
Итак, чтобы изучить пример интернет-магазина под названием “Peedy’s Pizza Palace”, в котором Попугай (Peedy) продает пиццу, выполняем следующее.
1. Если мы не загрузили (бесплатно) компоненты Microsoft Agent версии 2.0, то делаем это, как описано в первой главе данной части книги.
2. Подключаемся к Интернету (если мы не подключились) при помощи браузера Internet Explorer и открываем пример по адресу:
agent.microsoft.com/agent2/sdk/samples/html/peedypza.htm
Проектирование финишных операций изготовления изделий
Листинг 89.1. Передача исходных данных с Form7 в Form9.
Form9 myForm9 = new Form9();
myForm9.d = d;
myForm9.H = H;
myForm9.Rn = Rn;
myForm9.s = s;
myForm9.Radio = Radio;
myForm9.s_sheet = s_sheet;
myForm9.C = C;
myForm9.L = L;
myForm9.rho = rho;
myForm9.nu = nu;
myForm9.El = El;
myForm9.sig_ys = sig_ys;
myForm9.sig_u = sig_u;
myForm9.e_u = e_u;
myForm9.e_tot = e_tot;
myForm9.Show(); //Выводим форму Form9.
Листинг 89.2. Код кнопки “=” для расчета четвертой операции.
textBox1.Text = d.ToString();
textBox2.Text = H.ToString();
textBox3.Text = Rn.ToString();
textBox4.Text = s.ToString();
textBox5.Text = Area().ToString();
textBox6.Text = Volume().ToString(); m = mass();
textBox7.Text = m.ToString(); Cutting();
//Число заготовок (деталей) из листа:
textBox8.Text = Nbl.ToString();
//Норма расхода материала на 1 деталь:
textBox9.Text = (s_sheet*C*L*rho*1.0e-9/Nbl).ToString();
//Коэффициент использования материала:
textBox10.Text = (m/(s_sheet*C*L*rho*1.0e-9/Nbl)).ToString();
Для программной реализации этого алгоритма дважды щелкаем значок timer1 (ниже формы в режиме проектирования). Появляется файл с шаблоном, в который записываем наш следующий код:
Листинг 89.3. Код для выполнения анимации.
bool myWidth = false;
private void timer1_Tick(object sender, EventArgs e)
{
if (myWidth == false)
{
this.button1.Width = 150; myWidth = true;
}
else
{
this.button1.Width = 100; myWidth = false;
}
}
Мы закончили часть книги и главы с описанием разработанной нами методологии (с конкретным примером) создания системы компьютерного проектирования технологических процессов (СИСКОМПРТП) изготовления различных изделий (деталей, вещей, продуктов) на практике.
В этих главах одновременно мы разработали методику программирования основных эффектов анимации для данной системы.
По этой методологии мы можем разрабатывать наши персональные или корпоративные вычислительные системы и СИСКОМПРТП (с использованием анимационных эффектов) применительно к конкретным условиям нашей практической деятельности и характера производства изделий.
Проектирование операций изготовления заготовок
Листинг 87.1. Передача исходных данных с Form3 в Form5.
//Объявляем объект myForm5 в классе Form5:
Form5 myForm5 = new Form5();
//Присваиваем исходные данные другим переменным
//класса панели Form5, но с такими же именами:
myForm5.d = d;
myForm5.H = H;
myForm5.Rn = Rn;
myForm5.s = s;
myForm5.Radio = Radio;
myForm5.s_sheet = s_sheet;
myForm5.C = C;
myForm5.L = L;
myForm5.rho = rho;
myForm5.nu = nu;
myForm5.El = El;
myForm5.sig_ys = sig_ys;
myForm5.sig_u = sig_u;
myForm5.e_u = e_u;
myForm5.e_tot = e_tot;
//Выводим диалоговую панель Form5:
myForm5.Show();
Открываем файл Form5.cs и в любом месте класса Form5 (например, после всех функций для кнопок) записываем наш основной код с методми для расчета параметров первой операции технологического процесса по разрезке листа на полосы; этот код мы берем без изменений с первого листинга предыдущей главы. Ниже записываем функцию для расчета силы вырубки Fcut круглой заготовки в штампе [1].
Листинг 87.2. Метод для расчета силы вырубки Fcut.
double ForceCut()
{
double A, k_cut, Fcut;
A=Area_Semiproduct();
D=Math.Sqrt(4*A/pi);
k_cut=1.2;
Fcut=pi*D*s_sheet*sig_u*k_cut/1000;
return Fcut;
}
Листинг 87.3. Код кнопки “=” Form5 для расчета второй операции.
//Объявляем переменные:
double Web_a, Web_b, Nblst, G, K_G;
//Расчет и передача в окна рациональных параметров
//разрезки листа на полосы, а полосы на заготовки:
Cutting(); //Эта функция нужна для расчета W_F.
//Расчет и передача значения ширины полосы "B"
//в окно textBox1 на Form5:
B = Bstrip();
textBox1.Text = B.ToString();
//Расчет и передача в окна на Form5 параметров раскроя:
D = DBlank();
textBox2.Text = D.ToString();
t = D + a;
textBox3.Text = t.ToString();
Web_a = a;
textBox4.Text = Web_a.ToString();
Web_b = b;
textBox5.Text = Web_b.ToString();
if (W_F==C)
{
Nblst = Math.Floor((L - a)/t);
textBox6.Text = Nblst.ToString();
G = Nblst * t;
textBox7.Text = G.ToString();
textBox8.Text = (L - G).ToString();
K_G = L;
textBox9.Text = L.ToString();
}
else
{
Nblst = Math.Floor((C - a)/t);
textBox6.Text = Nblst.ToString();
G = Nblst * t;
textBox7.Text = G.ToString();
textBox8.Text = (C - G).ToString();
K_G = C;
textBox9.Text = C.ToString();
}
//Сила вырезки заготовки в штампе:
textBox10.Text = ForceCut().ToString();
// Коэффициент раскроя полосы на заготовки
//диаметром D или D_blank:
textBox11.Text = (pi*D*D/4*Nblst/(K_G*B)).ToString();
Листинг 87.4. Код для выполнения анимации.
//Объявляем булеву переменную myText со значением false:
bool myText = false;
private void timer1_Tick(object sender, EventArgs e)
{
//Программируем анимацию:
if (myText == false)
{
//Выводим текст внутри элемента управления:
this.button1.Text = "Click";
//Изменяем значение myText на противоположное:
myText = true;
}
else
{
//Выводим другой текст внутри элемента управления: this.button1.Text = "OK";
//Изменяем значение myText на противоположное:
myText = false;
}
}
Аналогично вычисляются параметры раскроя других полос и для других изделий. По этим параметрам, задавая различные размеры листа (полосы или ленты, если заказчик получает от поставщика не листы, а полосы и ленты), пользователь может выбрать рациональный материал (размеры листа, полосы или ленты) и его оптимальный раскрой. Методика печати на принтере информации с панели описана выше.
На практике часто ограничиваются только двумя операциями разрезки листа на полосы и полосы на заготовки; в этом случае следующая операция технологического процесса не нужна, и, соответственно, не нужна и кнопка Next>> на Form5.
В нашем общем случае требуется, чтобы (после щелчка кнопки Next>> на Form5, рис. 87.1) выходила следующая форма Form7 со следующей операцией технологического процесса (и со следующим эффектом анимации), к разработке которой мы и приступаем.