source: OGRE/trunk/ogrenew/Tools/3dsmaxExport/OgreExport/src/OgreMaxExport.cpp @ 692

Revision 692, 46.0 KB checked in by mattausch, 19 years ago (diff)

adding ogre 1.2 and dependencies

Line 
1/*
2-----------------------------------------------------------------------------
3This source file is part of OGRE
4(Object-oriented Graphics Rendering Engine)
5For the latest info, see http://www.ogre3d.org/
6
7Copyright (c) 2000-2005 The OGRE Team
8Also see acknowledgements in Readme.html
9
10This program is free software; you can redistribute it and/or modify it under
11the terms of the GNU Lesser General Public License as published by the Free Software
12Foundation; either version 2 of the License, or (at your option) any later
13version.
14
15This program is distributed in the hope that it will be useful, but WITHOUT
16ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
18
19You should have received a copy of the GNU Lesser General Public License along with
20this program; if not, write to the Free Software Foundation, Inc., 59 Temple
21Place - Suite 330, Boston, MA 02111-1307, USA, or go to
22http://www.gnu.org/copyleft/lesser.txt.
23-----------------------------------------------------------------------------
24*/
25
26#include "windows.h"
27#include "max.h"
28#include "plugapi.h"
29#include "stdmat.h"
30#include "impexp.h"
31#include "CS/BipedApi.h"
32#include "CS/KeyTrack.h"
33#include "CS/phyexp.h"
34#include "iparamb2.h"
35#include "iskin.h"
36#include "OgreExport.h"
37#include "resource.h"
38
39#include <string>
40#include <fstream>
41#include <list>
42#include <queue>
43
44static OgreMaxExport* _exp = 0;
45
46INT_PTR CALLBACK ExportPropertiesDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) {
47
48        std::string filename;
49
50        switch(message) {
51                case WM_INITDIALOG:
52                        _exp = (OgreMaxExport*) lParam;
53
54                        if (_exp == 0) {
55                                MessageBox(NULL, "Error: Cannot initialize exporter options dialog, aborting", "Error", MB_ICONEXCLAMATION);
56                                EndDialog(hDlg, 0);
57                                return TRUE;
58                        }
59
60                        _exp->m_hWndDlgExport = hDlg;
61               
62                        CenterWindow(hDlg,GetParent(hDlg));
63
64                        // initialize controls on the dialog
65                        EnableWindow(GetDlgItem(hDlg, IDC_CHK_SHARE_SKELETON), FALSE);
66                        CheckDlgButton(hDlg, IDC_RADIO_EXPORT_SUBMESHES, BST_CHECKED);
67                        CheckDlgButton(hDlg, IDC_CHK_SHARE_SKELETON, BST_CHECKED);
68                        CheckDlgButton(hDlg, IDC_CHK_FLIP_YZ, BST_CHECKED);
69                        CheckDlgButton(hDlg, IDC_CHK_REBUILD_NORMALS, BST_CHECKED);
70                        CheckDlgButton(hDlg, IDC_RADIO_UV, BST_CHECKED);
71
72                        SendMessage(GetDlgItem(hDlg, IDC_TXT_SCALE), WM_SETTEXT, 0, (LPARAM)_T("1.0"));
73                        SendMessage(GetDlgItem(hDlg, IDC_TXT_DEFAULT_MATERIAL), WM_SETTEXT, 0, (LPARAM)_T("DefaultMaterial"));
74
75                        // populate the output directory box
76                        filename = _exp->m_filename;
77                        _exp->m_exportPath = filename.substr(0, filename.find_last_of("\\"));
78                        _exp->m_exportFilename = filename.substr(filename.find_last_of("\\") + 1);
79
80                        _exp->m_materialFilename = _exp->m_exportFilename;
81                        _exp->m_materialFilename = _exp->m_materialFilename.substr(0, _exp->m_materialFilename.find(".mesh.xml")) + ".material";
82                        _exp->m_skeletonFilename = _exp->m_exportFilename.substr(0, _exp->m_exportFilename.find(".mesh.xml")) + ".skeleton.xml";
83                        SendMessage(GetDlgItem(hDlg, IDC_TXT_MATERIAL_FILENAME), WM_SETTEXT, 0, (LPARAM)_exp->m_materialFilename.c_str());
84                        SendMessage(GetDlgItem(hDlg, IDC_TXT_SKELETON_FILENAME), WM_SETTEXT, 0, (LPARAM)_exp->m_skeletonFilename.c_str());
85
86                        SendMessage(GetDlgItem(hDlg, IDC_TXT_EXPORT_DIR), WM_SETTEXT, 0, (LPARAM)_exp->m_exportPath.c_str());
87                        EnableWindow(GetDlgItem(hDlg, IDC_TXT_EXPORT_DIR), FALSE);
88
89                        // set up animation listbox
90                        {
91                                int frameStart = _exp->m_i->GetAnimRange().Start();
92                                int frameEnd = _exp->m_i->GetAnimRange().End();
93
94                                HWND anims = GetDlgItem(hDlg, IDC_LIST_ANIMATIONS);
95
96                                Rect r;
97                                GetWindowRect(anims, &r);
98
99                                LVCOLUMN lvc;
100                                ZeroMemory(&lvc, sizeof(LVCOLUMN));
101                                lvc.mask = LVCF_TEXT | LVCF_WIDTH;
102                                lvc.cx = r.w() * 0.6;
103                                lvc.pszText = "Animation";
104                                ListView_InsertColumn(anims, 0, &lvc);
105                                lvc.cx = r.w() * 0.2;
106                                lvc.pszText = "Begin";
107                                ListView_InsertColumn(anims, 1, &lvc);
108                                lvc.pszText = "End";
109                                ListView_InsertColumn(anims, 2, &lvc);
110
111                                // add a spanning entry to the animation list as a default
112                                LVITEM lvi;
113                                char buf[32];
114                                ZeroMemory(&lvi, sizeof(LVITEM));
115
116                                lvi.mask = LVIF_TEXT;
117                                lvi.pszText = "Animation";
118                                lvi.iItem = 10000;
119                                int idx = ListView_InsertItem(anims, &lvi);
120
121                                sprintf(buf, "%d", frameStart / GetTicksPerFrame());
122                                lvi.iItem = idx;
123                                lvi.iSubItem = 1;
124                                lvi.pszText = buf;
125                                ListView_SetItem(anims, &lvi);
126
127                                sprintf(buf, "%d", frameEnd / GetTicksPerFrame());
128                                lvi.iSubItem = 2;
129                                lvi.pszText = buf;
130                                ListView_SetItem(anims, &lvi);
131
132                                // populate the frame range info box
133                                sprintf(buf, "%d to %d", frameStart / GetTicksPerFrame(), frameEnd / GetTicksPerFrame());
134                                SendMessage(GetDlgItem(hDlg, IDC_TXT_FRAME_RANGE), WM_SETTEXT, 0, (LPARAM)buf);
135                        }
136
137                        return TRUE;
138                        break;
139                case WM_COMMAND:
140                        switch(LOWORD(wParam)) {
141                                case IDC_SELECT_EXPORT_DIR:
142                                        break;
143                                case IDC_RADIO_EXPORT_FILES:
144                                case IDC_RADIO_EXPORT_SUBMESHES:
145                                        _exp->updateExportOptions(hDlg);
146                                        break;
147                                case IDOK:
148                                case IDC_EXPORT:
149                                        if (_exp->export())
150                                                EndDialog(hDlg, 1);
151                                        else
152                                                EndDialog(hDlg, 2);
153                                        return TRUE;
154                                case IDCANCEL:
155                                        EndDialog(hDlg, 0);
156                                        return TRUE;
157
158                                case IDC_CMD_ADD_ANIMATION:
159                                        _exp->addAnimation();
160                                        break;
161
162                                case IDC_CMD_DELETE_ANIMATION:
163                                        _exp->deleteAnimation();
164                                        break;
165                        }
166                        break;
167        }
168        return FALSE;
169
170}
171
172void OgreMaxExport::addAnimation() {
173        char buf[256];
174        int start, end;
175        HWND anims = GetDlgItem(m_hWndDlgExport, IDC_LIST_ANIMATIONS);
176
177        // ignore FPS field for now
178        SendMessage(GetDlgItem(m_hWndDlgExport, IDC_TXT_FPS), WM_GETTEXT, 256, (LPARAM)buf);
179        int fps = atoi(buf);
180
181        if (fps < 0) {
182                MessageBox(NULL, "FPS must be >= 0", "Invalid Entry", MB_ICONEXCLAMATION);
183                return;
184        }
185
186        int minAnimTime = m_i->GetAnimRange().Start() / GetTicksPerFrame();
187        int maxAnimTime = m_i->GetAnimRange().End() / GetTicksPerFrame();
188
189        // get animation start and end times
190        SendMessage(GetDlgItem(m_hWndDlgExport, IDC_TXT_ANIM_START), WM_GETTEXT, 256, (LPARAM)buf);
191        start = atoi(buf);
192
193        if (start < minAnimTime) {
194                sprintf(buf, "Start time must be >= %d", start);
195                MessageBox(NULL, buf, "Invalid Entry", MB_ICONEXCLAMATION);
196                return;
197        }
198
199        SendMessage(GetDlgItem(m_hWndDlgExport, IDC_TXT_ANIM_END), WM_GETTEXT, 256, (LPARAM)buf);
200        end = atoi(buf);
201
202        if (end > maxAnimTime) {
203                sprintf(buf, "End time must be <= %d", end);
204                MessageBox(NULL, buf, "Invalid Entry", MB_ICONEXCLAMATION);
205                return;
206        }
207
208        // get animation name
209        SendMessage(GetDlgItem(m_hWndDlgExport, IDC_TXT_ANIMATION_NAME), WM_GETTEXT, 256, (LPARAM)buf);
210        std::string name(buf);
211
212        if (name.length() == 0) {
213                MessageBox(NULL, "Animation name must not be empty", "Invalid Entry", MB_ICONEXCLAMATION);
214                return;
215        }
216
217        // if, after all that, we have valid data, stick it in the listview
218        LVITEM lvi;
219        ZeroMemory(&lvi, sizeof(LVITEM));
220
221        lvi.mask = LVIF_TEXT;
222        lvi.pszText = buf;
223        lvi.iItem = 10000;
224        int idx = ListView_InsertItem(anims, &lvi);
225
226        lvi.iItem = idx;
227        lvi.iSubItem = 1;
228        sprintf(buf, "%d", start);
229        lvi.pszText = buf;
230        ListView_SetItem(anims, &lvi);
231        lvi.iSubItem = 2;
232        sprintf(buf, "%d", end);
233        lvi.pszText = buf;
234        ListView_SetItem(anims, &lvi);
235
236        // Finally, clear out the entry controls
237        SetWindowText(GetDlgItem(m_hWndDlgExport, IDC_TXT_ANIMATION_NAME), "");
238        SetWindowText(GetDlgItem(m_hWndDlgExport, IDC_TXT_ANIM_START), "");
239        SetWindowText(GetDlgItem(m_hWndDlgExport, IDC_TXT_ANIM_END), "");
240}
241
242void OgreMaxExport::deleteAnimation() {
243
244        HWND anims = GetDlgItem(m_hWndDlgExport, IDC_LIST_ANIMATIONS);
245
246        // delete selected animation(s) from the listview
247        int idx;
248        while ((idx=ListView_GetNextItem(anims, -1, LVNI_SELECTED)) != -1)
249                ListView_DeleteItem(anims, idx);
250}
251
252void OgreMaxExport::updateExportOptions(HWND hDlg) {
253
254        // ***************************************************************************
255        // adjust enabled state of share-skeleton checkbox if the user chose to
256        // export each mesh to an individual file -- this ultimately will instruct the exporter
257        // not to create a .skeleton.xml file for each .mesh.xml, and instead assign a
258        // common .skeleton filename to each exported .mesh.xml
259
260        HWND hChk = GetDlgItem(hDlg, IDC_CHK_SHARE_SKELETON);
261        EnableWindow(hChk, IsDlgButtonChecked(hDlg, IDC_RADIO_EXPORT_FILES));
262
263        m_exportMultipleFiles = (BST_CHECKED == IsDlgButtonChecked(hDlg, IDC_RADIO_EXPORT_FILES));
264        m_useSingleSkeleton = (BST_CHECKED == IsDlgButtonChecked(hDlg, IDC_CHK_SHARE_SKELETON));
265        m_rebuildNormals = (BST_CHECKED == IsDlgButtonChecked(hDlg, IDC_CHK_REBUILD_NORMALS));
266        m_invertNormals = (BST_CHECKED == IsDlgButtonChecked(hDlg, IDC_CHK_INVERT_NORMALS));
267        m_flipYZ = (BST_CHECKED == IsDlgButtonChecked(hDlg, IDC_CHK_FLIP_YZ));
268        m_exportVertexColors = (BST_CHECKED == IsDlgButtonChecked(hDlg, IDC_CHK_VERTEX_COLORS));
269
270        if (SendMessage(GetDlgItem(hDlg, IDC_TXT_MATERIAL_FILENAME), WM_GETTEXTLENGTH, 0, 0) == 0) {
271                m_exportMaterial = false;
272        }
273        else {
274                m_exportMaterial = true;
275        }
276
277        if (SendMessage(GetDlgItem(hDlg, IDC_TXT_SCALE), WM_GETTEXTLENGTH, 0, 0) == 0)
278                m_scale = 1.0f;
279        else {
280                char buf[16];
281                SendMessage(GetDlgItem(hDlg, IDC_TXT_SCALE), WM_GETTEXT, 16, (LPARAM)buf);
282                m_scale = atof(buf);
283
284                if (m_scale == 0.0f)
285                        m_scale = 1.0f;
286        }
287
288        TCHAR buf[256];
289        if (SendMessage(GetDlgItem(hDlg, IDC_TXT_DEFAULT_MATERIAL), WM_GETTEXT, 256, (LPARAM)buf) > 0)
290                m_defaultMaterialName = buf;
291        else
292                m_defaultMaterialName = _T("DefaultMaterial");
293
294        if (SendMessage(GetDlgItem(hDlg, IDC_TXT_SKELETON_FILENAME), WM_GETTEXT, 256, (LPARAM)buf) > 0)
295                m_skeletonFilename = buf;
296
297        if (BST_CHECKED == IsDlgButtonChecked(hDlg, IDC_RADIO_UV))
298                m_2DTexCoord = UV;
299        if (BST_CHECKED == IsDlgButtonChecked(hDlg, IDC_RADIO_VW))
300                m_2DTexCoord = VW;
301        if (BST_CHECKED == IsDlgButtonChecked(hDlg, IDC_RADIO_WU))
302                m_2DTexCoord = WU;
303
304        // update the list of named animations from the list view control
305        int animIdx = -1;
306        HWND anims = GetDlgItem(hDlg, IDC_LIST_ANIMATIONS);
307
308        m_animations.clear();
309        while ((animIdx=ListView_GetNextItem(anims, animIdx, LVNI_ALL)) != -1) {
310               
311                LVITEM lvi;
312                NamedAnimation anim;
313                char buf[256];
314                ZeroMemory(&lvi, sizeof(LVITEM));
315
316                lvi.mask = LVIF_TEXT;
317                lvi.cchTextMax = 256;
318                lvi.pszText = buf;
319                lvi.iItem = animIdx;
320
321                ListView_GetItem(anims, &lvi);
322                anim.name = std::string(buf);
323
324                lvi.iSubItem = 1;
325                ListView_GetItem(anims, &lvi);
326                anim.start = atoi(buf);
327
328                lvi.iSubItem = 2;
329                ListView_GetItem(anims, &lvi);
330                anim.end = atoi(buf);
331
332                m_animations.push_back(anim);
333        }
334}
335
336OgreMaxExport::OgreMaxExport(HINSTANCE hInst) : m_exportPath(""), m_exportFilename("") {
337        m_hInstance = hInst;
338        m_hWndDlgExport = 0;
339        m_ei = 0;
340        m_i = 0;
341
342        m_exportMultipleFiles = true;           // default is to export a file per mesh object in the scene
343        m_useSingleSkeleton = true;                     // default for multiple meshes is to reference a single .skeleton file where applicable
344        m_rebuildNormals = false;                       // rebuild the normals before exporting mesh data
345
346        m_exportMaterial = true;                        // default is to export material scripts
347        m_defaultMaterialName = "DefaultMaterial";
348        m_2DTexCoord = UV;                                      // default is UV interpretation of 2D tex coords
349
350        m_exportOnlySelectedNodes = false;      // this corresponds to the "Export..." vs "Export Selected..." menu items
351        m_invertNormals = false;                        // flip normals; will also reorder triangle vertex indices
352        m_flipYZ = false;                                       // swap X and Z axes, so that Y becomes the One True Up Vector
353        m_exportVertexColors = false;           // useful for vertex painting
354        m_scale = 1.0f;                                         // export at normal size (scale) -- all vertices get multiplied by this
355
356        m_skeletonLink = false;                         // initially we don't assume any skeletal data
357        m_currentBoneIndex = 0;                         // used to map bone names to bone indices for vertex assignment and later skeleton export
358
359        m_fps = 25;                                                     // used for controller types (such as Biped) that do not support keyframes directly -- this is the sampling rate
360}
361
362OgreMaxExport::~OgreMaxExport() {
363}
364
365int OgreMaxExport::ExtCount() {
366        // only support one filename extension in this plugin
367        return 1;
368}
369
370const TCHAR * OgreMaxExport::Ext(int n) {
371        switch (n) {
372                case 0:
373                        return _T("xml");
374                        break;
375                default:
376                        return 0;
377                        break;
378        }
379}
380
381const TCHAR * OgreMaxExport::LongDesc() {
382        return _T("Ogre Mesh/Animation/Material Exporter");
383}
384
385const TCHAR * OgreMaxExport::ShortDesc() {
386        return _T("Ogre XML");
387}
388
389const TCHAR * OgreMaxExport::AuthorName() {
390        return _T("Gregory 'Xavier' Junker");
391}
392
393const TCHAR * OgreMaxExport::CopyrightMessage() {
394        return _T("The OGRE Team (c) 2006");
395}
396
397const TCHAR * OgreMaxExport::OtherMessage1() {
398        return 0;
399}
400
401const TCHAR * OgreMaxExport::OtherMessage2() {
402        return 0;
403}
404
405unsigned int OgreMaxExport::Version() {
406        return 100;
407}
408
409void OgreMaxExport::ShowAbout(HWND hWnd) {
410        MessageBox(hWnd, "Ogre (Dagon) Mesh, Material and Animation Exporter", "About", 0);
411}
412
413int     OgreMaxExport::DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts, DWORD options) {
414
415        // massage the filename -- if it does not end with .mesh.xml, make it do so
416        m_filename = name;
417        if (m_filename.find(".mesh.xml") == std::string::npos) {
418                m_filename = m_filename.substr(0, m_filename.find(".XML"));
419                m_filename += ".mesh.xml";
420        }
421
422        m_ei = ei;
423        m_i = i;
424
425        // Max will supply a nonzero (specifically, SCENE_EXPORT_SELECTED) value for options if the user
426        // chose "Export Selected..." instead of "Export..." from the File menu
427        m_exportOnlySelectedNodes = (options == SCENE_EXPORT_SELECTED);
428
429        int result = DialogBoxParam(m_hInstance,
430                                                                        MAKEINTRESOURCE(IDD_EXPORT),
431                                                                        GetActiveWindow(),
432                                                                        ExportPropertiesDialogProc,
433                                                                        (LPARAM) this);
434
435        switch (result) {
436                case 0:
437                        return IMPEXP_CANCEL;
438                        break;
439                case 1:
440                        MessageBox(GetActiveWindow(), "Export Succeeded", "Sucessful Export", MB_ICONINFORMATION);
441                        return IMPEXP_SUCCESS;
442                        break;
443                default:
444                        return IMPEXP_FAIL;
445                        break;
446        }
447}
448
449BOOL OgreMaxExport::SupportsOptions(int ext, DWORD options) {
450
451        // currently, only SCENE_EXPORT_SELECTED is passed to this; we support exporting
452        // of selected files only, so return TRUE (if they ever add anything later, we'll
453        // either support it too, or check what they are asking and return accordingly).
454        return TRUE;
455}
456
457// pulled directly from the Sparks site:
458// http://sparks.discreet.com/Knowledgebase/sdkdocs_v8/prog/cs/cs_physique_export.html
459// Also available in the SDK docs. Used to find out if this node has a physique modifier or not.
460// If it does, it returns a pointer to the modifier, and if not, returns NULL. This can be used to
461// determine whether a node is bone or mesh -- mesh nodes will have Physique modifiers, bone nodes
462// will not.
463Modifier* OgreMaxExport::FindPhysiqueModifier (INode* nodePtr)
464{
465        // Get object from node. Abort if no object.
466        Object* ObjectPtr = nodePtr->GetObjectRef();
467
468        if (!ObjectPtr) return NULL;
469
470        // Is derived object ?
471        while (ObjectPtr->SuperClassID() == GEN_DERIVOB_CLASS_ID && ObjectPtr)
472        {
473                // Yes -> Cast.
474                IDerivedObject *DerivedObjectPtr = (IDerivedObject *)(ObjectPtr);
475                                               
476                // Iterate over all entries of the modifier stack.
477                int ModStackIndex = 0;
478                while (ModStackIndex < DerivedObjectPtr->NumModifiers())
479                {
480                        // Get current modifier.
481                        Modifier* ModifierPtr = DerivedObjectPtr->GetModifier(ModStackIndex);
482
483                        // Is this Physique ?
484                        if (ModifierPtr->ClassID() == Class_ID(PHYSIQUE_CLASS_ID_A, PHYSIQUE_CLASS_ID_B))
485                        {
486                                // Yes -> Exit.
487                                return ModifierPtr;
488                        }
489
490                        // Next modifier stack entry.
491                        ModStackIndex++;
492                }
493                ObjectPtr = DerivedObjectPtr->GetObjRef();
494        }
495
496        // Not found.
497        return NULL;
498}
499
500// "callback" is called in response to the EnumTree() call made below. That call visits every node in the
501// scene and calls this procedure for each one.
502int OgreMaxExport::callback(INode *node) {
503
504        // SKELOBJ_CLASS_ID = 0x9125 = 37157
505        // BIPED_CLASS_ID = 0x9155 = 37205
506        // BIPSLAVE_CONTROL_CLASS_ID = 0x9154 = 37204
507        // BIPBODY_CONTROL_CLASS_ID = 0x9156 = 37206
508        // FOOTPRINT_CLASS_ID = 0x3011 = 12305
509        // DUMMY_CLASS_ID = 0x876234 = 8872500
510
511        TimeValue start = m_i->GetAnimRange().Start();
512        Object *obj = node->EvalWorldState(start).obj;
513        Class_ID cid = obj->ClassID();
514
515        // nodes that have Biped controllers are bones -- ignore the ones that are dummies
516        if (cid == Class_ID(DUMMY_CLASS_ID, 0))
517                return TREE_CONTINUE;
518
519        Control *c = node->GetTMController();
520        if ((c->ClassID() == BIPSLAVE_CONTROL_CLASS_ID) ||
521                (c->ClassID() == BIPBODY_CONTROL_CLASS_ID) ||
522                (c->ClassID() == FOOTPRINT_CLASS_ID)) {
523
524                        if (node->GetParentNode() != NULL) {
525                                // stick this in the bone-index map for later use
526                                m_boneIndexMap.insert(std::map< std::string, int >::value_type(std::string(node->GetName()), m_currentBoneIndex++));
527                        }
528
529                        return TREE_CONTINUE;
530        }
531        // if the node cannot be converted to a TriObject (mesh), ignore it
532        if (!obj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID, 0)))
533                return TREE_CONTINUE;
534
535        // create a list of nodes to process
536        if (m_exportOnlySelectedNodes) {
537                if (node->Selected())
538                        m_nodeList.push_back(node);
539        }
540        else {
541                m_nodeList.push_back(node);
542        }
543
544        return TREE_CONTINUE;
545}
546
547bool OgreMaxExport::export() {
548
549        // make sure we have the latest options
550        updateExportOptions(m_hWndDlgExport);
551
552        try {
553
554                // all options have been set when actions were taken, so we can just start exporting stuff here
555                std::ofstream of;
556                bool rtn = true;
557
558                m_nodeList.clear();
559                while (!m_submeshNames.empty())
560                        m_submeshNames.pop();
561
562                m_ei->theScene->EnumTree(this);
563
564                // check to see if there's anything to export
565                if (m_nodeList.size() == 0) {
566                        MessageBox(GetActiveWindow(), "No nodes available to export, aborting...", "Nothing To Export", MB_ICONINFORMATION);
567                        return false;
568                }
569
570                std::string fileName;
571                // if we are writing everything to one file, use the name provided when the user first started the export
572                if (!m_exportMultipleFiles)
573                        fileName = m_filename;
574                else {
575                        fileName = m_exportPath + "\\";
576                        INode *n = *(m_nodeList.begin());
577                        fileName += n->GetName();
578                        fileName += ".mesh.xml";
579                }
580
581                of.open(fileName.c_str(), std::ios::out);
582
583                if (of.is_open())
584                        streamFileHeader(of);
585                else {
586                        std::string msg("Could not open output file");
587                        msg += fileName;
588                        msg += ", aborting...";
589                        MessageBox(GetActiveWindow(), msg.c_str(), "File Output Error", MB_ICONEXCLAMATION);
590                        return false;
591                }
592
593                std::list<INode *>::iterator it = m_nodeList.begin();
594
595                Mtl *nodeMtl = (*it)->GetMtl();
596                if (nodeMtl == NULL) {
597                        std::string mtlName;
598                        mtlName = m_defaultMaterialName;
599                        m_materialMap.insert(std::map< std::string, Mtl * >::value_type(mtlName, NULL));
600                }
601
602                while (it != m_nodeList.end()) {
603
604                        // we already filtered out nodes that had NULL materials, and those that could
605                        // not be converted to TriObjects, so now we know everything we have is good
606
607                        INode *node = *it;
608                        std::string mtlName;
609                       
610                        Mtl *mtl = node->GetMtl();
611                        if (mtl == NULL)
612                                mtlName = m_defaultMaterialName;
613                        else
614                                mtlName = mtl->GetName();
615
616                        // duplicate map keys will cause an exception; ignore it and keep going
617                        try {
618                                // map a material name to its Mtl pointer so that we can retrieve these later
619                                std::string::size_type pos;
620
621                                // clean out any spaces the user left in their material name
622                                while ((pos = mtlName.find_first_of(' ')) != std::string::npos)
623                                        mtlName.replace(pos, 1, _T("_"));
624
625                                m_materialMap.insert(std::map< std::string, Mtl * >::value_type(mtlName, mtl));
626                        } catch (...) {}
627
628                        if (streamSubmesh(of, node, mtlName))
629                                m_submeshNames.push(std::string((*it)->GetName()));
630
631                        it++;
632
633                        // if we are doing one mesh per file, then close this one and open a new one
634                        if (m_exportMultipleFiles || it == m_nodeList.end()) {
635                                streamFileFooter(of);
636                                of.close();
637
638                                if (it != m_nodeList.end()) {
639                                        fileName = m_exportPath + "\\";
640                                        INode *n = *it;
641                                        fileName += n->GetName();
642                                        fileName += ".mesh.xml";
643
644                                        of.open(fileName.c_str(), std::ios::out);
645
646                                        if (of.is_open())
647                                                streamFileHeader(of);
648                                        else {
649                                                std::string msg("Could not open output file");
650                                                msg += fileName;
651                                                msg += ", aborting...";
652                                                MessageBox(GetActiveWindow(), msg.c_str(), "File Output Error", MB_ICONEXCLAMATION);
653                                                return false;
654                                        }
655                                }
656                        }
657                }
658
659                // if skeleton data is present, stream skeleton file
660                if (m_skeletonLink) {
661                        // open the skeleton.xml output file
662                        of.open((m_exportPath + "\\" + m_skeletonFilename).c_str(), std::ios::out);
663
664                        // stream the skeleton file
665                        streamSkeleton(of);
666                        of.close();
667                }
668
669                // stream material file(s)
670                TCHAR fName[256];
671                HWND hWnd = GetDlgItem(m_hWndDlgExport, IDC_TXT_MATERIAL_FILENAME);
672
673                SendMessage(hWnd, WM_GETTEXT, 256, (LPARAM)fName);
674               
675                std::string mtlFilename(fName);
676                mtlFilename = m_exportPath + "\\" + fName;
677                of.open(mtlFilename.c_str(), std::ios::out);
678                of.precision(6);
679                of << std::fixed;
680                streamMaterial(of);
681                of.close();
682
683                return rtn;
684        }
685        catch (...) {
686                MessageBox(GetActiveWindow(), "An unexpected error has occurred while trying to export, aborting", "Error", MB_ICONEXCLAMATION);
687                return false;
688        }
689}
690
691bool OgreMaxExport::streamFileHeader(std::ostream &of) {
692
693        // write the XML header tags
694        of << "<?xml version=\"1.0\"?>" << std::endl;
695        of << "<mesh>" << std::endl;
696
697        // *************** Export Submeshes ***************
698        of << "\t<submeshes>" << std::endl;
699
700        of.precision(6);
701        of << std::fixed;
702
703        return true;
704}
705
706bool OgreMaxExport::streamFileFooter(std::ostream &of) {
707
708        of << "\t</submeshes>" << std::endl;
709        // *************** End Submeshes Export ***********
710
711        // if there is a skeleton involved, link that filename here
712        if (m_skeletonLink) {
713                std::string skeletonFilename(m_skeletonFilename);
714                skeletonFilename = skeletonFilename.substr(0, skeletonFilename.find(".xml"));
715
716                of << "\t<skeletonlink name=\"" << skeletonFilename << "\" />" << std::endl;
717        }
718
719
720        // *************** Export Submesh Names ***************
721        of << "\t<submeshnames>" << std::endl;
722
723        int idx = 0;
724        while (!m_submeshNames.empty()) {
725                of << "\t\t<submeshname name=\"" << m_submeshNames.front() << "\" index=\"" << idx << "\" />" << std::endl;
726                idx++;
727                m_submeshNames.pop();
728        }
729
730        of << "\t</submeshnames>" << std::endl;
731        // *************** End Submesh Names Export ***********
732
733        of << "</mesh>" << std::endl;
734
735        return true;
736}
737
738bool OgreMaxExport::streamPass(std::ostream &of, Mtl *mtl) {
739        of << "\t\tpass" << std::endl;
740        of << "\t\t{" << std::endl;
741
742        BMM_Color_32 amb32, diff32, spec32, em32;
743        ZeroMemory(&amb32, sizeof(BMM_Color_32));
744        ZeroMemory(&diff32, sizeof(BMM_Color_32));
745        ZeroMemory(&spec32, sizeof(BMM_Color_32));
746        ZeroMemory(&em32, sizeof(BMM_Color_32));
747
748        if (mtl != NULL) {
749                Color ambient = mtl->GetAmbient();
750                amb32 = BMM_Color_32(ambient);
751
752                Color diffuse = mtl->GetDiffuse();
753                diff32 = BMM_Color_32(diffuse);
754
755                Color specular = mtl->GetSpecular();
756                spec32 = BMM_Color_32(specular);
757
758                Color emissive = mtl->GetSelfIllumColor();
759                em32 = BMM_Color_32(emissive);
760        }
761
762        of << "\t\t\tambient " << (float)amb32.r/255.0f << " " << (float)amb32.g/255.0f << " " << (float)amb32.b/255.0f << " " << (float)amb32.a/255.0f << std::endl;
763        of << "\t\t\tdiffuse " << (float)diff32.r/255.0f << " " << (float)diff32.g/255.0f << " " << (float)diff32.b/255.0f << " " << (float)diff32.a/255.0f << std::endl;
764        of << "\t\t\tspecular " << (float)spec32.r/255.0f << " " << (float)spec32.g/255.0f << " " << (float)spec32.b/255.0f << " " << (float)spec32.a/255.0f << " 0.0" << std::endl;
765        of << "\t\t\temissive " << (float)em32.r/255.0f << " " << (float)em32.g/255.0f << " " << (float)em32.b/255.0f << " " << (float)em32.a/255.0f << std::endl;
766
767        if (mtl != NULL) {
768                // check for diffuse texture
769                Texmap *tMap = mtl->GetSubTexmap(ID_DI);
770                if (tMap) {
771                        if (tMap->ClassID() == Class_ID(BMTEX_CLASS_ID, 0)) {
772
773                                BitmapTex *bmt = (BitmapTex*) tMap;
774                                std::string mapName(bmt->GetMapName());
775                                mapName = mapName.substr(mapName.find_last_of('\\') + 1);
776
777                                of << "\t\t\ttexture_unit " << std::endl;
778                                of << "\t\t\t{" << std::endl;
779                                of << "\t\t\t\ttexture " << mapName << std::endl;
780                                of << "\t\t\t}" << std::endl;
781                        }
782                }
783        }
784
785        of << "\t\t}" << std::endl;
786
787        return true;
788}
789
790bool OgreMaxExport::streamMaterial(std::ostream &of) {
791
792        // serialize this information to the material file
793        std::map< std::string, Mtl * >::iterator it = m_materialMap.begin();
794
795        while (it != m_materialMap.end()) {
796                std::string matName(it->first);
797                Mtl *mtl = it->second;
798
799                of << "material " << matName << std::endl;
800                of << std::showpoint;
801                of << "{" << std::endl;
802
803                of << "\ttechnique" << std::endl;
804                of << "\t{" << std::endl;
805
806                int numSubMtl = 0;
807               
808                if (mtl != NULL) {
809                        numSubMtl = mtl->NumSubMtls();
810
811                        if (numSubMtl > 0) {
812                                int i;
813                                for (i=0; i<numSubMtl; i++) {
814                                        streamPass(of, mtl->GetSubMtl(i));
815                                }
816                        }
817                        else
818                                streamPass(of, mtl);
819                }
820                else {
821                        streamPass(of, mtl);
822                }
823
824                of << "\t}" << std::endl;
825                of << "}" << std::endl;
826
827                it++;
828        }
829
830        m_materialMap.clear();
831
832        return true;
833}
834
835bool OgreMaxExport::streamSubmesh(std::ostream &of, INode *node, std::string &mtlName) {
836       
837        Object *obj = node->EvalWorldState(m_i->GetTime()).obj;
838        TriObject *tri = (TriObject *) obj->ConvertToType(m_i->GetTime(), Class_ID(TRIOBJ_CLASS_ID, 0));
839
840        int i;
841        Mesh mesh = tri->GetMesh();
842        Matrix3 ptm = node->GetObjTMAfterWSM(m_i->GetTime());
843
844        int vertCount = mesh.getNumVerts();
845        int faceCount = mesh.getNumFaces();
846
847        of << "\t\t<submesh ";
848       
849        if (mtlName.length() > 0)
850                of << "material=\"" << mtlName << "\" ";
851
852        of << "usesharedvertices=\"false\" use32bitindexes=\"";
853        of << (vertCount > 65535);
854        of << "\">" << std::endl;
855
856        // *************** Export Face List ***************
857        of << "\t\t\t<faces count=\"" << faceCount << "\">" << std::endl;
858
859        for (i=0; i<faceCount; i++) {
860                int v1 = mesh.faces[i].v[0];
861                int v2 = mesh.faces[i].v[1];
862                int v3 = mesh.faces[i].v[2];
863
864                if (m_invertNormals) {
865                        int tmp = v2;
866                        v2 = v3;
867                        v3 = tmp;
868                }
869
870                of << "\t\t\t\t<face v1=\"" << v1 << "\" v2=\"" << v2 << "\" v3=\"" << v3 << "\" />" << std::endl;
871        }
872
873        of << "\t\t\t</faces>" << std::endl;
874        // *************** End Export Face List ***************
875
876
877        // *************** Export Geometry ***************
878        of << "\t\t\t<geometry vertexcount=\"" << vertCount << "\">" << std::endl;
879
880        // *************** Export Vertex Buffer ***************
881        if (m_rebuildNormals) {
882                mesh.buildNormals();
883        }
884
885        bool exportNormals = (mesh.normalsBuilt > 0);
886
887        of << std::boolalpha;
888
889        // TODO: get the actual number and dimemsion of tex maps from Max -- for now, fake it
890        // NB: we don't export tex coords unless we are exporting a material as well
891        int numTexMaps = m_exportMaterial ? 1 : 0;
892        of << "\t\t\t\t<vertexbuffer positions=\"true\" normals=\"" << exportNormals << "\" colours_diffuse=\"" << m_exportVertexColors << "\" texture_coords=\"" << numTexMaps << "\"";
893       
894        for (i=0; i<numTexMaps; i++)
895                of << " texture_coords_dimensions_" << i << "=\"2\"";
896       
897        of << ">" << std::endl;
898
899        for (i=0; i<vertCount; i++) {
900                Point3 v = mesh.getVert(i);
901
902                // transform v into parent's coord system
903                v = v * ptm;
904
905                Point3 vc;
906                vc.x = 0.0f;
907                vc.y = 0.0f;
908                vc.z = 0.0f;
909
910                if (mesh.vertCol != 0) {
911                        vc = mesh.vertCol[i];
912                }
913
914                of << "\t\t\t\t\t<vertex>" << std::endl;
915                of << std::showpoint;
916
917                float x = v.x;// * m_scale;
918                float y = v.y;// * m_scale;
919                float z = v.z;// * m_scale;
920
921                if (m_flipYZ) {
922                        float tmp = y;
923                        y = z;
924                        z = -tmp;
925                }
926
927                of << "\t\t\t\t\t\t<position x=\"" << x << "\" y=\"" << y << "\" z=\"" << z << "\" />" << std::endl;
928
929                if (m_exportVertexColors)
930                        of << "\t\t\t\t\t\t<colour_diffuse value=\"\t" << vc.x << "\t" << vc.y << "\t" << vc.z << "\" />" << std::endl;
931               
932                if (exportNormals) {
933//                      Point3 n = mesh.getNormal(i);
934                        RVertex *rv = mesh.getRVertPtr(i);
935                        Point3 n = rv->rn.getNormal();
936                        n.Normalize();
937
938                        float x = n.x;
939                        float y = n.y;
940                        float z = n.z;
941
942                        if (m_flipYZ) {
943                                float tmp = y;
944                                y = z;
945                                z = -tmp;
946                        }
947                       
948                        if (m_invertNormals)
949                                of << "\t\t\t\t\t\t<normal x=\"" << -x << "\" y=\"" << -y << "\" z=\"" << -z << "\" />" << std::endl;
950                        else
951                                of << "\t\t\t\t\t\t<normal x=\"" << x << "\" y=\"" << y << "\" z=\"" << z << "\" />" << std::endl;
952                }
953
954                if (i < mesh.getNumTVerts()) {
955                        for (int t=0; t<numTexMaps; t++) {
956
957                                UVVert uv = mesh.getTVert(i);
958
959                                switch (m_2DTexCoord) {
960                                        case UV:
961                                                of << "\t\t\t\t\t\t<texcoord u=\"" << uv.x << "\" v=\"" << (1.0f - uv.y) << "\" />" << std::endl;
962                                                break;
963                                        case VW:
964                                                of << "\t\t\t\t\t\t<texcoord v=\"" << uv.y << "\" w=\"" << (1.0f - uv.z) << "\" />" << std::endl;
965                                                break;
966                                        case WU:
967                                                of << "\t\t\t\t\t\t<texcoord w=\"" << uv.z << "\" u=\"" << (1.0f - uv.x) << "\" />" << std::endl;
968                                                break;
969                                }
970                        }
971                }
972               
973                of << std::noshowpoint;
974                of << "\t\t\t\t\t</vertex>" << std::endl;
975        }
976
977        of << "\t\t\t\t</vertexbuffer>" << std::endl;
978        // *************** End Export Vertex Buffer ***************
979
980        of << "\t\t\t</geometry>" << std::endl;
981        // *************** End Export Geometry ***********
982
983        // this skin extraction code based on an article found here:
984        // http://www.cfxweb.net/modules.php?name=News&file=article&sid=1029
985        Object *oRef = node->GetObjectRef();
986
987        if (oRef->SuperClassID() == GEN_DERIVOB_CLASS_ID) {
988                IDerivedObject *dObj = (IDerivedObject *)oRef;
989                Modifier *oMod = dObj->GetModifier(0);
990
991                if (oMod->ClassID() == SKIN_CLASSID) {
992
993                        // flag the export of a skeleton link element
994                        m_skeletonLink = true;
995
996                        // stream the boneassignments element
997                        streamBoneAssignments(of, oMod, node);
998                }
999        }
1000
1001        of << "\t\t</submesh>" << std::endl;
1002
1003        if (obj != tri)
1004                delete tri;
1005
1006        return true;
1007}
1008
1009bool OgreMaxExport::streamBoneAssignments(std::ostream &of, Modifier *oMod, INode *node) {
1010       
1011
1012        // wrangle a pointer to the skinning data
1013        ISkin *skin = (ISkin *) oMod->GetInterface(I_SKIN);
1014        ISkinContextData *skinData = skin->GetContextInterface(node);
1015
1016        // loop through all the vertices, writing out skinning data as we go
1017        int skinnedVertexCount = skinData->GetNumPoints();
1018
1019        if (skinnedVertexCount > 0) {
1020
1021                of << "\t\t\t<boneassignments>" << std::endl;
1022
1023                int i;
1024                for(i=0; i<skinnedVertexCount; i++) {
1025
1026                        // grab the bone indices for this vertex
1027                        int vertexBoneInfluences = skinData->GetNumAssignedBones(i);
1028
1029                        if (vertexBoneInfluences > 0) {
1030
1031                                int j;
1032                                for (j=0; j < vertexBoneInfluences; j++) {
1033
1034                                        // get weight per bone influence -- Ogre will ignore bones above
1035                                        // 4 and sum the weights to make it work, so leverage that feature
1036                                        int boneIdx = getBoneIndex(skin->GetBoneName(skinData->GetAssignedBone(i, j)));
1037                                        float vertexWeight = skinData->GetBoneWeight(i, j);
1038
1039                                        of << "\t\t\t\t<vertexboneassignment vertexindex=\"" << i << "\" boneindex=\"" << boneIdx << "\" weight=\"" << vertexWeight << "\" />" << std::endl;
1040                                }
1041                        }
1042                }
1043       
1044                of << "\t\t\t</boneassignments>" << std::endl;
1045        }
1046
1047        return true;
1048}
1049
1050int OgreMaxExport::getBoneIndex(char *boneName) {
1051
1052        std::map< std::string, int >::const_iterator it = m_boneIndexMap.find(std::string(boneName));
1053        if (it == m_boneIndexMap.end()) {
1054                m_boneIndexMap.insert(std::map< std::string, int >::value_type(std::string(boneName), m_currentBoneIndex));
1055                return m_currentBoneIndex++;
1056        }
1057        else
1058                return it->second;
1059}
1060
1061// *******************************************************************************
1062// Skeleton streaming functions
1063// *******************************************************************************
1064
1065std::string OgreMaxExport::removeSpaces(const std::string &src) {
1066        std::string s(src);
1067        std::string::size_type pos;
1068        while ((pos=s.find_first_of(" \t\n")) != std::string::npos)
1069                s.replace(pos, 1, "_");
1070
1071        return s;
1072}
1073
1074bool OgreMaxExport::streamSkeleton(std::ostream &of) {
1075
1076        // go through and sort out the bone hierarchy (include all of the non-null bones that were not
1077        // skinned, as those could still be needed in the application)
1078        of << "<?xml version=\"1.0\"?>" << std::endl << "<skeleton>" << std::endl;
1079        of << "\t<bones>" << std::endl;
1080
1081        // write out the bone rest pose data
1082        std::map< std::string, int >::const_iterator it = m_boneIndexMap.begin();
1083        while (it != m_boneIndexMap.end()) {
1084
1085                INode *thisNode = m_i->GetINodeByName(it->first.c_str());
1086
1087                of << "\t\t<bone id=\"" << it->second << "\" name=\"" << removeSpaces(it->first) << "\" >" << std::endl;
1088
1089                // assume rest pose is at time zero
1090                TimeValue start = m_i->GetAnimRange().Start();
1091                ObjectState os = thisNode->EvalWorldState(start);
1092                Object *obj = os.obj;
1093                SClass_ID scid = obj->SuperClassID();
1094
1095                // SKELOBJ_CLASS_ID = 0x9125 = 37157
1096                // BIPED_CLASS_ID = 0x9155 = 37205
1097                // BIPSLAVE_CONTROL_CLASS_ID = 0x9154 = 37204
1098                // BIPBODY_CONTROL_CLASS_ID = 0x9156 = 37206
1099                // FOOTPRINT_CLASS_ID = 0x3011 = 12305
1100                // DUMMY_CLASS_ID = 0x876234 = 8872500
1101                Matrix3 tm(thisNode->GetNodeTM(start));
1102                Matrix3 ptm(thisNode->GetParentTM(start));
1103                Control *tmc = thisNode->GetTMController();
1104
1105                TCHAR *nm = thisNode->GetName();
1106                Class_ID cid = tmc->ClassID();
1107
1108                if (cid == BIPBODY_CONTROL_CLASS_ID || cid == BIPED_CLASS_ID) {
1109                        if (m_flipYZ) {
1110                                Matrix3 m = RotateXMatrix(PI / 2.0f);
1111                                tm = tm * Inverse(m);
1112                        }
1113                }
1114                else
1115                        tm = tm * Inverse(ptm);
1116
1117                Point3 pos = tm.GetTrans();
1118                AngAxis aa(tm);
1119
1120                of << "\t\t\t<position x=\"" << pos.x << "\" y=\"" << pos.y << "\" z=\"" << pos.z << "\" />" << std::endl;
1121
1122                // there is still a lingering Max/Ogre handed-ness issue even after rotating to get the axes correct
1123                // so we negate the angle of rotation here
1124                of << "\t\t\t<rotation angle=\"" << -aa.angle << "\">" << std::endl;
1125                of << "\t\t\t\t<axis x=\"" << aa.axis.x << "\" y=\"" << aa.axis.y << "\" z=\"" << aa.axis.z << "\" />" << std::endl;
1126                of << "\t\t\t</rotation>" << std::endl;
1127                of << "\t\t</bone>" << std::endl;
1128
1129                it++;
1130        }
1131
1132        of << "\t</bones>" << std::endl;
1133
1134        // write out the bone hierarchy
1135        it = m_boneIndexMap.begin();
1136        of << "\t<bonehierarchy>" << std::endl;
1137        while (it != m_boneIndexMap.end()) {
1138                INode *thisNode = m_i->GetINodeByName(it->first.c_str());
1139
1140                if (thisNode != 0) {
1141                        INode *parentNode = thisNode->GetParentNode();
1142
1143                        if (parentNode != 0 && parentNode != m_i->GetRootNode())
1144                                of << "\t\t<boneparent bone=\"" << removeSpaces(it->first) << "\" parent=\"" << removeSpaces(std::string(parentNode->GetName())) << "\"/>" << std::endl;
1145                }
1146
1147                it++;
1148        }
1149        of << "\t</bonehierarchy>" << std::endl;
1150
1151        // the fun bits....
1152        // Animations are named by the user during export; Max has no concept of animation subset names,
1153        // so we have to get the user to do that manually. If the user has entered anything for animations,
1154        // spit it all out here.
1155        std::list<NamedAnimation>::iterator anim = m_animations.begin();
1156
1157        if (anim != m_animations.end()) {
1158                of << "\t<animations>" << std::endl;
1159
1160                while (anim != m_animations.end()) {
1161
1162                        NamedAnimation a = *anim;
1163                        anim++;
1164
1165                        float fps = (float)GetFrameRate();
1166                        float length = (a.end - a.start) / fps;
1167
1168                        of << "\t\t<animation name=\"" << removeSpaces(a.name) << "\" length=\"" << length << "\">" << std::endl;
1169
1170                        streamAnimTracks(of, a.start, a.end);
1171
1172                        of << "\t\t</animation>" << std::endl;
1173                }
1174
1175                of << "\t</animations>" << std::endl;
1176        }
1177
1178        of << "</skeleton>" << std::endl;
1179
1180        return true;
1181}
1182
1183static int _compare_func(const void *a, const void *b) { return *(( int *)a) - *(( int *)b); }
1184
1185bool OgreMaxExport::streamAnimTracks(std::ostream &of, int startFrame, int endFrame) {
1186
1187        int start = startFrame * GetTicksPerFrame();
1188        int end = endFrame * GetTicksPerFrame();
1189
1190        std::map< std::string, int >::const_iterator it = m_boneIndexMap.begin();
1191
1192        of << "\t\t\t<tracks>" << std::endl;
1193
1194        // need this for calculating keyframe values
1195        Matrix3 initTM, bipedMasterTM0;
1196        IBipMaster *bip = 0;
1197        bipedMasterTM0.IdentityMatrix();
1198
1199        while (it != m_boneIndexMap.end()) {
1200
1201                INode *thisNode = m_i->GetINodeByName(it->first.c_str());
1202                it++;
1203
1204                Control *c = thisNode->GetTMController();
1205                Class_ID cid = c->ClassID();
1206
1207                Tab<TimeValue> keyTimes;
1208                Interval interval(start, end);
1209
1210                /*
1211                -- gets initial transform at frame 0f
1212                at time 0f (
1213                        initTform = d.transform ;
1214                        if (not isRootUniversal2 d) then (
1215                                mparent = d.parent.transform ;
1216                                initTform = initTform*inverse(mparent) ;
1217                        )
1218                        else if (flipYZ) then (
1219                                if (not g_MAX) then
1220                                        format " - flipping root track..." ;
1221                                -- we add the bip Transform
1222                                --initTform = initTform * d.controller.rootNode.transform ;
1223                                initTform = flipYZTransform initTform ;
1224                        )
1225                )
1226                */
1227
1228                initTM = thisNode->GetNodeTM(0);
1229
1230                // must have at least a frame at the start...
1231                keyTimes.Append(1, &start);
1232
1233                TCHAR *tch = thisNode->GetName();
1234
1235                // SKELOBJ_CLASS_ID = 0x9125 = 37157
1236                // BIPED_CLASS_ID = 0x9155 = 37205
1237                // BIPSLAVE_CONTROL_CLASS_ID = 0x9154 = 37204
1238                // BIPBODY_CONTROL_CLASS_ID = 0x9156 = 37206
1239                // FOOTPRINT_CLASS_ID = 0x3011 = 12305
1240                // DUMMY_CLASS_ID = 0x876234 = 8872500
1241
1242                // three-part controller for Biped root -- taking this cue from the old MaxScript exporter code
1243                if (cid == BIPBODY_CONTROL_CLASS_ID) {
1244
1245                        // we deal with the initial transform as-is, except that it might need to
1246                        // be rotated (since the root transform is in world coords)
1247                        if (m_flipYZ)
1248                                initTM = initTM * Inverse(RotateXMatrix(PI/2.0f));
1249
1250                        if (cid == BIPBODY_CONTROL_CLASS_ID) {
1251                                // get the keys from the horiz, vert and turn controllers
1252                                bip = GetBipMasterInterface(c);
1253                                Control *biph = bip->GetHorizontalControl();
1254                                Control *bipv = bip->GetVerticalControl();
1255                                Control *bipr = bip->GetTurnControl();
1256
1257                                biph->GetKeyTimes(keyTimes, interval, KEYAT_POSITION | KEYAT_ROTATION);
1258                                bipv->GetKeyTimes(keyTimes, interval, KEYAT_POSITION | KEYAT_ROTATION);
1259                                bipr->GetKeyTimes(keyTimes, interval, KEYAT_POSITION | KEYAT_ROTATION);
1260                        }
1261                }
1262                else if (cid == BIPSLAVE_CONTROL_CLASS_ID) {
1263                        // slaves just have keys, apparently
1264                        c->GetKeyTimes(keyTimes, interval, KEYAT_POSITION | KEYAT_ROTATION);
1265               
1266                        // put initial transform into local coordinates -- since this is relative to the
1267                        // parent, we don't need to sweat that possible rotations here
1268                        initTM = initTM * Inverse(thisNode->GetParentTM(0));
1269                }
1270
1271                // ...and stick a frame at the end as well...it will get sorted out if it is redundant
1272                keyTimes.Append(1, &end);
1273
1274                // skip redundant key times here
1275                keyTimes.Sort(_compare_func);
1276
1277//              if (cid == BIPSLAVE_CONTROL_CLASS_ID || cid == BIPBODY_CONTROL_CLASS_ID || cid == FOOTPRINT_CLASS_ID) {
1278//             
1279//                      if (cid == BIPBODY_CONTROL_CLASS_ID) {
1280//                              initTM = thisNode->GetNodeTM(0);
1281//
1282//                              if (m_flipYZ)
1283//                                      initTM = initTM * RotateXMatrix(PI/2.0f);
1284//                              bipedMasterTM0 = initTM;
1285//                      }
1286//                      else
1287//                              initTM = bipedMasterTM0;
1288//
1289//                      streamBipedKeyframes(of, bip, thisNode, keyTimes, interval, initTM);
1290//              }
1291//              else
1292                        streamKeyframes(of, thisNode, keyTimes, interval, initTM);
1293        }
1294
1295        of << "\t\t\t</tracks>" << std::endl;
1296
1297        return true;
1298}
1299
1300bool OgreMaxExport::streamKeyframes(std::ostream &of, INode *thisNode, Tab<TimeValue> &keyTimes, Interval &interval, Matrix3 &initTM) {
1301
1302        of << "\t\t\t\t<track bone=\"" << removeSpaces(std::string(thisNode->GetName())) << "\">" << std::endl;
1303        of << "\t\t\t\t\t<keyframes>" << std::endl;
1304
1305        int i;
1306        int keyTime = -1;
1307        int start = interval.Start();
1308        int end = interval.End();
1309
1310        /*
1311       
1312        -- gets initial transform at frame 0f
1313        at time 0f (
1314                initTform = d.transform ;
1315                if (not isRootUniversal2 d) then (
1316                        mparent = d.parent.transform ;
1317                        initTform = initTform*inverse(mparent) ;
1318                )
1319                else if (flipYZ) then (
1320                        if (not g_MAX) then
1321                                format " - flipping root track..." ;
1322                        -- we add the bip Transform
1323                        --initTform = initTform * d.controller.rootNode.transform ;
1324                        initTform = flipYZTransform initTform ;
1325                )
1326        )
1327        */
1328        initTM = thisNode->GetNodeTM(0);
1329
1330        Control *c = thisNode->GetTMController();
1331        Control *pc = thisNode->GetParentNode()->GetTMController();
1332        bool isRoot = false;
1333
1334        if (c > 0)
1335                if (c->ClassID() == BIPBODY_CONTROL_CLASS_ID)
1336                        isRoot = true;
1337//      if (pc > 0)
1338//              if (pc->ClassID() == BIPBODY_CONTROL_CLASS_ID || pc->ClassID() == FOOTPRINT_CLASS_ID)
1339//                      isRoot = true;
1340
1341        TCHAR *tc = thisNode->GetName();
1342        if (!isRoot) {
1343                Matrix3 ptm = thisNode->GetParentTM(0);
1344                initTM = initTM * Inverse(ptm);
1345        }
1346        else if (m_flipYZ) {
1347                initTM = initTM * Inverse(RotateXMatrix(PI/2.0f));
1348        }
1349
1350        for (i=0; i<keyTimes.Count(); i++) {
1351                       
1352                // only operate within the supplied keyframe time range
1353                if (keyTimes[i] < start)
1354                        continue;
1355                if (keyTimes[i] > end)
1356                        break;
1357
1358                // ignore key times we've already processed
1359                if (keyTimes[i] != keyTime) {
1360
1361                        keyTime = keyTimes[i];
1362                        float keyTimef = (float) (keyTimes[i] - start) / (float)GetTicksPerFrame() / (float)GetFrameRate();
1363
1364                        of << "\t\t\t\t\t\t<keyframe time=\"" << keyTimef << "\">" << std::endl;
1365
1366                        /*
1367
1368                        function flipYZTransform Tform = (
1369                                local axis1,axis2,axis3,t,m
1370                               
1371                                -- computes the matrix
1372                                axis1 = point3 1 0 0 ;
1373                                axis2 = point3 0 0 1 ;
1374                                axis3 = point3 0 -1 0 ;
1375                                t = point3 0 0 0 ;
1376                                m=matrix3 axis1 axis2 axis3 t ;
1377                               
1378                                -- multiplies by the inverse
1379                                Tform = Tform*inverse(m) ;
1380
1381                                return Tform ;
1382                        )
1383
1384
1385                        -- First, rotation which depends on initial transformation
1386                        Tform = d.transform ;
1387                        */
1388                        Matrix3 tm = thisNode->GetNodeTM(keyTime);
1389
1390                        /*
1391                        -- if this is the pelvis
1392                        if (isRootUniversal2 d) then (
1393                                mparent = matrix3 1 ;
1394
1395                                if (flipYZ) then
1396                                        Tform = flipYZTransform Tform ;
1397                        )                       
1398                        else
1399                                mparent = d.parent.transform ;
1400                        */
1401
1402                        // if this node's parent's controller is the biped controller, then this is either Pelvis or Footsteps,
1403                        // and both should be treated as root nodes
1404
1405                        Matrix3 ident;
1406                        Matrix3 ptm;
1407                        ident.IdentityMatrix();
1408                        Control *tmc = thisNode->GetTMController();
1409                        TCHAR *tc = thisNode->GetName();
1410
1411                        if (tmc->ClassID() == BIPBODY_CONTROL_CLASS_ID) {
1412
1413                                ptm = ident;
1414                                if (m_flipYZ) {
1415                                        tm = tm * Inverse(RotateXMatrix(PI/2.0f));
1416                                }
1417                        }
1418                        else
1419                                ptm = thisNode->GetParentNode()->GetNodeTM(keyTime);
1420
1421                        /*
1422
1423
1424                        -- computes rotation
1425                        mref = initTform*mparent ;     
1426                        Tform = Tform*inverse(mref) ;
1427                        */
1428
1429                        Matrix3 mref = initTM * ptm;
1430                        tm = tm * Inverse(mref);
1431
1432                        /*
1433                       
1434                        -- rotation part is saved.
1435                        rot = toAngleAxis Tform.rotation ;
1436                        axis = rot.axis;
1437                        angle = - rot.angle;
1438                        */
1439
1440                        AngAxis aa(tm);
1441
1442                        /*
1443                        -- Then, position which depends on parent                       
1444                        Tform=d.transform ;
1445                        Tform=Tform*inverse(mparent) ;
1446
1447                        */
1448
1449                        tm = thisNode->GetNodeTM(keyTime) * Inverse(ptm);
1450
1451                        /*
1452
1453                        -- if this is the root bone and flipYZ == true
1454                        if (isRootUniversal2 d and flipYZ) then (
1455                                Tform = flipYZTransform Tform ;
1456                        )
1457
1458                        */
1459
1460                        if (m_flipYZ && thisNode->GetParentNode()->GetParentTM(0).IsIdentity()) {
1461                                tm = tm * Inverse(RotateXMatrix(PI/2.0f));
1462                        }
1463
1464                        /*
1465                        -- substracts position of the initial transform
1466                        Tform.pos -= initTform.pos ;
1467                        Tform.pos = Tform.pos * scale ;
1468                       
1469                        pos = Tform.pos ;
1470                        */
1471                        Point3 trans = tm.GetTrans();
1472                        trans -= initTM.GetTrans();
1473
1474                        of << "\t\t\t\t\t\t\t<translate x=\"" << trans.x << "\" y=\"" << trans.y << "\" z=\"" << trans.z << "\" />" << std::endl;
1475                        of << "\t\t\t\t\t\t\t<rotate angle=\"" << -aa.angle << "\">" << std::endl;
1476                        of << "\t\t\t\t\t\t\t\t<axis x=\"" << aa.axis.x << "\" y=\"" << aa.axis.y << "\" z=\"" << aa.axis.z << "\" />" << std::endl;
1477                        of << "\t\t\t\t\t\t\t</rotate>" << std::endl;
1478
1479                        of << "\t\t\t\t\t\t</keyframe>" << std::endl;
1480                }
1481        }
1482
1483        of << "\t\t\t\t\t</keyframes>" << std::endl;
1484        of << "\t\t\t\t</track>" << std::endl;
1485
1486        return true;
1487}
1488
1489bool OgreMaxExport::streamBipedKeyframes(std::ostream &of, IBipMaster *bip, INode *thisNode, Tab<TimeValue> &keyTimes, Interval &interval, Matrix3 &initTM) {
1490
1491        of << "\t\t\t\t<track bone=\"" << removeSpaces(std::string(thisNode->GetName())) << "\">" << std::endl;
1492        of << "\t\t\t\t\t<keyframes>" << std::endl;
1493
1494        int i;
1495        int keyTime = -1;
1496        int start = interval.Start();
1497        int end = interval.End();
1498        Matrix3 tm(thisNode->GetNodeTM(start));
1499        Matrix3 ptm(thisNode->GetParentTM(start));
1500
1501        for (i=0; i<keyTimes.Count(); i++) {
1502                       
1503                // only operate within the supplied keyframe time range
1504                if (keyTimes[i] < start)
1505                        continue;
1506                if (keyTimes[i] > end)
1507                        break;
1508
1509                // ignore key times we've already processed
1510                if (keyTimes[i] != keyTime) {
1511
1512                        keyTime = keyTimes[i];
1513                        float keyTimef = (float) (keyTimes[i] - start) / (float)GetTicksPerFrame() / (float)GetFrameRate();
1514
1515                        of << "\t\t\t\t\t\t<keyframe time=\"" << keyTimef << "\">" << std::endl;
1516
1517                        Control *tmc = thisNode->GetTMController();
1518
1519                        TCHAR *nm = thisNode->GetName();
1520                        Class_ID cid = tmc->ClassID();
1521
1522                        if (cid == BIPBODY_CONTROL_CLASS_ID || cid == BIPED_CLASS_ID) {
1523                                if (m_flipYZ) {
1524                                        Matrix3 m = RotateXMatrix(PI / 2.0f);
1525                                        tm = tm * Inverse(m);
1526                                }
1527                        }
1528                        else
1529                                tm = tm * Inverse(ptm);
1530
1531                        //Point3 trans = bip->GetBipedPos(keyTime, thisNode);
1532                        //Quat q = bip->GetBipedRot(keyTime, thisNode);
1533
1534                        Point3 trans = tm.GetTrans();
1535                        trans = trans * Inverse(initTM);
1536                        trans -= initTM.GetTrans();
1537
1538                        //AngAxis aa(q);
1539                        AngAxis aa(tm);
1540                        float ang = aa.angle;
1541                        Point3 axis = aa.axis;
1542       
1543                        of << "\t\t\t\t\t\t\t<translate x=\"" << trans.x << "\" y=\"" << trans.y << "\" z=\"" << trans.z << "\" />" << std::endl;
1544                        of << "\t\t\t\t\t\t\t<rotate angle=\"" << -ang << "\">" << std::endl;
1545                        of << "\t\t\t\t\t\t\t\t<axis x=\"" << axis.x << "\" y=\"" << axis.y << "\" z=\"" << axis.z << "\" />" << std::endl;
1546                        of << "\t\t\t\t\t\t\t</rotate>" << std::endl;
1547
1548                        of << "\t\t\t\t\t\t</keyframe>" << std::endl;
1549                }
1550        }
1551
1552        of << "\t\t\t\t\t</keyframes>" << std::endl;
1553        of << "\t\t\t\t</track>" << std::endl;
1554
1555        return true;
1556}
Note: See TracBrowser for help on using the repository browser.