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

Использование нескольких портов просмотра

Во всех приложениях, рассмотренных нами до этого момента, для отображения сцены использовался единственный порт просмотра. Для большинства приложений это замечательно работает, но в некоторых случаях можно использовать несколько портов просмотра, чтобы показывать одну и ту же сцену одновременно с нескольких разных точек.

Приложение MultiView

Приложение MultiView отображает единственную вращающуюся сетку, но для ее показа используются три порта просмотра. Приложение предоставляет меню, позволяющее настроить или отключить любой из портов просмотра. Кроме того, приложение позволяет загружать другие сетки. Вид окна приложения MultiView показан на рис. 9.4.


Рис. 9.4. Приложение MultiView

Рис. 9.4. Приложение MultiView


Приложение MultiView демонстрирует использование следующих технологий:

Код приложения MultiView

Приложение MultiView написано слегка иначе, чем остальные демонстрационные программы на CD-ROM. Все примеры для своего класса приложения в качестве базового класса используют класс RMWin. Большинство демонстрационных программ использует одну и ту же версию RMWin, но в приложении MultiView применяется модифицированная версия.

Версия класса RMWin, которая используется в приложении MultiView включает поддержку трех портов просмотра. Это потребовало внесения изменений в ряд функций класса RMWin. Таким образом, при обсуждении кода приложения MultiView мы будем рассматривать как функции класса RMWin так и функции класса MultiViewWin.

Класс MultiViewWin

Основная (но не вся) функциональность приложения MultiView предоставляется классом MultiViewWin:

class MultiViewWin : public RMWin
{
public:
    MultiViewWin();
    BOOL CreateScene();
protected:
    //{{AFX_MSG(MultiViewWin)
    afx_msg void OnRenderWireframe();
    afx_msg void OnRenderFlat();
    afx_msg void OnRenderGouraud();
    afx_msg void OnUpdateRenderFlat(CCmdUI* pCmdUI);
    afx_msg void OnUpdateRenderGouraud(CCmdUI* pCmdUI);
    afx_msg void OnUpdateRenderWireframe(CCmdUI* pCmdUI);
    afx_msg void OnFileOpen();
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
private:
    LPDIRECT3DRMMESHBUILDER meshbuilder;
    LPDIRECT3DRMFRAME meshframe;
};

В классе объявлены две открытые функции: конструктор и функция CreateScene(). Конструктор инициализирует две переменные класса. Функция CreateScene() создает сцену приложения, но отличается от подобных функций в других демонстрационных программах, поскольку не создает никаких портов просмотра. Код функции CreateScene() мы рассмотрим в следующем разделе.

Далее объявлены семь защищенных функций. Шесть из них являются обработчиками сообщений меню Render и присутствуют в большинстве других приложений. Седьмая функция является обработчиком сообщений для команды Open меню File. Мы будем использовать ее для отображения диалогового окна выбора файлов и загрузки выбранной сетки с диска.

Две переменных класса являются указателями, используемыми для доступа к единственной сетке приложения и фрейму, к которому эта сетка присоединена.

Функция MultiViewWin::CreateScene()

Сцена для приложения MultiView конструируется в функции CreateScene(), код которой приведен в листинге 9.4.

Листинг 9.4. Функция MultiViewWin::CreateScene()

BOOL MultiViewWin::CreateScene()
{
    // ------- СЕТКА --------
    D3DRMLOADRESOURCE resinfo;
    resinfo.hModule = NULL;
    resinfo.lpName = MAKEINTRESOURCE(IDR_MESH);
    resinfo.lpType = "MESH";
    d3drm->CreateMeshBuilder(&meshbuilder);
    meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE,
            NULL, NULL);
    ScaleMesh(meshbuilder, D3DVALUE(30));

    //------- ФРЕЙМ СЕТКИ ------
    d3drm->CreateFrame(scene, &meshframe);
    meshframe->SetRotation(scene,
            D3DVALUE(0), D3DVALUE(1), D3DVALUE(0),
            D3DVALUE(.1));
    meshframe->AddVisual(meshbuilder);
    meshframe->Release();

    // --------- СВЕТ --------
    LPDIRECT3DRMLIGHT dlight;
    d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL,
            D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00),
            &dlight);
    LPDIRECT3DRMLIGHT alight;
    d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT,
            D3DVALUE(0.40), D3DVALUE(0.40), D3DVALUE(0.40),
            &alight);

    LPDIRECT3DRMFRAME lightframe;
    d3drm->CreateFrame(scene, &lightframe);
    lightframe->SetOrientation(scene,
            D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1),
            D3DVALUE(0), D3DVALUE(1), D3DVALUE(0));

    lightframe->AddLight(dlight);
    lightframe->AddLight(alight);

    dlight->Release();
    dlight = 0;
    alight->Release();
    alight = 0;
    lightframe->Release();
    lightframe = 0;

    return TRUE;
}

Функция CreateScene() выполняет следующие действия:

  1. Создание сетки.
  2. Создание фрейма для сетки.
  3. Создание и размещение двух источников света.

Обратите внимание, что порты просмотра не создаются. Как вы увидите, код, относящийся к портам просмотра, был перемещен в класс RMWin. Функция CreateScene() подготавливает сцену, но не определяет, каким образом эта сцена будет показана зрителю.

Сначала выполняется создание сетки. Интерфейс Direct3DRMMeshBuilder используется для загрузки сетки из ресурсов приложения. Функция ScaleMesh() используется для изменения размеров сетки, если это необходимо.

На следующем этапе создается фрейм для сетки. С помощью функции SetRotation() фрейму назначаются атрибуты вращения, после чего к нему присоединяется созданная ранее сетка с помощью функции AddVisual().

Затем создаются два источника света и присоединяются к собственному фрейму.

Изменения в классе RMWin

Определение версии класса RMWin, используемой в приложении MultiView, приведено в листинге 9.5.

Листинг 9.5. Определение класса RMWin

class RMWin : public CFrameWnd
{
public:
    RMWin();
    RMWin(int w,int h);
    BOOL Create(const CString& sTitle,int icon,int menu);
    void SetColorModel(D3DCOLORMODEL cm ) { colormodel = cm; }
    inline COLORREF D3DCOLOR_2_COLORREF(D3DCOLOR d3dclr);
    inline D3DCOLOR COLORREF_2_D3DCOLOR(COLORREF cref);
    void Render();
protected:
    static int GetMouseX()    { return mousex; }
    static int GetMouseY()    { return mousey; }
    void ScaleMesh(LPDIRECT3DRMMESHBUILDER, D3DVALUE);
protected:
    //{{AFX_MSG(RMWin)
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnDestroy();
    afx_msg void OnActivate(UINT state, CWnd* other, BOOL minimize);
    afx_msg void OnPaint();
    afx_msg void OnSize(UINT type, int cx, int cy);
    afx_msg void OnMouseMove(UINT state, CPoint point);
    afx_msg BOOL OnEraseBkgnd(CDC* pDC);
    afx_msg void OnViewport1Disabled();
    afx_msg void OnViewport1Front();
    afx_msg void OnViewport1Left();
    afx_msg void OnViewport1Right();
    afx_msg void OnViewport1Top();
    afx_msg void OnViewport2Disabled();
    afx_msg void OnViewport2Front();
    afx_msg void OnViewport2Left();
    afx_msg void OnViewport2Right();
    afx_msg void OnViewport2Top();
    afx_msg void OnViewport3Disabled();
    afx_msg void OnViewport3Front();
    afx_msg void OnViewport3Left();
    afx_msg void OnViewport3Right();
    afx_msg void OnViewport3Top();
    afx_msg void OnUpdateViewport1Disabled(CCmdUI* pCmdUI);
    afx_msg void OnUpdateViewport1Front(CCmdUI* pCmdUI);
    afx_msg void OnUpdateViewport1Left(CCmdUI* pCmdUI);
    afx_msg void OnUpdateViewport1Right(CCmdUI* pCmdUI);
    afx_msg void OnUpdateViewport1Top(CCmdUI* pCmdUI);
    afx_msg void OnUpdateViewport2Disabled(CCmdUI* pCmdUI);
    afx_msg void OnUpdateViewport2Front(CCmdUI* pCmdUI);
    afx_msg void OnUpdateViewport2Left(CCmdUI* pCmdUI);
    afx_msg void OnUpdateViewport2Right(CCmdUI* pCmdUI);
    afx_msg void OnUpdateViewport2Top(CCmdUI* pCmdUI);
    afx_msg void OnUpdateViewport3Disabled(CCmdUI* pCmdUI);
    afx_msg void OnUpdateViewport3Front(CCmdUI* pCmdUI);
    afx_msg void OnUpdateViewport3Left(CCmdUI* pCmdUI);
    afx_msg void OnUpdateViewport3Right(CCmdUI* pCmdUI);
    afx_msg void OnUpdateViewport3Top(CCmdUI* pCmdUI);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
private:
    void Initvars();
    virtual BOOL CreateScene() = 0;
    BOOL CreateDevice();
    GUID* GetGUID();
    void ConfigViewport(LPDIRECT3DRMFRAME camera, int view);
    void CreateViewports();
protected:
    static LPDIRECT3DRM d3drm;
    LPDIRECT3DRMFRAME scene;
    LPDIRECT3DRMDEVICE device;
    D3DCOLORMODEL colormodel;
private:
    LPDIRECT3DRMFRAME camera1, camera2, camera3;
    LPDIRECT3DRMVIEWPORT viewport1, viewport2, viewport3;
    int view1setting, view2setting, view3setting;
    CRect winrect;
    LPDIRECTDRAWCLIPPER clipper;
    static int mousex;
    static int mousey;
    static UINT mousestate;
    friend class RMApp;
};

Ясно видно, что это сложный класс, и мы в данном разделе не будем обсуждать все, входящие в него функции. Вместо этого мы сосредоточим внимание на тех частях класса, которые отличаются от оригинальной версии RMWin. Обсуждение остальных функций класса RMWin вы найдете в главе 4.

Были добавлены три переменные: camera1, camera2 и camera3. Это указатели на интерфейс Direct3DRMFrame, которые будут использоваться для создания и перемещения трех, созданных в приложении, портов просмотра. Тот факт, что эти переменные объявлены закрытыми, говорит нам, что классы, производные от RMWin не имеют возможности манипулировать этими указателями. Эта задача возложена исключительно на класс RMWin.

Переменные viewport1, viewport2 и viewport3 будут использованы для доступа к трем созданным в приложении портам просмотра. Эти переменные также закрытые, поэтому для их инициализации могут использоваться только функции класса RMWin.

Кроме того, добавлены еще три переменные: view1setting, view2setting и view3setting. Эти значения используются для того, чтобы указать, как должен быть расположен каждый из портов просмотра. Переменные используются совместно со следующими константами (определенными в файле resource.h):

Были добавлены две закрытые функции: ConfigViewport() и CreateViewports(). Функция ConfigViewport() используется для назначения новой ориентации порта просмотра с учетом его текущей конфигурации. Функция CreateViewports() инициализирует три используемых в приложении порта просмотра.

Остальные добавленные функции являются обработчиками сообщений команд меню, добавленными с помощью ClassWizard. Как вы увидите позднее, эти функции используются для изменения местоположения и ориентации портов просмотра.

Функция RMWin::CreateDevice()

Изменения, внесенные в класс RMWin, не ограничиваются добавлением новых переменных и функций. Кроме того, был изменен ряд функций, одна из которых — CreateDevice(). Функция CreateDevice() отвечает за создание нескольких ключевых элементов программ, использующих Direct3D. Версия CreateDevice() используемая в приложении MultiView представлена в листинге 9.6.

Листинг 9.6. Функция RMWin::CreateDevice()

BOOL RMWin::CreateDevice()
{
    HRESULT r;

    r = DirectDrawCreateClipper(0, &clipper, NULL);
    if (r != D3DRM_OK)
    {
        AfxMessageBox("DirectDrawCreateClipper() failed");
        return FALSE;
    }

    r = clipper->SetHWnd(NULL, m_hWnd);
    if (r != DD_OK)
    {
        AfxMessageBox("clipper->SetHWnd() failed");
        return FALSE;
    }

    RECT rect;
    ::GetClientRect(m_hWnd, &rect);

    r = d3drm->CreateDeviceFromClipper(clipper, GetGUID(),
            rect.right, rect.bottom,
            &device);
    if (r != D3DRM_OK)
    {
        AfxMessageBox("CreateDeviceFromClipper() failed");
        return FALSE;
    }

    device->SetQuality(D3DRMRENDER_GOURAUD);

    HDC hdc = ::GetDC(m_hWnd);
    int bpp = ::GetDeviceCaps(hdc, BITSPIXEL);
    ::ReleaseDC(m_hWnd, hdc);

    switch (bpp)
    {
    case 1:
        device->SetShades(4);
        d3drm->SetDefaultTextureShades(4);
        device->SetDither(TRUE);
        break;
    case 8:
        // ...
        break;
    case 16:
        device->SetShades(32);
        d3drm->SetDefaultTextureColors(64);
        d3drm->SetDefaultTextureShades(32);
        device->SetDither(FALSE);
        break;
    case 24:
    case 32:
        device->SetShades(256);
        d3drm->SetDefaultTextureColors(64);
        d3drm->SetDefaultTextureShades(256);
        device->SetDither(FALSE);
        break;
    }
    d3drm->CreateFrame(NULL, &scene);

    if (CreateScene() == FALSE)
    {
        AfxMessageBox("CreateScene() failed");
        return FALSE;
    }

    d3drm->CreateFrame(scene, &camera1);
    ConfigViewport(camera1, view1setting);

    d3drm->CreateFrame(scene, &camera2);
    ConfigViewport(camera2, view2setting);

    d3drm->CreateFrame(scene, &camera3);
    ConfigViewport(camera3, view3setting);

    CreateViewports();

    return TRUE;
}

Вместо того, чтобы обсуждать весь код функции, мы сосредоточимся на внесенных изменениях. Нас интересует завершающая часть кода функции, следующая за инициализацией указателя на фрейм scene:

if (CreateScene() == FALSE)
{
    AfxMessageBox("CreateScene() failed");
    return FALSE;
}
d3drm->CreateFrame(scene, &camera1);
ConfigViewport(camera1, view1setting);
d3drm->CreateFrame(scene, &camera2);
ConfigViewport(camera2, view2setting);
d3drm->CreateFrame(scene, &camera3);
ConfigViewport(camera3, view3setting);
CreateViewports();

Данная часть кода начинается с вызова функции CreateScene(). Если функция CreateScene() возвращает FALSE, выводится окно с сообщением об ошибке и функция CreateDevice() также возвращает FALSE.

Если функция CreateScene() завершается успешно, функция CreateDevice() инициализирует три фрейма: camera1, camera2 и camera3. Эти фреймы используются для создания и размещения трех используемых в приложении портов просмотра. После создания каждого из фреймов, он передается функции ConfigViewport() вместе с целым числом, хранящим конфигурацию порта просмотра. Функция ConfigViewport() позиционирует указанный фрейм, согласно значению, переданному во втором аргументе. При инициализации эти значения устанавливаются следующим образом:

view1setting = VIEWPORT_FRONT;
view2setting = VIEWPORT_LEFT;
view3setting = VIEWPORT_TOP;

Данные значения указывают, что первый порт просмотра будет отображать вид на сцену спереди. Второй порт просмотра будет отображать вид на сцену слева, а третий порт просмотра будет выводить вид сверху.

После инициализации всех трех фреймов и вызова для каждого из них функции ConfigViewport(), выполняется действительное создание портов просмотра функцией CreateViewports().

Функция RMWin::ConfigViewport()

Функция ConfigViewport() получает два аргумента: указатель на интерфейс Direct3DRMFrame и целое число, указывающее желаемую позицию фрейма. Код функции выглядит так:

void RMWin::ConfigViewport(LPDIRECT3DRMFRAME camera, int view)
{
    if (view == VIEWPORT_FRONT)
    {
        camera->SetPosition(scene,
                D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50));
        camera->SetOrientation(scene,
                D3DVALUE(0), D3DVALUE(0), D3DVALUE(1),
                D3DVALUE(0), D3DVALUE(1), D3DVALUE(0));
    }
    else if (view == VIEWPORT_LEFT)
    {
        camera->SetPosition(scene,
                D3DVALUE(-50), D3DVALUE(0), D3DVALUE(0));
        camera->SetOrientation(scene,
                D3DVALUE(1), D3DVALUE(0), D3DVALUE(0),
                D3DVALUE(0), D3DVALUE(1), D3DVALUE(0));
    }
    else if (view == VIEWPORT_RIGHT)
    {
        camera->SetPosition(scene,
                D3DVALUE(50), D3DVALUE(0), D3DVALUE(0));
        camera->SetOrientation(scene,
                D3DVALUE(-1), D3DVALUE(0), D3DVALUE(0),
                D3DVALUE(0), D3DVALUE(1), D3DVALUE(0));
    }
    else if (view == VIEWPORT_TOP)
    {
        camera->SetPosition(scene,
                D3DVALUE(0), D3DVALUE(50), D3DVALUE(0));
        camera->SetOrientation(scene,
                D3DVALUE(0), D3DVALUE(-1), D3DVALUE(0),
                D3DVALUE(0), D3DVALUE(0), D3DVALUE(1));
    }
}

Для позиционирования фрейма используются функции SetPosition() и SetOrientation(). Местоположение и ориентация зависят от значения параметра view.

Функция ConfigViewport() используется как в функции CreateDevice() (что мы уже видели) так и в обработчиках сообщений команд меню Viewport (что мы увидим чуть позже).

функция RMWin::CreateViewports()

Функция CreateViewports() создает три используемых в приложении порта просмотра:

void RMWin::CreateViewports()
{
    int newwidth = device->GetWidth();
    int newheight = device-7gt;GetHeight();
    int onethird = newwidth / 3;
    int halfheight = newheight / 2;
    d3drm->CreateViewport(device, camera1,
            0, 0,
            onethird * 2, newheight,
            &viewport1);
    d3drm->CreateViewport(device, camera2,
            onethird * 2, 0,
            onethird, halfheight,
            &viewport2);
    d3drm->CreateViewport(device, camera3,
            onethird * 2, halfheight,
            onethird, halfheight,
            &viewport3);
}

Функция делит доступное для вывода изображений пространство устройства на три части. Первый порт просмотра занимает первые две трети пространства устройства, а оставшиеся два порта просмотра делят оставшуюся треть. Каждый порт просмотра создается функцией CreateViewport() интерфейса Direct3DRM.

Функция RMWin::Render()

Мы завершили обсуждение кода, который создает и конфигурирует сцену приложения и внутренние компоненты. Теперь посмотрим, что происходит после инициализации.

Важным действием является обновление сцены и визуализация выходных данных во время работы приложения. В других приложениях эту задачу выполняла функция RMApp::OnIdle(). Код функции OnIdle(), используемой в предыдущих приложениях, выглядел следующим образом:

BOOL RMApp::OnIdle(LONG count)
{
    ASSERT(RMWin::d3drm);
    ASSERT(rmwin);
    rmwin->OnIdle(count);
    RMWin::d3drm->Tick(D3DVALUE(1));
    return TRUE;
}

Функция Tick() интерфейса Direct3DRM использовалась для обновления данных программы и формирования нового изображения с учетом произошедших изменений. Этот метод прекрасно работал, когда у нас был один порт просмотра, но теперь нам требуется больший контроль. В приложении MultiView используется следующая версия функции RMApp::OnIdle():

BOOL RMApp::OnIdle(LONG lCount)
{
    ASSERT(rmwin);
    rmwin->Render();
    return TRUE;
}

Эта версия передает отвественность за обновление данных программы функции RMWin::Render(), которая выглядит следующим образом:

void RMWin::Render()
{
   scene->Move(D3DVALUE(1.0));
   if (view1setting != VIEWPORT_DISABLED)
   {
       viewport1->Clear();
       viewport1->Render(scene);
   }
   if (view2setting != VIEWPORT_DISABLED)
   {
       viewport2->Clear();
       viewport2->Render(scene);
   }
   if (view3setting != VIEWPORT_DISABLED)
   {
       viewport3->Clear();
       viewport3->Render(scene);
   }
   device->Update();
}

Вспомните, что функция Tick() интерфейса Direct3DRM выполняет как обновление данных программы, так и формирование нового изображения. Поскольку мы не можем использовать функцию Tick(), нам придется выполнять эти задачи по отдельности и самостоятельно.

Сначала для обновления данных программы вызывается функция Move() интерфейса Direct3DRMFrame. Она применяет атрибуты движения и вызывает функции обратного вызова иерархии фреймов. Мы используем корневой фрейм сцены (scene), поэтому данный вызов функции гарантирует обновление всей сцены.

Затем необходимо создать новое изображение. Это делается с помощью функций Clear() и Render() интерфейса Direct3DRMViewport. Если порт просмотра не отключен, сперва вызывается функция Clear() для его очистки, а затем используется функция Render() для создания нового изображения.

Хотя новое изображение уже создано функцией Render() интерфейса Direct3DRMViewport, оно пока остается невидимым. Для фактического вывода визуализированного изображения используется функция Update() интерфейса Direct3DRMDevice.

Функция RMWin::OnSize()

Функция OnSize() вызывается при изменении размеров окна. Это важно, поскольку размер устройства Direct3D не может быть изменен. Следовательно, в случае изменения размеров окна функция OnSize() должна уничтожить устройство и создать его заново. Поскольку порты просмотра присоединены к устройству, они также должны быть уничтожены и созданы заново. Функция OnSize() приложения MultiView выглядит следующим образом:

void RMWin::OnSize(UINT type, int cx, int cy)
{
    CFrameWnd::OnSize(type, cx, cy);

    if (!device)
        return;

    int newwidth = cx;
    int newheight = cy;

    if (newwidth && newheight)
    {
        int old_dither = device->GetDither();
        D3DRMRENDERQUALITY old_quality = device->GetQuality();
        int old_shades = device->GetShades();
        viewport1->Release();
        viewport2->Release();
        viewport3->Release();
        device->Release();
        d3drm->CreateDeviceFromClipper(clipper, GetGUID(),
                newwidth, newheight, &device);

        device->SetDither(old_dither);
        device->SetQuality(old_quality);
        device->SetShades(old_shades);

        CreateViewports();
    }
}

Сначала функция сохраняет текущие параметры устройства. Эти параметры будут использованы позднее для настройки вновь созданного устройства. Для получения параметров устройства применяются функции GetDither(), GetQuality() и GetShades() интерфейса Direct3DRMDevice.

Затем освобождаются все три порта просмотра и устройство. После этого создается новое устройство функцией CreateDeviceFromClipper() интерфейса Direct3DRM. Новое устройство конфигурируется с учетом сохраненных ранее параметров.

Обратите внимание, что нет необходимости уничтожать и создавать три фрейма портов просмотра. Функция CreateViewports() (последняя функция, вызываемая в функции OnSize()) будет использовать при создании и размещении новых портов просмотра существующие фреймы.

Функции меню Viewport в классе RMWin

Приложение MultiView предоставляет команды меню, позволяющие настраивать любой из портов просмотра. Для каждого из портов просмотра предусмотрено отдельное меню. Здесь мы обсудим обработчики сообщений для меню первого порта просмотра. Код для оставшихся портов просмотра практически идентичен приведенному.

void RMWin::OnViewport1Disabled()
{
    view1setting = VIEWPORT_DISABLED;
    viewport1->Clear();
}

void RMWin::OnViewport1Front()
{
    view1setting = VIEWPORT_FRONT;
    ConfigViewport(camera1, view1setting);
}

void RMWin::OnViewport1Left()
{
    view1setting = VIEWPORT_LEFT;
    ConfigViewport(camera1, view1setting);
}

void RMWin::OnViewport1Right()
{
    view1setting = VIEWPORT_RIGHT;
    ConfigViewport(camera1, view1setting);
}

void RMWin::OnViewport1Top()
{
    view1setting = VIEWPORT_TOP;
    ConfigViewport(camera1, view1setting);
}

Каждая из функций присваивает свое значение члену данных view1setting. Функция OnViewportDisabled() использует функцию Clear() интерфейса Direct3DRMViewport для очистки порта просмотра. В оставшихся функциях используется функция ConfigViewport() для настройки порта просмотра в соответствии с новыми параметрами.


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

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