D3DRM Tutorials Уроки по DirectX
Урок 2. D3DRM

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

 

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

 

Исходный код примера к этой статье можно найти по адресу: rm02.zip. Исходный код примера Вам будет необходим, так как полностью в статье он не приводится.

 

Непосредственно сам демонстрационный пример создается в функции CreateScene (файл scene.cpp):

 

// Создание сцены

BOOL CreateScene()

{

static int first=0;

 

  if ( first==0 )

  {

    AddPath();

    CreateLight();

    SetCamera();

    if ( CreateBoard()==FALSE ) return FALSE;

    first=1;

  }

  rm_info.rm->CreateFrame(rm_info.scene, &main_frame);

 

  switch ( n_scene )

  {

  case 0: if ( DemoCubes()==FALSE ) return FALSE; break;

  case 1: if ( DemoShapesRotate()==FALSE ) return FALSE; break;

  case 2: if ( DemoMaterial()==FALSE ) return FALSE; break;

  case 3: if ( DemoTexture()==FALSE ) return FALSE; break;

  }

 

  return TRUE;

}

 

Вначале добавляем пути для поиска файлов (AddPath), затем создаем свет на сцене (CreateLight), камеру (SetCamera) и пол похожий на шахматную доску (CreateBoard). Эти функции выполняются всего один раз при старте приложения. Затем создается фрейм main_frame. Это главный фрейм всех демонстрационных сцен примера, относительно него и будут добавляться объекты. Пример позволяет создавать несколько демонстрационных сцен и переключать их с помощью переменной n_scene. Смена номера сцены осуществляется клавишами ‘1’, ‘2’, ‘3’, ‘4’.

 

В зависимости от номера n_scene создается соответствующая сцена. В этой статье будет рассмотрена сцена под номером 0 (выбор клавишей ‘1’), в которой иллюстрировано создание простейших кубов (функция DemoCubes).

 

Для уничтожения сцены вызывается функция:

 

// Удаление сцены

void DestroyScene()

{

  rm_info.scene->DeleteVisual(main_frame);

  rm_info.scene->DeleteChild(main_frame);

  DD_RELEASE(main_frame);

}

 

Вначале удаляются все визуальные объекты связанные с main_frame, затем уничтожается main_frame, как дочерний фрейм главного фрейма rm_info.scene. Структура rm_info  содержит указатели на основные интерфейсы созданные при инициализации RM, а также часть вспомогательных переменных (полное описание структуры приведено в файле rm.h).

 

После удаления main_frame, со сцены пропадают все созданные объекты за исключением света, камеры и шахматной доски, так как они привязаны к фрейму rm_info.scene. Это в дальнейшем и позволяет менять сцену без повторного создания света, камеры и пола.

 

В первой демонстрационной сцене создаются три куба и одна плоская четырехгранная грань. При запуске примера камера (и зритель соответственно) сдвинута относительно центра координат на -50 единиц по оси Z. Вы можете передвигать камеру с помощью клавиш управления курсором и менять наклон камеры с помощью мыши, но везде в статье считается, что камера находится в первоначальном положении. Напомню, что в RM ось Z идет от зрителя, ось Y вверх, ось X вправо (положительные части осей).

 

 

Рис.1 Сцена с кубами.

 

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

 

Рис.2 Грани, ребра и вершины.

 

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

 

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

 

Довольно сложно вначале разобраться со всеми этими режимами и их комбинациями, по этому для простоты используется наиболее часто используемая комбинация всевозможных режимов.

 

Перейдем непосредственно к моделированию. Создадим простейший куб из 6 граней (сторон). Это будет красный куб в середине сцены. Все функции для моделирования находятся в файле cubes.cpp.

 

// Создание демонстрационных кубов

BOOL DemoCubes()

{

  LPDIRECT3DRMFRAME3       frame=0;

  LPDIRECT3DRMMESHBUILDER3 builder_faces=0;

  LPDIRECT3DRMMESHBUILDER3 builder_triangles=0;

  LPDIRECT3DRMMESHBUILDER3 builder_clone=0;

  LPDIRECT3DRMMESH mesh=0;

  HRESULT rc;

   

  // Создание куба из граней

  // Фрейм

  rc=rm_info.rm->CreateFrame( main_frame, &frame );

  DD_CHK(rc,_gError);

  // Позиция

  frame->SetPosition( main_frame, 0.0f, 0.0f, 0.0f );

  // Построитель сетки

  rc=rm_info.rm->CreateMeshBuilder(&builder_faces);

  DD_CHK(rc,_gError);

  // Создание

  build_cube_faces(builder_faces,DEGREES_0);

  // Красный цвет

  builder_faces->SetColor(RGBA_MAKE(255,0,0,255));

  // Масштабирование

  ScaleMesh(builder_faces,D3DVAL(10));

  // Выделим сетку

  builder_faces->CreateMesh(&mesh);

  // Сделаем ее видимой

  frame->AddVisual( (LPDIRECT3DRMVISUAL)mesh );

  DD_RELEASE( frame );

  DD_RELEASE( mesh );

 

Создадим фрейм для куба (CreateFrame), зададим его позицию (SetPosition), создадим интерфейс для Построителя Сетки (CreateMeshBuilder) и с помощью его создадим куб (build_cube_faces).  Закрасим весь куб, все 6 граней (SetColor), масштабируем (ScaleMesh). Для ускорения вывода на экран создадим интерфейс Сетки (CreateMesh) и сделаем его видимым на экране (AddVisual).

 

Т.е. для того чтобы любой объект был видим на экране в RM необходимо создать сам объект, закрасить его и придать ему объем (чтобы он занимал место на сцене). Кроме этого,  надо не забыть создать фрейм для объекта и сделать объект видимым в этом фрейме. Если что-то упустить объект не будет виден на сцене для зрителя.

 

Теперь посмотрим, как происходит моделирование простейшего куба из четырехугольных граней.

 

// Вершины куба

D3DVECTOR verts[] = {

   1.0f,  1.0f, -1.0f,  1.0f, -1.0f, -1.0f,

  -1.0f, -1.0f, -1.0f, -1.0f,  1.0f, -1.0f,

   1.0f,  1.0f,  1.0f,  1.0f, -1.0f,  1.0f,

  -1.0f, -1.0f,  1.0f, -1.0f,  1.0f,  1.0f };

 

// Грани куба

DWORD face_data[] = {

  4, 0, 1, 2, 3,

//  4, 3, 2, 1, 0,

  4, 7, 6, 5, 4,

  4, 4, 5, 1, 0,

  4, 3, 2, 6, 7,

  4, 4, 0, 3, 7,

  4, 1, 5, 6, 2,

  0};

 

 

// Построение куба

BOOL build_cube_faces( LPDIRECT3DRMMESHBUILDER3 mb, float angle )

{

 

  // Добавление граней

  mb->AddFaces( 16, verts, 0, 0, face_data, NULL );

  // Генерация нормалей

  mb->GenerateNormals(angle,

    D3DRMGENERATENORMALS_USECREASEANGLE );

 

  return TRUE;

 

}

 

Для того чтобы создать куб воспользуемся функцией AddFaces. Она имеет 6 параметров, из них используются три. Первый задает число вершин (их у куба 8), второй задает массив с вершинами, пятый параметр задает массив, в котором каждой грани сопоставлены свои вершины.

 

В массиве verts заданы значения вершин. Каждая вершина задается тремя числами – это координаты точки в пространстве (x, y, z). Здесь мы строим куб относительно центра координат, размером две единицы по любой из осей. Порядок расположения вершин в массиве verts показан на рисунке 3.

Рис.3 Расположение вершин в кубе

 

Но вершины сами по себе не задают грани. Какие грани мы создадим, определяет массив face_data. Первое значение массива задает, сколько точек в первой грани, затем идут индексы вершин в массиве verts, затем также задается вторая вершина и так далее. Последнее значение в этом массиве должно быть ноль. Номера вершин указаны в таком порядке, чтобы в каждой грани они добавлялись по часовой стрелке. Тем самым задается направление вектора нормали. У всех граней вектора нормалей направлены от куба, а не внутрь его. Поэтому они видны снаружи куба, а не изнутри. Если добавлять вершины в грани против часовой стрелки, то грани будут видны изнутри куба, а не снаружи. А если добавлять в беспорядке, то, скорее всего она будет отображена некорректно. Для того чтобы можно было посмотреть на неправильный способ создания граней, уберите комментарий в массиве verts и посмотри на результат.

 

Вершины куба заданы таким образом, чтобы куб был создан в начале координат. Грани куба создаются в таком порядке: передняя, задняя, правая, левая, верхняя и нижняя. Например, левая грань сначала включает ближние точки к зрителю, потом дальние. Нижняя грань включает правую ближнюю вершину и правую дальнюю вершину, а потом уже левые вершины. Нужно обязательно соблюдать порядок добавления граней. В частности, грань из вершин 0,1,6,7 будет разрезать куб по диагонали. Если Вы поместите камеру вглубь куба, то куб пропадет, Вы не увидите ни одной из его граней, так как RM не будет их отображать.

 

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

 

Мы можем попросить интерфейс Построителя Сетки сгенерировать нормали для вершин и граней автоматически, с помощью функции GenerateNormals. Так как направление нормали грани уже задано (порядком задания вершин в гранях). С помощью этих нормалей можно вычислить нормали в вершинах, усредняя нормали граней прилегающих к вершине. Новая нормаль к вершине создается, если угол между гранями, прилегающими к вершине больше угла, который мы указали (angle).  В данном случае 0 градусов, т.е. нормаль будет сгенерирована всегда. С помощью этой функции можно также объединить все одинаковые вершины, а потом уже вычислить нормали, это делается с помощью указания флага D3DRMGENERATENORMALS_PRECOMPACT.

 

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

 

Рис. 4 Грани

 

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

 

  // Создание куба из треугольников

  // Фрейм

  rc=rm_info.rm->CreateFrame( main_frame, &frame );

  DD_CHK(rc,_gError);

  // Позиция

  frame->SetPosition( main_frame, 15.0f, 0.0f, 0.0f );

  // Построитель сетки

  rc=rm_info.rm->CreateMeshBuilder(&builder_triangles);

  DD_CHK(rc,_gError);

  // Создание

  build_cube_triangles(builder_triangles,DEGREES_0);

  // закраска

  builder_triangles->SetColor(RGBA_MAKE(0,255,0,128));

  // Масштабирование

  ScaleMesh(builder_triangles,D3DVAL(10));

  // Выделим сетку

  builder_triangles->CreateMesh(&mesh);

  // Сделаем ее видимой

  frame->AddVisual( (LPDIRECT3DRMVISUAL)mesh );

  DD_RELEASE( mesh );

  DD_RELEASE( frame );

  DD_RELEASE( builder_triangles );

 

Создание фрейма куба и его визуализация выполнена также как для предыдущего куба. Построение куба из треугольников выполнено в функции build_cube_triangles.

 

#define MAX_TRI 6*2*3

VertexTriangle_t v_tri[MAX_TRI];

 

// Грани куба из треугольников

DWORD tri_data[MAX_TRI] = {

  0,1,2, 2,3,0,

  7,6,5, 5,4,7,

  4,5,1, 1,0,4,

  3,2,6, 6,7,3,

  4,0,3, 3,7,4,

  1,5,6, 6,2,1,

}

 

// Построение куба из треугольников

BOOL build_cube_triangles( LPDIRECT3DRMMESHBUILDER3 mb, float angle )

{

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

  {

    v_tri[i].type=D3DRMVERTEX_LIST;

    memcpy(&v_tri[i].pos,&verts[tri_data[i]],sizeof(D3DVECTOR));

  }

  // Добавление граней

  mb->AddTriangles( 0, D3DRMFVF_TYPE | D3DRMFVF_NORMAL, MAX_TRI,

                   (LPVOID) &v_tri );

  // Генерация нормалей

  mb->GenerateNormals(angle,

    D3DRMGENERATENORMALS_USECREASEANGLE );

  return TRUE;

 

}

 

Структура VertexTriangle определена:

 

// Вершины для AddTriangles

typedef struct

{

  DWORD type;

  D3DVECTOR pos;

  D3DVECTOR normal;

} VertexTriangle_t;

 

С помощью нее задаются вершины треугольников. Переменная type указывает тип вершины, пока воспользуемся типом D3DRMVERTEX_LIST, pos – позиция вершины, normal – вектор нормали вершины. Нормали по-прежнему мы сгенерируем функцией GenerateNormals. Тем самым надо задать только позиции вершин. Позиции, как и в предыдущей функции, берутся из массива verts, при этом номера вершин из массива tri_data.

 

Тип D3DRMVERTEX_LIST позволяет задавать треугольники из 3 вершин, т.е. на передней грани куба будет 2 треугольника из 3 вершин – всего 6 вершин. Тем самым всего на 6 гранях куба будет 36 вершин (#define MAX_TRI 6*2*3). Порядок добавления вершин опять же по часовой стрелке и порядок создания сторон тот же, что и для предыдущего примера.

 

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

 

В отличие от предыдущего, здесь не надо задавать грани, треугольник это и есть грань. Вы задаете три точки и получаете грань.

 

Добавление треугольников делается функцией AddTriangles. У нее 4 параметра. Первый всегда 0, второй задает тип структуры для хранения вершин треугольника, третий количество вершин, а последний указатель на массив структур, где хранятся вершины.

 

Структура для хранения вершин обязательно должна содержать поле позиции вершины. Наличие или отсутствие других полей задается флагами. Помимо типа вершины и нормали, можно указать цвет (D3DRMFVF_COLOR) и координаты текстуры (D3DRMFVF_TEXTURECOORDS) для вершины.

 

Если Вы заметили, то это куб полупрозрачный. Прозрачность задается четвертым параметром в макросе RGBA_MAKE:

 

  // закраска

  builder_triangles->SetColor(RGBA_MAKE(0,255,0,128));

 

Иногда бывает необходимо повторить (клонировать) объект, давайте посмотрим, как это делается. Кроме этого, мы рассмотрим, как окрасить грани в разные цвета. В результате получится разноцветный куб слева на демонстрационной сцене.

 

  // Закраска куба с клонированием

  // Фрейм

  rc=rm_info.rm->CreateFrame( main_frame, &frame );

  DD_CHK(rc,_gError);

  // Позиция

  frame->SetPosition( main_frame, -15.0f, 0.0f, 0.0f );

  // Создание клона

  rc=builder_faces->Clone(0, IID_IDirect3DRMVisual,

                          (LPVOID *)&builder_clone);

  DD_CHK(rc,_gError);

  // Закраска граней

  color_cube_faces(builder_clone);

  // Выделим сетку

  builder_clone->CreateMesh(&mesh);

  // Визуализация сетки

  frame->AddVisual( (LPDIRECT3DRMVISUAL)mesh );

  DD_RELEASE( frame );

  DD_RELEASE( mesh );

  DD_RELEASE( builder_clone );

  DD_RELEASE( builder_faces );

 

Возьмем первый куб, который мы создали из 6 четырехугольных граней. Клонирование его производится функцией Clone. Фактически, это создание и заполнение нового интерфейса Построителя Сетки. Далее надо его раскрасить в функции color_cube_faces.

 

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

 

// закраска граней куба

void color_cube_faces( LPDIRECT3DRMMESHBUILDER3 mb )

{

  D3DCOLOR colors[6] ={

    RGBA_MAKE(255,0,0,128),RGBA_MAKE(0,255,0,128),RGBA_MAKE(0,0,255,128),

    RGBA_MAKE(255,255,0,128),RGBA_MAKE(255,0,255,128),RGBA_MAKE(0,255,255,128),

  };

  int i;

  LPDIRECT3DRMFACE2 face;

 

  for ( i=0; i<mb->GetFaceCount(); i++ )

  {

    if ( GetFace(mb,i,&face)==FALSE ) break;

    face->SetColor(colors[i%6]);

    DD_RELEASE( face );

  }

}

 

В этой функции вначале задается массив цветов с помощью макроса RGBA_MAKE, причем у всех граней цвет разный и все грани полупрозрачные. Далее, мы получаем число граней (GetFaceCount), получаем очередную грань (GetFace) по номеру, и назначаем ей цвет.

 

Функция GetFace несколько сложнее.

 

// Получить грань

BOOL GetFace(

  LPDIRECT3DRMMESHBUILDER3 mb,

  int n, LPDIRECT3DRMFACE2* face)

{

  if (n>= mb->GetFaceCount()) return FALSE;

  // Список граней

  IDirect3DRMFaceArray* arr_faces = NULL;

  mb->GetFaces(&arr_faces);

  // Нужная грань

  IDirect3DRMFace* tmp_face = NULL;

  arr_faces->GetElement(n, &tmp_face);

  tmp_face->QueryInterface(IID_IDirect3DRMFace2,

    (LPVOID *)face);

  DD_RELEASE( tmp_face );

  DD_RELEASE( arr_faces );

  return TRUE;

}

 

Для того, что получить нужную грань, надо вначале создать интерфейс Массива Граней (IDirect3DRMFaceArray), затем по номеру запросить нужную грань (GetElement). Но  интерфейс Массива Граней не поддерживает версию 2 интерфейса Граней, поэтому надо запросить у старого интерфейса IDirect3DRMFace новый интерфейс IDirect3DRMFace2 (QueryInterface), после этого мы получаем указатель на нужную грань.

 

Визуализация клонированного и раскрашенного куба производиться как ранее.

 

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

 

  // Создание плоской грани

  // Фрейм

  rc=rm_info.rm->CreateFrame( main_frame, &frame );

  DD_CHK(rc,_gError);

  // Позиция

  frame->SetPosition( main_frame, 0.0f, 10.0f, 55.0f );

  // Построитель сетки

  rc=rm_info.rm->CreateMeshBuilder(&builder_faces);

  DD_CHK(rc,_gError);

  // Создание

  build_face(builder_faces);

  // Масштабирование

  builder_faces->Scale(D3DVAL(55),D3DVAL(20),D3DVAL(0));

  // Выделим сетку

  builder_faces->CreateMesh(&mesh);

  // Сделаем ее видимой

  frame->AddVisual( (LPDIRECT3DRMVISUAL)mesh );

  DD_RELEASE( frame );

  DD_RELEASE( mesh );

  DD_RELEASE( builder_faces );

 

Для создания грани потребуются четыре массива. Первый массив вершин позиций граней, затем массив нормалей (все нормали направлены на зрителя), затем массив, который задает порядок вершин и нормалей в грани. В отличие от первого примера, мы зададим нормали непосредственно. Массив colors_face задает цвета вершин грани. Значения получены с помощью Adobe PhotoShop (взяты крайние точки с одного из срезов цветового куба).

 

// Данные для создания плоской грани

D3DVECTOR verts_face[4] = {  1.0f,  1.0f, 0.0f,  1.0f, -1.0f, 0.0f,

                        -1.0f, -1.0f, 0.0f, -1.0f,  1.0f, 0.0f };

D3DVECTOR normals_face[4] =  { 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f,

                       0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f };

DWORD face_normal_data[] = { 4, 0, 0, 1, 1, 2, 2, 3, 3, 0 };

D3DCOLOR colors_face[4]={

  0xFF7AFC,0x017AFC,0x057A03,0xFD7A02,

};

 

Построение грани делается в функции build_face.

 

// Построение плоской грани

BOOL build_face( LPDIRECT3DRMMESHBUILDER3 mb )

{

  int i;

  // Создание вершин

  for ( i=0; i<4; i++ )

  {

    mb->AddVertex(verts_face[i].x,verts_face[i].y,verts_face[i].z);

    mb->AddNormal(

        normals_face[i].x,normals_face[i].y,normals_face[i].z);

    mb->SetVertexColor(i,colors_face[i]);

  }

  // Создание граней

  mb->AddFacesIndexed(0,face_normal_data,0,0);

  // Цвет из вершин

  mb->SetColorSource(D3DRMCOLOR_FROMVERTEX);

  // Убрать освещение

  mb->SetQuality(D3DRMRENDER_GOURAUD&(~D3DRMLIGHT_ON)|(D3DRMLIGHT_OFF));

     return TRUE;

}

 

В цикле создаются четыре вершины. Происходит добавление позиции вершины (AddVertex), нормали (AddNormal), установка цвета вершины по номеру (SetVertexColor). Непосредственно грань создается функцией AddFacesIndexed. Первый параметр (флаги настройки) должен быть в нашем случае 0, т.к. мы задаем и нормали. Если нормали не нужны, то его значение - D3DRMADDFACES_VERTICESONLY. Второй параметр задает массив данных, которые задают номера вершин и нормалей, причем последние уже должны быть добавлены в интерфейс Построителя Сетки. Первое значение массива номеров задает число вершин в грани, далее идут пары значений, которые задают номер вершины и номер нормали. Нуль в конце задавать не надо. Последние два параметра возвращают номер первой созданной грани и число добавленных граней.

 

Функцией SetColorSource устанавливается тип цветового источника: из грани - D3DRMCOLOR_FROMFACE, или из вершины – D3DRMCOLOR_FROMVERTEX. Так как надо получить цветовой градиент, то устанавливается тип цветового источника из вершины.

 

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

 

  // Убрать освещение

  mb->SetQuality(D3DRMRENDER_GOURAUD&(~D3DRMLIGHT_ON)|(D3DRMLIGHT_OFF));

 

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

 

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

PMG  11 февраля 2003 (c)  Сергей Анисимов