[ AmberSkyNet VR ]

Текст, один текст... В консоли, в логах.. Пора чего-нибудь графическое сотворить уже... Например - менеджер графики. Думаю, что далее его интерфейсы всё-таки будут немного меняться, а пока предлагаю вашему вниманию первое приближение...
Мне не очень хочется делать функции вывода примитивов - текстурированный квадрат, треугольник, итп, как было сделано в предыдущей итерации движка. Вместо этих функций создам пока одну - отрисовка объекта DrawObject. Рисовать его будет графический мендежер, он же и будет создавать экземпляры данного класса, что позволит относительно без больших переделок изменять его внутреннюю структуру, которую будет знать только сам менеджер графики, а для всех внешних классов она будет скрыта за интерфейсом. Это позволит в перспективе подключать различные менеджеры графики, учитывающие особенности старых и современных видеокарт. Вообще, написание разных менеджеров не планирую, но вдруг..

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

IBaseObject и его реализация

Но для начала - введём базовый объект. Набор его функций позволит нам добавлять функциональность в классы без больших изменений интерфейса. Как это работает покажем это чуть ниже, на примере класса материала для нашего графического менеджера.

class IBaseObject {
public: 

virtual ~IBaseObject(){};

// получить имя базового класса
virtual std::string GetBaseClass() =0;
// получить тип объекта - имя реализации базового класса
virtual std::string GetType() =0;
// получить имя объекта
virtual std::string GetName() =0;

// функции установки/чтения указателей на менеджеры и пр.
virtual void SetPtrParam(const std::string& param_name, void *param_value) =0;
virtual void *GetPtrParam(const std::string& param_name) =0;
virtual void DelPtrParam(const std::string& param_name) =0;

// функции установки/чтения переменных
virtual void SetParam(const std::string& param_name,const std::string& param_value) =0;
virtual std::string GetParam(const std::string& param_name) =0;
virtual void DelParam(const std::string& param_name) =0;

};

Функция GetBaseClass возвращает имя базового класса объекта. Ну, например класс реализации менеджера GUI возвратит значение "GUI Manager"
Функция GetType возвращает имя реализации базового класса. Например, менеджер основанный на библиотеке guichan возвратит имя "guichan", а менеджер какой-нибудь guiSDL возвратит "guiSDL".
Функция GetName возвратит имя объекта, как правило, уникальное.

Да, напоминает набор функций в IEngine. Возможно в будущем так и сделаю - наследую IEngine от него.

Класс BaseObject реализует некоторую функциональность интерфейсного класса IEngine по установке/удалению указателей и переменных. Поэтому в описании BaseObject.h есть секция protected с хранящимися в ней списками переменных и указателей в виде std:map. Жаль,что не удалось в папке include собрать заголовки только абстрактных классов :(( Впрочем реализация BaseObject.h всё равно скрыта в asnCommon и по идее может быть недоступна сферическому разработчику в вакууме, который вдруг ни с того ни с сего захочет использовать наш движок для написания своей программы.

IDrawObject

Напишем интерфейс объекта отрисовки, который будет связующим звеном между данными внешней программы и нашим граф. менеджером. Указатель на него и будет посылаться нашему графическому менеджеру в команде Draw(). Конечно можно передавать указатели на данные прямо в наш менеджер (скажем, в ту же команду Draw()), но в этом случае менеджер будет каждый раз заново подключать одни и те же текстуры, устанавливать указатели на массивы, включать/выключать освещение объекта, устанавливать переменные среды в соответствии с материалом объекта.
В случае передачи указателя менеджер может оптимизировать отрисовку, упорядочив очередь отрисовки по материалам, мешам, координатам, итп... Но может этого и не делать :-)
Поэтому на будущее мы такую возможность оставим, но выводить будем пока без оптимизации.

Как всегда - сначала интерфейс.

// виды буферов
enum ASN_DATA_Buffer{
ASN_DATA_VERTEX_BUFF=0,
ASN_DATA_NORMAL_BUFF,
ASN_DATA_COLOR_BUFF,
ASN_DATA_UV_BUFF,
};


class IDrawObject{
public:

// материал
virtual bool SetMaterial(IMaterial *Material)=0;
// буфер данных - в одном массиве храним вертексы, нормали, текстурные координаты
// на вход подаём указатель на буфер, размер одного элемента, кол-во элементов в буфере
virtual bool SetDataBuffer(void *buffer, unsigned int element_size, unsigned int buf_size)=0;
// задаём сдвиг данных
virtual bool SetDataOffset(ASN_DATA_Buffer buffer_type, unsigned int data_offset)=0;
// буфер индекса
virtual bool SetIndexBuffer(void *buffer, unsigned int element_size, unsigned int buf_size)=0;
};

Функция SetMaterial присваивает объекту определённый материал. О материалах будет чуть ниже.

Функция SetDataBuffer устанавливает указатель на массив данных, которые будут отрисованы. Во входных параметрах функции указывается размер структуры, которая хранит в себе один вертекс (возможно, с нормалями и текстурными координатами).
Все данные хранятся в одном массиве однотипных структур. Чтобы не перепутать где какие при отрисовке - задаём смещение каждого типа хранящихся данных относительно начала структуры при помощи функции SetDataOffset.

Пояснение: пусть данные хранятся у нас в таком виде
<вертекс x,y,z><нормаль z,y,z><текстура x,y><вертекс x,y,z><нормаль z,y,z><текстура x,y>...
Таким образом чтобы считывать из массива параметры нормали нам надо задать для нормали смещение, равное размеру <вертекс x,y,z>. А чтобы считывать из массива текстурные координаты надо задать смещение равное сумме размеров <вертекс x,y,z> и <нормаль x,y,z>.

Функция SetIndexBuffer устанавливает указатель на массив с индексами, указывающими какие данные отрисовывть из массива данных. Смещение в данном случае указывать не требуется, т.к. массив состоит только из индексов.

IDevice

Так как в SDL очень тесно интегрированы ввод и отрисовка мы тоже сделаем для этого единый менеджер - менеджер графики и ввода. Поэтому интерфейс у него будет несколько совмещённый:



// события, получаемые через функцию GetInput()
enum ASN_INPUT_EVENTS_Types {
ASN_NONE, // NULL
ASN_EVNT_EMPTY,
ASN_EVNT_KEYPRESSED, //нажата кнопка
ASN_EVNT_KEYUP, //отжата кнопка
ASN_EVNT_MOUSEMOVE, //мышка сдвинулась
ASN_EVNT_MOUSEDOWN, //нажата кнопка мышки
ASN_EVNT_MOUSEUP, //отжата кнопка мышки
ASN_MOUSE_BUTTON_LEFT, //нажата левая кнопка мышки
ASN_MOUSE_BUTTON_RIGHT, //нажата правая кнопка мышки
ASN_MOUSE_BUTTON_MIDDLE, //нажата средняя кнопка мышки
ASN_MOUSE_WHEEL_UP, //колёсико мышки вертится вверх
ASN_MOUSE_WHEEL_WHEELDOWN, //колёсико мышки вертится вниз
ASN_QUIT, // закрытие Device
};

// типы граф. режимов - 2d, 3d
enum ASN_GRAPH_MODE_Types {
ASN_GRAPH_2D,
ASN_GRAPH_3D,
};

class IDevice {
public:
virtual ~IDevice(){};

// инициализация графики
virtual bool Init(int width=640, int height=480, int bpp=32, int param=0)=0;
// установка режима - 2d, 3d
virtual bool SetMode(ASN_GRAPH_MODE_Types Typ)=0;

// читаем события из очереди
virtual int GetInput()=0;

// начало отрисовки сцены
virtual void StartDraw()=0;
// рисуем объект
virtual bool DrawObject(IDrawObject *SceneElem)=0;
// заканчиваем отрисовку сцены
virtual void EndDraw()=0;

// материалы
virtual IMaterial *AddMaterial (const void* MaterialInfo)=0;
virtual IMaterial *GetMaterial (const std::string& MaterialName)=0;

// меши отрисовки
virtual IDrawObject *CreateDrawObject(const std::string& SceneElemInfo)=0;
virtual IDrawObject *GetDrawObject(const std::string& SceneElemName)=0;

// позиционирование

 // работа с мышкой
// перевод координат мышки в мировые координаты
virtual void MouseToWorld(int mouse_x, int mouse_y, CVector& p1, CVector& p2)=0;
 // работа с мышкой
virtual void CenterMouse(bool ShowCursor)=0;

 //позиционирование 
// получаем матрицы
virtual void GetMatrixs(double *Model_Matrix, double *Project_Matrix)=0;
// устанавливаем матрицы
virtual void SetMatrixs(double *Model_Matrix, double *Project_Matrix)=0;
// вращаем на XYZ
virtual void Rotate(CVector& Point1)=0;
// двигаем на XYZ
virtual void Move(const CVector& Point1)=0;
// масштабируем на XYZ
virtual void SetScale(const CVector& Point1)=0;
// помещаем матрицы в стек
virtual void PushMatrix()=0;
// забираем матрицы из стека
virtual void PopMatrix()=0;

// FPS - считается в EndDraw()
int FPS; 
// интервал между фреймами для корректной скорости анимации
float FrameInterval;
// код последней нажатой клавиши
int LastKey;
// код последней нажатой клавиши в unicode
unsigned short UnicodeKey;

// положение мышки на экране
int mouse_x;
int mouse_y;
unsigned char mouse_butt;
};

Некоторые значения (fps, код нажатой клавиши, координаты мышки) доступны напрямую, без вызовов функций - это в данном случае считаю ненужным.

Граф. менеджер производит экземпляры классов СMaterial и СDrawObject и выдаёт их через функции AddMaterial и CreateDrawObject. Если мы вдруг захотим использовать уже имеющийся материал или объект отрисовки, то можем получить указатель на него по его имени - при помощи функций GetMaterial и GetObject. Пока в функции AddMaterial и AddDrawObject подаётся только указатель на имя материала, потом - сделаю нормальный парсер входных значений и буду подавать входные параметры через знак-разделитель |, как это было в предыдущей реинкарнации движка.. может быть...

IMaterial и дальнейшее развитие CMaterial

Графический менеджер возвращает экземпляр класса CMaterial под видом интерейсного класса IMaterial, поэтому public-поля класса, хорошо видимые менеджером графики, будут невидимы для внешней прогаммы. Это даст нам возможность как угодно менять строение класса материала, изменения потребуется внести только в пределах классов модуля DeviceGL. Все остальные классы движка и внешней программы останутся без изменений.

CMaterial на данный момент реализует только хранение текстуры. Одной. Без бампа, параметров диффузного, зеркального цвета, итп.. Не всё сразу, я думаю... Текстура задаётся при помощи вызова функции
SetParam("Texture", <Имя текстуры>); При вызове функции SetParam проверяется значение какой переменной будет установлено. В случае, если изменяется значение переменной "Texture" указатель на текстуру у класса сбрасывается, и при отрисовке менеджер проанализирует значение этого указателя и в случае, если его значение равно NULL - загрузит текстуру с именем в переменной "Texture" нашего класса материала и сохранит указатель на неё в классе материала. Все эти манипуляции проходят внутри классов CMaterial и CDeviceGL, не выходя наружу.

Если вдруг мы захотим сделать мультитекстурирование, то достаточно будет добавить в класс CMaterial указатель на дополнительную текстуру, учесть установку переменных, связанных с мультитекстурированием и доработать в DeviceGL вывод мешей с несколькими текстурами.
Изменения всей остальной части движка не потребуются, а во внешней программе просто появится еще одна строчка, скажем вот такая:

myMaterial->SetParam("Texture1","graystone.png");

Посмотрим... Может быть потом вынесу из CDeviceGL функцию установки параметров окружения в соответствии с материалом в класс материала. Это позволит создавать материалы разных типов (например - с бампом и без, с параметрами освещения, итп) и не проверять в CDeviceGL что установлено а что нет в классе материала...

Взаимодействие с граф. менеджером

Engine после разбора ini-файла читает значение переменной [Modules]DeviceManager и пытается загрузить через систему плагинов графический менеджер с таким названием. Полученный указатель запоминается в указателях окружения с именем DeviceManager. Для обращения к менеджеру во внешних программах можно использовать определённое через макрос в файле IDevice.h слово DEVICER. Например, получить значение FPS можно вот так:

DEVICER->FPS;

Переменные окружения:

  • "[DeviceManager]window_name" - заголовок графического окна.
  • "[DeviceManager]height", "[DeviceManager]width" - размеры (в пикселях) высоты и ширины графического окна

    Изменения в исходниках

    include/: Добавлен интерфейсы IBaseObject.h, BaseObject.h, IDevice.h, IDrawObject.h, IMaterial.h

    src/asnMain/:Небольшая демка с летающими "бубликами", показывающий как использовать всё то, что было описано выше. Класс формирования геометрии тора взят из примера на сайте 3dsteps.narod.ru, но где-то подобное я уже видел еще...

    src/asnEngine/: добавлена инициализация графического менеджера.

    src/asnDeviceGL/: Новый плагин - менеджер графической системы и ввода/вывода с реализацией материалов и объектов отрисовки.

    Исходники этого шага выложены в SVN. Скачать их можно набрав команду:

    svn co https://svn.sourceforge.net/svnroot/ambernet/tags/AmberSkyNet-0.7 ambernet_0.7
    
  • Powered by: SourceForge.net Logo