инженерная графика. Лабораторная работа введение в opengl цель изучения темы. Изучение принципов применения библиотеки Opengl при разработке приложений в C#
Скачать 1.39 Mb.
|
Визуализация объектов Теперь рассмотрим функцию, визуализирующую трехмерную сферу. Код этой функции: // обработчик кнопки "визуализировать" private void button1_Click(object sender, EventArgs e) { Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT); Gl.glLoadIdentity(); Gl.glPushMatrix(); Gl.glTranslated(0,0,-6); Gl.glRotated(45, 1, 1, 0); // рисуем сферу с помощью библиотеки FreeGLUT Glut.glutWireSphere(2, 32, 32); Gl.glPopMatrix(); Gl.glFlush(); AnT.Invalidate(); } Когда пользователь нажимает на кнопку и вызывается данная функ- ция, первым делом производится очистка окна (так как до этого уже мог быть реализован какой-либо вывод; очистка экрана перед визуализацией кадра − это стандартный шаг). Для этого используется функция glClear. В качестве параметра функ- ция получает наименования буферов, которые необходимо очистить; в нашем случае это буфер цвета и буфер глубины. Далее мы очищаем объектно-видовую матрицу – таким образом каме- ра устанавливается в начало координат. Теперь мы можем совершить ее перемещение в пространстве. Но перед этим мы вызываем функцию Gl.glColor3f(255, 0, 0); 15 устанавливающую красный цвет для отрисовки (основывается на RGB со- ставляющих). Займемся перемещением. Функция glPushMatrix помещает текущую матрицу в стек матриц, откуда в дальнейшем мы сможем ее вернуть с по- мощью функции glPopMatrix. Таким способом мы осуществим перемещение отрисовываемого объ- екта в пространстве, не изменив саму матрицу, отвечающую за положение камеры (наблюдателя). Если не использовать такой подход, то с каждым визуализированным кадром камера будет перемещаться и очень скоро мы вообще не сможем ее найти. Сохранив матрицу в стеке, мы производим перемещение объекта на 6 единиц по оси Z, после чего выполняем поворот сцены на 45 градусов сра- зу по двум осям Gl.glTranslated(0,0,-6); Gl.glRotated(45, 1, 1, 0); После этого мы выполняем рисование объекта в той области, куда мы переместились (сфера радиусом 2 в виде сетки). Сфера будет разбита на 32 меридиана и 32 параллели. Для визуализации используется библиотека FreeGlut. // рисуем сферу с помощью библиотеки FreeGLUT Glut.glutWireSphere(2, 32, 32); Возвращаем сохраненную в стеке матрицу Gl.glPopMatrix(); Дожидаемся, пока библиотека OpenGL завершит визуализацию этого кадра Gl.glFlush(); и посылаем нашему элементу AnT, в котором происходит визуализация сцены, сигнал о том, что необходимо обновить отображаемый кадр, т.е. вызываем его перерисовку. AnT.Invalidate(); На этом визуализация объекта заканчивается. 4.2. Визуализация 2D-примитивов Процесс рисования всех объектов в компьютерной графике сводится к вычислению координат точек, которые затем соединяются линиями, обра- зуя рисунок. Точками же создаются линии, из которых могут быть постро- 16 ены простейшие геометрические фигуры – полигоны. Полигоны могут со- здавать сетку, которая представляет в трехмерном пространстве какую- либо модель. Полигон – это область в пространстве, которая ограничивается одной замкнутой ломаной линией, причем каждый отрезок данной ломаной ли- нии задается с помощью координат двух точек − начальной и конечной. Описанный таким образом полигон может оказаться очень сложным гео- метрическим объектом. Полигон обязательно должен быть выпуклым, поэтому его ребра не должны пересекаться. При условии соблюдения двух предыдущих условий нет никаких ограничений на то, из скольких точек состоит полигон. Для рисования примитивов в OpenGL мы будем использовать различ- ные режимы визуализации, но сначала рассмотрим, как происходит указа- ние вершин в OpenGl. Для указания вершин используется специальная команда glVertex*() Данная функция имеет следующий формат: void glVertex{234}{ifd}(TYPE coords); Одной цифрой (2, 3 или 4) обозначается то количество координат, ко- торыми будет задаваться вершина. В OpenGL точки задаются 4 координа- тами: x, y, z, r. Таким образом, положение точки описывается как x/r, y/r, z/r. Когда параметр r не указывается, он принимается равным единице. Ес- ли указываются точки в двухмерной системе координат, параметр z счита- ется равным нулю. Символ i, f или d обозначает тип принимаемых значений. Например, если написать команду glVertex3f(,,) то в скобках должны последовать три переменных типа float. Для d – double, для i – int. Вызов функции glVertex может привести к желаемому результату, только если он сделан между вызовами функций glBegin() и glEnd(). Для построения объекта из точек используются разные режимы, кото- рые описываются различными правилами объединения вершин в полигоны. Такие режимы рисования возможны лишь с помощью перечисления точек между командами glBegin() и glEnd(), причем при вызове функции glBegin указывается, в каком режиме будут соединяться полигоны. Значения параметров, которые может принимать glBegin: GL_POINTS − на месте каждой указанной с помощью команды glVertex вершины рисуется точка. 17 GL_LINES − каждые 2 вершины объединяются в отрезок. GL_LINE_STRIP − вершины соединяются последовательно, образуя кривую линию. GL_LINE_LOOP − то же, что и GL_LINE_STRIP, но от последней точ- ки будет идти отрезок к начальной точке. GL_TRIANGLES − каждые три вершины объединятся в треугольник. GL_TRIANGLE_STRIP − треугольники рисуются таким образом, что последнее ребро треугольника является начальным ребром следующего. GL_TRIANGLE_FAN − то же, что и GL_TRIANGLE_STRIP, только из- менен порядок следования вершин. GL_QUADS − каждые четыре вершины объединяются в квадрат. GL_QUAD_STRIP − вершины объединяются в квадраты, причем по- следнее ребро квадрата является первым ребром следующего. GL_POLYGON − рисуется полигон на основе вершин; вершин не должно быть менее 3 и должны отсутствовать самопересечения, а также должно соблюдаться условие выпуклости. Теперь попробуем нарисовать какой-либо рисунок. Для этого создайте приложение, основываясь на программе из п. 4.3 (а именно подключите необходимые ссылки к библиотекам Tao, подключите необходимые про- странства имен, расположите элементы на окне, а также назначьте необхо- димые обработчики событий и инициализируйте необходимые элементы). Эта программа будет отличаться от программы, написанной в п. 4.3, лишь тем, что здесь будут использоваться другие проекции в функции Form1_Load и код, отвечающий за визуализацию в функции button1_Click. Перейдя к свойствам формы, измените название окна на «Рисование 2D-примитивов». Рассмотрим код функции Form1_Load. Теперь он будет выглядеть следующим образом. private void Form1_Load(object sender, EventArgs e) { // инициализация Glut Glut.glutInit(); Glut.glutInitDisplayMode(Glut.GLUT_RGB | Glut.GLUT_DOUBLE | Glut.GLUT_DEPTH); // очистка окна Gl.glClearColor(255, 255, 255, 1); // установка порта вывода в соответствии с размерами элемента AnT 18 Gl.glViewport(0, 0, AnT.Width, AnT.Height); // настройка проекции Gl.glMatrixMode(Gl.GL_PROJECTION); Gl.glLoadIdentity(); // теперь необходимо корректно настроить 2D ортогональную проекцию // в зависимости от того, какая сторона больше // мы немного варьируем то, как будут сконфигурированы настройки проекции if ((float)AnT.Width <= (float)AnT.Height) { Glu.gluOrtho2D(0.0, 30.0 * (float)AnT.Height / (float)AnT.Width, 0.0, 30.0); } else { Glu.gluOrtho2D(0.0, 30.0 * (float)AnT.Width / (float)AnT.Height, 0.0, 30.0); } Gl.glMatrixMode(Gl.GL_MODELVIEW); Gl.glLoadIdentity(); // настройка параметров OpenGL для визуализации Gl.glEnable(Gl.GL_DEPTH_TEST); Gl.glEnable(Gl.GL_COLOR_MATERIAL); } Как видно из кода, за настройку 2D ортогональной проекции отвечает функция gluOrtho2D, реализация которой предоставлена библиотекой Glu. Эта функция помещает начало координат в самый левый нижний квадрат, а камера (наблюдатель) в таком случае находится на оси Z; таким образом визуализируется графика в 2D-режиме. Мы должны передать в качестве параметров координаты области ви- димости окна проекции: left, right, bottom и top. В нашем случае мы пере- даем параметры таким образом, чтобы исключить искажения, связанные с тем, что область вывода не квадратная, поэтому параметр top рассчитыва- ется как произведение числа 30 и отношения высоты элемента AnT (в ко- тором будет идти визуализация) к его ширине. Если высота больше ширины, используется отношение ширины к вы- соте. Теперь рассмотрим код функции button1_Click. В ней будет производиться очистка буфера цвета кадра, после чего будет очищаться текущая объектно-видовая матрица. Затем мы будем в режиме рисования линий задавать координаты для того, чтобы нарисовать объект − букву А. 19 После этого мы перерисовываем содержимое элемента AnT. Перед тем как нарисовать наш объект (букву А), попробуем просто провести линию из начала координат в противоположенный угол окна. Код такой функции будет выглядеть следующим образом. private void button1_Click(object sender, EventArgs e) { // очищаем буфер цвета и глубины Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT); // очищаем текущую матрицу Gl.glLoadIdentity(); // устанавливаем текущий цвет - красный Gl.glColor3f(255, 0, 0); // активируем режим рисования линий на основе // последовательного соединения всех вершин в отрезки Gl.glBegin(Gl.GL_LINE_STRIP); // первая вершина будет находиться в начале координат Gl.glVertex2d(0, 0); // теперь в зависимости от того, как была определена проекция, if ((float)AnT.Width <= (float)AnT.Height) { // рисуем вторую вершину в противоположенном углу Gl.glVertex2d(30.0f * (float)AnT.Height / (float)AnT.Width, 30); } else { // рисуем вторую вершину в противоположенном углу Gl.glVertex2d(30.0f * (float)AnT.Width / (float)AnT.Height, 30); } // завершаем режим рисования Gl.glEnd(); // дожидаемся конца визуализации кадра Gl.glFlush(); // посылаем сигнал перерисовки элемента AnT. AnT.Invalidate(); } Теперь если откомпилировать проект и запустить приложение (F5), а затем нажать на кнопку « визуализировать», мы увидим следующий ре- зультат (рис. 4.2). 20 Рис. 4.2 Теперь заменим код Gl.glBegin(Gl.GL_LINE_STRIP); // первая вершина будет находиться в начале координат Gl.glVertex2d(0, 0); // теперь в зависимости от того, как была определена проекция if ((float)AnT.Width <= (float)AnT.Height) { // рисуем вторую вершину в противоположенном углу Gl.glVertex2d(30.0f * (float)AnT.Height / (float)AnT.Width, 30); } else { // рисуем вторую вершину в противоположенном углу Gl.glVertex2d(30.0f * (float)AnT.Width / (float)AnT.Height, 30); } // завершаем режим рисования Gl.glEnd(); на код, который будет рисовать букву А. Нам необходимо правильно указать координаты вершин. Букву будем рисовать с помощью двух линий. 21 Форма объекта и координаты соответствующих вершин представлены на рис. 4.3. Рис. 4.3 Код рисования буквы А (буква будет рисоваться двумя линиями) // активируем режим рисования линий на основе // последовательного соединения всех вершин отрезками Gl.glBegin(Gl.GL_LINE_LOOP); // первая вершина будет находиться в начале координат Gl.glVertex2d(8, 7); Gl.glVertex2d(15, 27); Gl.glVertex2d(17, 27); Gl.glVertex2d(23, 7); Gl.glVertex2d(21, 7); Gl.glVertex2d(19, 14); Gl.glVertex2d(12.5, 14); Gl.glVertex2d(10, 7); Gl.glEnd(); 22 // вторая линия Gl.glBegin(Gl.GL_LINE_LOOP); Gl.glVertex2d(18.5, 16); Gl.glVertex2d(16, 25); Gl.glVertex2d(13.2, 16); Gl.glEnd(); Пример нарисованной в программе буквы представлен на рис. 4.4. Рис. 4.4 4.3. Визуализация графика функции Рассмотрим процесс создания программы, задачей которой будет ви- зуализация графика заданной функции с анимационной демонстрацией изменения значения функции на графике. Это будет реализовано следующим образом: по графику двигается красная точка, принимающая значения y для заданного x в нашем графике (по всей видимой области). Кроме того, возле курсора будут визуализироваться его координаты (рис. 4.5). 23 Рис. 4.5 Создайте основу приложения, как это было описано в п. 3.3, но не добавляйте кнопки «Визуализиро- вать» и «Закрыть», ограничьтесь элементом SimpleOpenGLControl. Окно должно иметь форму, представленную на рис. 4.5. Добавьте в проект один таймер. Для того чтобы добавить этот объ- ект, перейдите к окну ToolBox, вы- берите в свитке « Компоненты» эле- мент Timer ( рис. 4.6) и перетащите его на форму. Рис. 4.6 24 Место, куда вы перетащите этот элемент, не важно, поскольку по- следний не занимает его на форме и отобразится в полоске таких же («не занимающих место») объектов под формой. Щелкните по элементу и перейдите в его свойства. Смените параметр name в свойствах таймера на значение PointInGrap, а параметр Interval − на значение 30 (раз в 30 мс будет вызываться событие OnTimer). Теперь щелкните по значку таймера на форме двойным щелчком ле- вой клавиши мыши. Создастся функция PointInGrap_Tick, отвечающая за обработку события OnTimer. Отсюда мы будем затем вызывать функцию Draw. Теперь, когда основа приложения создана, мы перейдем к исходному коду. Он будет основан на семи функциях, которые мы сейчас рассмотрим, но сначала перед кодом функции-конструктора класса добавьте инициали- зацию следующих переменных: // размеры окна double ScreenW, ScreenH; // отношения сторон окна визуализации // для корректного перевода координат мыши в координаты, // принятые в программе private float devX; private float devY; // массив, который будет хранить значения x,y точек графика private float[,] GrapValuesArray; // количество элементов в массиве private int elements_count = 0; // флаг, означающий, что массив со значениями координат графика пока еще не за- полнен private bool not_calculate = true; // номер ячейки массива, из которой будут взяты координаты для красной точки // для визуализации текущего кадра private int pointPosition = 0; // вспомогательные переменные для построения линий от курсора мыши к коорди- натным осям float lineX, lineY; // текущие координаты курсора мыши float Mcoord_X = 0, Mcoord_Y = 0; Перед каждой переменной в комментарии приведено ее назначение. Теперь вернемся к нашим семи функциям. 25 Первая функция – это обработчик события загрузки формы − Form1_Load. Здесь при загрузке приложения будет произведена инициали- зация OpenGL для последующей визуализации. Инициализацию OpenGL с двухмерной проекцией мы рассматривали ранее. Код этой функции с более подробными комментариями: private void Form1_Load(object sender, EventArgs e) { // инициализация библиотеки glut Glut.glutInit(); // инициализация режима экрана Glut.glutInitDisplayMode(Glut.GLUT_RGB | Glut.GLUT_DOUBLE); // установка цвета очистки экрана (RGBA) Gl.glClearColor(255, 255, 255, 1); // установка порта вывода Gl.glViewport(0, 0, AnT.Width, AnT.Height); // активация проекционной матрицы Gl.glMatrixMode(Gl.GL_PROJECTION); // очистка матрицы Gl.glLoadIdentity(); // определение параметров настройки проекции в зависимости от размеров сторон элемента AnT. if ((float)AnT.Width <= (float)AnT.Height) { ScreenW = 30.0; ScreenH = 30.0 * (float)AnT.Height / (float)AnT.Width; Glu.gluOrtho2D(0.0, ScreenW, 0.0, ScreenH); } else { ScreenW = 30.0 * (float)AnT.Width / (float)AnT.Height; ScreenH = 30.0; Glu.gluOrtho2D(0.0, 30.0 * (float)AnT.Width / (float)AnT.Height, 0.0, 30.0); } // сохранение коэффициентов, которые нам необходимы для перевода координат указателя в оконной системе, в координаты // принятые в нашей OpenGL сцене devX = (float)ScreenW / (float)AnT.Width; devY = (float)ScreenH / (float)AnT.Height; // установка объектно-видовой матрицы Gl.glMatrixMode(Gl.GL_MODELVIEW); // старт счетчика, отвечающего за вызов функции визуализации сцены PointInGrap.Start(); } Теперь обратимся к функции PointInGrap_Tick. Эта функция вызыва- ется с задержкой в 30 мс. 26 В ней определяется, из какого элемента массива с координатами графика мы возьмем координаты, которые используем для рисования красной точки. Отсюда также вызывается функция Draw, отвечающая за визуализацию. Код этой функции: // функция обработчик события таймера private void PointInGrap_Tick(object sender, EventArgs e) { // если мы дошли до последнего элемента массива if (pointPosition == elements_count-1) pointPosition = 0; // переходим к начальному элементу // функция визуализации Draw(); // переход к следующему элементу массива pointPosition++; } Перед тем как перейти к функциям, отвечающим за визуализацию, рассмотрим несколько небольших вспомогательных функций. Начнем с функции AnT_MouseMove. Эта функция добавляется созданием события MouseMove для элемен- та SimpleOpnGLControl (AnT). Событие создается аналогично тому, как мы его создавали ранее. Только в данном случае мы переходим к свойствам элемента AnT и уже в них переходим во вкладку Event, добавляем событие MouseMove. В данной функции мы производим сохранение текущих координат мыши, чтобы в будущем использовать их при визуализации графика, а также производим вычисление размеров двух красных линий, которые бу- дут по нормалям соединять координаты указателя мыши с координатными осями (рис. 4.5). Код этой функции выглядит следующим образом: // обработка движения мыши над элементом AnT private void AnT_MouseMove(object sender, MouseEventArgs e) { // сохраняем координаты мыши Mcoord_X = e.X; Mcoord_Y = e.Y; // вычисляем параметры для будущей дорисовки линий от указателя мыши к коор- динатным осям lineX = devX * e.X; lineY = (float)(ScreenH - devY * e.Y); } 27 Теперь рассмотрим функцию, которая будет осуществлять визуализа- цию текстовых строк. Эта функция устанавливает координаты вывода растровых символов в соответствии с координатами, переданными в параметрах x и y, а затем в цикле перебирает все символы из указанной в параметре строки текста. Каждый символ визуализируется с помощью функции glutBitmapCharacter. В этой функции указывается шрифт для вывода и переменная типа char для визуализации. Код функции выглядит следующим образом: // функция визуализации текста private void PrintText2D(float x, float y, string text) { // устанавливаем позицию вывода растровых символов // в переданных координатах x и y Gl.glRasterPos2f(x, y); // в цикле foreach перебираем значения из массива text, // который содержит значение строки для визуализации foreach (char char_for_draw in text) { // визуализируем символ с помощью функции glutBitmapCharacter, // используя шрифт GLUT_BITMAP_9_BY_15 Glut.glutBitmapCharacter(Glut.GLUT_BITMAP_9_BY_15, char_for_draw); } } Следующая функция, которая нам понадобится, − это функция, вы- числяющая координаты для построения графика. В ней инициализируется массив координат и производится вычисление всех координат графика в зависимости от указанного диапазона значений x и шага приращения этих значений. Обратите внимание на то, что при инициализации массива для хране- ния координат должно быть указано такое количество элементов массива, чтобы в дальнейшем их хватило для размещения всех координат, иначе произойдет исключение, так как программа в процессе работы попытается обратиться к области памяти, которая ей не принадлежит. Код этой функции с комментариями: // функция, производящая вычисления координат графика // и заносящая их в массив GrapValuesArray private void functionCalculation() { 28 // определение локальных переменных X и Y float x = 0, y = 0; // инициализация массива, который будет хранить значение 300 точек // из которых будет состоять график GrapValuesArray = new float[300, 2]; // счетчик элементов массива elements_count = 0; // вычисления всех значений y для x, принадлежащего промежутку от -15 до 15, с шагом в 0.01f for (x = -15; x < 15; x += 0.1f) { // вычисление y для текущего x // по формуле y = (float)Math.Sin(x)*3 + 1; // эта строка задает формулу, описывающую график функции для нашего урав- нения y = f(x). y = (float)Math.Sin(x)*3 + 1; // запись координаты x GrapValuesArray[elements_count, 0] = x; // запись координаты y GrapValuesArray[elements_count, 1] = y; // подсчет элементов elements_count++; } // изменяем флаг, сигнализировавший о том, что координаты графика не вычисле- ны not_calculate = false; } Рассмотрим функцию, выполняющую визуализацию графика. Так как визуализацию графика можно отнести к конкретной подзада- че функции визуализации сцены, вынесем визуализацию графика в от- дельную функцию (чтобы не загромождать функцию визуализации сцены). В этой функции сначала будет проверен флаг, сигнализирующий о том, что координаты графика вычислены и занесены в массив (переменная not_calculate). В том случае если флаг указывает, что просчета значений еще не было, вызывается функция, которая посчитает значения координат точек графика и заполнит ими массив. Далее реализуется проход циклом for по массиву значений координат точек графика и их визуализация, причем мы визуализируем не точки, а соединяем эти точки линией, основываясь на значении координат точек (вершины линии). 29 По завершению отрисовки графика производится рисование красной точки в тех координатах, до которых мы дошли, последовательно переби- рая значения элементов в массиве координат. Исходный код данной функции: // визуализация графика private void DrawDiagram() { // проверка флага, сигнализирующего о том, что координаты графика вычислены if (not_calculate) { // если нет, то вызываем функцию вычисления координат графика functionCalculation(); } // стартуем отрисовку в режиме визуализации точек // объединяемых в линии (GL_LINE_STRIP) Gl.glBegin(Gl.GL_LINE_STRIP); // рисуем начальную точку Gl.glVertex2d(GrapValuesArray[0, 0], GrapValuesArray[0, 1]); // проходим по массиву с координатами вычисленных точек for (int ax = 1; ax < elements_count; ax+=2) { // передаем в OpenGL информацию о вершине, участвующей в построении ли- ний Gl.glVertex2d(GrapValuesArray[ax, 0], GrapValuesArray[ax, 1]); } // завершаем режим рисования Gl.glEnd(); // устанавливаем размер точек, равный 5 пикселям Gl.glPointSize(5); // устанавливаем текущий цвет - красный Gl.glColor3f(255, 0, 0); // активируем режим вывода точек (GL_POINTS) Gl.glBegin(Gl.GL_POINTS); // выводим красную точку, используя ту ячейку массива, до которой мы дошли (вычисляется в функии-обработчике событий таймера) Gl.glVertex2d(GrapValuesArray[pointPosition, 0], GrapValuesArray[pointPosition, 1]); // завершаем режим рисования Gl.glEnd(); // устанавливаем размер точек, равный единице Gl.glPointSize(1); } Теперь нам осталось рассмотреть последнюю функцию – Draw. В ней визуализируется координатная сетка под графиком, координат- ные оси и буквы для их обозначений, а также вызывается функция рисова- 30 ния графика и выводятся координаты мыши с линиями, соединяющими указатель мыши и оси координат. Код этой функции выглядит следующим образом. // функция, управляющая визуализацией сцены private void Draw() { // очистка буфера цвета и буфера глубины Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT); // очистка текущей матрицы Gl.glLoadIdentity(); // установка черного цвета Gl.glColor3f(0, 0, 0); // помещаем состояние матрицы в стек матриц Gl.glPushMatrix(); // выполняем перемещение в пространстве по осям X и Y Gl.glTranslated(15, 15, 0); // активируем режим рисования (указанные далее точки будут выводиться как точки GL_POINTS) Gl.glBegin(Gl.GL_POINTS); // с помощью прохода двумя циклами создаем сетку из точек for (int ax = -15; ax < 15; ax++) { for (int bx = -15; bx < 15; bx++) { // вывод точки Gl.glVertex2d(ax, bx); } } // завершение режима рисования примитивов Gl.glEnd(); // активируем режим рисования, каждые две точки, последовательно вызванные ко- мандой glVertex, объединяются в линию Gl.glBegin(Gl.GL_LINES); // рисуем координатные оси и стрелки на них Gl.glVertex2d(0, -15); Gl.glVertex2d(0, 15); Gl.glVertex2d(-15, 0); Gl.glVertex2d(15, 0); // вертикальная стрелка Gl.glVertex2d(0, 15); Gl.glVertex2d(0.1, 14.5); 31 Gl.glVertex2d(0, 15); Gl.glVertex2d(-0.1, 14.5); // горизонтальная стрелка Gl.glVertex2d(15, 0); Gl.glVertex2d(14.5, 0.1); Gl.glVertex2d(15, 0); Gl.glVertex2d(14.5, -0.1); // завершаем режим рисования Gl.glEnd(); // выводим подписи осей "x" и "y" PrintText2D(15.5f, 0, "x"); PrintText2D(0.5f, 14.5f, "y"); // вызываем функцию рисования графика DrawDiagram(); // возвращаем матрицу из стека Gl.glPopMatrix(); // выводим текст со значением координат возле курсора PrintText2D(devX * Mcoord_X + 0.2f, (float)ScreenH - devY * Mcoord_Y + 0.4f, "[ x: " + (devX * Mcoord_X - 15).ToString() + " ; y: " + ((float)ScreenH - devY * Mcoord_Y - 15).ToString() + "]"); // устанавливаем красный цвет Gl.glColor3f(255, 0, 0); // включаем режим рисования линий, для того чтобы нарисовать // линии от курсора мыши к координатным осям Gl.glBegin(Gl.GL_LINES); Gl.glVertex2d(lineX, 15); Gl.glVertex2d(lineX, lineY); Gl.glVertex2d(15, lineY); Gl.glVertex2d(lineX, lineY); Gl.glEnd(); // дожидаемся завершения визуализации кадра Gl.glFlush(); // сигнал для обновления элемента, реализующего визуализацию AnT.Invalidate(); } В этой функции также нет ничего сложного, необходимо только разо- браться с координатным методом построения примитивов. Если какая- либо часть кода остается непонятной, измените параметры в коде и по- смотрите на результат этих изменений, чтобы сделать вывод о работе про- граммы. |