Поведение дочерних окон в этой программе необходимо рассмотреть особо тщательно. Заметьте, что при потере активности дочернего окна оно закрывается, чтобы не загромождать экран. Также для каждого дочернего окна предусмотрены перехватчики сообщений, связанных с перемещением окна. При перемещении и изменении размера каждого дочернего окна содержимое главного окна перерисовывается. Конечно, это приводит к потере скорости обработки сообщения, зато главное окно не засоряется по ходу работы, покрываясь серыми дырами. Подобными мелочами не стоит пренебрегать, если мы хотим придать нашему приложению черты профессиональной работы.
Точку зрения наблюдателя можно переместить еще одним способом: просто щелкнуть на букве, именующей ось. В этом случае точка зрения займет такое положение, что выбранная буква повернется лицом к наблюдателю. Для выбора элементов в этом проекте я использовал механизм, основанный на применении буфера выбора. Если смотреть на площадку сверху, то при прохождении курсора над границей площадки он принимает вид двунаправленных стрелок, подсказывающих пользователю, что размеры площадки в этих направлениях можно менять. Если после этого удерживать кнопку мыши и перемещать курсор, площадка изменяется в размерах вслед за ним.
Разберем принципы трансформаций площадки, они лежат также в основе перемещений и изменения размеров объектов модели.
При воспроизведении площадки я пользуюсь небольшой уловкой, чтобы выделить ее границы. По границе площадки рисуются отдельные линии, каждая из которых при выборе именуется уникально, что дает возможность определить, над какой стороной площадки производятся манипуляции.
Главное, на что надо обратить особое внимание - это процедура, переводящая экранные координаты в мировые:
procedure TfrmMain.ScreenToSpace (mouseX, mouseY : GLInt; var X, Y :
GLFloat);
var
xO, xW, yO, yH : GLFloat;
begin
xO := 4 * zFar * vLeft / (zFar + zNear); // 0
xW := 4 * zFar * vRight / (zFar + zNear); // Width
yO := 4 * zFar * vTop / (zFar + zNear); // 0
yH := 4 * zFar * vBottom / (zFar + zNear); // Heigth
X := xO + mouseX * (xW - xO) / (ClientWidth - Panell.Width);
Y := yO + mouseY * (yH - yO) / ClientHeight;
end;
Перевод здесь условный, в результате него получаются только две пространственные координаты из трех. Первые два аргумента процедуры - экранные координаты. Для перевода в пространственные координаты используется то,
что перспектива задается с помощью команды glFrustum. Координаты крайних точек окна выражаются через координаты плоскостей отсечения, передаваемые экранные координаты приводятся к этому интервалу. При изменении размеров площадки с помощью этой процедуры получаются условные пространственные координаты для предыдущего и текущего положений курсора. Приращение этих координат дает величину, на которую пользователь сместился в пространстве. Но это условная величина, и при переводе в реальное пространство требуется ее корректировка (в рассматриваемой программе при переводе она умножается на 5). В результате не получается, конечно, совершенно точного соответствия, но погрешность здесь вполне терпимая, и пользователь, в принципе, не должен испытывать сильных неудобств. Что касается изменения размеров площадки, то ее длина умножается на 10, т. е. приращение удваивается, чтобы собственно координаты вершин смещались с множителем 5. При изменении размеров и положения объектов модели два этих множителя комбинируются, чтобы получить удовлетворительную чувствительность.
Если же требуется точное соответствие с позицией курсора, можно опираться на значение вспомогательной переменной Perspective, косвенно связанной с текущими видовыми параметрами.
При трансформациях объектов в изометрической проекции для устранения неопределенности с положением точки в пространстве приращение по каждой экранной оси по отдельности переводится в пространственные координаты и трактуется в контексте объемных преобразований. То есть, если пользователь передвигает курсор вверх, уменьшается координата Y объекта, если курсор идет вправо, увеличивается координату X . Объект "уплывает" из-под курсора, однако у наблюдателя не возникает вопросов по поводу третьей координаты. Если же удерживается клавиша <Ctrl>, то изменение экранной координаты Y указателя берется для трансформации по оси Z мировой системы координат. Конечно, это не идеальный интерфейс, но я нахожу его вполне удовлетворительным. Иначе придется заставлять пользователя работать только в плоскостных проекциях, наблюдая постоянно модель со стороны какой-либо из осей. Для хранения данных об объекте модели введен тип:
TGLObject = record
Kind : (Cube, Sphere, Cylinder); // тип объекта
X, Y, Z, // координаты в пространстве
L, W, H : GLDouble; // длина, ширина, высота
RotX, RotY, RotZ : GLDouble; // углы поворота по осям
Color : Array [0..2] of GLFloat; // цвет
end;
В качестве примера взяты три базовые фигуры: параллелепипед, сфера и цилиндр. Этот список базовых фигур можно произвольно дополнять, единственное, что необходимо при этом сделать - подготовить дисплейный список для нового объекта.
Система, связанная с проектируемой моделью, хранится в массиве objects При воспроизведении системы трансформируем систему координат и вызываем соответствующий дисплейный список:
For i := 1 to objectcount do begin
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, @objects[i] .color) ;:
glPushMatrix;
glTranslatef (objects[i].X, objects[i].Y, objects[i].Z);
glScalef (objects[i].L, objects[i].W, objects[i].H);
glRotatef (objects[i].RotX, 1.0, 0.0, 0.0);
glRotatef (objects[i].RotY, 0.0, 1.0, 0.0);
glRotatef (objects[i].RotZ, 0.0, 0.0, 1.0);
If mode = GL_SELECT then glLoadName (i + startObjects);
case objects [i].Kind of
Cube : glCallList (DrawCube);
Sphere : glCallList (DrawSphere);
Cylinder : glCallList (DrawCylinder);
end;
{case}
If i = MarkerObject then MarkerCube (i, mode);
glPopMatrix;
end;
Один из объектов может быть помеченным, маркированным. Такой объект выделяется среди прочих тем, что объем, занимаемый им, размечен восемью маленькими кубиками, маркерами (Рисунок 6.17).