Множество трехмерных объектов
108Silverlight 5 --- Множество трехмерных объектов
При создании достаточно сложного трехмерного объекта (хотя бы такого, как куб в примерах из предыдущих статей) необходимо управлять огромным количеством параметров и писать много кода. В рассмотренных ранее примерах приложение помещает фигуру в пользовательский элемент управления, представляющий страницу. Однако лучше поместить ее в выделенный класс, служащий оболочкой для всего кода рисования и предоставляющий легкий доступ к настраиваемым свойствам. Этим вы не только упростите код, но и облегчите использование копий объекта в трехмерной сцене.
Рассмотрим пример с текстурированным кубом. Переместим весь алгоритм рисования в отдельный класс Cube3D:
public class Cube3D
{
// Сохранение буфера вершин и параметров эффекта
private VertexBuffer vertexBuffer;
private BasicEffect effect;
// Свойство доступа к параметрам мировой матрицы (необходимо
// для перемещения и поворота объекта)
public Matrix World
{
get { return effect.World; }
set { effect.World = value; }
}
// Свойство доступа к матрице вида (необходимо
// для перемещения и поворота камеры)
public Matrix View
{
get { return effect.View; }
set { effect.View = value; }
}
// Свойство доступа к проекции вида (необходимо
// для изменения пропорций DrawingSurface)
public Matrix Projection
{
get { return effect.Projection; }
set { effect.Projection = value; }
}
...
Класс Cube3D предоставляет доступ к трем матрицам, которые необходимы для манипулирования моделью в приложении при кодировании средств интерактивности и анимации. Все подробности рисования, закодированные в объектах VertexBuffer и BasicEffect, скрыты.
У класса Cube3D есть два конструктора. Оба они принимают несколько ключевых параметров: используемое устройство GraphicsDevice, объект Vector3, позиционирующий левый нижний задний угол куба, ширину куба, адрес URI текстуры и пропорцию поверхности рисования DrawingSurface. Второй конструктор, кроме того, принимает матрицу вида, отвечающую за расположение камеры. Для ее получения первый конструктор вызывает второй и применяет жестко закодированную матрицу вида.
public Cube3D(GraphicsDevice device, Vector3 bottomLeftBack, float width,
string textureUri, float aspectRatio)
: this(device, bottomLeftBack, width, textureUri, aspectRatio,
Matrix.CreateLookAt(new Vector3(0, 0, 5), Vector3.Zero, Vector3.Up))
{
// Код не нужен, потому что вызывается второй конструктор
}
Второй конструктор содержит код, который в предыдущих примерах находился в методе PrepareDrawing(). Единственное отличие нового кода состоит в том, что он немного скорректирован для приема аргументов конструктора. Например, все вершины теперь определяются относительно заднего левого нижнего угла куба и на основе заданной ширины:
public Cube3D(GraphicsDevice device, Vector3 bottomLeftBack, float width,
string textureUri, float aspectRatio, Matrix view)
{
Vector2 textureTopLeft = new Vector2(0, 0);
Vector2 textureTopRight = new Vector2(1, 0);
Vector2 textureBottomLeft = new Vector2(0, 1);
Vector2 textureBottomRight = new Vector2(1, 1);
Vector3 topLeftFront = new Vector3(bottomLeftBack.X, bottomLeftBack.Y + width, bottomLeftBack.Z + width);
Vector3 bottomLeftFront = new Vector3(bottomLeftBack.X, bottomLeftBack.Y, bottomLeftBack.Z + width);
Vector3 topRightFront = new Vector3(bottomLeftBack.X + width, bottomLeftBack.Y + width, bottomLeftBack.Z + width);
Vector3 bottomRightFront = new Vector3(bottomLeftBack.X + width, bottomLeftBack.Y, bottomLeftBack.Z + width);
Vector3 topLeftBack = new Vector3(bottomLeftBack.X, bottomLeftBack.Y + width, bottomLeftBack.Z);
Vector3 topRightBack = new Vector3(bottomLeftBack.X + width, bottomLeftBack.Y + width, bottomLeftBack.Z);
Vector3 bottomRightBack = new Vector3(bottomLeftBack.X + width, bottomLeftBack.Y, bottomLeftBack.Z);
VertexPositionTexture[] vertices = new VertexPositionTexture[36];
// Передняя грань
vertices[0] = new VertexPositionTexture(topRightFront, textureTopRight);
vertices[1] = new VertexPositionTexture(bottomLeftFront, textureBottomLeft);
vertices[2] = new VertexPositionTexture(topLeftFront, textureTopLeft);
vertices[3] = new VertexPositionTexture(topRightFront, textureTopRight);
vertices[4] = new VertexPositionTexture(bottomRightFront, textureBottomRight);
vertices[5] = new VertexPositionTexture(bottomLeftFront, textureBottomLeft);
// Задняя грань
vertices[6] = new VertexPositionTexture(bottomLeftBack, textureBottomRight);
vertices[7] = new VertexPositionTexture(topRightBack, textureTopLeft);
vertices[8] = new VertexPositionTexture(topLeftBack, textureTopRight);
vertices[9] = new VertexPositionTexture(bottomRightBack, textureBottomLeft);
vertices[10] = new VertexPositionTexture(topRightBack, textureTopLeft);
vertices[11] = new VertexPositionTexture(bottomLeftBack, textureBottomRight);
// Верхняя грань
vertices[12] = new VertexPositionTexture(topLeftBack, textureTopLeft);
vertices[13] = new VertexPositionTexture(topRightBack, textureTopRight);
vertices[14] = new VertexPositionTexture(topLeftFront, textureBottomLeft);
vertices[15] = new VertexPositionTexture(topRightBack, textureTopRight);
vertices[16] = new VertexPositionTexture(topRightFront, textureBottomRight);
vertices[17] = new VertexPositionTexture(topLeftFront, textureBottomLeft);
// Нижняя грань
vertices[18] = new VertexPositionTexture(bottomRightBack, textureTopLeft);
vertices[19] = new VertexPositionTexture(bottomLeftBack, textureTopRight);
vertices[20] = new VertexPositionTexture(bottomLeftFront, textureBottomRight);
vertices[21] = new VertexPositionTexture(bottomRightFront, textureBottomLeft);
vertices[22] = new VertexPositionTexture(bottomRightBack, textureTopLeft);
vertices[23] = new VertexPositionTexture(bottomLeftFront, textureBottomRight);
// Левая грань
vertices[24] = new VertexPositionTexture(bottomLeftFront, textureBottomRight);
vertices[25] = new VertexPositionTexture(bottomLeftBack, textureBottomLeft);
vertices[26] = new VertexPositionTexture(topLeftFront, textureTopRight);
vertices[27] = new VertexPositionTexture(topLeftFront, textureTopRight);
vertices[28] = new VertexPositionTexture(bottomLeftBack, textureBottomLeft);
vertices[29] = new VertexPositionTexture(topLeftBack, textureTopLeft);
// Правая грань
vertices[30] = new VertexPositionTexture(bottomRightBack, textureBottomRight);
vertices[31] = new VertexPositionTexture(bottomRightFront, textureBottomLeft);
vertices[32] = new VertexPositionTexture(topRightFront, textureTopLeft);
vertices[33] = new VertexPositionTexture(bottomRightBack, textureBottomRight);
vertices[34] = new VertexPositionTexture(topRightFront, textureTopLeft);
vertices[35] = new VertexPositionTexture(topRightBack, textureTopRight);
// Установка буфера вершин
vertexBuffer = new VertexBuffer(device, typeof(VertexPositionTexture), vertices.Length, BufferUsage.WriteOnly);
vertexBuffer.SetData(0, vertices, 0, vertices.Length, 0);
// Настройка камеры
Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspectRatio, 1, 100);
// Задание эффекта
effect = new BasicEffect(device);
effect.World = Matrix.Identity;
effect.View = view;
effect.Projection = view * projection;
// Загрузка текстуры из рисунка в объект BitmapImage
Stream s = Application.GetResourceStream(new Uri(textureUri, UriKind.Relative)).Stream;
BitmapImage bmp = new BitmapImage();
bmp.SetSource(s);
// Передача рисунка объекту Texture2D
Texture2D texture;
texture = new Texture2D(device, bmp.PixelWidth, bmp.PixelHeight);
bmp.CopyTo(texture);
// Установка текстуры
effect.TextureEnabled = true;
effect.Texture = texture;
}
Класс Cube3D содержит также метод Draw(), который может быть вызван приложением для отображения куба на поверхности рисования. При вызове метода Draw(), код передает ему соответствующее устройство GraphicsDevice:
public void Draw(GraphicsDevice device)
{
device.SetVertexBuffer(vertexBuffer);
device.SamplerStates[0] = SamplerState.LinearClamp;
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Apply();
device.DrawPrimitives(PrimitiveType.TriangleList, 0, vertexBuffer.VertexCount / 3);
}
}
Затратив некоторое время на перемещение кода в отдельный класс, вы будете вознаграждены за проделанную работу упрощением кода и готовой инфраструктурой обработки событий пользовательского элемента управления. В следующем примере демонстрируется создание трех экземпляров класса Cube3D для прорисовки трех кубов разных размеров и в разных позициях. Как видите, код чистый и не загроможден деталями:
public partial class MainPage : UserControl
{
private Cube3D cube;
private Cube3D cube2;
private Cube3D cube3;
private Cube3D cube4;
public MainPage()
{
InitializeComponent();
GraphicsDevice device = GraphicsDeviceManager.Current.GraphicsDevice;
string uri = "Silverlight 3D;component/super_man.png";
// Создание вида, общего для всех трех кубов
Matrix view = Matrix.CreateLookAt(new Vector3(1, 1, 3), Vector3.Zero,
Vector3.Up);
// Создание четырех кубов
cube = new Cube3D(device, new Vector3(-1, -1, -1), 2, uri, 1.33f, view);
cube2 = new Cube3D(device, new Vector3(-2, -2, 1), 1.5f, uri, 1.33f, view);
cube3 = new Cube3D(device, new Vector3(-5, -2, -2.5f), 2, uri, 1.33f, view);
cube4 = new Cube3D(device, new Vector3(-6, -3.2f, -0.5f), 2, uri, 1.33f, view);
}
private void DrawingSurface_Draw_1(object sender, DrawEventArgs e)
{
GraphicsDevice device = GraphicsDeviceManager.Current.GraphicsDevice;
device.Clear(new Color(0, 0, 0));
cube.Draw(device);
cube2.Draw(device);
cube3.Draw(device);
cube4.Draw(device);
}
}