/* maxEggExpOptions.cxx Created by Phillip Saltzman, 2/15/05 Carnegie Mellon University, Entetainment Technology Center This file implements the classes that are used to choose what to export from 3D Studio max */ #include "maxEgg.h" //Disable the forcing int to true or false performance warning #pragma warning(disable: 4800) void SetICustEdit(HWND wnd, int nIDDlgItem, char *text) { ICustEdit *edit = GetICustEdit(GetDlgItem(wnd, nIDDlgItem)); edit->SetText(text); ReleaseICustEdit(edit); } void SetICustEdit(HWND wnd, int nIDDlgItem, int n) { char text[80]; sprintf(text, "%d", n); ICustEdit *edit = GetICustEdit(GetDlgItem(wnd, nIDDlgItem)); edit->SetText(text); ReleaseICustEdit(edit); } char *GetICustEditT(HWND wnd) { static char buffer[2084]; ICustEdit *edit = GetICustEdit(wnd); edit->GetText(buffer,2084); ReleaseICustEdit(edit); return buffer; } int GetICustEditI(HWND wnd, BOOL *valid) { ICustEdit *edit = GetICustEdit(wnd); int i = edit->GetInt(valid); ReleaseICustEdit(edit); return i; } void ChunkSave(ISave *isave, int chunkid, int value) { ULONG nb; isave->BeginChunk(chunkid); isave->Write(&value, sizeof(int), &nb); isave->EndChunk(); } void ChunkSave(ISave *isave, int chunkid, ULONG value) { ULONG nb; isave->BeginChunk(chunkid); isave->Write(&value, sizeof(ULONG), &nb); isave->EndChunk(); } void ChunkSave(ISave *isave, int chunkid, bool value) { ULONG nb; isave->BeginChunk(chunkid); isave->Write(&value, sizeof(bool), &nb); isave->EndChunk(); } void ChunkSave(ISave *isave, int chunkid, char *value) { ULONG nb; isave->BeginChunk(chunkid); int bytes = strlen(value) + 1; isave->Write(&bytes, sizeof(int), &nb); isave->Write(value, bytes, &nb); isave->EndChunk(); } char *ChunkLoadString(ILoad *iload, char *buffer, int maxBytes) { ULONG nb; int bytes; IOResult res; res = iload->Read(&bytes, sizeof(int), &nb); assert(res == IO_OK && bytes <= maxBytes); res = iload->Read(buffer, bytes, &nb); assert(res == IO_OK); return buffer; } int ChunkLoadInt(ILoad *iload) { ULONG nb; int value; IOResult res; res = iload->Read(&value, sizeof(int), &nb); assert(res == IO_OK); return value; } int ChunkLoadULONG(ILoad *iload) { ULONG nb, value; IOResult res; res = iload->Read(&value, sizeof(ULONG), &nb); assert(res == IO_OK); return value; } bool ChunkLoadBool(ILoad *iload) { ULONG nb; bool value; IOResult res; res = iload->Read(&value, sizeof(bool), &nb); assert(res == IO_OK); return value; } void showAnimControls(HWND hWnd, BOOL val) { ShowWindow(GetDlgItem(hWnd, IDC_EF_LABEL), val); ShowWindow(GetDlgItem(hWnd, IDC_EF), val); ShowWindow(GetDlgItem(hWnd, IDC_SF_LABEL), val); SetWindowText(GetDlgItem(hWnd, IDC_EXP_SEL_FRAMES), val ? "Use Range:" : "Use Frame:"); } void enableAnimControls(HWND hWnd, BOOL val) { EnableWindow(GetDlgItem(hWnd, IDC_SF_LABEL), val); EnableWindow(GetDlgItem(hWnd, IDC_SF), val); EnableWindow(GetDlgItem(hWnd, IDC_EF_LABEL), val); EnableWindow(GetDlgItem(hWnd, IDC_EF), val); } void enableChooserControls(HWND hWnd, BOOL val) { EnableWindow(GetDlgItem(hWnd, IDC_LIST_EXPORT), val); EnableWindow(GetDlgItem(hWnd, IDC_ADD_EXPORT), val); EnableWindow(GetDlgItem(hWnd, IDC_REMOVE_EXPORT), val); } #define ANIM_RAD_NONE 0 #define ANIM_RAD_EXPALL 1 #define ANIM_RAD_EXPSEL 2 #define ANIM_RAD_ALL 3 void enableAnimRadios(HWND hWnd, int mask) { EnableWindow(GetDlgItem(hWnd, IDC_EXP_ALL_FRAMES), mask & ANIM_RAD_EXPALL); EnableWindow(GetDlgItem(hWnd, IDC_EXP_SEL_FRAMES), mask & ANIM_RAD_EXPSEL); } // Handles the work of actually picking the target. class AddNodeCB : public HitByNameDlgCallback { public: MaxOptionsDialog *ph; //Pointer to the parent class HWND hWnd; //Handle to the parent dialog AddNodeCB (MaxOptionsDialog *instance, HWND wnd) : ph(instance), hWnd(wnd) {} virtual TCHAR *dialogTitle() {return _T("Objects to Export");} virtual TCHAR *buttonText() {return _T("Select");} virtual int filter(INode *node); virtual void proc(INodeTab &nodeTab); }; //This tells what should be in the list //Allow only triangular objects, nurbs, and joints int AddNodeCB::filter(INode *node) { if (!node) return 0; Object *obj = node->EvalWorldState(0).obj; Control *c = node->GetTMController(); NURBSSet getSet; bool is_bone = (node->GetBoneNodeOnOff() || //True for bones (c && //True for bipeds ((c->ClassID() == BIPSLAVE_CONTROL_CLASS_ID) || (c->ClassID() == BIPBODY_CONTROL_CLASS_ID) || (c->ClassID() == FOOTPRINT_CLASS_ID)))); if (IsDlgButtonChecked(hWnd, IDC_ANIMATION) == BST_CHECKED) return is_bone && !ph->FindNode(node->GetHandle()); else return ( is_bone || ((obj->SuperClassID() == GEOMOBJECT_CLASS_ID && //Allow geometrics obj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID, 0))) || (obj->SuperClassID() == SHAPE_CLASS_ID && //Allow CV NURBS obj->ClassID() == EDITABLE_SURF_CLASS_ID && GetNURBSSet(obj, 0, getSet, TRUE) && getSet.GetNURBSObject(0)->GetType() == kNCVCurve)) && !ph->FindNode(node->GetHandle())); //Only allow items not already selected } //Adds all of the selected items to the list void AddNodeCB::proc(INodeTab &nodeTab) { for (int i = 0; i < nodeTab.Count(); i++) ph->AddNode(nodeTab[i]->GetHandle()); ph->RefreshNodeList(hWnd); } //This callback class generates a list of nodes that have previously been selected class RemoveNodeCB : public HitByNameDlgCallback { public: MaxOptionsDialog *ph; //Pointer to the parent class HWND hWnd; //Handle to the parent dialog RemoveNodeCB (MaxOptionsDialog *instance, HWND wnd) : ph(instance), hWnd(wnd) {} virtual TCHAR *dialogTitle() {return _T("Objects to Remove");} virtual TCHAR *buttonText() {return _T("Remove");} virtual int filter(INode *node) {return (node && ph->FindNode(node->GetHandle()));} virtual void proc(INodeTab &nodeTab); }; //Adds all of the selected items to the list void RemoveNodeCB::proc(INodeTab &nodeTab) { for (int i = 0; i < nodeTab.Count(); i++) ph->RemoveNodeByHandle(nodeTab[i]->GetHandle()); ph->RefreshNodeList(hWnd); } MaxEggOptions::MaxEggOptions() { _max_interface = NULL; _anim_type = MaxEggOptions::AT_model; _start_frame = INT_MIN; _end_frame = INT_MIN; _double_sided = false; _file_name[0]=0; _short_name[0]=0; _path_replace = new PathReplace; _path_replace->_path_store = PS_relative; _export_whole_scene = true; _export_all_frames = true; _successful = false; } BOOL CALLBACK MaxOptionsDialogProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { char tempFilename[2048]; //We pass in our plugin through the lParam variable. Let's convert it back. MaxOptionsDialog *imp = (MaxOptionsDialog*)GetWindowLongPtr(hWnd,GWLP_USERDATA); if ( !imp && message != WM_INITDIALOG ) return FALSE; switch(message) { case WM_INITDIALOG: // this line is very necessary to pass the plugin as the lParam SetWindowLongPtr(hWnd,GWLP_USERDATA,lParam); ((MaxOptionsDialog*)lParam)->UpdateUI(hWnd); return TRUE; break; case WM_CLOSE: EndDialog(hWnd, FALSE); return TRUE; break; case WM_COMMAND: //The modified control is found in the lower word of the wParam long. switch( LOWORD(wParam) ) { case IDC_MODEL: if (HIWORD(wParam) == BN_CLICKED) { SetWindowText(GetDlgItem(hWnd, IDC_EXPORT_SELECTED), "Export Meshes:"); enableAnimRadios(hWnd, ANIM_RAD_NONE); showAnimControls(hWnd, TRUE); enableAnimControls(hWnd, FALSE); if (imp->_prev_type == MaxEggOptions::AT_chan) imp->ClearNodeList(hWnd); imp->_prev_type = MaxEggOptions::AT_model; return TRUE; } break; case IDC_ANIMATION: if (HIWORD(wParam) == BN_CLICKED) { SetWindowText(GetDlgItem(hWnd, IDC_EXPORT_SELECTED), "Export Bones:"); enableAnimRadios(hWnd, ANIM_RAD_ALL); showAnimControls(hWnd, TRUE); enableAnimControls(hWnd, IsDlgButtonChecked(hWnd, IDC_EXP_SEL_FRAMES)); if (imp->_prev_type != MaxEggOptions::AT_chan) imp->ClearNodeList(hWnd); imp->_prev_type = MaxEggOptions::AT_chan; return TRUE; } break; case IDC_BOTH: if (HIWORD(wParam) == BN_CLICKED) { SetWindowText(GetDlgItem(hWnd, IDC_EXPORT_SELECTED), "Export Models:"); enableAnimRadios(hWnd, ANIM_RAD_ALL); showAnimControls(hWnd, TRUE); enableAnimControls(hWnd, IsDlgButtonChecked(hWnd, IDC_EXP_SEL_FRAMES)); if (imp->_prev_type == MaxEggOptions::AT_chan) imp->ClearNodeList(hWnd); imp->_prev_type = MaxEggOptions::AT_both; return TRUE; } break; case IDC_POSE: if (HIWORD(wParam) == BN_CLICKED) { SetWindowText(GetDlgItem(hWnd, IDC_EXPORT_SELECTED), "Export Meshes:"); enableAnimRadios(hWnd, ANIM_RAD_EXPSEL); showAnimControls(hWnd, FALSE); enableAnimControls(hWnd, TRUE); CheckRadioButton(hWnd, IDC_EXP_ALL_FRAMES, IDC_EXP_SEL_FRAMES, IDC_EXP_SEL_FRAMES); if (imp->_prev_type == MaxEggOptions::AT_chan) imp->ClearNodeList(hWnd); imp->_prev_type = MaxEggOptions::AT_pose; return TRUE; } break; case IDC_EXP_ALL_FRAMES: if (HIWORD(wParam) == BN_CLICKED) { enableAnimControls(hWnd, FALSE); return TRUE; } break; case IDC_EXP_SEL_FRAMES: if (HIWORD(wParam) == BN_CLICKED) { enableAnimControls(hWnd, TRUE); return TRUE; } break; case IDC_EXPORT_ALL: if (HIWORD(wParam) == BN_CLICKED) { enableChooserControls(hWnd, FALSE); return TRUE; } break; case IDC_EXPORT_SELECTED: if (HIWORD(wParam) == BN_CLICKED) { enableChooserControls(hWnd, TRUE); return TRUE; } break; case IDC_ADD_EXPORT: { if (!imp->_choosing_nodes) { AddNodeCB PickCB(imp, hWnd); imp->_choosing_nodes = true; imp->_max_interface->DoHitByNameDialog(&PickCB); imp->_choosing_nodes = false; } } return TRUE; break; case IDC_REMOVE_EXPORT: { if (!imp->_choosing_nodes) { imp->_choosing_nodes = true; RemoveNodeCB PickCB(imp, hWnd); imp->_max_interface->DoHitByNameDialog(&PickCB); imp->_choosing_nodes = false; } } return TRUE; break; case IDC_OK: if (imp->UpdateFromUI(hWnd)) EndDialog(hWnd, TRUE); return TRUE; break; case IDC_CANCEL: EndDialog(hWnd, FALSE); return TRUE; break; case IDC_BROWSE: OPENFILENAME ofn; strcpy(tempFilename, GetICustEditT(GetDlgItem(hWnd, IDC_FILENAME))); memset(&ofn, 0, sizeof(ofn)); ofn.nMaxFile = 2047; ofn.lpstrFile = tempFilename; ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = hWnd; ofn.Flags = OFN_HIDEREADONLY | OFN_NOREADONLYRETURN | OFN_PATHMUSTEXIST; ofn.lpstrDefExt = "egg"; ofn.lpstrFilter = "Panda3D Egg Files (*.egg)\0*.egg\0All Files (*.*)\0*.*\0"; SetFocus(GetDlgItem(hWnd, IDC_FILENAME)); if (GetSaveFileName(&ofn)) SetICustEdit(hWnd, IDC_FILENAME, ofn.lpstrFile); //else { // char buf[255]; // sprintf(buf, "%d", CommDlgExtendedError()); // MessageBox(hWnd, buf, "Error on GetSaveFileName", MB_OK); //} return TRUE; break; case IDC_CHECK1: if (IsDlgButtonChecked(hWnd, IDC_CHECK1)) if (MessageBox(hWnd, "Warning: Exporting double-sided polygons can severely hurt " "performance in Panda3D.\n\nAre you sure you want to export them?", "Panda3D Exporter", MB_YESNO | MB_ICONQUESTION) != IDYES) CheckDlgButton(hWnd, IDC_CHECK1, BST_UNCHECKED); return TRUE; break; default: //char buf[255]; //sprintf(buf, "%d", LOWORD(wParam)); //MessageBox(hWnd, buf, "Unknown WParam", MB_OK); break; } } return FALSE; } void MaxOptionsDialog::SetAnimRange() { // Get the start and end frames and the animation frame rate from Max Interval anim_range = _max_interface->GetAnimRange(); _min_frame = anim_range.Start()/GetTicksPerFrame(); _max_frame = anim_range.End()/GetTicksPerFrame(); } MaxOptionsDialog::MaxOptionsDialog() : MaxEggOptions(), _min_frame(0), _max_frame(0), _checked(true), _choosing_nodes(false), _prev_type(AT_model) { } MaxOptionsDialog::~MaxOptionsDialog () { } void MaxOptionsDialog::UpdateUI(HWND hWnd) { int typeButton = IDC_MODEL; int anim_exp = _export_all_frames ? IDC_EXP_ALL_FRAMES : IDC_EXP_SEL_FRAMES; int model_exp = _export_whole_scene ? IDC_EXPORT_ALL : IDC_EXPORT_SELECTED; switch (_anim_type) { case MaxEggOptions::AT_chan: typeButton = IDC_ANIMATION; break; case MaxEggOptions::AT_both: typeButton = IDC_BOTH; break; case MaxEggOptions::AT_pose: typeButton = IDC_POSE; break; case MaxEggOptions::AT_model: typeButton = IDC_MODEL; break; } _prev_type = _anim_type; CheckRadioButton(hWnd, IDC_MODEL, IDC_POSE, typeButton); SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(typeButton, BN_CLICKED), 0); CheckRadioButton(hWnd, IDC_EXPORT_ALL, IDC_EXPORT_SELECTED, model_exp); SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(model_exp, BN_CLICKED), 0); CheckRadioButton(hWnd, IDC_EXP_ALL_FRAMES, IDC_EXP_SEL_FRAMES, anim_exp); SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(anim_exp, BN_CLICKED), 0); CheckDlgButton(hWnd, IDC_CHECK1, _double_sided ? BST_CHECKED : BST_UNCHECKED); SetICustEdit(hWnd, IDC_FILENAME, _file_name); if (_start_frame != INT_MIN) { SetICustEdit(hWnd, IDC_SF, _start_frame); SetICustEdit(hWnd, IDC_EF, _end_frame); } else { SetICustEdit(hWnd, IDC_SF, _min_frame); SetICustEdit(hWnd, IDC_EF, _max_frame); } RefreshNodeList(hWnd); } void MaxOptionsDialog::ClearNodeList(HWND hWnd) { _node_list.clear(); RefreshNodeList(hWnd); } void MaxOptionsDialog::RefreshNodeList(HWND hWnd) { //Clear and repopulate the node box HWND nodeLB = GetDlgItem(hWnd, IDC_LIST_EXPORT); SendMessage(nodeLB, LB_RESETCONTENT, 0, 0); for (int i = 0; i < _node_list.size(); i++) { INode *temp = _max_interface->GetINodeByHandle(_node_list[i]); TCHAR *name = _T("Unknown Node"); if (temp) name = temp->GetName(); SendMessage(nodeLB, LB_ADDSTRING, 0, (LPARAM)name); } } bool MaxOptionsDialog::UpdateFromUI(HWND hWnd) { BOOL valid; Anim_Type newAnimType; int newSF = INT_MIN, newEF = INT_MIN; char msg[1024]; if (IsDlgButtonChecked(hWnd, IDC_MODEL)) newAnimType = MaxEggOptions::AT_model; else if (IsDlgButtonChecked(hWnd, IDC_ANIMATION)) newAnimType = MaxEggOptions::AT_chan; else if (IsDlgButtonChecked(hWnd, IDC_BOTH)) newAnimType = MaxEggOptions::AT_both; else newAnimType = MaxEggOptions::AT_pose; if (newAnimType != MaxEggOptions::AT_model && IsDlgButtonChecked(hWnd, IDC_EXP_SEL_FRAMES)) { newSF = GetICustEditI(GetDlgItem(hWnd, IDC_SF), &valid); if (!valid) { MessageBox(hWnd, "Start Frame must be an integer", "Invalid Value", MB_OK | MB_ICONEXCLAMATION); return false; } if (newSF < _min_frame) { sprintf(msg, "Start Frame must be at least %d", _min_frame); MessageBox(hWnd, msg, "Invalid Value", MB_OK | MB_ICONEXCLAMATION); return false; } if (newSF > _max_frame) { sprintf(msg, "Start Frame must be at most %d", _max_frame); MessageBox(hWnd, msg, "Invalid Value", MB_OK | MB_ICONEXCLAMATION); return false; } if (newAnimType != MaxEggOptions::AT_pose) { newEF = GetICustEditI(GetDlgItem(hWnd, IDC_EF), &valid); if (!valid) { MessageBox(hWnd, "End Frame must be an integer", "Invalid Value", MB_OK | MB_ICONEXCLAMATION); return false; } if (newEF > _max_frame) { sprintf(msg, "End Frame must be at most %d", _max_frame); MessageBox(hWnd, msg, "Invalid Value", MB_OK | MB_ICONEXCLAMATION); return false; } if (newEF < newSF) { MessageBox(hWnd, "End Frame must be greater than the start frame", "Invalid Value", MB_OK | MB_ICONEXCLAMATION); return false; } } } char *temp = GetICustEditT(GetDlgItem(hWnd, IDC_FILENAME)); if (!strlen(temp)) { MessageBox(hWnd, "The filename cannot be empty", "Invalid Value", MB_OK | MB_ICONEXCLAMATION); return false; } if (strlen(temp) < 4 || strncmp(".egg", temp+(strlen(temp) - 4), 4)) sprintf(_file_name, "%s.egg", temp); else strcpy(_file_name, temp); temp = strrchr(_file_name, '\\'); if (!temp) temp = _file_name; else temp++; if (strlen(temp) > sizeof(_short_name)) sprintf(_short_name, "%.*s...", sizeof(_short_name)-4, temp); else { strcpy(_short_name, temp); _short_name[strlen(_short_name) - 4] = NULL; //Cut off the .egg } _start_frame = newSF; _end_frame = newEF; _anim_type = newAnimType; _double_sided = IsDlgButtonChecked(hWnd, IDC_CHECK1); _export_whole_scene = IsDlgButtonChecked(hWnd, IDC_EXPORT_ALL); _export_all_frames = IsDlgButtonChecked(hWnd, IDC_EXP_ALL_FRAMES); return true; } bool MaxOptionsDialog::FindNode(ULONG INodeHandle) { for (int i = 0; i < _node_list.size(); i++) if (_node_list[i] == INodeHandle) return true; return false; } void MaxOptionsDialog::AddNode(ULONG INodeHandle) { if (FindNode(INodeHandle)) return; _node_list.push_back(INodeHandle); } void MaxOptionsDialog::CullBadNodes() { if (!_max_interface) return; std::vector good; for (int i=0; i<_node_list.size(); i++) { ULONG handle = _node_list[i]; if (_max_interface->GetINodeByHandle(handle)) { good.push_back(handle); } } _node_list = good; } void MaxOptionsDialog::RemoveNode(int i) { if (i >= 0 && i < _node_list.size()) { for (int j = i+1; j < _node_list.size(); j++) _node_list[i++] = _node_list[j++]; _node_list.pop_back(); } } void MaxOptionsDialog::RemoveNodeByHandle(ULONG INodeHandle) { for (int i = 0; i < _node_list.size(); i++) { if (_node_list[i] == INodeHandle) { RemoveNode(i); return; } } } IOResult MaxOptionsDialog::Save(ISave *isave) { isave->BeginChunk(CHUNK_EGG_EXP_OPTIONS); ChunkSave(isave, CHUNK_ANIM_TYPE, _anim_type); ChunkSave(isave, CHUNK_FILENAME, _file_name); ChunkSave(isave, CHUNK_SHORTNAME, _short_name); ChunkSave(isave, CHUNK_SF, _start_frame); ChunkSave(isave, CHUNK_EF, _end_frame); ChunkSave(isave, CHUNK_DBL_SIDED, _double_sided); ChunkSave(isave, CHUNK_EGG_CHECKED, _checked); ChunkSave(isave, CHUNK_EXPORT_FULL, _export_whole_scene); ChunkSave(isave, CHUNK_ALL_FRAMES, _export_all_frames); isave->BeginChunk(CHUNK_NODE_LIST); for (int i = 0; i < _node_list.size(); i++) ChunkSave(isave, CHUNK_NODE_HANDLE, _node_list[i]); isave->EndChunk(); isave->EndChunk(); return IO_OK; } IOResult MaxOptionsDialog::Load(ILoad *iload) { IOResult res = iload->OpenChunk(); while (res == IO_OK) { switch(iload->CurChunkID()) { case CHUNK_ANIM_TYPE: _anim_type = (Anim_Type)ChunkLoadInt(iload); break; case CHUNK_FILENAME: ChunkLoadString(iload, _file_name, sizeof(_file_name)); break; case CHUNK_SHORTNAME: ChunkLoadString(iload, _short_name, sizeof(_short_name)); break; case CHUNK_SF: _start_frame = ChunkLoadInt(iload); break; case CHUNK_EF: _end_frame = ChunkLoadInt(iload); break; case CHUNK_DBL_SIDED: _double_sided = ChunkLoadBool(iload); break; case CHUNK_EGG_CHECKED: _checked = ChunkLoadBool(iload); break; case CHUNK_EXPORT_FULL: _export_whole_scene = ChunkLoadBool(iload); break; case CHUNK_ALL_FRAMES: _export_all_frames = ChunkLoadBool(iload); break; case CHUNK_NODE_LIST: res = iload->OpenChunk(); while (res == IO_OK) { if (iload->CurChunkID() == CHUNK_NODE_HANDLE) AddNode(ChunkLoadULONG(iload)); iload->CloseChunk(); res = iload->OpenChunk(); } break; } iload->CloseChunk(); res = iload->OpenChunk(); } if (res == IO_END) return IO_OK; return IO_ERROR; }