netlib.narod.ru | < Назад | Оглавление | Далее > |
Тела вращения я впервые увидел в раннем детстве. Мой отец работал токарем, стоял целый день у токарного станка и точил фланцы, стержни, винты с нарезкой, трубы и т.д. (рис. 4.19). Токарный станок вращает металлическую заготовку, зажатую в патроне. К заготовке подносится резец, который срезает ненужный материал при вращении заготовки; положение резца определяет радиус детали. Продольное перемещение резца с одновременным вращением заготовки позволяет выточить стержень (гладкий или нарезной). Основная идея моего рассказа заключается в том, что твердый объект можно описать с помощью простой функции, определяющей радиус объекта в любой точке его длины.
Рис. 4.19. Токарный станок
Процесс построения тел вращения мало чем отличается от создания других трехмерных объектов. Необходимо задать набор вершин, определить, к каким граням они относятся, и затем при желании указать нормали. Поскольку тело вращения можно описать функцией зависимости радиуса от продольной координаты, нетрудно создать фрагмент программы, в котором такая функция используется для генерации вершин и данных граней. На рис. 4.20 изображено тело вращения, построенное именно этим способом. Чтобы увидеть тело вращения на экране, выполните команду Edit | Solid of Revolution.
Рис. 4.20. Тело вращения
Ниже приводится фрагмент приложения Shapes, в котором создается изображенный на рисунке объект:
double RevolveFn(double z, void* pArg) { if (z < -1.1) { return sqrt(1 — (z + 2)*(z + 2)); } else if (z > 0.8) { return sqrt(2 — (z — 2)*(z — 2)); } else { return 0.5; } } // Создать тело вращения void CMainFrame::OnEditSolidr() { // Создать объект по заданной функции NewScene(); m_pShape = new C3dShape; m_pShape->CreateRSolid(-3.0, 2.2, 0.2, TRUE, TRUE, RevolveFn, NULL, 16); m_pScene->AddChild(m_pShape); // Развернуть объект, чтобы показать его со стороны m_pShape->SetDirection(0, -1, 0); }
Как видите, в данном случае мы имеем дело с двумя функциями. RevolveFn возвращает значения радиуса для заданной продольной координаты, а функция OnEditSolidr вызывается при выполнении команды меню Edit | Solid of Revolution. Для построения объекта внутри функции OnEditSolidr вызываются функции RevolveFn и C3dShape::CreateRSolid. Аргументы CreateRSolid выглядят несколько необычно, поэтому позвольте мне объяснить их назначение.
Создавая функцию CreateRSolid, я не собирался делать ее универсальной. Вместо того чтобы обрабатывать координаты концов объекта, я решил всегда строить фигуру вдоль оси Z. Таким образом, написанная вами функция (в данном случае RevolveFn) возвращает радиус объекта как функцию координаты по оси Z. Аргументами CreateRSolid являются максимальное и минимальное значения координат по оси Z, приращение по оси Z, две логические величины, определяющие необходимость замыкания концов фигуры, указатель на функцию радиуса, необязательный аргумент, передаваемый в функцию радиуса, и наконец количество граней в круговой поверхности.
В нашем случае необязательный аргумент функции радиуса не используется, однако вы, например, можете передать в нем указатель на какую-нибудь таблицу данных. При таком подходе можно написать обобщенную функцию, которая строила бы более сложные тела вращения с использованием данных таблицы.
После того как объект будет создан, вам наверняка захочется развернуть его и переместить в итоговое положение. Как видно на примере функции OnEditSolidr в приведенном выше фрагменте, я развернул объект так, чтобы он был обращен вдоль оси Y, а не вдоль своей первоначальной оси Z.
Давайте посмотрим, как работает функция C3dShape::CreateRSolid. Приведенный ниже фрагмент взят из библиотеки 3dPlus:
// Создание тел вращения BOOL C3dShape::CreateRSolid(double z1, double z2, double dz, BOOL bClosed1, BOOL bClosed2, SOLIDRFN pfnRad, void* pArg, int nFacets) { New(); ASSERT(pfnRad); ASSERT(dz != 0); int iZSteps = (int)((z2 — z1) / dz); if (iZSteps < 1) return FALSE; int iRSteps = nFacets; if (iRSteps < 8) iRSteps = 8; double da = _twopi / iRSteps; // Создать массив для вершин int iVertices = (iZSteps + 1) * iRSteps; D3DVECTOR* Vertices = new D3DVECTOR [iVertices]; D3DVECTOR* pv = Vertices; // Создать массив для данных граней. // Каждая грань имеет 4 вершины, за исключением торцов. int iFaces = iZSteps * iRSteps; int iFaceEntries = iFaces * 5 + 1; if (bClosed1) iFaceEntries += iRSteps + 1; if (bClosed2) iFaceEntries += iRSteps + 1; int* FaceData = new int [iFaceEntries]; int* pfd = FaceData; // Заполнить координаты вершин double z = z1; double r, a; for (int iZ = 0; iZ <= iZSteps; iZ++) { r = pfnRad(z, pArg); a = 0; for (int iR = 0; iR < iRSteps; iR++) { pv->x = D3DVAL(r * sin(a)); pv->y = D3DVAL(r * cos(a)); pv->z = D3DVAL(z); pv++; a += da; } z += dz; } // Заполнить список граней int iFirst = iRSteps; for (iZ = 0; iZ < iZSteps; iZ++) { for (int iR = 0; iR < iRSteps; iR++) { *pfd++ = 4; // Количество вершин в грани *pfd++ = iFirst + iR; *pfd++ = iFirst + ((iR + 1) % iRSteps); *pfd++ = iFirst — iRSteps + ((iR + 1) % iRSteps); *pfd++ = iFirst — iRSteps + iR; } iFirst += iRSteps; } *pfd = 0; // Завершить список // Создать круговую поверхность с автоматической // генерацией нормалей BOOL b = Create(Vertices, iVertices, NULL, 0, FaceData, TRUE); delete [] FaceData; FaceData = new int [iRSteps * 2 + 2]; D3DVECTOR nvect [] = { {0, 0, 1}, {0, 0, -1} }; if (bClosed1) { pfd = FaceData; *pfd++ = iRSteps; for (int iR = 0; iR < iRSteps; iR++) { *pfd++ = iR; *pfd++ = 1; } *pfd = 0; m_hr = m_pIMeshBld->AddFaces(iVertices, Vertices, 2, nvect, (ULONG*)FaceData, NULL); ASSERT(SUCCEEDED(m_hr)); } if (bClosed2) { pfd = FaceData; *pfd++ = iRSteps; iFirst = iRSteps * iZSteps; for (int iR = 0; iR < iRSteps; iR++) { *pfd++ = iRSteps — 1 — iR + iFirst; *pfd++ = 0; } *pfd = 0; m_hr = m_pIMeshBld->AddFaces(iVertices, Vertices, 2, nvect, (ULONG*)FaceData, NULL); ASSERT(SUCCEEDED(m_hr)); } delete [] Vertices; delete [] FaceData; m_strName = "Solid of revolution"; return b; }
На первом этапе, после выполнения инициализации, по функции радиуса заполняется список вершин. Затем генерируются данные боковых граней и создается исходная форма. Если при вызове функции были заданы замкнутые концы фигуры, создается новый набор данных для граней-торцов, однако на этот раз с нормалями, управляющими процессом закраски, чтобы торцы фигуры казались плоскими. Новые грани добавляются к существующей фигуре.
Возможно, вы обратили внимание на то, что фигуре присваивается имя (в данном случае — Solid of Revolution, хранящееся в переменной m_strName). Когда мы будем заниматься выбором объектов в макете, имя сообщит пользователю, какая фигура выбрана им в настоящий момент.
Мы очень кратко пробежались по большому фрагменту программы, и у вас наверняка осталось много вопросов по поводу его работы и назначению отдельных функций. Если вы хотите понять, как устроена функция CreateRSolid, возьмите лист бумаги в клетку, сверните его в трубку и затем представьте себе, что вам потребовалось описать каждое продольное ребро и каждую грань этой решетки. Именно это и происходит в приведенном выше фрагменте, а эксперимент с трубкой описывает мой подход к его написанию. Во фрагменте присутствуют несколько вызовов функций интерфейсов Direct3D, назначение которых можно узнать в документации по DirectX 2 SDK. Заодно найдите в SDK макрос D3DVAL и посмотрите, что он делает.
В главе 5 мы научимся преобразовывать фигуры, применяя к ним операции вращения, переноса и масштабирования. После прочтения этой главы можете модифицировать функцию CreateRSolid так, чтобы при ее вызове можно было бы задать координаты конечных точек фигуры — функция станет более полезной.
netlib.narod.ru | < Назад | Оглавление | Далее > |