Список примеров

Создание SDK-плагина 3ds Max

Содержание

Введение

Рассматривается технологическая цепочка создания плагина 3ds Max в среде Microsoft Visual Studio с употреблением 3ds Max SDK и языка программирования C++. Также реализуется MAXScript-версия плагина. Предоставляемый материал – это лишь начальная точка на пути освоения техники разработки 3ds Max SDK-плагинов.
Плагин – это программный модуль, подключаемый к основной программе и либо реализующий часть функционала основной программы, либо расширяющий ее возможности.
Так, в 3ds Max к первой группе плагинов относятся стандартные плагины, хранящиеся в папке stdplugs и загружаемые при запуске приложения. Их список отображается в приведенном на рис. 1 диалоге Plug-in Manager (меню Customize – Plug-in Manager).

Plug-in Manager

Рис. 1. Plug-in Manager

Эти плагины, их около 350, реализуют значительную часть функционала 3ds Max. Например, плагин prim.dlo обеспечивает создание стандартных примитивов, сплайнов и стандартных источников света.
Прочие, нестандартные плагины предпочтительнее размещать в других папках и загружать по мере необходимости.
3ds Max SDK (Solution Development Kit, инструментарий разработчика) имеется в профессиональной версии 3ds Max и содержит заголовочные и библиотечные файлы, обеспечивающие доступ к классам и другим данным 3ds Max. Кроме того, в поставке имеется помощник и большое число примеров.
Плагин 3ds Max может быть написан на языке MAXScript и на языке C++ с употреблением 3ds Max SDK.
В первом случае плагин создается в среде 3ds Max, а во втором – в среде Microsoft Visual Studio.
Во многих случаях MAXScript может обеспечить такой же функционал, как и SDK. Однако скорость SDK-плагинов выше.
SDK-плагин – это откомпилированный и собранный dll-файл C++ (DLL, Dynamic Linked Library, динамически подключаемая библиотека). Рекомендованное расширение файла зависит от назначения плагина. В стандартной поставке 3ds Max плагины, оперирующие материалами и их картами, имеют расширение DLT, а плагинам, создающим объекты, дается расширение DLO, плагины-модификаторы выделяются расширением DLM и так далее.
Поставка 3ds Max SDK включает помощник создания плагинов Plug-in Wizard. Он поддерживает (или планирует поддерживать) создание около 40 следующих видов плагинов (в скобках указывается стандартное расширение):

После уяснения задачи написание SDK-плагина, как правило, предполагает создание надлежащего интерфейса пользователя и реализацию намеченных процедур. Например, плагин gSphere.dlo обеспечивает приведенный на рис. 2 интерфейс и программно поддерживает соответствующую реакцию приложения на предусмотренные интерфейсом действия.

Интерфейс плагина gSphere.dlo

Рис. 2. Пользовательский интерфейс плагина gSphere.dlo

При разработке SDK-плагинов каждая версия 3ds Max предполагает использование соответствующей версии Microsoft Visual Studio, что отражено в следующей таблице:

 Версия 
 3ds Max 
 Операционная система 
(32 и 64 бит) 
 Совместимые версии 
 3ds Max SDK 
 Microsoft Visual C++ версия компилятора
2011Windows 7
Windows Vista
Windows XP Pro SP2
2011, 2010 Visual C++ 9.0 (Visual Studio 2008) Service Pack 1
с установленным от 28 июля 2009 security patch
2010Windows Vista
Windows XP Pro SP2
2010Visual C++ 9.0 (Visual Studio 2008) Service Pack 1
2009Windows Vista
Windows XP Pro SP2
2009Visual C++ 8.0 (Visual Studio 2005) Service Pack 1

Постановка задачи

Порядок разработки плагина рассмотрим на следующем примере: создать плагин, формирующий примитив куб (Cube).
Такой простой объект отвечает цели работы, заключающейся в демонстрации технологической цепочки разработки SDK-плагинов.
Примитив имеет один параметр Size, для управления которым используется редактируемое поле со счетчиком (рис. 3).

Интерфейс плагина Cube

Рис. 3. Интерфейс плагина Cube

Как и другие примитивы, куб вводится в сцену мышкой с прижатой левой кнопкой.

MAXScript реализация плагина

Одну и ту же задачу можно решить средствами 3ds Max SDK и MAXScript. Для иллюстрации этого положения реализуем прежде плагин создания куба на MAXScript, дав плагину имя CubeMS.

plugin simpleObject CubeMS name:"CubeMS" classID:#(145340, 543210) category:"Scripted Primitives" (
 parameters main rollout:params (
  size type:#worldUnits ui:size default:0
 )
 rollout params "Cube Size" (
  spinner size "Size: " type:#worldunits range:[0, 100, 0] scale:1
 )
 on buildMesh do (
  h = 0.5 * size
  arrVrts = #([-h, -h, -h], [h, -h, -h], [-h, h, -h], [h, h, -h], \
   [-h, -h, h], [h, -h, h], [-h, h, h], [h, h, h])
  arrFcs = #([1, 3, 4], [4, 2, 1], [5, 6, 8], [8, 7, 5], \
   [1, 2, 6], [6, 5, 1], [2, 4, 8], [8, 6, 2], \
   [4, 3, 7], [7, 8, 4], [3, 1, 5], [5, 7, 3])
  setMesh mesh verts:arrVrts faces:arrFcs
  setFaceSmoothGroup mesh 1 2; setFaceNormal mesh 1 [0, 0, -1]
  setFaceSmoothGroup mesh 2 2; setFaceNormal mesh 2 [0, 0, -1]
  setFaceSmoothGroup mesh 3 4; setFaceNormal mesh 3 [0, 0, 1]
  setFaceSmoothGroup mesh 4 4; setFaceNormal mesh 4 [0, 0, 1]
  setFaceSmoothGroup mesh 5 8; setFaceNormal mesh 5 [0, -1, 0]
  setFaceSmoothGroup mesh 6 8; setFaceNormal mesh 6 [0, -1, 0]
  setFaceSmoothGroup mesh 7 16; setFaceNormal mesh 7 [1, 0, 0]
  setFaceSmoothGroup mesh 8 16; setFaceNormal mesh 8 [1, 0, 0]
  setFaceSmoothGroup mesh 9 32; setFaceNormal mesh 9 [0, 1, 0]
  setFaceSmoothGroup mesh 10 32; setFaceNormal mesh 10 [0, 1, 0]
  setFaceSmoothGroup mesh 11 64; setFaceNormal mesh 11 [-1, 0, 0]
  setFaceSmoothGroup mesh 12 64; setFaceNormal mesh 12 [-1, 0, 0]
  for k = 1 to 12 do setEdgeVis mesh k 3 false
 )
 tool create (
  on mousePoint click do
   case click of (
    1: nodeTM.Translation = gridPoint
    3: #stop
   )
  on mouseMove click do
   case click of (
    2: size = amax gridDist.X gridDist.Y
    3: size = gridDist.Z
   )
 )
)

Программа содержит секцию (Rollout) params, обеспечивающую задание размера куба (счетчик Size). Значение счетчика связано с одноименным параметром, заданным в блоке Parameters.
Инструмент создания куба (мышка) задается блоком Tool Create. После выбора позиции (событие mousePoint с параметром click = 1) определяется часть Translation (перемещение) матрицы аффинных преобразований куба (nodeTM.Translation = gridPoint). Далее при нажатой левой кнопке мыши фиксируется ее перемещение в видовом порте; величина перемещения определяет размер куба.
После освобождения мыши сцена может принять очередной куб.
Родительским классом куба является класс SimpleObject. Класс куба имеет имя CubeMS.
Для получения идентификатора класса classID следует употребить имеющуюся в папке ..\Autodesk\3ds Max 20хх SDK\maxsdk\help программу gencid.exe (рис. 4).

Пример генерации идентификатора класса плагина

Рис. 4. Генератор идентификатора класса плагина

Параметризованный куб создается обработчиком buildMesh в результате применения метода SetMesh, получающего массив arrVrts с координатами вершин примитива и массив его граней arrFcs. Размер куба определяется значением параметра Size. Сглаживающие группы и нормали куба заданы по аналогии с примитивом Box.
После копирования и запуска кода в MAXScript Editor, плагин будет доступен на вкладке Create – Geometry – Scripted Primitives (рис. 5).

Будет запущен плагин CubeMS

Рис. 5. Вызов плагина CubeMS

Категория (Scripted Primitives) и положение элемента в командном окне определяются параметрами выражения Plugin. Так, класс SimpleObject указывает на принадлежность примитива к геометрическим объектам, понятно и назначение свойства Category.
После загрузки плагина кубом можно оперировать средствами языка MAXScript, например:

delete $*
cb = cubeMS size:40
animate on at time 100 (
 cb.Size = 60
 rotate cb 360 [0, 0, 1]
)
playAnimation()

Заметим, что созданный объект не имеет текстурных координат, поэтому при употреблении материала следует позаботиться о создании таких координат, например при помощи модификатора UVWmap:

delete $*
cb = cubeMS size:50
addModifier cb (UVWmap maptype:4 ui:on)
chk = checker()
chk.Coordinates.U_Tiling = chk.Coordinates.V_Tiling = 2
std = standard diffuseMap:chk showInViewport:true diffuseMapEnable:true
cb.Material = std

Настройка помощника Plug-in Wizard

Необходимые для использования помощника файлы расположены в папке MAXSDK\Howto\3DSMaxPluginWizard.
Откроем в текстовом редакторе имеющийся в этой папке файл 3dsmaxPluginWizard.vsz и определим в нем следующее значение параметра:

Param="ABSOLUTE_PATH = C:\Program Files\Autodesk\3ds Max 2009 SDK\maxsdk\howto\3dsmaxPluginWizard"

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

и вставим их в VC/vcprojects директорию установки Microsoft Visual Studio (это может быть C:\Program Files\Microsoft Visual Studio 5\VC\vcprojects).
Этого достаточно, чтобы 3ds Max Plug-in Wizard оказался доступным как шаблон в Microsoft Visual Studio.
Проверим это, запустив Visual Studio и выбрав File > New:Projects > Visual C++ > 3ds Max Plug-in Wizard (рис. 6).

Настройка помощника Plug-in Wizard

Рис. 6. Создание проекта Visual C++ с помощью 3ds Max Plug-in Wizard

Порядок создания 3ds Max SDK-плагина

После разработки проекта плагина и оформления проекта, например, в виде технического задания запускается Microsoft Visual Studio, где и выполняются все последующие действия.
В проекте SDK-плагина полезно указать следующие характеристики:

 Характеристика утилиты  Пример значения 
Имя файлаcube.dlo
Идентификатор класса утилитыClass_ID(0xd667c5aa, 0xb65e9ddb)
Описание IDS_LIBDESCRIPTION"Cube"
Категория IDS_CATEGORY"SDK simple object"
Имя класса IDS_CLASS_NAME"Cube"

Интерфейс пользователя, предоставляемый плагином, сформируем в соответствии с рис. 7, добавив возможность ручного ввода (Keyboard Entry) примитива Cube.

Уточняем интерфейс плагина Cube

Рис. 7. Уточненный пользовательский интерфейс плагина

При вводе посредством нажатия на кнопку Create центр куба будет размещен в начале мировой системы координат.
Создадим теперь в Microsoft Visual Studio проект C++ Win32, компиляция и сборка которого (Compile and Link) обеспечат создание запрошенного плагина.
Проект создается как многониточная библиотека (Multy-threaded DLL). Используется гибридная (Hybrid) конфигурация, пригодная и для отладки (Debugging), и для построения готового решения (Solution).

Создание нового проекта

Запустим Visual Studio и используем для создания проекта помощник 3ds Max Plug-in Wizard (см. рис. 6). В поле Name введем имя Cube, а в поле Location укажем, например, имя папки C:\sdk. Снимем флажок Create directory for solution. Нажмем на ОК и в открывшемся диалоге выберем Procedural Objects (процедурные объекты, рис. 8).

Вид плагина Cube – Procedural Objects

Рис. 8. Выбор вида плагина

Нажмем на Next и введем в появившемся диалоге указанные на рис. 9 значения.

Базовый класс плагина Cube – SimpleObjects2

Рис. 9. Детализация описания плагина Cube. В качестве базового выбран класс SimpleObject2

Нажмем на Next и в появившемся диалоге проверим наличие указанных на рис. 10 значений.

Задание свойств Visual C++ проекта 3ds Max SDK-плагина Cube

Рис. 10. Некоторые детали проекта

В поле MAXSDK path указан путь

C:\Program Files\Autodesk\3ds Max 9 SDK\maxsdk

а в поле 3dsmax.exe path указан путь

C:\Program Files\Autodesk\3ds Max 2009

Ваши значения могут быть иными.
Нажмем на кнопку Finish.

Свойства (Properties) проекта Cube

Практически все свойства проекта Cube будут установлены помощником. Наша задача просмотреть эти свойства и внести незначительные коррективы.
На вкладке Solution Explorer расположится дерево решения (рис. 11).

Дерево решения проекта 3ds Max SDK-плагина Cube

Рис. 11. Дерево решения Cube

Первый заголовочный файл 3dsmaxsdk_preinclude.h удален, а файл cube.h приведен к следующему виду:

#include "Max.h"
#include "resource.h"
#include "istdplug.h"
#include "iparamb2.h"
#include "iparamm2.h"
#include "Simpobj.h"
extern TCHAR *GetString(int id);
extern HINSTANCE hInstance;

Удаление файла 3dsmaxsdk_preinclude.h потребует изъятия всех #pragma message из файла cube.cpp. Такие куски кода появятся в файле, если в качестве базового класса выбрать GeomObject.
Добавим теперь в проект конфигурацию Hybrid.
Выберем в дереве проекта вершину cube (см. рис. 11), нажмем на правую кнопку мыши и в появившемся меню выберем Properties.
В открывшемся окне нажмем на кнопку Configuration Manager и в списке Active Solution Configuration добавим новый вид конфигурации Hybrid; этот же вид выберем и в нижерасположенной таблице диалога (рис. 12).

В проект 3ds Max SDK-плагина Cube добавлена конфигурация Hybrid

Рис. 12. Добавление конфигурации Hybrid

Далее все настройки будут выполнены для этой конфигурации.
Ветвь Configuration Properties – General после внесенных изменений будет содержать следующие сведения (рис. 13):

Ветвь Configuration Properties – General

Рис. 13. Ветвь Configuration Properties – General

Переместимся в ветвь C++ – General. В поле Additional Include Directories проверим путь к include-директории 3ds Max SDK:

"C:\Program Files\Autodesk\3ds Max 2009 SDK\maxsdk\include"

В вашем случае путь может быть иным. Если путь содержит пробелы, то его нужно заключить в кавычки.
Прочие поля диалога оставим без изменений. Нажмем на кнопку Применить.
В ветви C++ – Command Line поле Additional options (дополнительные опции) вставим следующие значения:

/GR /we4706 /we4390 /we4557 /we4546 /we4545 /we4295 /we4310 /we4130 /we4611 /we4213 /we4121 /w34701 /wd4244 /wd4018

Они взяты из файла AdditionalCompilerOptions.txt, имеющегося в поставке 3ds Max SDK. Применить.
Имя выходного файла (создаваемого плагина) найдем в ветви Linker – General – Output File. В рассматриваемом примере помощник указал имя C:\sdk\cube.dlo.
Проверим путь к SDK библиотекам (Additional Library Directories):

"C:\Program Files\Autodesk\3ds Max 2009 SDK\maxsdk\lib"

Если путь имеет пробелы, то его следует заключить в кавычки. Применить.
Прочие значения свойств проекта оставим без изменений.

Файл ресурсов cube.rc

В этом файле создадим указанные на вышеприведенном рис. 7 диалоги Keyboard Entry и Parameters. Также отредактируем таблицу символов (String Table) с идентификаторами ресурса.
Перейдем в Visual Studio на вкладку Resource View. Откроем созданную помощником заготовку диалога IDD_PANEL, выберем форму диалога, в окне свойств изменим его идентификатор (свойство ID) на IDD_KBRD, а саму форму диалога приведем к следующему виду (рис. 14):

Форма диалога 3ds Max SDK-плагина Cube для ручного ввода примитива

Рис. 14. Форма диалога IDD_KBRD

Этот диалог будет употреблен для ручного ввода примитива. Его размер 108*63 единицы (пикселя).
Свойства (ID и Caption) существующих в заготовке элементов (текст, поле ввода и счетчик) установим в соответствии с рис 15.

Свойства элементов диалога 3ds Max SDK-плагина Cube

Рис. 15. Свойства элементов диалога IDD_KBRD

Ниже этих полей добавим кнопку класса CustButton (пользовательский класс). Это можно сделать, употребив инструмент Custom Control и приведя свойства (Caption, Class, ID и Style) добавленного элемента в соответствие с рис. 16.

Добавление и настройка кнопки Create 3ds Max SDK-плагина Cube

Рис. 16. Добавление и настройка кнопки Create

Выделим теперь в дереве файла cube.rc ветвь IDD_KBRD, выполним ее копирование и вставку. Изменим идентификатор добавленного диалога на IDD_PARAMS (рис. 17), а его форму и свойства его элементов установим в соответствии с рис. 18.

Поле со счетчиком для управления параметром Size плагина Cube

Рис. 17. Добавлен диалог IDD_PARAMS


Настройка элементов диалога IDD_PARAMS 3ds Max SDK-плагина Cube

Рис. 18. Свойства элементов диалога IDD_PARAMS

Диалог IDD_PARAMS отражает текущее значение параметра Size и может быть, в частности, употреблен для изменения размера куба на вкладке Modify.
Откроем теперь таблицу символов ресурса и приведем ее в соответствие с рис. 19.

Таблица символов ресурса 3ds Max SDK-плагина Cube

Рис. 19. Таблица символов ресурса cube.rc

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

Файл resource.h

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

#define IDS_LIBDESCRIPTION 1
#define IDS_CATEGORY 2
#define IDS_CLASS_NAME 3
#define IDS_KBRD 4
#define IDS_PARAMS 5
#define IDS_CB_SIZE 6
#define IDD_PARAMS 101
#define IDD_KBRD 102
#define IDC_KBSZ 1001
#define IDC_KBSZSPIN 1002
#define IDC_KBSTATIC 1003
#define IDC_CREATE 1004
#define IDC_SZSTATIC 1005
#define IDC_SZ 1490
#define IDC_SZSPIN 1496

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

Код файла cube.rc

Перейдем на вкладку Solution Explorer и просмотрим код файла cube.rc (выбрать имя cube.rc – правая кнопка мыши – View Code). Код должен, помимо прочих, содержать следующие определения:

// Dialog
//
IDD_KBRD DIALOGEX 0, 0, 108, 63
STYLE DS_SETFONT | WS_CHILD | WS_VISIBLE
FONT 8, "MS Sans Serif", 0, 0, 0x0
BEGIN

  CONTROL  "",IDC_KBSZ,"CustEdit",WS_TABSTOP,43,17,35,10
  CONTROL  "",IDC_KBSZSPIN,"SpinnerControl",0x0,79,17,7,10
  LTEXT  "Size",IDC_KBSTATIC,21,18,14,8
  CONTROL  "Create",IDC_CREATE,"CustButton",WS_TABSTOP,31,36,44,12

END

IDD_PARAMS DIALOGEX 0, 0, 108, 48
STYLE DS_SETFONT | WS_CHILD | WS_VISIBLE
FONT 8, "MS Sans Serif", 0, 0, 0x0
BEGIN

  CONTROL  "",IDC_SZ,"CustEdit",WS_TABSTOP,43,17,35,10
  CONTROL  "",IDC_SZSPIN,"SpinnerControl",0x0,79,17,7,10
  LTEXT  "Size",IDC_SZSTATIC,21,18,14,8

END


// String Table
//
STRINGTABLE
BEGIN

  IDS_LIBDESCRIPTION  "Cube"
  IDS_CATEGORY  "SDK simple object"
  IDS_CLASS_NAME  "Cube"
  IDS_KBRD  "Keyboard Entry"
  IDS_PARAMS  "Parameters"
  IDS_CB_SIZE  "Size"

END

После просмотра окно с кодом следует закрыть, чтобы не создавать препятствий для работы с ресурсом на вкладке Resource View.

Файл cube.def

Файл cube.def генерируется помощником. Он содержит имя dll-файла и используемые плагином виды определений (описание библиотеки, число классов, описание классов, версия библиотеки, инициализация и закрытие библиотеки):

LIBRARY cube.dlo
EXPORTS

  LibDescription  @1  PRIVATE
  LibNumberClasses  @2  PRIVATE
  LibClassDesc  @3  PRIVATE
  LibVersion  @4  PRIVATE
  LibInitialize  @6  PRIVATE
  LibShutdown  @7  PRIVATE

SECTIONS
  .data READ WRITE

При изменении в свойствах проекта имени библиотеки соответствующую правку нужно выполнить и в def-файле.

Основные характеристики примитива

Создаваемый куб имеет следующие особенности:

Код плагина cube.dlo

С позиции пользователя код, в частности, должен решать две следующие задачи:

Сгенерированное помощником решение включает два cpp-файла с исходным кодом – это DllEntry.cpp и cube.cpp. Первый файл содержит код, обслуживающий dll-библиотеку. Это файл в нашем случае практически не требует изменений:

#include "cube.h"
extern ClassDesc2 *GetCubeDesc();
HINSTANCE hInstance;
// Функция DllMain вызывается Windows при загрузке DLL
// Также функция может вызываться во время таких операций,
// как воспроизведение изображения (Rendering)
BOOL WINAPI DllMain(HINSTANCE hinstDLL, ULONG fdwReason, LPVOID) {
 if (fdwReason == DLL_PROCESS_ATTACH) {
  hInstance = hinstDLL;
  DisableThreadLibraryCalls(hInstance);
 }
 return TRUE;
}
// Возвращает строку с описанием DLL
__declspec( dllexport ) const TCHAR* LibDescription() {return GetString(IDS_LIBDESCRIPTION);}
// Возвращает число классов плагина
// В нашем случае DLL позволяет оперировать одним классом Cube
__declspec( dllexport ) int LibNumberClasses() {return 1;}
// Возвращает описание i-го класса плагина
__declspec( dllexport ) ClassDesc *LibClassDesc(int i) {
 switch(i) {
  case 0: return GetCubeDesc();
  default: return 0;
 }
}
__declspec( dllexport ) ULONG LibVersion() {return VERSION_3DSMAX;}
// Вызывается один раз при загрузке плагина в 3ds Max
// Если в качестве результата указать FALSE, то система не будет загружать плагин,
// а DLL будет интерпретироваться как свободная библиотека
__declspec( dllexport ) int LibInitialize(void) {return TRUE;}
// Вызывается один раз при выгрузке плагина из 3ds Max
// Возвращаемый результат приложением не используется
__declspec( dllexport ) int LibShutdown(void) {return TRUE;}
//
// Возвращает строку таблицы символов ресурса
TCHAR *GetString(int id) {
 static TCHAR buf[256];
 if (hInstance)
  return LoadString(hInstance, id, buf, sizeof(buf)) ? buf : NULL;
 return NULL;
}

Главная функция формирует DllMain hInstance – дескриптор экземпляра плагина, передаваемый файлу cube.cpp посредством заголовочного файла cube.h.
Имена declspec-функций файла отвечают имеющимся в def-файле определениям.
Функция GetString получает значение (Value) идентификатора ресурса и возвращает значение поля Caption таблицы символов (String Table) ресурса cube.rc проекта.
Код, обеспечивающий функционал плагина, размещен в файле cube.cpp.
Код перимущественно сформирован помощником и включает набор классов и функций (методов), необходимых для создания и управления процедурными объектами. При необходимости разработчик может добавить свои классы и методы, а также внести отвечающие цели проекта изменения в предоставленный помощником код.
Поскольку удален заголовочный файл 3dsmaxsdk_preinclude.h, то в файле cube.cpp не должны присутствовать #pragma message.
Код содержит определения следующих четырех классов:

На рис. 20 и 21 показаны иерархии классов, лежащих в основе классов cube и cubeClassDesc.

Иерархия родительских классов класса cube

Рис. 20. Класс cube: иерархия родительских классов

Иерархия родительских классов класса cubeClassDesc

Рис. 21. Класс cubeClassDesc: иерархия родительских классов

Класс cube обеспечивает создание и управление примитивом.
Класс cubeClassDesc обеспечивает регистрацию объекта в 3ds Max.
Класс cubeKBDlgProc отвечает за связь плагина с диалогом ручного создания куба: его функция DlgProc регистрирует нажатие на кнопку Create (IDC_CREATE) диалога и обеспечивает создание куба заданного размера с центром в начале мировой системы координат.
Класс cubeCreateCallBack отвечает за ввод в сцену примитива посредством мыши: его функция proc получает информацию о мышиных событиях – это сообщения 3ds Max с именами MOUSE_POINT, MOUSE_MOVE и MOUSE_ABORT и соответствующим образом реагирует на эти события. Метод SetObj класса ассоциирует созданную меш с кубом.
Обе функции (DlgProc и proc) употребляют метод BuildMesh класса cube, используя соответственно методы NonMouseCreate (класс IObjParam) и InvalidateUI (класс ParamBlockDesc2).
Прочие пояснения см. в комментариях к приводимому ниже коду. При этом прежде следует комментарий, а затем комментируемый код.

#include "cube.h"
#define cube_CLASS_ID Class_ID(0xd667c5aa, 0xb65e9ddb)
#define PBLOCK_REF 0
class cube : public SimpleObject2 {
 public:
  // Ссылка на интерфейс
  static IObjParam *ip;
  // Флаг ручного (по кнопке Create) ввода примитива
  static BOOL kbrdCreate;
  // Из класса BaseObject
  CreateMouseCallBack *GetCreateMouseCallBack();
  // Из класса Object
  BOOL HasUVW();
  void SetGenUVW(BOOL sw);
  int CanConvertToType(Class_ID obtype);
  Object *ConvertToType(TimeValue t, Class_ID obtype);
  void GetCollapseTypes(Tab<Class_ID> &clist,Tab<TSTR*> &nlist);
  // Из класса GeomObject
  int IntersectRay(TimeValue t, Ray &ray, float &at, Point3 &norm);
  // Возвращает структуру ObjectState
  ObjectState Eval(TimeValue t) {return ObjectState(this);};
  // Из класса Animatable
  void BeginEditParams(IObjParam *ip, ULONG flags, Animatable *prev);
  void EndEditParams(IObjParam *ip, ULONG flags, Animatable *next);
  // Из класса SimpleObject
  // Строит меш
  void BuildMesh(TimeValue t);
  // Проверяет корректность задания параметров объекта
  BOOL OKtoDisplay(TimeValue t);
  // Обновляет пользовательский интерфейс
  void InvalidateUI();
  // Загрузка и сохранение данных плагина
  IOResult Load(ILoad *iload);
  IOResult Save(ISave *isave);
  // Из класса Animatable
  Class_ID ClassID() {return cube_CLASS_ID;}
  SClass_ID SuperClassID() {return GEOMOBJECT_CLASS_ID;}
  void GetClassName(TSTR& s) {s = GetString(IDS_CLASS_NAME);}
  void DeleteThis() {delete this;}
  //
  RefTargetHandle Clone(RemapDir &remap);
  //
  // Получает из таблицы символов имя класса
  TCHAR *GetObjectName() {return GetString(IDS_CLASS_NAME);}
  // Число блоков параметров
  int NumParamBlocks() {return 1;}
  // Возвращает блок параметров по его номеру
  IParamBlock2 *GetParamBlock(int i) {return pblock2;}
  // Возвращает блок параметров по его идентификатору
  IParamBlock2 *GetParamBlockByID(BlockID id) {return (pblock2->ID() == id) ? pblock2 : NULL;}
  // Конструктор / Деструктор
  cube();
  ~cube();
};
class cubeClassDesc : public ClassDesc2 {
 public:
  int IsPublic() {return TRUE;}
  void *Create(BOOL) {return new cube();}
  const TCHAR *ClassName() {return GetString(IDS_CLASS_NAME);}
  SClass_ID SuperClassID() {return GEOMOBJECT_CLASS_ID;}
  Class_ID ClassID() {return cube_CLASS_ID;}
  const TCHAR *Category() {return GetString(IDS_CATEGORY);}
  const TCHAR *InternalName() {return _T("cube");}
  HINSTANCE HInstance() {return hInstance;}
};
static cubeClassDesc cubeDesc;
ClassDesc2 *GetCubeDesc() {return &cubeDesc;}
// Имена диалогов IDD_KBRD и IDD_PARAMS
enum {cube_kbrd, cube_params};
// Имена ассоцируемые с упраляющими элементами диалогов
// Элементы IDC_KBSZ, IDC_KBSZSPIN
enum {cube_kb_size};
// Элементы IDC_SZ, IDC_SZSPIN
enum {cube_size};
// Блок параметров диалога IDD_KBRD
static ParamBlockDesc2 cube_kbrd_blk (cube_kbrd, _T("cubeKbrd"), 0, &cubeDesc, P_CLASS_PARAMS + P_AUTO_UI,
 IDD_KBRD, IDS_KBRD, BEGIN_EDIT_CREATE, APPENDROLL_CLOSED, NULL,
 cube_kb_size, _T("kbSize"), TYPE_FLOAT, 0, IDS_CB_SIZE,
  p_default, 40.0, p_range, 0.0f, 100.0f,
  p_ui, TYPE_SPINNER, EDITTYPE_UNIVERSE, IDC_KBSZ, IDC_KBSZSPIN, 0.1f,
  end,
 end
);
// Блок параметров диалога IDD_PARAMS
static ParamBlockDesc2 cube_param_blk (cube_params, _T("params"), 0, &cubeDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, PBLOCK_REF,
 IDD_PARAMS, IDS_PARAMS, 0, 0, NULL,
  cube_size, _T("size"), TYPE_FLOAT, P_ANIMATABLE, IDS_CB_SIZE,
  p_default, 0.0f, p_range, 0.0f, 100.0f,
  p_ui, TYPE_SPINNER, EDITTYPE_UNIVERSE, IDC_SZ, IDC_SZSPIN, 0.1f,
  end,
 end
);
// Инициализация свойств ip и kbrdCreate класса cube
IObjParam *cube::ip = NULL;
BOOL cube::kbrdCreate = FALSE;
// Создаем диалог с P_AUTO_CONSTRUCT-блоком параметров
// Диалог IDD_KBRD будет создан при обращении cubeDesc.BeginEditParams
cube::cube() {cubeDesc.MakeAutoParamBlocks(this);}
cube::~cube() { }
IOResult cube::Load(ILoad *iload) {return IO_OK;}
IOResult cube::Save(ISave *isave) {return IO_OK;}
class cubeKBDlgProc : public ParamMap2UserDlgProc {
 public:
  cube *ob;
  cubeKBDlgProc(cube *cb) {ob = cb;}
  INT_PTR DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
  void DeleteThis() {delete this;}
};
// Обеспечивает создание куба после нажатия на кнопку Create диалога IDD_KBRD
INT_PTR cubeKBDlgProc::DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
 if (msg != WM_COMMAND) return FALSE;
 if (LOWORD(wParam) != IDC_CREATE) return FALSE;
 float cbSize = cube_kbrd_blk.GetFloat(cube_kb_size);
 if (cbSize == 0.0) return TRUE;
 // Устанавливаем IDC_SZ = IDC_KBSZ
 if (ob->TestAFlag(A_OBJ_CREATING)) ob->pblock2->SetValue(cube_size, 0, cbSize);
 // Флаг ручного ввода
 ob->kbrdCreate = TRUE;
 // Формируем единичную матрицу
 Matrix3 tm(1);
 // Устанавливаем в матрице порцию Translate (перемещение) аффинных преобразований
 tm.SetTrans(Point3(0, 0, 0));
 ob->suspendSnap = FALSE;
 // Формируем куб
 ob->ip->NonMouseCreate(tm);
 return TRUE;
}
// Вызывается при создании очередного экземпляра куба (из класса Animatable)
void cube::BeginEditParams(IObjParam *ip,ULONG flags, Animatable *prev) {
 SimpleObject::BeginEditParams(ip, flags, prev);
 this->ip = ip;
 if (kbrdCreate) {
  // Если ранее был выполнен ручной ввод, то устанавливаем IDC_SZ = IDC_KBSZ
  pblock2->SetValue(cube_size, 0, cube_kbrd_blk.GetFloat(cube_kb_size));
  kbrdCreate = FALSE;
 }
 cubeDesc.BeginEditParams(ip, this, flags, prev);
 // Фиксируем пользовательскую процедуру,
 // ассоциированную с блоком параметров cube_kbrd_blk
 cube_kbrd_blk.SetUserDlgProc(new cubeKBDlgProc(this));
}
void cube::EndEditParams(IObjParam *ip, ULONG flags, Animatable *next) {
 SimpleObject::EndEditParams(ip, flags, next);
 cubeDesc.EndEditParams(ip, this, flags, next);
 // Плагин должен вызывать методы интерфейса ip только
 // между BeginEditParams и EndEditParams
 this->ip = NULL;
}
// Из класса Object
// Вернуть флаг наличия у объекта UVW-координат
BOOL cube::HasUVW() {return TRUE;}
// Можно модифицировать, исходя из целей проекта
void cube::SetGenUVW(BOOL sw) {if (sw == HasUVW()) return;}
// Класс обработки мышиных событий
class cubeCreateCallBack : public CreateMouseCallBack {
 cube *ob;  // Указатель на объект
 Point3 p0;  // Первая точка в мировой системе координат
 Point3 p1;  // Вторая точка в мировой системе координат
 public:
  int proc(ViewExp *vpt, int msg, int point, int flags, IPoint2 m, Matrix3 &mat);
  void SetObj(cube *cb) {ob = cb;}
};
int cubeCreateCallBack::proc(ViewExp *vpt, int msg, int point, int flags, IPoint2 m, Matrix3 &mat){
 if (msg == MOUSE_POINT || msg == MOUSE_MOVE) {
  switch(point) {
   case 0:
    ob->suspendSnap = TRUE;
    // m - позиция мыши в оконных координатах
    // p0 - позиция мыши в мировых координатах
    p0 = vpt->SnapPoint(m, m, NULL, SNAP_IN_PLANE);
    // Порция Translate (перемещение) аффинных преобразований позиции
    mat.SetTrans(p0);
    // Изменяем значение параметра cube_size диалога IDD_PARAMS
    ob->pblock2->SetValue(cube_size, ob->ip->GetTime(), 0.0f);
    break;
   case 1: {
    ob->suspendSnap = TRUE;
    // p1 - новая позиция мыши в мировых координатах
    p1 = vpt->SnapPoint(m, m, NULL, SNAP_IN_PLANE);
    // Управляем размером куба в зависимости от положения мыши
    ob->pblock2->SetValue(cube_size, ob->ip->GetTime(), 0.5f * Length(p1 - p0));
    // Создаем и отображаем меш в видовом порте
    cube_param_blk.InvalidateUI();
    break;
   }
   case 2:
    return CREATE_STOP;
  }
 }
 else
  if (msg == MOUSE_ABORT) return CREATE_ABORT;
 return TRUE;
}
static cubeCreateCallBack cubeCreateCB;
CreateMouseCallBack *cube::GetCreateMouseCallBack() {
 cubeCreateCB.SetObj(this);
 return &cubeCreateCB;
}
// Получает размер куба, строит его меш и формирует группы сглаживания
void cube::BuildMesh(TimeValue t) {
 float size, h;
 ivalid = FOREVER;
 pblock2->GetValue(cube_size, t, size, ivalid);
 h = 0.5 * size;
 // Число вершин и граней в меш
 mesh.setNumVerts(8);
 mesh.setNumFaces(12);
 // Координаты вершин
 mesh.setVert(0, Point3(-h, -h, -h));
 mesh.setVert(1, Point3(h, -h, -h));
 mesh.setVert(2, Point3(-h, h, -h));
 mesh.setVert(3, Point3(h, h, -h));
 mesh.setVert(4, Point3(-h, -h, h));
 mesh.setVert(5, Point3(h, -h, h));
 mesh.setVert(6, Point3(-h, h, h));
 mesh.setVert(7, Point3(h, h, h));
 // Состав граней
 mesh.faces[0].setVerts(0, 2, 3);
 mesh.faces[1].setVerts(3, 1, 0);
 mesh.faces[2].setVerts(4, 5, 7);
 mesh.faces[3].setVerts(7, 6, 4);
 mesh.faces[4].setVerts(0, 1, 5);
 mesh.faces[5].setVerts(5, 4, 0);
 mesh.faces[6].setVerts(1, 3, 7);
 mesh.faces[7].setVerts(7, 5, 1);
 mesh.faces[8].setVerts(3, 2, 6);
 mesh.faces[9].setVerts(6, 7, 3);
 mesh.faces[10].setVerts(2, 0, 4);
 mesh.faces[11].setVerts(4, 6, 2);
 // Группы сглаживания
 mesh.faces[0].setSmGroup(2);
 mesh.faces[1].setSmGroup(2);
 mesh.faces[2].setSmGroup(4);
 mesh.faces[3].setSmGroup(4);
 mesh.faces[4].setSmGroup(8);
 mesh.faces[5].setSmGroup(8);
 mesh.faces[6].setSmGroup(16);
 mesh.faces[7].setSmGroup(16);
 mesh.faces[8].setSmGroup(32);
 mesh.faces[9].setSmGroup(32);
 mesh.faces[10].setSmGroup(64);
 mesh.faces[11].setSmGroup(64);
 // Устанавливаем видимость внешних ребер граней (диагональные ребра не видны)
 for (int k = 0; k < 12; k++) {
  mesh.faces[k].setEdgeVisFlags(1, 1, 0);
  mesh.faces[k].setMatID(1);
 }
 // Назначаем UVW-координаты
 Matrix3 tm(1);
 tm.Scale(Point3(h, h, h));
 tm = Inverse(tm);
 mesh.ApplyUVWMap(MAP_BOX, 1.0f, 1.0f, 1.0f, 0, 0, 0, 0, tm);
 mesh.InvalidateTopologyCache();
}
// Добавлен код проверки корректности параметра size
BOOL cube::OKtoDisplay(TimeValue t) {
 float size;
 pblock2->GetValue(cube_size, t, size, FOREVER);
 return (size <= 0.0f) ? FALSE : TRUE;
}
void cube::InvalidateUI() {cube_param_blk.InvalidateUI(pblock2->LastNotifyParamID());}
// Находит точку пересечения луча ray с поверхностью (см. класс Ray)
// и нормаль к поверхности в этой точке
int cube::IntersectRay(TimeValue t, Ray &ray, float &at, Point3 &norm) {
 return SimpleObject::IntersectRay(t, ray, at, norm);
}
// Методы, обеспечивающие преобразование и копирование объекта
Object* cube::ConvertToType(TimeValue t, Class_ID obtype) {
 return SimpleObject::ConvertToType(t, obtype);
}
int cube::CanConvertToType(Class_ID obtype) {
 if (obtype == defObjectClassID || obtype == triObjectClassID)
  return 1;
 else
  return SimpleObject::CanConvertToType(obtype);
}
void cube::GetCollapseTypes(Tab<Class_ID> &clist, Tab<TSTR*> &nlist) {
 Object::GetCollapseTypes(clist, nlist);
}
RefTargetHandle cube::Clone(RemapDir &remap) {
 cube *newob = new cube();
 newob->ReplaceReference(0, remap.CloneRef(pblock2));
 newob->ivalid.SetEmpty();
 BaseClone(this, newob, remap);
 return newob;
}

Приведенный код можно скопировать в созданный помощником проект Visual Studio. При этом следует копировать коды обоих файлов – и DllEntry.cpp, и cube.cpp.
Построение DLL осуществляется после нажатия на F7 (меню Build - Build Solution).

Загрузка и вызов плагина

Созданный плагин, файл cube.dlo следует загружать в начале сеанса работы с 3ds Max, открыв меню Customize – Plug-in Manager – табличная часть диалога – правая кнопка мыши – Load New Plug-in – найти загружаемый файл.
Загруженный плагин доступен в командном окне: вкладка Create – Geometry – SDK simple object (рис. 22).

Вызываем плагин Cube

Рис. 22. Вызов плагина Cube

Если загружать плагин не вначале сеанса, а позже, то может возникнуть указанная на рис. 23 ошибка.

Ошибка инициализации SDK-плагина

Рис. 23. Ошибка инициализации плагина

Заключение

Создание дополнительного инструмента 3ds Max – это достаточно трудоемкая работа. Поэтому должна быть серьезная мотивация для ее выполнения. Например, наличие идеи расширения функционала 3ds Max, интересной широкому кругу пользователей приложения. Удачное расширение может быть либо направлено Autodesk и затем включено в состав его стандартной поставки, либо тиражироваться как самостоятельное решение на условиях, приемлемых и для разработчика, и для потребителя.
При реализации расширения у разработчика имеется выбор между MAXScript и 3ds Max SDK.

Источники

  1. Autodesk® 3ds Max® 2009 MAXScript Reference.
  2. Autodesk® 3ds Max® 2009 SDK Help.
  3. Бартеньев О. В. Программирование модификаторов 3ds Max. Учебно-справочное пособие. – М.:Физматкнига, 2009. – 341 с.

Список примеров

Рейтинг@Mail.ru