netlib.narod.ru< Назад | Оглавление | Далее >

Тела вращения

Тела вращения я впервые увидел в раннем детстве. Мой отец работал токарем, стоял целый день у токарного станка и точил фланцы, стержни, винты с нарезкой, трубы и т.д. (рис. 4.19). Токарный станок вращает металлическую заготовку, зажатую в патроне. К заготовке подносится резец, который срезает ненужный материал при вращении заготовки; положение резца определяет радиус детали. Продольное перемещение резца с одновременным вращением заготовки позволяет выточить стержень (гладкий или нарезной). Основная идея моего рассказа заключается в том, что твердый объект можно описать с помощью простой функции, определяющей радиус объекта в любой точке его длины.


Рис. 4.19. Токарный станок

Рис. 4.19. Токарный станок


Процесс построения тел вращения мало чем отличается от создания других трехмерных объектов. Необходимо задать набор вершин, определить, к каким граням они относятся, и затем при желании указать нормали. Поскольку тело вращения можно описать функцией зависимости радиуса от продольной координаты, нетрудно создать фрагмент программы, в котором такая функция используется для генерации вершин и данных граней. На рис. 4.20 изображено тело вращения, построенное именно этим способом. Чтобы увидеть тело вращения на экране, выполните команду Edit | Solid of Revolution.


Рис. 4.20. Тело вращения

Рис. 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< Назад | Оглавление | Далее >

Сайт управляется системой uCoz