Компонент TVport для видовых окон
Из примера первой программы вам известно о назначении и необходимости использования интерфейса IDirectDrawClipper. С его помощью задается окно для видового устройства и изображение ограничивается пределами рамки окна. Создавая компонент для представления видового окна (TVport), мы не должны наследовать его от оконного компонента, иначе он перекроет используемую видовым устройством область для изображения сцены.
Таким образом, компонент TVport наследует от компонента TGraphicControl, не имеющего собственного дескриптора окна, а при использовании экземпляров TVport их владельцами и родителями (Owner и Parent) должны обязательно быть экземпляры TD3dView.
Компонент TVport является оболочкой для интерфейса IDirect3DRMViewport, указатель на него хранится в общедоступном поле IRMVport. Поэтому компонент не ограничивает вызов интерфейсных методов видового окна. Виртуальные функции, которые перекрывает компонент у своего родителя, – это Update() и SetBounds(). Единственным новым свойством в компоненте является GDI. Его надо использовать вместо обычного свойства Canvas при рисовании посредством стандартных функций.
Объявление класса TVport (фрагмент _Vport.h):
//--------------------------------------------------------------------------- class TVport: public TGraphicControl { friend class TD3dView; TVportData ViewportData; TCanvas* __fastcall GetGDI(); void __fastcall Init(LPDIRECT3DRM IRM, LPDIRECT3DRMDEVICE Idevice, LPDIRECT3DRMFRAME Icamera); void __fastcall Save(); void __fastcall Restore(); void __fastcall Release(); protected: void __fastcall Update(); public: LPDIRECT3DRMVIEWPORT IRMVport; __fastcall TVport(TComponent* Owner); __fastcall ~TVport(); void __fastcall SetBounds(int ALeft, int ATop, int AWidth, int AHeight); LPDIRECT3DRMFRAME __fastcall GetCamera(); void __fastcall SetCamera(LPDIRECT3DRMFRAME Icamera, bool update = true); void __fastcall SetCameraTransform(LPDIRECT3DRMFRAME Iframe, bool update = true); void __fastcall Vec2xy(LPD3DVECTOR pVec, int& x, int& y); void __fastcall MoveTo3D(LPD3DVECTOR pVec); void __fastcall MoveTo3D(D3DVALUE x, D3DVALUE y, D3DVALUE z); void __fastcall LineTo3D(LPD3DVECTORpVec); void __fastcall LineTo3D(D3DVALUE x, D3DVALUE y, D3DVALUE z); void __fastcall Line3D(LPD3DVECTOR pVecl, LPD3DVECTOR pVec2); void __fastcall TextOut3D(LPD3DVECTOR pVec, char *t); __property TCanvas* GDI = {read=GetGDI, nodefault}; __published: __property Align; __property DragCursor; __property DragMode; __property Enabled; __property Font; __property ParentFont; __property ParentShowHint; __property PopupMenu; __property ShowHint; __property Visible; // Cвойства o6pa6oтки событий, yнаследованные от TControl __property OnClick; __property OnDblClick; __property OnDragDrop; __property OnDragOver; __property OnEndDrag; __property OnMouseDown; __property OnMouseMove; __property OnMouseUp; __property OnStartDrag; }; //---------------------------------------------------------------------------
Создание видового окна посредством функции компонента Init() выполняется внутри компонента TD3dView, и нет необходимости это делать пользователю компонента. Созданное видовое окно занимает всю границу компонента TVport и получает его имя. Созданный интерфейс видового устройства освобождается при разрушении компонента в его деструкторе.
Метод Updated перекрыт компонентом для того, чтобы заставить видовое окно обновить содержимое при соответствующей необходимости. Обновление видового окна выполняется интерфейсным методом ForceUpdate ().
Компонент TGraphicControl не получает сообщения WM_SIZE, поскольку он не имеет собственного дескриптора окна. Для изменения размеров компонента пользователь или владелец компонента вызывают функцию-член компонента SetBounds(). Эта функция также перекрыта для синхронизации изменения размеров компонента с размерами видового окна. В ней вызывается интерфейсный метод Configure(). Результат выполнения этого метода не следует проверять, хотя при одном изменении размера формы SetBounds() компонента выполняется несколько раз, и не каждый раз габариты удовлетворяют требованиям Configure(): в последнем обращении они удовлетворяются всегда.
Компонент TVport имеет способность сохранять и восстанавливать настройки имеющегося в его распоряжении видового окна функциями Save() и Restore(). Ими пользуется владелец TD3dView для перестройки окон под новый драйвер и при этом также вызывает функцию Release() для сохранения настроек разрушаемых окон.
Компонент имеет три функции для работы с камерами. Функция GetCamera() возвращает указатель на используемое видовым окном строение камеры. Полученный от GetCamera() указатель не надо освобождать методом Release(). Устанавливать новую камеру предпочтительнее не интерфейсным методом, а функцией TVport::SetCamera(), поскольку она вызывает обновление изображения функцией владельца Refresh(). Если имеется несколько видовых окон, которым требуется одновременно изменить камеры, то во избежание многократного обновления экрана после установки каждой камеры SetCamera() можно вызвать для всех с аргументом update, равным false, и только при последней установке update сделать равным true.
Функция SetCameraTransform() используется иногда вместо SetCamera() для того, чтобы установить камеру в новое место, не изменяя ее саму. Вместо того, чтобы копировать матрицу трансформации строения аргумента, из него извлекаются только положение и направление и присваиваются строению камеры. При этом масштабы и деформации сдвига в строении камеры остаются неизменными.
В настоящее время Direct3D не имеет методов для рисования в пространстве таких геометрических примитивов, как линия или текст. Компонент TVport пытается компенсировать этот пробел. Он предоставляет аналоги имеющихся функций объекта TCanvas для рисования линий и текста в пространстве по трем координатам: MoveTo3D(), LineTo3D() и TextOut3D(). Этим функциям и их вариациям помогает функция преобразования трехмерной точки в пространстве сцены в двумерную точку на плоскости окна Vec2xy(). Далее рисование выполняется обычными функциями TCanvas. Однако поскольку поверхность, на которой будет рисовать TCanvas, может оказаться скрытой за плоскостью рисования графической библиотеки, то необходимо ее перевести на передний план. В этом помогает функция TVport::GetGDI() и TD3dView::FlipToGDISurfасе() .
Реализация класса TVport (фрагмент _Vport.cpp):
//--------------------------------------------------------------------------- __fastcall TVport::TVport(TComponent* Owner): TGraphicControl(Owner) { Width = 100; Height = 75; } //--------------------------------------------------------------------------- __fastcall TVport::~TVport() { RELEASE(IRMVport) ; } //--------------------------------------------------------------------------- void __fastcall TVport::Init(LPDIRECT3DRM IRM, LPDIRECT3DRMDEVICE Idevice, LPDIRECT3DRMFRAME Icamera) { if(!Idevice || !IRM || !Icamera) return; TRY(IRM->CreateViewport(Idevice, Icamera, Left, Top, Width, Height, &IRMVport)); Restore(); IRMVport->SetName(Name.c_str()); } //--------------------------------------------------------------------------- void __fastcall TVport::Update() { if(IRMVport) IRMVport->ForceUpdate(Left, Top, Width, Height); } //--------------------------------------------------------------------------- void __fastcall TVport::SetBounds(int ALeft, int ATop, int AWidth, int AHeight) { TGraphicControl::SetBounds(ALeft, ATop, AWidth, AHeight); if(!IRMVport) return; IRMVport->Configure(Left, Top, Width, Height); } //--------------------------------------------------------------------------- void __fastcall TVport::Save() { ViewportData.Save(IRMVport); } //--------------------------------------------------------------------------- void __fastcall TVport::Restore() { ViewportData.Restore(IRMVport); } //--------------------------------------------------------------------------- void __fastcall TVport::Release() { ViewportData.Save(IRMVport); RELEASE(IRMVport); } //--------------------------------------------------------------------------- LPDIRECT3DRMFRAME __fastcall TVport::GetCamera() { if(!IRMVport) return NULL; LPDIRECT3DRMFRAME Icamera = NULL, result; IRMVport->GetCamera(&Icamera); result = Icamera; RELEASE(Icamera); return result; } //--------------------------------------------------------------------------- void __fastcall TVport::SetCamera(LPDIRECT3DRMFRAME Icamera, bool update) { if(!Icamera || !IRMVport) return; TRY(IRMVport->SetCamera(Icamera)); if Parent && update) { TD3dView* D3dView = dynamic_cast<TD3dView*>(0wner); if(D3dView) D3dView->Refresh(); } } //--------------------------------------------------------------------------- void __fastcall TVport::SetCameraTransform(LPDIRECT3DRMFRAME frame, bool update) { if(!Iframe || !IRMVport) return; D3DVECTOR pos, dir, up; LPDIRECT3DRMFRAME Iscene = NULL, Icamera; Icamera = GetCamera(); if(!Icamera) return; Iframe->GetScene(&Iscene); TRY(Iframe->GetPosition(Iscene, &pos)); TRY(Iframe->GetOrientation(Iscene, &dir, &up)); Icamera->SetPosition(Iscene, pos.x, pos.y, pos.z); Icamera->SetOrientation(Iscene, dir.x, dir.y, dir.z, up.x, up.y, up.z); RELEASE(Iscene); if(Parent && update) { TD3dView* D3dView = dynamic_cast<TD3dView*>(0wner); if (D3dView) D3dView->Refresh(); } } //--------------------------------------------------------------------------- TCanvas* __fastcall TVport::GetGDI() { TD3dView* D3dView = dynamic_cast<TD3dView*>(0wner); if(D3dView) D3dView->FlipToGDISurface(); return Canvas; } //--------------------------------------------------------------------------- void __fastcall TVport::Vec2xy(LPD3DVECTOR pVec, int& x, int& y) { D3DVECTOR scene_pos; D3DRMVECTOR4D plane_pos; TD3dView* D3dView = dynamic_cast<TD3dView*>(0wner); if (!D3dView || !D3dView->IcsFrame) return; D3dView->IcsFrame->Transform(&scene_pos, pVec); x = 0; y = 0; if(!IRMVport) return; IRMVport->Transform(&plane_pos, &scene_pos); D3DVALUE w = plane_pos.w; if ((w != 0) && (plane_pos.z > 0) && (plane_pos.z < w)) { x = int(plane_pos.x/w); y = int(plane_pos.y/w); } } //--------------------------------------------------------------------------- void __fastcall TVport::MoveTo3D(LPD3DVECTOR pVec) { int x, y; Vec2xy(pVec, x, y); GDI->MoveTo(x, y); } //--------------------------------------------------------------------------- void __fastcall TVport::MoveTo3D(D3DVALUE x, D3DVALUE y, D3DVALUE z) { int ix, iy; D3DVECT0R Vec; Vec.x = x; Vec.y = y; Vec.z = z; Vec2xy(&Vec, ix, iy); GDI->MoveTo(ix, iy); } //--------------------------------------------------------------------------- void __fastcall TVport::LineTo3D(LPD3DVECTOR pVec) { if((GDI->PenPos.x == 0) && (GDI->PenPos.y == 0)) return; int x, y; Vec2xy(pVec, x, y); if (x && y) GDI->LineTo(x, y); } //--------------------------------------------------------------------------- void __fastcall TVport::LineTo3D(D3DVALUE x, D3DVALUE y, D3DVALUE z) { if((GDI->PenPos.x == 0) && (GDI->PenPos.y == 0)) return; int ix, iy; D3DVECTOR Vec; Vec.x = x; Vec.y = y; Vec.z = z; Vec2xy(&Vec, ix, iy); if (ix && iy) GDI->LineTo(ix, iy); } //--------------------------------------------------------------------------- void __fastcall TVport::Line3D(LPD3DVECTOR pVecl, LPD3DVECTOR pVec2) { MoveTo3D(pVecl); LineTo3D(pVec2); } //--------------------------------------------------------------------------- void __fastcall TVport::TextOut3D(LPD3DVECTOR pVec, char *t) { int x, y; Vec2xy(pVec, x, y); if(x && y) GDI->TextOut(x, y, t); } //---------------------------------------------------------------------------
