ПрограммированиеСтатьиГрафика

Создание Export плагина для 3D Studio MAX.

Автор:

Построение плагина
Системные требования
Общие сведения и подготовительные операции.
Базовый каркас
Реализация экспорта
Архитектура сцены и экспорт

Построение плагина

Лирическое отступление.

Все, наверняка, учились геймдевелоперскому делу, пробуя писать, пусть небольшую, но собственную игру. В процессе разработки оттачивали полученные в процессе чтения книг и статей навыки. Начинают обычно с достаточно простых вещей: система частиц, менеджер ресурсов, подсистема ввода, вывод текста. Но, в конце-концов, наступал момент, когда на экране хотелось видеть что-то более красивое, чем затекстурированный кубик вращающийся в центре. :) Каждый выходил из положения по-своему. Кто-то качал из Интернета готовый лоадер популярного формата 3D файлов, кто-то писал сам, а самые настойчивые, рвущиеся к знаниям и большему опыту геймдевелоперы, решали создать собственный экспортер из одного из распространенных  3D редакторов. Естественно (никто и не утверждает обратного), тем, кто только начинает, за достаточно сложное дело написания экспортера, браться не стоит. А вот для тех, кто уже уверен в своих силах, и готов попробовать их на новом поприще, данная статья может стать хорошей базой для воплощения своих задумок в жизнь. Как можно понять из заголовка, я выбрал в качестве «подопытного» 3D Studio MAX.

Системные требования

Нет, о системных требованиях компьютера я рассказывать не буду. :) Опишу то, что желательно иметь в наличии (как в голове, так и на жестком диске) для успешной работы:

1.  Установленный 3D Studio MAX 5.0 c SDK. Учтите, что типичная установка MAX’а не включает SDK. Замечу также, что данный код наверняка, будет работать и с Максом начиная от версии 3.0, но необходимо знать, что плагины скомпилированные с разными версиями SDK не совместимы, т.е. плагины к 5-му максу не будут работать с 4-м и т.д.

2.  Visual Studio версии 6.0 и выше. Пример написан мною под VC++ 6.0, плюс все подготовительные процедуры я буду описывать, неявно ссылаясь именно на эту версию среды.

3.  Достаточно твердое знание C++ и среды разработки. Тут, я думаю, комментарии излишни. :)

4.  Поверхностное знание 3D Studio MAX.

5.  Настоятельно рекомендуется прочесть сначала статью: «Основы плагиностроения к 3D Studio MAX». Я в тексте многое упомяну, но более поверхностно.

6.  Как говорит англоязычное население земли, at last but not at least: настойчивость и любознательность. Без этого совсем никак.

Общие сведения и подготовительные операции.

И вот мы добрались, собственно, до плагинов. 3D Studio MAX (далее просто MAX) SDK позволяет создавать великое множество разнообразных типов плагинов. Среди них такие как: рендеры, модификаторы, плагины материалов,  текстур и атмосферы, и, конечно же, импортеры и экспортеры геометрии. Вот, последним типом мы сегодня и займемся вплотную. Все плагины представляют собой обычную dll которая экспортирует несколько функций. Для каждого типа плагина предусмотрено свое расширение для имени файла, именно так МАХ распознает их тип во время загрузки. Для экспортеров предопределено расширение «.dle».

Итак начнем с создания проекта пустой dll. Перво-наперво, нам требуется сконфигурировать проект для дальнейшей работы. Известно, что по умолчанию VC++ создает 2 конфигурации сборки проекта. Это Release и Debug. Для успешной компиляции и работы необходимо сделать кое-какие изменения.

Начнем с Release. Единственное, что жизненно необходимо поменять, это во вкладке C/C++, Category->Code Generation, Use run-time library: выбираем Multithreaded DLL, вместо просто Multithreaded. Если этого не сделать, то ваш плагин будет крашиться с сообщениями об ошибках доступа к памяти.

Теперь Debug. Здесь немного сложнее. Проблема в том, что Debug конфигурацию проекта могут использовать только зарегистрированные разработчики, у которых есть специальная Debug версия МАХ’а (с которой, кстати, идет в поставке специальная версия SDK). У нас же, простых смертных, установлена обычная, Release версия 3d студии (я в этом уверен, так как если бы было иначе, ты не читал бы это сейчас :) ), так что мы сделаем небольшой финт ушами, для того чтобы можно было отлаживать наши наработки. Необходимо создать новую конфигурацию проекта под названием Hybrid, путем копирования Debug конфигурации. Далее необходимо заменить, также как и в Release, Use run-time library: с Debug Multithreaded, на Multithreaded DLL. И не забудьте во вкладке General конфигурации Hybrid заменить содержимое полей Intermediate files, и Output files на Hybrid. Вот и все.

HINT: во всех конфигурациях удобно указать в поле Link->Output file name полный путь с именем плагина в папку stdplugs МАX’a. У меня, например, так:
c:\program files\3Dmax\stdplugs\MyExpPlg.dle. Это избавит от копирования собранной библиотеки в папку, где размещаются все плагины редактора.

Теперь подключаемые библиотеки. Для успешной компиляции необходимо подключить: comctl32.lib (Common Controls), core.lib, maxutil.lib, geom.lib и mesh.lib. Последние 4 лежат в каталоге maxsdk\lib\. Core и Maxutil обязательные для всех типов плагинов.

Подготовка окончена, теперь можно приступать к программированию.

Базовый каркас

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

Каждая dll плагина должна обязательно реализовывать следующие функции:

DllMain(HINSTANCE hinstDLL,ULONG fdwReason,LPVOID lpvReserved);

LibNumberClasses();
LibClassDesc(i);
LibDescription();
LibVersion();

DllMain();

С этой все ясно. Точка входа, ее не может не быть. Типичная реализация:

BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
  hInstance = hModule;
  if (!ControlsInit)
  {
    ControlsInit = true;
    InitCustomControls(hInstance);
    InitCommonControls();
  }
    return TRUE;
}

LibNumberClasses().

Разве я вам еще не говорил, что в одной библиотекой можно реализовать несколько плагинов? Так вот, эта функция возвращает количество плагинов в dll. Типичная реализация:

__declspec(dllexport) int LibNumberClasses()
{
  return 1;
}

LibClassDesc().

МАХ, должен знать какой тип плагина содержится в библиотеке, так же необходимо знать некоторый другие параметры для нормального функционирования. Для этого служит класс описания плагина (об этом чуть позже), функция LibClassDesc, в зависимости от номера, передаваемого в качестве параметра, возвращает адрес того или иного класса описания. Типичная реализация:

__declspec(dllexport) ClassDesc *LibClassDesc(int i)
{
  switch (i)
  {
  case 0: return &MyPlugCD;
  default: return 0;
  }
}

LibDescription().

Представьте такую ситуацию: вы использовали какой-то плагин, сохранили свою работу и дали другу, у которого этого плагина нет. Как быть? Естественно без плагина результат, который получился у вас, у друга не будет. Так вот, функция LibDescription возвращает строку, которая копируется в сейв-файл и отображается, в случае, когда при загрузке нужный плагин не найден. Для плагинов экспорта такая ситуация не представляет угрозы, но стандарт есть стандарт, так что вот вам типичная реализация:

__declspec(dllexport) const TCHAR *LibDescription()
{
  return _T("My first export file plugin");
}

LibVersion().

Контроль версий. Возвращаем версию МАХ’а, под которую скомпилирован плагин. С другими версиями работать не будет.

__declspec(dllexport) ULONG LibVersion()
{
  return VERSION_3DSMAX;
} 

С экспортируемыми функциями все. Осталось сделать def файл, для того чтобы МАХ увидел экспортируемые нами функции.

Теперь давайте посмотрим на класс описания плагина. Так как, наша dll реализует всего один плагин, то, соответственно, имеем всего один класс описания. Сначала код:

class MyPlugClassDesc : public ClassDesc  
{
public:
  int      IsPublic() { return 1; }
  void*      Create(BOOL Loading = FALSE) { return new MyExp; }
  const TCHAR  *  ClassName() { return _T("MyExp"); }
  SClass_ID    SuperClassID() { return SCENE_EXPORT_CLASS_ID; }
  Class_ID    ClassID() { return MYEXP_CLASS_ID; }
  const TCHAR  *  Category() { return _T(""); }
};

Как можно заметить, дескриптор класса  должен быть порождён от базового класса ClassDesc. Это абстрактный класс, и для возможности создания экземпляра от его наследника (в нашем случае MyPlugClassDesc), необходимо переопределить все чисто-виртуальные функции. Начнем по порядку.
int IsPublic(). Возвращает FALSE, если пользователь 3D Studio не должен иметь возможности использовать этот плагин, и TRUE если такая возможность присутствует. Нужно для случая, когда один плагин использует другой (приватный), и этот приватный плагин не предназначен для взаимодействия с пользователем.

void *Create(BOOL Loading = FALSE). МАХ вызывает эту функцию, когда ему требуется указатель на новый экземпляр класса плагина.

const TCHAR* ClassName(). Возвращает имя класса. Это имя будет высвечено на кнопке плагина в интерфейсе МАХ’а. Так как, экспортерам не соответствуют какие-либо кнопки, то данной надписи мы не увидим.

SClass_ID SuperClassID(),Class_ID ClassID(). А теперь очень важный момент. Каждый плагин должен иметь уникальный (!) идентификатор, который состоит из двух 32 битных квалификаторов. Для каждого плагина эти номера должны быть уникальные, иначе возникнет конфликт при загрузке, и плагин работать не будет. Учтите это также, когда будете брать как примеры для своих собственных наработок код из примеров поставляемых с МAX SDK. Для генерации уникального идентификатора discreet поставляет утилитку под названием gencid.exe, которая расположена в папке maxsdk\help\. Запустите ее, сгенерируйте новый Class_ID, и вставьте в программу в виде макроса:

#define MYEXP_CLASS_ID Class_ID(0x280d4a49, 0x4abe3694)

Вернемся к нашим функциям. ClassID возвращает уникальный Class_ID нашего плагина, а SuperClassID возвращает ID соответствующий типу созданного нами плагина. Для экспортеров это будет SCENE_EXPORT_CLASS_ID.

const TCHAR* Category() определяет принадлежность нашего плагина к какой-либо категории на командной панели (“Standard Primitives”, “Particle Systems”, “NURBS Surfaces”, вы все их видели). Имя заданное этой функцией, определяет в какую категорию попадет плагин. Осторожно, МАХ разрешает использовать не более 12 плагинов в одной категории, так что, если будете писать какой-то визуализационный плагин, то discreet настоятельно рекомендует создавать для него собственную категорию, а не помещать в уже существующую. Наш плагин вообще в категории не нуждается, так что возвращаем пустую строку.

Реализация экспорта

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

Итак, все классы экспорта сцены должны быть порождены от класса SceneExport. Смотрите код:

class MyExp : public SceneExport  
{
public:
  int        ExtCount()      { return 1; }
  const TCHAR*  Ext(int i)      { if (i == 0) return _T("MMM"); else return _T(""); }
  const TCHAR*  LongDesc()      { return _T("My export first plugin"); }
  const TCHAR*  ShortDesc()      { return _T("My exporter"); }
  const TCHAR*  AuthorName()    { return _T("Roman Marchenko"); }
  const TCHAR*  CopyrightMessage()  { return _T("Copyleft (C) 2003"); }
  const TCHAR*  OtherMessage1()    { return _T(""); }
  const TCHAR*  OtherMessage2()    { return _T(""); }
  unsigned int  Version()      { return 100; }
  void      ShowAbout(HWND hWnd){ MessageBox(hWnd, "First exporter plugin", "About", MB_OK); }
  BOOL      SupportsOptions(int ext, DWORD options);

  // Это функция и производит экспорт.
  int        DoExport(const TCHAR *name, ExpInterface *ei,
               Interface *i, BOOL suppressPromts = FALSE, DWORD options = 0);
  MyExp();
  virtual ~MyExp();
};

Как видите, функций много (все они должны быть определены, так как в классе SceneExport являются чисто-виртуальными), но всего одна служит для основной цели – экспорта. Остальные выполняют сервисные функции для правильного функционирования плагина. Давайте по порядку.

int  ExtCount (). Возвращает количество расширений файлов поддерживаемых плагином.
const TCHAR* Ext(i). В зависимости от передаваемого номера возвращает собственно расширение.
const TCHAR* LongDesc(). Длинное описание экспортируемого файла.
const TCHAR* ShortDesc(). Короткое описание экспортируемого файла.
const TCHAR*  AuthorName(). Имя автора плагина.
const TCHAR*  CopyrightMessage(). Тут ясно.
const TCHAR*  OtherMessage*(). Другие сообщения.
unsigned int Version(). Версия плагина умноженная на 100.
void ShowAbout(HWND hWnd). Информация про плагин.
BOOL SupportsOptions(int ext, DWORD options). Эту функцию можно не определять вообще. Она описывает поддерживаемые опции плагином. Наш, учебный, плагин не будет поддерживать никаких опций, так что simply return FALSE.

int DoExport(const TCHAR *name, ExpInterface *ei,

            Interface *i, BOOL suppressPromts = FALSE, DWORD options = 0);

А вот и главная функция. Она вызывается, когда происходит, собственно, экспорт. В качестве параметров МАХ передает: name – имя сохраняемого файла, ei – интерфейс доступа к геометрии сцены, i – интерфейс через который мы можем вызывать функции, предоставляемые 3d студией разработчику. suppressPromts – если TRUE плагин не должен выводить никаких диалоговых окон требующих ввода пользовательских данных. Такая функциональность требуется для выполнения действий в «тихом» режиме. options – передаваемые опции. Пока поддерживается только одна: SCENE_EXPORT_SELECTED – экспорт не всей сцены, а только выделенных элементов.

Страницы: 1 2 Следующая »

#3D Studio MAX, #плагины, #экспорт

18 сентября 2003 (Обновление: 15 июня 2009)

Комментарии [87]