Библиотека трехмерной графики Open gl


Преобразование к координатам отсечения



бет9/15
Дата29.05.2016
өлшемі1.07 Mb.
#100718
1   ...   5   6   7   8   9   10   11   12   ...   15

6 Преобразование к координатам отсечения


В этом разделе вводится ряд новых методов OpenGL, которые определяют порядок преобразования от одних систем координат к другим.
Если в имеющейся версии проекта составить из точек круг, например, единичного радиуса, а затем менять размеры окна, то круг будет деформироваться. Ведь в прямоугольном (не квадратном) окне точки, лежащие с одной стороны круга (например, по горизонтали) и имеющие то же единичное расстояние до центра, окажутся в изображении либо дальше от центра, если окно вытянуто по горизонтали, либо ближе к нему.

Круг можно изобразить, используя, например, код

gl.Begin(gl.POINTS);

for (int i = 0; i < 100; i++)

gl.Vertex((float)Math.Cos(2.0 * Math.PI * i / 100), (float)Math.Sin(2.0 * Math.PI * i / 100), 0);

gl.End();

Решить проблему можно двумя способами. Параметрам метода Viewport можно придавать всегда такие значения, чтобы вывод проводился только в максимальный квадрат окна. Но в этом случае, оставшаяся часть окна окажется недоступной для изображения. Другой способ состоит в таком преобразовании от координат наблюдения (которые совпадают пока с объектными координатами) к координатам отсечения, которое бы устранило с одной стороны этот дефект, а с другой стороны позволило бы использовать всю площадь окна для вывода изображения.

Как уже указывалось выше, преобразования от объектных координат xo, yo, zo , wo к координатам наблюдения xe, ye, ze, we и, далее, к координатам отсечения xc, yc, zc, wc по умолчанию определяются единичными матрицами. Поэтому значения объектных координат xo, yo, zo и "нормализованных координат устройства" xd, yd, zd совпадают, если wo = 1. В общем случае команды OpenGL позволяют вводить произвольные матрицы двух типов. Первый тип преобразования – "от модели к наблюдению", или тип MODELVIEW определяет преобразования между координатами объектной системы и системы координат наблюдения. Второй тип преобразования – от координат наблюдения к координатам отсечения называется проекционным (PROJECTION). Матрицы 4х4 обоих типов преобразований вводятся в отдельные стеки и присутствуют там к моменту построения объекта из примитивов. Эти матрицы уже использовались выше в применении к методу Project. Они были единичными по умолчанию. Но эти матрицы могут быть любыми.


Методы MatrixMode


Этот метод регулирует режим предполагаемого преобразования. Он используется для указания типа матрицы преобразования, вводимой в стек.

///

/// Устанавливает тип последующих преобразований координат.

///

///

/// Определяет тип стека матриц, на содержание которого

/// будут влиять последующие преобразования.

/// Доступны три значения,

/// определяемые символьными постоянными MODELVIEW, PROJECTION и TEXTURE.

///

[DllImport("OPENGL32.DLL", EntryPoint = "glMatrixMode")]

public static extern void MatrixMode(int mode);

Метод MatrixMode управляется тремя символьными постоянными, указывающими тип преобразования, к которому будут относиться вводимые далее матрицы. Пока что рассмотрим лишь работу с двумя из них

///

/// Последующие операции с матрицами относятся

/// к преобразованиям от координат наблюдения

/// к координатам отсечения (действуют на стек проекционной матрицы).

///

public const int PROJECTION = 0x1701;

///

/// Последующие операции с матрицами относятся

/// к преобразованиям от объектных координат к координатам наблюдения.

///

public const int MODELVIEW = 0x1700;

Эти описания следует поместить в класс gl библиотеки GL.
Для определения текущего состояния режима MatrixMode можно использовать метод Get в форме

(int)gl.Get(gl. MATRIX_MODE, 1)[0]

Здесь аргументом является константа

///

/// Аргумент Get требует возврата значения 1 параметра -

/// целой символьной постоянной, определяющей какой матричный стек в настоящий момент

/// может меняться (метод MatrixMode).

///

public const int MATRIX_MODE = 0x0BA0;

Методы LoadIdentity и Ortho


После вызова метода MatrixMode с соответствующим параметром можно использовать методы, помещающие в указанный стек конкретные матрицы преобразований координат.

Метод LoadIdentity вводит в стек единичную матрицу

///

/// Заменяет текущую матрицу в стеке на единичную матрицу.

///

[DllImport("OPENGL32.DLL", EntryPoint = "glLoadIdentity")]

public static extern void LoadIdentity();

Метод Ortho умножает матрицу, находящуюся в стеке преобразования от координат наблюдения к координатам отсечения на матрицу ортографической (плоскопараллельной) проекции. Существует две версии этого метода.

///

/// Умножает содержимое стека преобразования

/// от координат наблюдения к координатам отсечения

/// на матрицу ортографической (параллельной) проекции.

///

///

/// Положение левой плоскости отсечения.

/// Значение x-координаты наблюдения,

/// которое проецируется на левую границу порта наблюдения.

///

///

/// Положение правой плоскости отсечения.

/// Значение x-координаты наблюдения,

/// которое проецируется на правую границу порта наблюдения.

///

///

/// Положение нижней плоскости отсечения.

/// Значение y-координаты наблюдения,

/// которое проецируется на нижнюю границу порта наблюдения.

///

///

/// Положение верхней плоскости отсечения.

/// Значение y-координаты наблюдения,

/// которое проецируется на верхнюю границу порта наблюдения.

///

///

/// Положение ближней плоскости отсечения.

///

///

/// Положение дальней плоскости отсечения.

///

///

/// Значения (left, bottom, - near) и (right, top, - near) задают координаты наблюдения точек

/// на ближней плоскости отсечения, которые отображаются соответственно

/// на левый нижний и правый верхний угол порта наблюдения

/// в предположении, что начало координат наблюдения находится в точке (0, 0, 0).

/// Значение -far задает положение дальней плоскости отсечения.

/// Значения far и near могут быть как положительными, так и отрицательными.

///

[DllImport("OPENGL32.DLL", EntryPoint = "glOrtho")]

public static extern void Ortho(double left, double right, double bottom, double top,

double near, double far);


///

/// Умножает содержимое стека преобразования

/// от координат наблюдения к координатам отсечения

/// на матрицу ортографической (параллельной) проекции.

/// Эквивалентен вызову метода Ortho с шестью параметрами, когда два последних параметра

/// имеют значения near = -1 и far = 1.

///

[DllImport("GLU32.DLL", EntryPoint = "gluOrtho2D")]

public static extern void Ortho(double left, double right, double bottom, double top);

Эти описания следует поместить в класс gl библиотеки GL.

Матрица преобразования Ortho с обозначениями l – left, r – right, t – top, b – bottom, n – near, f – far имеет вид


Преобразование Ortho позволит изменить отображение объектных координат в координаты отсечения таким образом, чтобы прямоугольник порта наблюдения совпадал по пропорциям с прямоугольником в объектных координатах. Это уберет искажения.
Применить описанные методы в коде проекта можно следующим образом.


  1. Ввести в класс формы f3D

// поля, хранящие область отсечения

float prjLeft, prjRight, prjBottom, prjTop;

// поля, хранящие ширину и высоту порта наблюдения.

float vpWidth, vpHeight;



  1. Ввести в класс f3D метод InitProjection со следующим содержанием

///

/// Устанавливает преобразование от координат наблюдения к координатам отсечения

///

void InitProjection()

{

// Определение текущей ширины и высоты порта наблюдения



vpWidth = gl.Get(gl.VIEWPORT, 4)[2];

vpHeight = gl.Get(gl.VIEWPORT, 4)[3];

if (vpWidth > vpHeight)

{

prjLeft = -(prjRight = vpWidth / vpHeight); prjBottom = -(prjTop = 1);



}

else


{

prjLeft = -(prjRight = 1); prjBottom = -(prjTop = vpHeight / vpWidth);

}

// Устанавливается режим ввода матрицы в стек преобразования



// от координат наблюдения к координатам отсечения

gl.MatrixMode(gl.PROJECTION);

// Ввод единичной матрицы

gl.LoadIdentity();

// Ввод матрицы ортографической проекции с учетом пропорций порта наблюдения

gl.Ortho(prjLeft, prjRight, prjBottom, prjTop);

}


  1. Поместить вызов метода InitProjection() в обработчик panelGL_SizeChanged сразу после вызова метода gl.Viewport.

Теперь пропорции будут соблюдены, и окружность сохраняет свою форму при изменении пропорций окна. Убедитесь в этом, вызвав иллюстрирующее приложение. Можете провести изменения в коде своего приложения, либо посмотреть, как на этом этапе редактируется авторское приложение.

Кроме сохранения пропорций, матрица ортографического преобразования, примененная здесь, отражает ось глубины, что делает систему объектных координат правой. Это следует из вида матрицы преобразования Ortho, приведенной выше. Если подставить в нее значения n = -1, f = 1, что соответствует вызову методе Ortho с 4-мя параметрами, то компонента zz матрицы ортографической проекции -2/(fn) будет равна -1. А это соответствует отражению по оси z. В этом можно также убедиться, используя пример кода, приведенный в разделе теста глубины.


Тест рубежного контроля

  1. Что делает метод MatrixMode?

  2. Что представляет собой преобразование, задаваемое методом Ortho?

  3. Как ввести на стек преобразований координат единичную матрицу?

В следующем разделе будут рассмотрены методы, преобразующие систему объектных координат к координатам наблюдения. Эти методы позволяют поворачивать, сдвигать и менять видимые размеры объекта на экране, а так же менять ракурс изображения, рассматривая объект с разных сторон.


7 Методы преобразования от объектных координат к координатам наблюдения


Часто бывает необходимо изменить положение, ориентацию и размеры объекта на экране, не меняя его геометрии. Для этого следует использовать преобразования от объектных координат, жестко связанных с объектом, к координатам наблюдения, жестко связанных с экраном. В этом разделе рассматриваются стандартные методы сдвига, вращения и масштабного преобразования объектной системы координат при переходе к системе наблюдения.

Координаты наблюдения по умолчанию совпадают с объектными координатами. Однако можно проводить преобразования, которые поворачивают, смещают объект или меняют его видимые размеры и пропорции. В этом случае объектная система координат поворачивается, смещается или меняются масштабы вдоль ее осей по сравнению с ее первоначальным положением. Неподвижное исходное положение объектной системы координат и представляет собой систему координат наблюдения.



Методы Translate, Rotate


Так выглядят заголовки этих двух методов

///

/// Умножает текущую матрицу на матрицу, транслирующую

/// начало системы координат на вектор x, y, z.

///

[DllImport("OPENGL32.DLL", EntryPoint = "glTranslatef")]

public static extern void Translate(float x, float y, float z);

///

/// Умножает текущую матрицу на матрицу поворота на угол angle, измеряемый в градусах,

/// относительно оси, заданной вектором x, y, z в объектной системе координат

///

[DllImport("OPENGL32.DLL", EntryPoint = "glRotatef")]

public static extern void Rotate(float angle, float x, float y, float z);

Эти описания следует поместить в класс gl библиотеки GL.

Матрица преобразования, отвечающая трансляции, имеет вид

.

Матрица преобразования, отвечающая вращению на угол θ относительно оси с единичным вектором u(x', y', z'), имеет вид



Здесь матрица поворота R определяется формулой



R = uuT + cosθ (I - uuT) + sinθ S,

Где символ T означает транспонирование, I – единичная матрица, а матрица S имеет вид



Методы Translate, Rotate и последующие методы, описанные в этом разделе, будут использоваться для преобразований объектной системы координат к координатам наблюдения. Поэтому они вводятся в соответствующий стек преобразований. Установить режим ввода в стек преобразований от объектных координат к координатам наблюдения можно в конце метода InitProjection в форме кода

// Устанавливается режим ввода матрицы в стек преобразования

// от объектных координат к координатам наблюдения

gl.MatrixMode(gl.MODELVIEW);

// На вершину стека помещается единичная матрица преобразований

// от объектных координат к координатам наблюдения

gl.LoadIdentity();

Методы Translate и Rotate можно использовать в сочетании с таймером.

Для использования таймера из раздела Components окна Toolbox перенесите на форму f3D объект класса Timer. Дайте ему имя (свойство Name в окне Properties) timer. Выберите обработчик события Tick таймера в этом же окне Properties странице обработчиков (кнопка с изображением молнии).

Добавьте к полям формы f3D описание 4-ех новых полей, регулирующих параметры вращения и трансляции изображения

///

/// Хранит текущий угол поворота объектной системы координат вокруг своей оси.

///

float angleRotate;

///

/// Хранит текущий угол поворота объектной системы координат вокруг внешней оси.

///

float angleTranslate;

// поля, хранящие состояние активности вращения и трансляции

bool rotateEnabled, translateEnabled;

На каждом тике таймера меняется параметр сдвига и/или поворота в зависимости от флагов, и изображается новый кадр, где объект уже оказывается сдвинутым и/или повернутым. Например, код в обработчике события Tick таймера может выглядеть следующим образом

if (translateEnabled) angleTranslate++;

if (rotateEnabled) angleRotate++;

RenderFrame();

Здесь параметры сдвига и поворота содержатся в полях angleTranslate, angleRotate, а флаги их активности – в полях translateEnabled и rotateEnabled соответственно (см. также ниже).


Метод Scale


Метод Scale меняет масштаб изображения и его удобно использовать в сочетании с вращением колесика мышки. Поворот колесика меняет масштабный коэффициент и создается впечатление, что наблюдатель удаляется или приближается к объекту.

///

/// Умножает текущую матрицу на матрицу масштабного преобразования.

/// Параметрами x, y, z задаются масштабные множители вдоль соответствующих осей.

///

[DllImport("OPENGL32.DLL", EntryPoint = "glScalef")]

public static extern void Scale(float x, float y, float z);

Поместите это описание в класс gl библиотеки GL.

Матрица преобразования масштабов имеет вид


Для примера использования метода Scale


  • в класс f3D добавьте поля

///

/// Определяет масштаб вращения колесика мышки

///

const float wheelScale = 0.003f;

///

/// Хранит текущий масштабный коэффициент изображения объекта.

///

float scaleCoeff;



  • в класс f3D добавьте обработчик вращения колесика мышки MouseWheel над панелью panelGL. Этот обработчик отсутствует в окне Properties, поэтому его следует добавить непосредственно в конструкторе формы f3D. С этой целью в конце тела этого конструктора (метод public f3D) начните набирать строку кода

panelGL.MouseWheel +=

На этом этапе должна возникнуть подсказка, предлагающая нажать клавишу Tab. Нажмите один раз и затем второй. Это приведет к тому, что



  1. строка кода автоматически завершится и примет вид

panelGL.MouseWheel += new MouseEventHandler(panelGL_MouseWheel);

  1. появится скелета обработчика panelGL_MouseWheel в форме

void panelGL_MouseWheel(object sender, MouseEventArgs e)

{

throw new NotImplementedException();



}

Уберите строку throw… внутри обработчика и поставьте вместо нее операторы, меняющие масштабный коэффициент в заисимости от поворота колесика

scaleCoeff *= (float)(e.Delta > 0 ? 1.0 / (1.0 + wheelScale * e.Delta) : 1.0 + wheelScale * Math.Abs(e.Delta));

RenderFrame();

Здесь поле scaleCoeff содержит текущий масштаб изображения (аргумент метода Scale, см. также ниже), а поле wheelScale хранит некоторое постоянное значение, характеризующие плавность изменения масштаба. В данном случае значение wheelScale принято равным 0,003.


  • Для обработки события от колесика необходимо, чтобы панель panelGL оказывалась в фокусе при входе в нее курсора мышки (если панель не в фокусе, она не будет воспринимать информацию о повороте колесика мышки). Это обеспечивается обработкой события MouseEnter. Поэтому, найдите в окне Properties панели panelGL событие MouseEnter, и в его обработчик поместите строку кода, устанавливающую фокус на панель panelGL при попадании туда курсора мышки

(sender as Control).Focus();

Метод LookAt


Метод LookAt вносит в стек такую матрицу, которая меняет положение наблюдателя по отношению к объекту

///

/// Определяет преобразование наблюдения, устанавливая точку положения наблюдателя

/// и видимое направление вертикали.

/// Вектор eyex, eyey, eyez определяет положение наблюдателя.

/// Вектор centerx, centery, centerz определяет положение центра сцены.

/// Вектор upx, upy, upz - видимое направление вертикали.

///

[DllImport("GLU32.DLL", EntryPoint = "gluLookAt")]

public static extern void LookAt(double eyex, double eyey, double eyez,

double centerx, double centery, double centerz, double upx, double upy, double upz);

Описания LookAt следует поместить в класс gl библиотеки GL.


Метод LookAt удобно использовать совместно с обработчиками событий MouseDown и MouseMove мышки на панели panelGL с тем, чтобы создать эффект регулируемого вращения наблюдателя относительно изображенного объекта.

Добавьте в класс f3D формы следующие поля

///

/// Хранит текущую ориентацию камеры вида по отношению к вертикальной оси.

/// Угол cameraFi отсчитывается от вертикальной средней линии вправо

/// до Pi на правой границе панели и влево до -Pi на левой границе.

///

double cameraFi;

double cameraSinFi, cameraCosFi;

///

/// Хранит текущую ориентацию камеры вида по отношению к горизонтальной оси.

/// Угол cameraTeta отсчитывается от горизонтальной плоскости.

/// Вверх - положительный, вниз - отрицательный. Это широта.

/// cameraTeta лежит в интервале -Pi/2;Pi/2.

///

double cameraTeta;

double cameraSinTeta, cameraCosTeta;

///

/// Хранит положение курсора в момент нажатия левой кнопки над glPanel

///

Point curCursorPos;

Найдите обработчики событий MouseDown и MouseMove мышки на панели panelGL (окно Properties) и внесите в них следующий код



  • В обработчике MouseDown

if (e.Button == MouseButtons.Left)

curCursorPos = e.Location;



  • В обработчике MouseMove

if (e.Button == MouseButtons.Left && panelGL.ClientRectangle.Contains(e.Location))

{

cameraFi += 2.0 * (e.X - curCursorPos.X) / panelGL.ClientSize.Width * Math.PI;



cameraTeta += (-1.0 * (e.Y - curCursorPos.Y) / panelGL.ClientSize.Height) * Math.PI;

// Запоминается новое положение курсора

curCursorPos = e.Location;

// Вычисляются тригонометрические функции, используемые

// в LookAt для расчета матрицы преобразования

cameraCosTeta = Math.Cos(cameraTeta);

cameraSinTeta = Math.Sin(cameraTeta);

cameraSinFi = Math.Sin(cameraFi);

cameraCosFi = Math.Cos(cameraFi);

RenderFrame();

}

Здесь поле curCursorPos формы f3D хранит текущее положение курсора мышки на панели panelGL. Его значение обновляется при нажатии левой клавиши мышки и при движении мышки по панели. Поля cameraCosTeta, cameraSinTeta, cameraSinFi, cameraCosFi хранят текущую информацию о положении наблюдателя по отношению к объекту. Эта информация передается методу LookAt (см. также ниже).


Все перечисленные методы преобразования к координатам наблюдения Translate, Rotate, Scale и LookAt, следует вызывать перед каждым фреймом.

Опишите новые поля в классе f3D формы

///

/// Радиус сферы наблюдения

///

const float radiusEye = .001f;

// Компоненты единичного вектора оси вращения.

float rX, rY, rZ;

///

/// Хранит текущий радиус окружности, в точки которой транслируется начало объектной стсьемы координат.

///

float radiusTranslate;

///

/// Хранит объект, возвращающий случайные числа.

///

Random rnd = new Random();

Добавьте к классу метод, вызывающий все четыре метода преобразования координат (отметим, что операции трансляции и вращения не коммутируют, то есть их результат зависит от последовательности, в которой операции совершались)

///

/// Устанавливает текущую матрицу преобразования от объектных координат

/// к координатам наблюдения

///

void SetCurModelViewTransformation()

{

gl.LoadIdentity();



gl.Translate(radiusTranslate * (float)Math.Cos(angleTranslate * Math.PI / 180),

radiusTranslate * (float)Math.Sin(angleTranslate * Math.PI / 180), 0);

gl.Rotate(angleRotate, rX, rY, rZ);

gl.Scale(scaleCoeff, scaleCoeff, scaleCoeff);

// Установка "камеры наблюдения" - ввод специальной матрицы

gl.LookAt(

//Текущие координаты наблюдателя на сфере с центром в начале координат

radiusEye * cameraCosTeta * cameraSinFi,

radiusEye * cameraSinTeta,

radiusEye * cameraCosTeta * cameraCosFi,

//Неподвижная точка наблюдения

0, 0, 0,


//Направление вверх, нормальное к линии, соединяющей наблюдателя и

// неподвижную точку (центр сферы)

-cameraSinTeta * cameraSinFi, cameraCosTeta, -cameraSinTeta * cameraCosFi);

}

Приведенный метод SetCurModelViewTransformation следует вызывать в начале метода BuildFrame.



В этом методе присутствуют поля, хранящие текущие значения различных параметров. Некоторые из них, такие как радиус окружности radiusTranslate, на который транслируется изображение методом Translate, единичный вектор оси поворота rX, rY, rZ в методе Rotate и радиус сферы наблюдения radiusEye в методе LookAt могут быть постоянными от фрейма к фрейму. В то же время, угол поворота по окружности angleTranslate в методе Translate, угол вращения angleRotate в методе Rotate, масштабный коэффициент scaleCoeff в методе Scale и тригонометрические функции cameraCosTeta, cameraSinFi, cameraSinTeta, cameraCosFi положения наблюдателя на сфере в методе LookAt могут меняться от фрейма к фрейму, если включен таймер (для Rotate и/или Translate), либо крутится колесико (для Scale), либо, наконец, меняется положение курсора мышки с нажатой левой кнопкой (для LookAt). Эти изменения даются в приведенных выше примерах.

Для инициализации рассмотренных параметров в начало метода InitSceneAttributes следует поместить вызов соответствующего метода

void InitModelViewTransfParameters()

{

rotateEnabled = translateEnabled = timer.Enabled = false;



// Случайные углы направления оси вращения объектной системы координат.

double fi = rnd.NextDouble() * 2 * Math.PI, teta = rnd.NextDouble() * Math.PI;

// Единичный вектор оси вращения объектной системы координат.

rX = (float)(Math.Sin(teta) * Math.Cos(fi));

rY = (float)(Math.Sin(teta) * Math.Sin(fi));

rZ = (float)Math.Cos(teta);

scaleCoeff = 1;

// Начальное положение камеры

cameraTeta = .0; cameraFi = .0;

cameraCosTeta = Math.Cos(cameraTeta);

cameraSinTeta = Math.Sin(cameraTeta);

cameraSinFi = Math.Sin(cameraFi);

cameraCosFi = Math.Cos(cameraFi);

angleRotate = angleTranslate = radiusTranslate = 0;

}

Включать и выключать процессы трансляции и поворота можно отдельными кнопками, по клику которых должны меняться значения флагов rotateEnabled, translateEnabled и включаться/ выключаться таймер. Так же кнопкой можно приводить изображение в начальный вид, вызывая метод InitModelViewTransfParameters. Кнопки можно добавить к левой или правой панели объекта toolStripContainer1, находящегося на форму f3D.



Например, для активации вращения можно

  • на правую панель перетащить компоненту класса ToolStrip из раздела Menus & Toolbars окна Toolbox;

  • Щелкнув по стрелочке добавленного объекта, из выпадающего списка можно выбрать кнопку (Button);

  • В окне Properties этой кнопки изменить значения некоторых ее свойств:

    • Дать ей имя (Name) btnRotate;

    • Свойство DisplayStyle - выбрать Text

    • Свойство Image – выбрать None (стереть картинку, данную по умолчанию)

    • Свойство Text – Rotate

    • Свойство TextDirection – Vertical90

    • Выбрать обработчик события Click этой кнопки и вписать в него код

btnRotate.Text = (rotateEnabled = !rotateEnabled) ? "Stop" : "Rotate";

if (rotateEnabled && !timer.Enabled)

timer.Enabled = true;

if (!rotateEnabled && !translateEnabled)

timer.Enabled = false;

Аналогичные действия следует провести для новой кнопки, которая будет активировать процесс трансляции изображения, назвав кнопку btnTranslate и поместив в обработчик ее клика код

btnTranslate.Text = (translateEnabled = !translateEnabled) ? "Stop" : "Translate";

radiusTranslate = translateEnabled ? .5f : 0;

RenderFrame();

if (translateEnabled && !timer.Enabled)

timer.Enabled = true;

if (!rotateEnabled && !translateEnabled)

timer.Enabled = false;

Разместите самостоятельно на левой панели новую кнопку, назвав ее btnRestore. Напишите код обработчика клика кнопки btnRestore, возвращающей изображение в исходное состояние.


Применение метода LookAt приведет к одному дефекту изображения. При некоторых положениях наблюдателя часть точек объекта, заключенного внутри куба (-1;1), который используется здесь в соответствии с параметрами метода Ortho, будет отсекаться. Эффект объясняется тем, что эти точки при некотором ракурсе, возникающем в преобразовании LookAt, оказываются на расстоянии по оси z большим единицы. Срабатывает эффект отсечения, установленный матрицей Ortho, где по умолчанию дальняя отсекающая плоскость по z находится на расстоянии 1 от наблюдателя. Но расстояние до самой дальней точки куба с ребром 2 от его центра равно половине длины его диагонали, то есть 31/2. Поэтому следует выбрать дальнюю плоскость отсечения на расстоянии, по крайней мере, равном 31/2 + радиус сферы наблюдения (radiusEye). Это расстояние до самой дальней точки куба, на котором может находиться наблюдатель при применении метода LookAt.

Так может выглядеть новая редакция метода InitProjection, устраняющая наблюдаемый дефект

void InitProjection()

{

// Определение текущей ширины и высоты порта наблюдения



vpWidth = gl.Get(gl.VIEWPORT, 4)[2];

vpHeight = gl.Get(gl.VIEWPORT, 4)[3];

if (vpWidth > vpHeight)

{

prjLeft = -(prjRight = vpWidth / vpHeight); prjBottom = -(prjTop = 1);



}

else


{

prjLeft = -(prjRight = 1); prjBottom = -(prjTop = vpHeight / vpWidth);

}

prjFar = -(prjNear = (float)Math.Sqrt(3) + radiusEye);



// Устанавливается режим ввода матрицы в стек преобразования

// от координат наблюдения к координатам отсечения

gl.MatrixMode(gl.PROJECTION);

// Ввод единичной матрицы

gl.LoadIdentity();

// Умножает текущую матрицу на матрицу ортографической проекции

// с учетом пропорций порта наблюдения

gl.Ortho(prjLeft, prjRight, prjBottom, prjTop, -prjNear, -prjFar);

// Устанавливается режим ввода матрицы в стек преобразования

// от объектных координат к координатам наблюдения

gl.MatrixMode(gl.MODELVIEW);

gl.LoadIdentity();

}

Посмотрите результат редакций этого раздела.



Для того, кто строит приложение, ссылка на код.
Кроме упомянутых методов преобразования координат OpenGL содержит и другие методы

  1. LoadMatrix загружает на вершину стека произвольную матрицу преобразования

  2. MultMatrix умножает матрицу, находящуюся в вершине стека, на произвольную матрицу

В дальнейшем в этом же руководстве будут рассмотрены методы Frustum и Perspective, которые обеспечивают изображение объектов в перспективной проекции. В отличие от ортографической проекции перспективная проекция создает эффект уменьшения удаляющегося объекта.
Тест рубежного контроля

  1. Что делает метод Translate?

  2. Какой метод совершает преобразование поворота?

  3. Какой метод производит масштабное преобразование?

  4. Что делает метод LookAt?

8 Дисплейные списки. Методы IsList, GenLists, DeleteLists, NewList, EndList, CallList

Построение любого объекта лучше оформлять в виде списка команд, который остается неизменным в памяти компьютера и может быть многократно воспроизведен ссылкой на его номер. Для формирования списков команд используются ряд методов, описанных в библиотеке OpenGL.


Вот описание констант и методов, которые участвуют в работе с дисплейными списками команд

///

/// Команды создаваемого списка компилируются и выполняются

///

public const int COMPILE_AND_EXECUTE = 0x1301;

///

/// Команды списка только компилируются при его создании

///

public const int COMPILE = 0x1300;

///

/// Проверяет наличие списка

///

///

/// Имя искомого списка

///

///

/// true, если список существует. В противном случае false.

///

[DllImport("OPENGL32.DLL", EntryPoint = "glIsList")]

public static extern bool IsList(uint list);

///

/// Убирает списки из памяти, освобождая их имена.

///

///

/// Номер первого из уничтожаемых списков

///

///

/// Число уничтожаемых списков

///

[DllImport("OPENGL32.DLL", EntryPoint = "glDeleteLists")]

public static extern void DeleteLists(uint list, int range);

///

/// Создает непрерывный ряд пустых списков

///

///

/// Число создаваемых списков

///

///

/// Номер (имя) первого из созданных списков

///

[DllImport("OPENGL32.DLL", EntryPoint = "glGenLists")]

public static extern uint GenLists(int range);

///

/// Создает новый список с данным именем или заменяет существующий

///

///

/// Имя (номер) списка.

///

///

/// Режим компиляции команд списка при его создании.

/// Может иметь значения COMPILE или COMPILE_AND_EXECUTE.

///

[DllImport("OPENGL32.DLL", EntryPoint = "glNewList")]

public static extern void NewList(uint list, int mode);

///

/// Завершает создание списка команд.

///

[DllImport("OPENGL32.DLL", EntryPoint = "glEndList")]

public static extern void EndList();

///

/// Исполняет команды списка.

///

///

/// Имя (номер) списка команд.

///

[DllImport("OPENGL32.DLL", EntryPoint = "glCallList")]

public static extern void CallList(uint list);

Текущие значения аргументов метода NewList в процессе создания списка (до обращения к EndList) можно получить с помощью метода Get с аргументами

///

/// Аргумент Get требует возврата значения 1 параметра - режима,

/// в котором создается текущий список (аргумент NewList).

///

public const int LIST_MODE = 0x0B30;

///

/// Аргумент Get требует возврата значения 1 параметра - текущего номера

/// создаваемого списка (аргумент NewList).

///

public const int LIST_INDEX = 0x0B33;

Эти описания следует поместить в класс gl библиотеки GL.

Использование методов работы с дисплейными списками можно проиллюстрировать на примере списка команд, создающих окружность из отдельных точек. Вот пример метода, создающего и выполняющего список точек окружности

///

/// Создает список окружности из точек и возвращает его номер

///

///

/// Число точек на окружности

///

///

/// Номер списка, возвращаемого методом

///

void MakePointsCircleList(int pointsNmb, ref uint pointsCircleList)

{

// Если номер списка не равен нулю и список существует,



// то список стирается и номер зануляется

if (pointsCircleList != 0 && gl.IsList(pointsCircleList))

{

gl.DeleteLists(pointsCircleList, 1);



pointsCircleList = 0;

}

// Номер списка выбирается из свободных номеров



pointsCircleList = gl.GenLists(1);

/*

// Работает так же более простой вариант кода



// Если список не создан (номер равен нулю)

if (0 == pointsCircleList)

// Номер списка выбирается из свободных номеров

pointsCircleList = gl.GenLists(1);

// Следующий далее метод NewList будет выполняться и в том случае,

// когда номер pointsCircleList списка занят.

// Прежнему номеру будет соотнесен новый список, а прежний список уже не будет доступен.

*/

// Начало создания нового списка.



// Список создается и тут же воспроизводится

gl.NewList(pointsCircleList, gl.COMPILE_AND_EXECUTE);

// Команды списка - окружность из точек

gl.Begin(gl.POINTS);

for (int i = 0; i < pointsNmb; i++)

gl.Vertex((float)Math.Cos(2.0 * Math.PI * i / pointsNmb),

(float)Math.Sin(2.0 * Math.PI * i / pointsNmb), 0);

gl.End();

// Завершаются команды списка

gl.EndList();

}

Метод MakePointsCircleList может вызываться внутри метода BuildFrame в том случае, если число точек окружности меняется, либо если список еще не готов. Если это число не меняется (например, окружность поворачивается, или воспроизводится в каком-то другом ракурсе), то вызывается просто сам готовый список методом CallList. Это может выглядеть следующим образом



if (curCirclePointsNmb != CirclePointsNmb || 0 == curPointsCircleList)

MakePointsCircleList(CirclePointsNmb = curCirclePointsNmb,

ref curPointsCircleList);

else


gl.CallList(curPointsCircleList);

Здесь поле curCirclePointsNmb содержит текущее значение числа точек окружности, а CirclePointsNmb – предыдущее значение этого же числа. Если эти два числа не совпадают, либо если текущий номер списка curPointsCircleList равен нулю, то вызывается метод MakePointsCircleList. При этом прежнее значение числа точек заменяется новым значением. В противном случае список curPointsCircleList просто воссоздается в прежнем виде вызовом метода CallList.

Поле curPointsCircleList следует обнулять, а поле CirclePointsNmb инициализировать каким-либо значением (например, 100) в методе InitSceneAttributes. Значение поля curCirclePointsNmb должно быть переменным в результате действий пользователя, как, например, в иллюстрирующем приложении.

Ту же идею воссоздания списка и его воспроизведения можно реализовать несколько иначе. Для этого в коде класса f3D



  • Используйте отдельные объекты-обработчики класса delegate со следующим описанием класса

///

/// Определяет тип методов, создающих дисплейные списки объектов

///

delegate void CreateList();



  • Опишите метод, использующий в качестве одного из своих параметров ссылку на объект этого класса, то есть на метод создания списка конкретного объекта

///

/// Создает список дисплейных команд, одновременно вызывая его, либо вызывает уже готовый список

///

///

/// Создан ли список

///

///

/// Номер списка

///

///

/// Метод создания списка класса CreateList

///

void CreateNCallList(bool isListCreated, uint listNmb, CreateList createList)

{

if (!isListCreated)



createList();

else


gl.CallList(listNmb);

}

Используя этот подход, приведенный пример организации списка точек, образующих окружность, можно сформулировать следующим образом



  • Опишите поля для хранения флага создания списка окружности, номера списка, текущего числа точек в окружности

///

/// Хранит true, если список с нужным числом точек создан

///

bool _isCircleListCreated;

///

/// Хранит текущий номер дисплейного списка окружности из точек.

///

uint _circleList;

///

/// Хранит текущее число точек в окружности.

///

int _pointsNmb = 100;



  • Опишите свойство числа точек, которое обеспечивает автоматический сброс флага создания списка окружности при изменении числа точек

///

/// Устанавливает и возвращает число точек в списке окружности.

/// При установке сбрасывает флаг создания создания списка.

///

int pointsNmb

{

set { _pointsNmb = value; _isCircleListCreated = false; }



get { return _pointsNmb; }

}


  • Опишите метод создания списка окружности, который должен быть третьим параметром при вызове метода CreateNCallList

///

/// Создает дисплейный список окружности, состоящей из точек

///

void CreateCircleList()

{

// Если список существует, то список стирается



if (gl.IsList(_circleList))

gl.DeleteLists(_circleList, 1);

// Номер списка выбирается из свободных номеров

_circleList = gl.GenLists(1);

// Начало создания нового списка.

// Список создается и тут же воспроизводится

gl.NewList(_circleList, gl.COMPILE_AND_EXECUTE);

// Команды списка - окружность из точек

// В стек заталкиваются атрибуты точек и буфера цвета

gl.PushAttrib(gl.POINT_BIT | gl.CURRENT_BIT);

// Устанавливаются свои атрибуты точек и их цвет

gl.PointSize(5);

gl.Enable(gl.POINT_SMOOTH);

gl.Color(0, 1, 1);

// Изображаются точки на окружности, число которых определяется свойством pointsNmb

gl.Begin(gl.POINTS);

for (int i = 0; i < pointsNmb; i++)

gl.Vertex((float)Math.Cos(2.0 * Math.PI * i / pointsNmb),

(float)Math.Sin(2.0 * Math.PI * i / pointsNmb), 0);

// Из стека удаляются временно установленные атрибуты точек и их цвет

gl.PopAttrib();

gl.End();

// Завершаются команды списка

gl.EndList();

// Поднимается флаг создания списка – создан новый список окружности

_isCircleListCreated = true;

}

После этих описаний в точке воспроизведения списка (метод BuildFrame) можно просто вызвать метод



CreateNCallList(_isCircleListCreated, _circleList, CreateCircleList);

В другой, более консервативной части кода (например, в обработчике какого-либо события, при котором необходимо изменить число точек на окружности) можно менять значение свойства pointsNmb. При этом флаг создания списка _isCircleListCreated будет сбрасываться и следующий вызов метода CreateNCallList приведет к созданию и воспроизведению нового списка, с новым числом точек.

Преимущество приведенного алгоритма в том, что для создания и вызовов других списков не надо менять метод CreateNCallList, а лишь его параметры.

Для того, кто создает авторское приложение, ссылка на код.

В следующих разделах будут использованы другие методы работы со списками на примере списков команд, изображающих символы.
Тест рубежного контроля


  1. Зачем нужны дисплейные списки команд?

  2. Какие два метода создают дисплейный список команд?

  3. Какой смысл параметра COMPILE метода NewList?

  4. Какой смысл параметра COMPILE_AND_EXECUTE метода NewList?

  5. Что делает метод GenLists?

  6. В чем смысл метода IsList?

  7. Что делает метод DeleteLists?

  8. Поясните смысл метода CallList.

9 Вывод символов. GDI-функции


Существует, по крайней мере, три способа подготовки и вывода символов и текста на панель воспроизведения команд OpenGL.

Первый способ состоит в использовании стандартных функций библиотеки GDI32. Текст выводится на панель panelGL так же, как если бы это было обычное окно.

Поместите в класс gl библиотеки GL описание трех методов и одной константы

///

/// Устанавливает цвет текста Color для указанного контекста устройства deviceContext

///

///

/// Установленный цвет при успешном завершении

///

[DllImport("GDI32")]

public static extern int SetTextColor(int deviceContext,int Color);

///

/// Устанавливает фоновый цвет Color для устройства deviceContext

///

///

/// Установленный фоновый цвет при успешном завершении

///

[DllImport("GDI32")]

public static extern int SetBkColor(int deviceContext, int Color);

///

/// Изображает строку символов в устройстве deviceContext, используя текущий шрифт

///

///

/// true при успешном завершении

///

[DllImport("GDI32")]

public static extern bool ExtTextOut(int deviceContext, int x, int y, uint options, IntPtr rect,

string text, uint count, IntPtr spacing);

///

/// Используется как аргумент метода Get, который в этом случае требует возврата значений

/// четырех параметров цвета заполнения буфера цвета.

///

public const int COLOR_CLEAR_VALUE = 0x0C22;

В класс f3D поместите описание структуры, содержащей данные о выводимом тексте

struct GDITextAttr

{

internal string gdiText;



internal Color gdiColor;

internal int X, Y;

}

Добавьте к форме f3D описание метода



///

/// Выводит текст на панель glPanel.

///

///

/// Строка текста.

///

void setGDIText(GDITextAttr gdiAttr)

{

// Установка цвета текста



gl.SetTextColor(port.deviceContext, gdiAttr.gdiColor.R + (gdiAttr.gdiColor.G << 8)

+ (gdiAttr.gdiColor.B << 16));

// Определение цвета наполнения буфера OpenGL - цвета фона для текста

int bkColor = 0;

for (int i = 0; i < 3; i++)

bkColor += (int)(gl.Get(gl.COLOR_CLEAR_VALUE, 4)[i] * 255) << i * 8;

// Установка цвета фона для текста

gl.SetBkColor(port.deviceContext, bkColor);

// Вывод строки текста в указанную точку панели

gl.ExtTextOut(port.deviceContext, gdiAttr.X, gdiAttr.Y, 0, IntPtr.Zero,

gdiAttr.gdiText, (uint)gdiAttr.gdiText.Length, IntPtr.Zero);

}

В конец метода RenderFrame формы f3D (после вызова метода SwapBuffers) добавьте вызов метода setGDIText с конкретным параметром. Параметр имеет тип структуры GDITextAttr, и конкретноые значения ее полей можно поставить в зависимость от данных, регулируемых пользователем извне, либо менять их редактированием в коде программы.



Для того, кто создает авторское приложение, ссылка на код.

Иллюстрацию к разделу можно посмотреть по ссылке.


Тест рубежного контроля

  1. Что делает метод SetTextColor?

  2. Зачем использовать метод SetBkColor?

  3. Что делает метод ExtTextOut?

  4. Как определяется положение GDI-текста в окне?

10 BMP-символы


Второй способ вывода текста использует команды OpenGL для создания битовых карт символов и произвольного размещения символов в объектной системе координат.

Для подготовки и использования списков символов в форме битовых карт используются дополнительные методы по работе с дисплейными списками.



Методы ListBase и CallLists


Метод ListBase устанавливает номер списка, который будет добавляться ко всем последующим номерам списков, вызываемых с помощью CallList. Это так называемый базовый номер.

///

/// Устанавливает целое смещение, которое после суммирования с относительными

/// номерами списков метода CallLists определяет абсолютные номера этих списков.

/// По умолчанию смещение равно 0.

///

///

/// Значение смещения.

///

[DllImport("OPENGL32.DLL", EntryPoint = "glListBase")]

public static extern void ListBase(uint basevalue);

Текущее значение базового номера списка можно получить с помощью метода Get с аргументом

///

/// Аргумент Get требует возврата значения 1 параметра -

/// текущего базового номера списков (аргумент метода ListBase).

///

public const int LIST_BASE = 0x0B32;

Для сохранения в стеке текущего номера базового списка следует вызвать метод PushAttrib с аргументом

///

/// Маска битов базового номера списков.

///

public const int LIST_BIT = 0x00020000;

Метод CallLists позволяет вызывать сразу несколько списков, что необходимо при формировании текста из нескольких символов.

///

/// Вызывает списки команд OpenGL, начиная с базового номера, установленного ListBase.

///

///

/// Число вызываемых списков.

///

///

/// Тип номера списка. Допустимы символьные константы BYTE, UNSIGNED_BYTE,

/// SHORT, UNSIGNED_SHORT, INT, UNSIGNED_INT, FLOAT, _2_BYTES, _3_BYTES и _4_BYTES.

///

///

/// Строка из n символов списков.

///

[DllImport("OPENGL32.DLL", EntryPoint = "glCallLists")]

public static extern void CallLists(int n, int type, string lists);

В данном случае будут использоваться списки, номера которых имеют тип беззнаковых байтов, поэтому понадобится только аргумент в виде символьной константы

///

/// Тип номеров списков - один из аргументов CallLists

///

public const int UNSIGNED_BYTE = 0x1401;

Эти описания следует поместить в класс gl библиотеки GL.


Методы RasterPos и UseFontBitmap


Для вывода битовой карты в буфер фрейма необходимо указать координаты ее верхнего левого угла. Это делает метод RasterPos

///

/// Устанавливает объектные координаты точки вывода изображения.

/// Используется при размещении битовых карт, символов.

///

[DllImport("OPENGL32.DLL", EntryPoint = "glRasterPos3f")]

public static extern void RasterPosf(float x, float y, float z);

Для определения текущих значений параметров метода RasterPos следует использовать метод Get с параметрами

///

/// Аргумент Get требует возврата значений 4 параметров цвета растра(метод RasterPos).

///

public const int CURRENT_RASTER_COLOR = 0x0B04;

///

/// Аргумент Get требует возврата 4 параметров положения растра (метод RasterPos).

///

public const int CURRENT_RASTER_POSITION = 0x0B07;

///

/// Аргумент Get требует возврата значения 1 параметра -

/// расстояния от начала координат наблюдения до положения растра (метод RasterPos).

///

public const int CURRENT_RASTER_DISTANCE = 0x0B09;

Для сохранения текущих значений параметров метода RasterPos следует вызвать метод PushAttrib с параметром CURRENT_BIT.

Для выбора шрифта символов используется специальная функция SelectObject из библиотеки GDI32

///

/// Сопоставляет объект objectHandle контексту устройства deviceContext.

/// Новый объект заменяет предыдущий объект того же типа.

///

///

/// Идентификатор (хэндл) замещенного объекта

///

[DllImport("GDI32")]

public static extern IntPtr SelectObject(int deviceContext, IntPtr objectHandle);

Главным методом, позволяющим формировать битовые карты из символов заданного шрифта, является метод UseFontBitmaps

///

/// Создает набор списков битовых карт символов текущего шрифта,

/// связанного с контекстом устройства deviceContext.

/// Списки нумеруются, начиная с номера listBase.

/// Номера символов шрифта нумеруются, начиная с номера start.

/// Число символов равно count.

/// Эти битовые карты могут использоваться для отображения символов в окне OpenGL.

///

///

/// true при успешном завершении

///

[DllImport("OPENGL32.DLL", EntryPoint = "wglUseFontBitmaps")]

public static extern bool UseFontBitmaps(int deviceContext, int start, int count, uint listBase);

Эти описания следует поместить в класс gl библиотеки GL.
Для использования всех этих методов в приложении можно в библиотеке GL описать дополнительный статический класс listMaker, в котором размещать необходимые методы по созданию дисплейных списков команд OpenGL. Если в этот класс поместить метод, создающий дисплейные списки битовых карт символов заданного шрифта, то соответствующий код будет выглядеть следующим образом

///

/// Состоит из статических методов, создающих дисплейные списки команд OpenGL

///

public static class listMaker

{

///



/// Создает 256 списков битовых карт символов (глифов) заданного типа шрифта

/// для вывода в заданный контекст устройства.

///

///

/// Объект используемого шрифта.

///

///

/// Контекст устройства.

///

///

/// Возвращаемый номер списка первого символа.

///

public static void BMPGlyphs(Font font, int deviceContext, ref uint glyphsBaseList)

{

// Если список не создан



if (0 == glyphsBaseList)

// Создается 256 непрерывно расположенных номеров пустых дисплейных списков,

// первый номер которых возвращается в glyphsBaseList,

// определяя базовый номер всех списков

glyphsBaseList = gl.GenLists(256);

// Новый шрифт временно заменяет существующий шрифт контекста устройства

IntPtr oldFont = gl.SelectObject(deviceContext, font.ToHfont());

// Создаются списки битовых карт символов от 0 до 255

// Нумерация списков начинается с gliphsBaseList

if (!gl.UseFontBitmaps(deviceContext, 0, 256, glyphsBaseList))

throw new Exception("Списки битовых карт символов не установлены.");

// Прежний шрифт возвращается контексту

gl.SelectObject(deviceContext, oldFont);

}

}



Класс Font описан в системной библиотеке System.Drawing. Поэтому необходимо добавить эту библиотеку к ресурсам библиотеки GL (окно Solution Explorer -> узел References библиотеки GL -> команда Add Reference… из контекстного меню -> закладка .NET открывшегося окна Add Reference, найти System.Drawing) и в заголовке файла GL.cs добавить директиву using System.Drawing.

Метод BMPGlyphs можно вызывать непосредственно в любом из методов формы для создания и обновления действующего списка битовых карт символов.

Алгоритм использования метода BMPGlyphs аналогичен уже рассмотренному алгоритму создания и вызова списка точек окружности. Внутри метода BuildFrame можно набрать следующий код

// Устанавливается текущий шрифт символов

using (Font curFont = new Font(ppBMPText.bmpFont.FontFamily,

ppBMPText.bmpFont.Size))

if (ppBMPText.IsChanged || curBMPGlyphsBaseList == 0)

listMaker.BMPGlyphs(curFont, port.deviceContext, ref curBMPGlyphsBaseList);

// Флаг изменения параметров очищается

curBMPFontChanged = false;

// Устанавливается базовый номер списков символов

gl.ListBase(curBMPGlyphsBaseList);

// Устанавливается текущий цвет символов

gl.Color(curBMPFontClrRed, curBMPFontClrGreen, curBMPFontClrBlue);

// Устанавливается позиция в окне, куда следует выводить символы

gl.RasterPosf(curBMPFontPosX, curBMPFontPosY, curBMPFontPosZ);

// Вызываются списки символов, необходимые для воспроизведения текущего текста

gl.CallLists(curBMPFontText.Length, gl.UNSIGNED_BYTE, curBMPFontText);

В этом коде есть поле curBMPGlyphsBaseList, которое следует обнулять в методе InitSceneAttributes. Другие поля curBMPFont, curBMPFontChanged, curBMPFontClrRed, curBMPFontClrGreen, curBMPFontClrBlue, curBMPFontPosX, curBMPFontPosY, curBMPFontPosZ, curBMPFontText являются переменными, которые регулируются либо пользователем через интерфейсные управляющие элементы, как в иллюстрирующем приложении, либо любым другим способом.

Ссылка на код и комментарии для того, кто строит авторское иллюстрирующее приложение.
Тест рубежного контроля


  1. Для чего используется метод ListBase?

  2. Как сохранить в стеке значение текущего базового номера списка?

  3. Как узнать значение текущего базового номера списка?

  4. Какой метод применяется для вызова последовательности дисплейных списков?

  5. Какой метод используется для создания дисплейных списков битовых карт символов?

  6. Что устанавливает метод RasterPos?

  7. Какую роль играет вызов метода SelectObject при формировании списков битовых карт символов?

11 Контурный шрифт


Третий способ вывода символов позволяет изображать символы в виде объемных фигур. Это так называемый контурный шрифт, где каждый символ является отдельным 3-мерным объектом.

Метод UseFontOutline


Для создания символов контурного шрифта используется метод UseFontOutlines со следующим заголовком

///

/// Создает списки контурных символов.

///

///

/// Контекст устройства контурного шрифта.

///

///

/// Номер первого символа, обращаемого в список.

///

///

/// Количество символов, обращаемых в списки.

///

///

/// Номер первого списка.

///

///

/// Определяет максимально допустимое отклонение от верного контура.

///

///

/// Глубина (толщина) символа в отрицательном направлении оси z.

///

///

/// Указывает тип сегментов контура символа -

/// контурный (FONT_LINES) или заполненный (FONT_POLYGONS).

///

///

/// Адрес буфера (массива метрических записей),

/// в который передаются метрические данные символов.

///

///

/// true при успешном завершении.

///

[DllImport("OPENGL32.DLL", EntryPoint = "wglUseFontOutlines")]

public static extern bool UseFontOutlines

(int deviceContext, uint first, uint count, uint listBase, float deviation,

float extrusion, int format, GLYPHMETRICSFLOAT[] glyphs);
Метод UseFontOutlines использует в качестве одного из аргументов одну из символьных постоянных

///

/// Постоянные формата контурного шрифта. Используются при вызове UseFontOutlines.

///

public const int FONT_LINES = 0;

public const int FONT_POLYGONS = 1;

Каждый из символов контурного шрифта характеризуется атрибутами, собранными в структуру типа

///

/// Тип структуры, хранящей информацию о размерах,

/// положении и внешнем виде контурных символов.

/// Массив объектов этой структуры является одним из параметров метода UseFontOutlines.

///

[StructLayout(LayoutKind.Sequential)]

public struct GLYPHMETRICSFLOAT

{

public float gmfBlackBoxX;



public float gmfBlackBoxY;

public float gmfptGlyphOriginX;

public float gmfptGlyphOriginY;

public float gmfCellIncX;

public float gmfCellIncY;

}

Эти описания следует добавить к классу gl библиотеки GL.


Для создания списков символов контурного шрифта можно поместить в класс listMaker библиотеки GL следующий метод

///

/// Создает 256 списков 3D-объектов символов контурного шрифта.

///

///

/// Объект используемого шрифта.

///

///

/// Массив возвращаемых записей метрик символов.

///

///

/// Максимальное отклонение шрифта от гладкости

///

///

/// Глубина шрифта в отрицательном направлении оси Z

///

///

/// Формат выводимых символов FONT_LINES или FONT_POLYGONS .

///

///

/// Контекст устройства.

///

///

/// Возвращаемый номер первого списка.

///

public static void OutlineGlyphs(Font font, gl.GLYPHMETRICSFLOAT[] metrics,

float deviation, float extrusion, int format, int deviceContext, ref uint outlineGlyphsBaseList)

{

// Новый шрифт временно заменяет существующий шрифт контекста устройства



IntPtr oldFont = gl.SelectObject(deviceContext, font.ToHfont());

// Если список не создан

if (0 == outlineGlyphsBaseList)

// Создается 256 непрерывно расположенных номеров пустых дисплейных списков,

// первый номер которых возвращается в outlineGlyphsBaseList,

// определяя базовый номер всех списков

outlineGlyphsBaseList = gl.GenLists(256);

// Создаются списки символов от 0 до 255 контурного шрифта

// Нумерация списков начинается с outlineBaseList

if (!gl.UseFontOutlines(deviceContext, 0, 256, outlineGlyphsBaseList,

deviation, extrusion, format, metrics))

throw new Exception("Списки символов контурного шрифта не установлены.");

// Прежний шрифт возвращается контексту

gl.SelectObject(deviceContext, oldFont);

}

При вызове этого метода используется поле fMetrics, которое следует описать в виде



///

/// Хранит массив метрических свойств символов

///

gl.GLYPHMETRICSFLOAT[] fMetrics = new gl.GLYPHMETRICSFLOAT[256];

В метод BuildFrame теперь можно добавить код алгоритма построения и использования символов контурного шрифта при произвольных атрибутах

// Устанавливается текущий шрифт символов

using (Font curFont = new Font(curOutlnFont.FontFamily, curOutlnFont.Size))

if (ppOutlineFont.IsChanged || curOutlnGlyphsBaseList == 0)

// Списки символов воссоздаются, если параметры изменены

listMaker.OutlineGlyphs(

curFont, fMetrics, ppOutlineFont.deviation, ppOutlineFont.extrusion,

ppOutlineFont.format, port.deviceContext, ref curOutlnGlyphsBaseList);

// Флаг изменения параметров очищается

curOutlineFontChanged = false;

// Устанавливается базовый номер списков символов

gl.ListBase(curOutlnGlyphsBaseList);

// Устанавливается текущий цвет символов

gl.Color(curOutlineFontClrRed, curOutlineFontClrGreen, curOutlineFontClrBlue);

// Устанавливается положение, куда следует выводить символы

// Матрица предыдущего преобразования заталкивается в стек

gl.PushMatrix();

gl.Translate(curOutlineFontPosX, curOutlineFontPosY, curOutlineFontPosZ);

gl.Scale(.1f, .1f, .1f);

// Вызываются списки символов, необходимые для воспроизведения текущего текста

gl.CallLists(curOutlineFontText.Length, gl.UNSIGNED_BYTE, curOutlineFontText);

// Бывшая матрица преобразования возвращается на поверхность стека,

// выталкивая последнюю матрицу.

gl.PopMatrix();

Здесь номер текущего списка curOutlnGlyphsBaseList следует обнулять в методе InitSceneAttributes. Поля curOutlnFont, curOutlineFontChanged, curOutlineFontdeviation, curOutlineFontextrusion, curOutlineFontformat, curOutlineFontClrRed, curOutlineFontClrGreen, curOutlineFontClrBlue, curOutlineFontPosX, curOutlineFontPosY, curOutlineFontPosZ и curOutlineFontText должны выбираться пользователем через интерфейсные контрольные элементы, как это сделано в авторском приложении, либо задаваться другим способом. Ссылка на код и комментарии для того, кто строит авторское иллюстрирующее приложение.

Примечание

При вызове списков символов контурного шрифта может оказаться необходимым поместить этот вызов внутрь пары PushAttrib/PopAttrib вида

gl.PushAttrib(gl.POLYGON_BIT);

gl.CallLists(curOutlineFontText.Length, gl.UNSIGNED_BYTE, curOutlineFontText);

gl.PopAttrib();

Здесь константа POLYGON_BIT определена следующим образом

///

/// Маска атрибутов многоугольника.

///

public const int POLYGON_BIT = 0x00000008;


Методы PushMatrix, PopMatrix


В приведенном выше фрагменте кода используются методы PushMatrix и PopMatrix.

public static extern void PopMatrix();

///

/// Проталкивает верхнюю матрицу в стек.

///

[DllImport("OPENGL32.DLL", EntryPoint = "glPushMatrix")]

public static extern void PushMatrix();

///

/// Выталкивает верхнюю матрицу из стека.

///

[DllImport("OPENGL32.DLL", EntryPoint = "glPopMatrix")]

Эти описания следует поместить в класс gl библиотеки GL.

Методы PushMatrix/PopMatrix работают так же, как методы PushAttrib/PopAttrib. Метод PushMatrix заталкивает в стек матриц копию матрицы предыдущего преобразования. Далее вводимые матрицы умножаются на предыдущую, но копия предыдущей матрицы остается нетронутой в глубине стека. После окончания формирования изображения с требуемым преобразованием матрица, сохраненная в стеке, возвращается на его поверхность методом PopMatrix.
Проектное задание

Проведите необходимые редакции кода в своей, либо в авторской версии проекта, достаточные для устойчивой работы кода.


Тест рубежного контроля

  1. Какой метод используется при создании списков контурного шрифта?

  2. Какой смысл имеет параметр deviation метода создания списков контурного шрифта?

  3. Какой смысл имеет параметр extursion метода создания списков контурного шрифта?

  4. Какой смысл имеет параметр format метода создания списков контурного шрифта?

  5. Какую функцию выполняет метод PushMatrix?

  6. Какой смысл имеет метод PopMatrix?


Достарыңызбен бөлісу:
1   ...   5   6   7   8   9   10   11   12   ...   15




©dereksiz.org 2024
әкімшілігінің қараңыз

    Басты бет