netlib.narod.ru | < Назад | Оглавление | Далее > |
Наше следующее приложение имитирует часовой механизм, состоящий из нескольких частей, которые находятся в непрерывном движении по отношению друг к другу. На рис. 6.9 изображен внешний вид окна приложения, находящегося в каталоге Clock.
Рис. 6.9. Часовой механизм
Механизм состоит из трех вращающихся стержней. К центральному стержню прикреплена минутная стрелка и небольшая шестеренка. Ко второму стержню, охватывающему часть центрального, прикреплена часовая стрелка и большая шестеренка. На третьем стержне имеются две шестеренки, большая и маленькая, которые сцеплены с шестеренками для двух стрелок — минутной (центральный стержень) и часовой (внешний стержень).
Когда я впервые попытался создать это приложение, то изобразил шестеренки в виде дисков, и «на все про все» у меня ушло около часа. Отладка работы шестеренок потребовала уже целых трех часов! Фрагмент кода, в котором конструируется данный механизм, выглядит довольно просто. Сначала мы создаем стержни и присоединяем к ним шестеренки и стрелки в качестве фреймов-потомков. Затем начинаем вращать стержни. Я подобрал частоту вращения таким образом, чтобы создать впечатление, будто механизм действительно приводится в действие шестеренками. Если внимательно присмотреться, можно заметить, что иллюзия получилась не полной.
Чтобы весь часовой механизм вращался в окне, я создал фрейм, являющийся родительским по отношению ко всем трем стержням. Когда этот фрейм поворачивается вокруг оси Y, изображение работающего механизма также начинает вращаться (наблюдательные читатели могли заметить, что передаточный коэффициент шестеренок составляет 4:1 вместо более привычного 12:1, как в большинстве часов). Давайте рассмотрим фрагмент файла MainFrm.cpp, в котором создается стержень с минутной стрелкой. Два других стержня определяются аналогичным образом:
BOOL CMainFrame::SetScene() { . . . // Создать часовой механизм C3dFrame clock; clock.Create(m_pScene); double dSpin = -0.1; // Создать стержень с минутной стрелкой C3dFrame s1; s1.Create(&clock); C3dShape r1; r1.CreateRod(0, 0, -0.5, 0, 0, 10, 0.4, 16); r1.SetColor(0, 0, 1); s1.AddChild(&r1); // Присоединить минутную стрелку CHand bighand(10); s1.AddChild(&bighand); bighand.SetPosition(0, 0, 0); // Присоединить шестеренку CGear g1(1.5, 1.5, 8); s1.AddChild(&g1); g1.SetPosition(0, 0, 5.5); // Привести стержень во вращение s1.SetRotation(0, 0, 1, dSpin); . . . }
Фрейм стержня создается как потомок по отношению к фрейму всего механизма. Затем к фрейму стержня присоединяется цилиндрический объект, который является его визуальным представлением. Минутная стрелка создается как объект класса CHand, производного от C3dShape, который мы рассмотрим чуть позже. Шестеренка тоже является объектом отдельного класса CGear, производного от C3dShape, и точно так же присоединяется к фрейму стержня. Последнее, что осталось сделать, — привести фрейм во вращение функцией C3dFrame::SetRotation.
Стрелки создаются из двух цилиндров и конуса:
CHand::CHand(double l) { CreateRod(0, 0, 0, 0, 0, 0.5, 1, 16); SetColor(1, 1, 0); C3dShape r; r.CreateRod(0, 0, 0.25, 0, l-3, 0.25, 0.20, 16); r.SetColor(0, 0, 1); AddChild(&r); C3dShape c; c.CreateCone(0, l-3, 0.25, 0.75, TRUE, 0, l, 0.25, 0, FALSE, 16); c.SetColor(1, 1, 0); AddChild(&c); }
С шестеренками дело обстоит несколько сложнее. Внешний и внутренний радиус зубцов определяется двумя окружностями. Затем окружности разбиваются на части по числу зубцов, что и дает нам положения вершин (рис. 6.10). Генерация списка данных для внешних граней зубцов завершает первую стадию создания фигуры.
Рис. 6.10. Конструирование зубцов шестеренки
При создании боковых граней шестеренки используются нормали, чтобы грани воспроизводились в виде плоских поверхностей. Если не задавать нормали, механизм визуализации скругляет стороны шестеренок, и на их гранях появляются какие-то странные треугольные ячейки. Разумеется, за пять минут работы с Autodesk 3D Studio можно было бы создать идеальные шестеренки и без этого кода:
CGear::CGear(double r, double t, int teeth) { double twopi = 6.28318530718; double r1 = r — 0.3; double r2 = r + 0.3; int nFaceVert = teeth * 4; int nVert = nFaceVert * 2; D3DVECTOR* Vertices = new D3DVECTOR[nVert]; D3DVECTOR* pv = Vertices; double da = twopi / (teeth * 4); double a = 0; for (int i = 0; i < teeth; i++) { pv->x = r1 * cos(a); pv->y = r1 * sin(a); pv->z = 0; pv++; a += da; pv->x = r2 * cos(a); pv->y = r2 * sin(a); pv->z = 0; pv++; a += da; pv->x = r2 * cos(a); pv->y = r2 * sin(a); pv->z = 0; pv++; a += da; pv->x = r1 * cos(a); pv->y = r1 * sin(a); pv->z = 0; pv++; a += da; } pv = Vertices; D3DVECTOR* pv2 = &Vertices[nFaceVert]; for (i = 0; i < nFaceVert; i++) { *pv2 = *pv; pv2->z = t; pv++; pv2++; } // Сгенерировать данные граней для зубцов. // Нервных просят не смотреть! int nf = (teeth * 5 * 4) + (teeth * 26) + 10; int* FaceData = new int[nf]; int* pfd = FaceData; for (i = 0; i < teeth*4; i++) { *pfd++ = 4; *pfd++ = i; *pfd++ = (i + 1) % (teeth*4); *pfd++ = nFaceVert + ((i + 1) % (teeth*4)); *pfd++ = nFaceVert + (i % (teeth*4)); } // Завершить список *pfd++ = 0; Create(Vertices, nVert, NULL, 0, FaceData, TRUE); // Добавить торцевые грани с заданием нормалей D3DVECTOR nvect [] = { {0, 0, 1}, {0, 0, -1} }; delete [] FaceData; FaceData = new int [teeth * 9 + teeth * 4 + 10]; pfd = FaceData; for (i = 0; i < teeth; i++) { *pfd++ = 4; *pfd++ = i*4; *pfd++ = 1; *pfd++ = i*4+3; *pfd++ = 1; *pfd++ = i*4+2; *pfd++ = 1; *pfd++ = i*4+1; *pfd++ = 1; } *pfd++ = teeth*2; for (i = teeth-1; i >= 0; i--) { *pfd++ = i*4+3; *pfd++ = 1; *pfd++ = i*4; *pfd++ = 1; } *pfd++ = 0; AddFaces(Vertices, nVert, nvect, 2, FaceData); pfd = FaceData; for (i = 0; i < teeth; i++) { *pfd++ = 4; *pfd++ = nFaceVert + i*4; *pfd++ = 0; *pfd++ = nFaceVert + i*4+1; *pfd++ = 0; *pfd++ = nFaceVert + i*4+2; *pfd++ = 0; *pfd++ = nFaceVert + i*4+3; *pfd++ = 0; } *pfd++ = teeth*2; for (i = 0; i < teeth; i++) { *pfd++ = nFaceVert + i*4; *pfd++ = 0; *pfd++ = nFaceVert + i*4+3; *pfd++ = 0; } *pfd = 0; AddFaces(Vertices, nVert, nvect, 2, FaceData); delete [] Vertices; delete [] FaceData; SetColor(1, 1, 0); }
К настоящему моменту весь этот фрагмент должен казаться вам вполне понятным. Когда у меня будет немного свободного времени, я непременно сделаю на основе данного приложения настоящие часы с секундной, минутной и часовой стрелками, с циферблатом и маятником.
Конечно, объект можно перемещать по любой траектории. Для этого необходимо задать либо набор координат в пространстве, либо функцию (например, генератор сплайнов), которая строит плавную кривую для траектории, описанной несколькими точками. При каждой новой итерации вы определяете новое положение объекта и перемещаете его туда. Не забывайте, что вам также придется вычислять вектор направления объекта и, возможно, его верхний вектор (если только перемещаемый объект не является сферой).
netlib.narod.ru | < Назад | Оглавление | Далее > |