С позиции пользователя код, в частности, должен решать две следующие задачи:
- создавать куб заданного размера после нажатия на кнопку Create;
- обеспечивать ввод в сцену куба посредством мыши.
Сгенерированное помощником решение включает два 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.
Код содержит определения следующих четырех классов:
- cube;
- cubeClassDesc;
- cubeKBDlgProc;
- cubeCreateCallBack.
На рис. 20 и 21 показаны иерархии классов, лежащих в основе классов cube и cubeClassDesc.
Рис. 20. Класс cube: иерархия родительских классов
Рис. 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 &clist,Tab &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 }
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 &clist, Tab &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-библиотека должна обеспечивать описанный в уроке функционал.
Построение DLL осуществляется после нажатия на F7 (меню Build — Build Solution).
