Аксонометрические проекции

c.jpg

Задача: создать приложение, использующее DirectX с управляемым кодом, на примере построения аксонометрических проекций.
Язык: MS Visual C# 2008
Среда разработки: MS Visual Studio 2008
Платформа: .NET Framework 3.5; Managed DirectX 11.0

Задача: создать приложение, использующее DirectX с управляемым кодом, на примере построения аксонометрических проекций.
Язык: MS Visual C# 2008
Среда разработки: MS Visual Studio 2008
Платформа: .NET Framework 3.5; Managed DirectX 11.0

Управляемый DirectX разрабатывался компанией Microsoft для совместного использования с платформой .NET Framework и впервые был выпущен вместе с девятой версией библиотеки. На сегодняшний день существует версия DirectX 11.0, поставляемая с операционной системой MS Windows 7, а также доступная на сайте производителя. Библиотеки функций располагаются в папке C:\Windows\Microsoft.NET\DirectX for Managed Code. Платформа .NET поддерживает создание приложений с помощью встроенного компилятора (расположенного в C:\Windows\Microsoft.NET\Framework\v[*], где [*] –версия установленной платформы), выполненного в виде консольного приложения, однако далее будем рассматривать работу в среде разработки Visual Studio 2008.
1. С чего все начинается.
В Visual Studio создаем проект C#, при этом выбрав в разделе Windows пункт приложение WindowsForms либо пустой проект. Можно также выбрать консольное приложение, так как целевая платформа легко изменяется через раздел меню Проект ->Свойства…->Тип выходных данных. В окне обозревателя решений в раздел Ссылки через контекстное меню добавим следующие ссылки: на вкладке .NET – Microsoft.DirectX, Microsoft.DirectX.Direct3D, Microsoft.DirectX.Direct3DX. Если в вашем типе проекта не присутствует форма, следует добавить ее таким же образом: обозреватель решений -> правый клик на имени проекта (не решения!) -> Добавить -> Форма Windows, или же создать свой класс-наследник Form. Далее, в файл с определением класса, наследующего класс Form, подключим пространства имен DirectX с помощью директив using:

using System;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

Метод Main(), точка входа в программу, должен выглядеть примерно следующим образом:
static void Main()
{
using (Form1 form = new Form1())
      {
      	    form.Show();
            form.GraphInit();
            Application.Run(form);
      }
}

Если Main() вынесен в отдельный файл, не забудьте подключить к нему пространство имен в котором находится ваш класс-форма. Также следует помнить, что в языке C# все методы должны находиться в классах, в том числе и Main().
2. Инициализация устройства DirectX.
После построения проекта с изменениями, внесенными в пункте 1, компилятор должен выдать только одну ошибку: UserProject.UserForm1 не содержит определения для “GraphInit”. Этим сейчас и займемся.
Вначале добавим в наш класс поле типа Device. Этот класс определен в пространстве имен Microsoft.DirectX.Direct3D. Также нам понадобятся целые переменные H и W – размеры экрана.
Device device = null; 
int H, W;

Далее в классе создадим метод GraphInit():
void GraphInit()
{
	// Описатель параметров отображения
        PresentParameters pp = new PresentParameters();
        // Установка режима свопинга (механизм работы буферов)
        pp.SwapEffect = SwapEffect.Discard;
	// Номер используемого графического адаптера
        int adapter = Manager.Adapters.Default.Adapter;
        // Получение возможностей устройства
        Caps caps = Manager.GetDeviceCaps(adapter, DeviceType.Hardware);
	// Флаги, используемые при создании устройства
	// Обработка вершин при помощи программного обеспечения
        CreateFlags flags = CreateFlags.SoftwareVertexProcessing;
	// Вершины и освещение могут обрабатываться аппаратно,
        if (caps.DeviceCaps.SupportsHardwareTransformAndLight)
      	flags = CreateFlags.HardwareVertexProcessing;
	// 
        if (caps.DeviceCaps.SupportsPureDevice)
              flags |= CreateFlags.PureDevice;
	// Определение типа формата отображения
        Format format = Manager.Adapters[0].CurrentDisplayMode.Format;
	// Если поддерживается полноэкранный режим
        if (Manager.CheckDeviceType(adapter, DeviceType.Hardware, format, format, false))
        {
		// Размеры графического устройства вывода
                H = Manager.Adapters[0].CurrentDisplayMode.Height;
                W = Manager.Adapters[0].CurrentDisplayMode.Width;
		// Настройка экранного буфера и отображения
         	pp.Windowed = false;
          	pp.BackBufferCount = 1;
                pp.BackBufferFormat = format;
		pp.BackBufferHeight = H;
                pp.BackBufferWidth = W;
        }
        // Установка для оконного приложения
        else
        {
         	pp.Windowed = true;
        }
	// Создание утсройства DirectX с выбранными ранее параметрами
        device = new Device(0, DeviceType.Hardware, this, flags, pp);
}

Также необходимо внести изменения в конструкторе:
// Форма отображается без рамки (необходимо для полноэкранного режима)
FormBorderStyle = FormBorderStyle.None;
// Установка флагов отрисовки для снижения мерцания
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque | ControlStyles.UserPaint, true);

В результате, если все правильно написано, при запуске приложения в полноэкранном режиме весь экран становится пустым (заливается черным цветом), а при запуске окна клиентская область становится белой.
3. Отображение.
Работа с выводом изображения с C# производится с помощью обработчика события OnPaint:
protected override void OnPaint(PaintEventArgs e)
{
      base.OnPaint(e);
      // Очищает окно просмотра заданным цветом, очищает буфер глубины и буфер шаблона
      device.Clear(ClearFlags.Target, Color.Bisque, 1.0f, 0);
 
      // Начало сцены
      device.BeginScene();
      // Конец сцены
      device.EndScene();
 
      // Предоставляет для отображения содержимое следующего буфера
      device.Present();
}

Все команды отрисовки должны находиться между вызовами BeginScene() и EndScene().
4. Считывание данных о фигуре.
Считывать информацию о фигуре будем из файла следующей структуры:
1 – количество вершин(n), количество ребер(m)
2…n+1 - координаты вершин фигуры(x, y, z)
n+2…n+m+2 – пары вершин, связанных ребрами(v1,v2)
Для работы с файлами подключим пространство имен System.IO в начале файла:
using System.IO;

Для хранения данных добавим следующие поля класса:
// Массив цветных вершин с преобразованными координатами
CustomVertex.TransformedColored[] v = null;
// Количество вершин и ребер
int NumOfVert = 0;
int NumOfEdges = 0;
float alpha = (float)Math.PI * 4 / 12;
float beta = (float)Math.PI * 5 / 12;
float sx = 1.0f, sy = .820f, sz = .820f;

Считанные данные будем сразу преобразовывать и выводить соответствующее изображение. Для построения проекции сначала повернем систему координат относительно оси Oz на угол alpha, а потом относительно оси Ox на угол beta. Также применим масштабирование по осям с помощью коэффициентов sx, sy, sz. Изменение этих углов и коэффициентов позволяет строить различные виды аксонометричеких проекций. Результат перемножения двух матриц поворота используем при вычислении координат. Отметим, что наше приложение использует механизмы двумерной графики DirectDraw (на сегодняшний день DirectDraw является встроенным в Direct3D). При работе с трехмерной графикой необходимо задать параметры видовой камеры и освещения, а матрицы преобразований формируются путем обращения к соответствующим методам и свойствам объекта типа Device.
Напомним, что работа проводится между вызовами BeginScene() и EndScene().
// Создание входного потока, связанного с файлом
StreamReader fin = new StreamReader(@"..\..\figure.dat");
// Массив строк s будет хранить данные считанной строки, разделенные в файле пробелом
string[] s = fin.ReadLine().Split(' ');
// В первой строке 2 числа: количество вершин
NumOfVert = Convert.ToInt32(s[0]);
// и количество ребер
NumOfEdges = Convert.ToInt32(s[1]);
 
// Создание массива для хранения точек
v = new CustomVertex.TransformedColored[NumOfVert];
// Переменные, используемые для масштабирования
float MaxX = float.MinValue;
float MinX = float.MaxValue;
float MaxY = float.MinValue;
float MinY = float.MaxValue;
 
// Считывание координат
for (int i = 0; i < NumOfVert; i++)
{
	// Следующая строка файла
    	s = fin.ReadLine().Split(' ');
	//Создание точки и задание свойств
        v[i] = new CustomVertex.TransformedColored();
        // Цвет
        v[i].Color = Color.DarkOrchid.ToArgb();
        // Преобразование координаты X
    	v[i].X = (sx * Convert.ToInt32(s[0]) * (float)Math.Cos(alpha) - sy * Convert.ToInt32(s[1]) * (float)Math.Sin(alpha));
	// Преобразование координаты Y
    	v[i].Y = (sx * Convert.ToInt32(s[0]) * (float)Math.Sin(alpha) * (float)Math.Cos(beta) + sy * Convert.ToInt32(s[1]) *   (float)Math.Cos(alpha) * (float)Math.Cos(beta) - sz * Convert.ToInt32(s[2]) * (float)Math.Sin(beta));
	// Нахождение пределов изображения
    	MaxX = Math.Max(MaxX, v[i].X);
    	MinX = Math.Min(MinX, v[i].X);
    	MaxY = Math.Max(MaxY, v[i].Y);
    	MinY = Math.Min(MinY, v[i].Y);
}
// Сдвиг в видимую часть экрана
if (MinX <= 0)
{
    for (int i = 0; i < NumOfVert; i++)
        v[i].X -= MinX;
    MaxX -= MinX;
    MinX = 0;
}
if (MinY <= 0)
{
    for (int i = 0; i < NumOfVert; i++)
        v[i].Y -= MinY;
    MaxY -= MinY;
    MinY = 0;
}
// Сдвиг к центру экрана
if (MaxX + MinX < W)
{
    for (int i = 0; i < NumOfVert; i++)
        v[i].X += (W - MaxX + MinX) / 2;
}
if (MaxY + MinY < H)
{
    for (int i = 0; i < NumOfVert; i++)
        v[i].Y += (H - MaxY + MinY) / 2;
}
 
// Вспомогательный массив, хранящий точки, предназначенные для вывода
CustomVertex.TransformedColored []q = new CustomVertex.TransformedColored[2];
// Считываем данные о ребрах и выводим линию, соединяющую соответствующие точки
for (int i = 0; i < NumOfEdges; i++)
{
	// Считывание
    	s = fin.ReadLine().Split(' ');
	// Считанные числа используем как индекс (номер) точки в массиве
    	q[0] = v[Convert.ToInt32(s[0])];
    	q[1] = v[Convert.ToInt32(s[1])];
 
	// Рисуем список линий из 1 элемента, используя данные из массива q
    	device.DrawUserPrimitives(PrimitiveType.LineList, 1, q);
}
// Закрываем файл
fin.Close();

Для наглядности можно добавить изображение координатных осей, задавая одну из точек в использованных выше формулах как (0; 0; 0), а вторую – соответственно изменяя по одной координате: (1000; 0; 0), (0; 1000; 0), (0; 0; 1000). Для использования формул необходимо сохранить значения MinX и MinY; используем для этого переменные minx и miny, скопировав значения после цикла считывания. Сразу применив масштабирование и сдвиги, получаем следующий код:
CustomVertex.TransformedColored[] axis = new CustomVertex.TransformedColored[2];
// Точка (0; 0; 0)
axis[0].X = (sx * 0 * (float)Math.Cos(alpha) - sy * 0 * (float)Math.Sin(alpha)) + (W - MaxX + MinX) / 2 - minx;
axis[0].Y = (sx * 0 * (float)Math.Sin(alpha) * (float)Math.Cos(beta) + sy * 0 * (float)Math.Cos(alpha) * (float)Math.Cos(beta) - sz * 0 * (float)Math.Sin(beta)) + (H - MaxY + MinY) / 2 - miny;
axis[0].Color = Color.Red.ToArgb();
 
// Точка (1000; 0; 0)
axis[1].X = (sx * 1000 * (float)Math.Cos(alpha) - sy * 0 * (float)Math.Sin(alpha)) + (W - MaxX + MinX) / 2 - minx;
axis[1].Y = (sx * 1000 * (float)Math.Sin(alpha) * (float)Math.Cos(beta) + sy * 0 * (float)Math.Cos(alpha) * (float)Math.Cos(beta) - sz * 0 * (float)Math.Sin(beta)) + (H - MaxY + MinY) / 2 - miny;
axis[1].Color = Color.Red.ToArgb();
 
// Рисуем Ox
device.DrawUserPrimitives(PrimitiveType.LineList, 1, axis);
 
// Точка (0; 1000; 0)
axis[0].Color = Color.Green.ToArgb();
axis[1].X = (sx * 0 * (float)Math.Cos(alpha) - sy * 1000 * (float)Math.Sin(alpha)) + (W - MaxX + MinX) / 2 - minx;
axis[1].Y = (sx * 0 * (float)Math.Sin(alpha) * (float)Math.Cos(beta) + sy * 1000 * (float)Math.Cos(alpha) * (float)Math.Cos(beta) - sz * 0 * (float)Math.Sin(beta)) + (H - MaxY + MinY) / 2 - miny;
axis[1].Color = Color.Green.ToArgb();
 
// Рисуем Oy
device.DrawUserPrimitives(PrimitiveType.LineList, 1, axis);
 
// Точка (0; 0; 1000)
axis[0].Color = Color.Blue.ToArgb();
axis[1].X = (sx * 0 * (float)Math.Cos(alpha) - sy * 0 * (float)Math.Sin(alpha)) + (W - MaxX + MinX) / 2 - minx;
axis[1].Y = (sx * 0 * (float)Math.Sin(alpha) * (float)Math.Cos(beta) + sy * 0 * (float)Math.Cos(alpha) * (float)Math.Cos(beta) - sz * 1000 * (float)Math.Sin(beta)) + (H - MaxY + MinY) / 2 - miny;
axis[1].Color = Color.Blue.ToArgb();
 
// Рисуем Oz
device.DrawUserPrimitives(PrimitiveType.LineList, 1, axis);

Для придания эффекта вращения добавим инкременты углов и вызов метода Invalidate(), вызывающего перерисовку рабочей области, в конце метода OnPaint():
alpha += 0.003f;
beta += 0.003f;
Invalidate();

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

Ключевые слова: 
.NET Framework, Managed DirectX, проекция, куб
ВложениеРазмер
Project2.zip36.88 кб
Managed DirectX и .NET Framework.doc79.5 кб