root/OpenSceneGraph/trunk/src/osgPlugins/fbx/ReaderWriterFBX.cpp @ 11110

Revision 11110, 14.5 kB (checked in by mplatings, 4 years ago)
Line 
1#include <sstream>
2#include <memory>
3
4#include <osg/Notify>
5#include <osg/MatrixTransform>
6#include <osg/Material>
7#include <osg/PositionAttitudeTransform>
8#include <osg/Texture2D>
9#include <osgDB/ConvertUTF>
10
11#include <osgDB/FileNameUtils>
12#include <osgDB/FileUtils>
13#include <osgDB/ReadFile>
14#include <osgDB/Registry>
15#include <osgAnimation/AnimationManagerBase>
16#include <osgAnimation/Bone>
17#include <osgAnimation/Skeleton>
18
19#if defined(_MSC_VER)
20    #pragma warning( disable : 4505 )
21#endif
22#include <fbxsdk.h>
23
24#include "ReaderWriterFBX.h"
25#include "fbxRNode.h"
26#include "fbxMaterialToOsgStateSet.h"
27#include "WriterNodeVisitor.h"
28
29#if defined(WIN32) && !defined(__CYGWIN__)
30#define WIN32_LEAN_AND_MEAN
31//For MultiByteToWideChar
32#include <Windows.h>
33#endif
34
35
36// This function belongs in osgDB. Delete this function and use the osgDB
37// version once Robert accepts the submission.
38std::string convertStringFromCurrentCodePageToUTF8(const std::string& str)
39{
40#if defined(WIN32) && !defined(__CYGWIN__)
41        if (str.length() == 0)
42        {
43                return std::string();
44        }
45
46        int utf16Length = MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), 0, 0);
47        if (utf16Length <= 0)
48        {
49                osg::notify(osg::WARN) << "Cannot convert multi-byte string to UTF-8." << std::endl;
50                return std::string();
51        }
52
53        std::wstring sUTF16(utf16Length, L'\0');
54        utf16Length = MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), &sUTF16[0], utf16Length);
55        if (utf16Length <= 0)
56        {
57                osg::notify(osg::WARN) << "Cannot convert multi-byte string to UTF-8." << std::endl;
58                return std::string();
59        }
60
61        return osgDB::convertUTF16toUTF8(sUTF16);
62#else
63
64        return str;
65#endif
66
67}
68
69/// Returns true if the given node is a basic root group with no special information.
70/// Used in conjunction with UseFbxRoot option.
71/// Identity transforms are considered as basic root nodes.
72bool isBasicRootNode(const osg::Node& node)
73{
74    const osg::Group* osgGroup = node.asGroup();
75    if (!osgGroup)
76    {
77        // Geodes & such are not basic root nodes
78        return false;
79    }
80
81    // Test if we've got an empty transform (= a group!)
82    const osg::Transform* transform = osgGroup->asTransform();
83    if (transform)
84    {
85        if (const osg::MatrixTransform* matrixTransform = transform->asMatrixTransform())
86        {
87            if (!matrixTransform->getMatrix().isIdentity())
88            {
89                // Non-identity matrix transform
90                return false;
91            }
92        }
93        else if (const osg::PositionAttitudeTransform* pat = transform->asPositionAttitudeTransform())
94        {
95            if (pat->getPosition() != osg::Vec3d() ||
96                pat->getAttitude() != osg::Quat() ||
97                pat->getScale() != osg::Vec3d(1.0f, 1.0f, 1.0f) ||
98                pat->getPivotPoint() != osg::Vec3d())
99            {
100                // Non-identity position attribute transform
101                return false;
102            }
103        }
104        else
105        {
106            // Other transform (not identity or not predefined type)
107            return false;
108        }
109    }
110
111    // Test the presence of a non-empty stateset
112    if (node.getStateSet())
113    {
114        osg::ref_ptr<osg::StateSet> emptyStateSet = new osg::StateSet;
115        if (node.getStateSet()->compare(*emptyStateSet, true) != 0)
116        {
117            return false;
118        }
119    }
120
121    return true;
122}
123
124
125class CleanUpFbx
126{
127    KFbxSdkManager* m_pSdkManager;
128public:
129    explicit CleanUpFbx(KFbxSdkManager* pSdkManager) : m_pSdkManager(pSdkManager)
130    {}
131
132    ~CleanUpFbx()
133    {
134        KFbxIOSettings::IOSettingsRef().FreeIOSettings();
135        m_pSdkManager->Destroy();
136    }
137};
138
139class ConvertBindMatrixVisitor : public osg::NodeVisitor
140{
141public:
142    ConvertBindMatrixVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) {}
143
144    virtual void apply(osg::MatrixTransform& node)
145    {
146        if (osgAnimation::Bone* bone = dynamic_cast<osgAnimation::Bone*>(&node))
147        {
148            bone->setInvBindMatrixInSkeletonSpace(osg::Matrix::inverse(bone->getMatrixInBoneSpace()));
149            if (const osgAnimation::Bone* parent = bone->getBoneParent())
150            {
151                bone->setInvBindMatrixInSkeletonSpace(parent->getInvBindMatrixInSkeletonSpace() * bone->getInvBindMatrixInSkeletonSpace());
152            }
153        }
154
155        traverse(node);
156    }
157};
158
159osgDB::ReaderWriter::ReadResult
160ReaderWriterFBX::readNode(const std::string& filenameInit,
161                          const Options* options) const
162{
163    try
164    {
165        std::string ext(osgDB::getLowerCaseFileExtension(filenameInit));
166        if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED;
167
168        std::string filename(osgDB::findDataFile(filenameInit, options));
169        if (filename.empty()) return ReadResult::FILE_NOT_FOUND;
170
171        KFbxSdkManager* pSdkManager = KFbxSdkManager::Create();
172
173        if (!pSdkManager)
174        {
175            return ReadResult::ERROR_IN_READING_FILE;
176        }
177
178        CleanUpFbx cleanUpFbx(pSdkManager);
179
180        KFbxScene* pScene = KFbxScene::Create(pSdkManager, "");
181
182        // The FBX SDK interprets the filename as UTF-8
183#ifdef OSG_USE_UTF8_FILENAME
184        const std::string& utf8filename(filename);
185#else
186        std::string utf8filename(convertStringFromCurrentCodePageToUTF8(filename));
187#endif
188
189        int fileFormat;
190        if (!pSdkManager->GetIOPluginRegistry()->DetectFileFormat(utf8filename.c_str(), fileFormat))
191        {
192            return ReadResult::FILE_NOT_HANDLED;
193        }
194        KFbxImporter* lImporter = KFbxImporter::Create(pSdkManager, "");
195        lImporter->SetFileFormat(fileFormat);
196
197        if (!lImporter->Initialize(utf8filename.c_str()))
198        {
199            return std::string(lImporter->GetLastErrorString());
200        }
201
202        if (!lImporter->IsFBX())
203        {
204            return ReadResult::ERROR_IN_READING_FILE;
205        }
206
207        for (int i = 0; i < lImporter->GetTakeCount(); i++)
208        {
209            KFbxTakeInfo* lTakeInfo = lImporter->GetTakeInfo(i);
210
211            lTakeInfo->mSelect = true;
212        }
213
214        if (!lImporter->Import(pScene))
215        {
216            return std::string(lImporter->GetLastErrorString());
217        }
218
219        //KFbxAxisSystem::OpenGL.ConvertScene(pScene);        // Doesn't work as expected. Still need to transform vertices.
220
221        if (KFbxNode* pNode = pScene->GetRootNode())
222        {
223            bool useFbxRoot = false;
224            if (options)
225            {
226                std::istringstream iss(options->getOptionString());
227                std::string opt;
228                while (iss >> opt)
229                {
230                    if (opt == "UseFbxRoot")
231                    {
232                        useFbxRoot = true;
233                    }
234                }
235            }
236
237            osg::ref_ptr<osgAnimation::AnimationManagerBase> pAnimationManager;
238            bool bNeedSkeleton = false;
239            int nLightCount = 0;
240            osg::ref_ptr<Options> localOptions = NULL;
241            if (options)
242                localOptions = options->cloneOptions();
243            else
244                localOptions = new osgDB::Options();
245            localOptions->setObjectCacheHint(osgDB::ReaderWriter::Options::CACHE_IMAGES);
246
247            std::string filePath = osgDB::getFilePath(filename);
248            FbxMaterialToOsgStateSet fbxMaterialToOsgStateSet(filePath, localOptions.get());
249           
250            ReadResult res = readFbxNode(*pSdkManager, pNode, pAnimationManager,
251                bNeedSkeleton, nLightCount, fbxMaterialToOsgStateSet, localOptions.get());
252
253            if (res.success())
254            {
255                osg::Node* osgNode = res.getNode();
256                if (bNeedSkeleton)
257                {
258                    ConvertBindMatrixVisitor convertBindMatrixVisitor;
259                    osgNode->accept(convertBindMatrixVisitor);
260                    osgAnimation::Skeleton* osgSkeleton = new osgAnimation::Skeleton;
261                    osgSkeleton->setDefaultUpdateCallback();
262                    osgSkeleton->addChild(osgNode);
263                    osgNode = osgSkeleton;
264                }
265                if (pAnimationManager.valid())
266                {
267                    if (osgNode->getUpdateCallback())
268                    {
269                        osg::Group* osgGroup = new osg::Group;
270                        osgGroup->addChild(osgNode);
271                        osgNode = osgGroup;
272                    }
273
274                    //because the animations may be altered after registering
275                    pAnimationManager->buildTargetReference();
276                    osgNode->setUpdateCallback(pAnimationManager.get());
277                }
278
279                KFbxAxisSystem fbxAxis = pScene->GetGlobalSettings().GetAxisSystem();
280
281                if (fbxAxis != KFbxAxisSystem::OpenGL)
282                {
283                    int upSign;
284                    KFbxAxisSystem::eUpVector eUp = fbxAxis.GetUpVector(upSign);
285                    bool bLeftHanded = fbxAxis.GetCoorSystem() == KFbxAxisSystem::LeftHanded;
286                    float fSign = upSign < 0 ? -1.0f : 1.0f;
287                    float zScale = bLeftHanded ? -1.0f : 1.0f;
288
289                    osg::Matrix mat;
290                    switch (eUp)
291                    {
292                    case KFbxAxisSystem::XAxis:
293                        mat.set(0,fSign,0,0,-fSign,0,0,0,0,0,zScale,0,0,0,0,1);
294                        break;
295                    case KFbxAxisSystem::YAxis:
296                        mat.set(1,0,0,0,0,fSign,0,0,0,0,fSign*zScale,0,0,0,0,1);
297                        break;
298                    case KFbxAxisSystem::ZAxis:
299                        mat.set(1,0,0,0,0,0,-fSign*zScale,0,0,fSign,0,0,0,0,0,1);
300                        break;
301                    }
302
303                    osg::Transform* pTransformTemp = osgNode->asTransform();
304                    osg::MatrixTransform* pMatrixTransform = pTransformTemp ?
305                        pTransformTemp->asMatrixTransform() : NULL;
306                    if (pMatrixTransform)
307                    {
308                        pMatrixTransform->setMatrix(pMatrixTransform->getMatrix() * mat);
309                    }
310                    else
311                    {
312                        pMatrixTransform = new osg::MatrixTransform(mat);
313                        if (useFbxRoot && isBasicRootNode(*osgNode))
314                        {
315                            // If root node is a simple group, put all FBX elements under the OSG root
316                            osg::Group* osgGroup = osgNode->asGroup();
317                            for(unsigned int i = 0; i < osgGroup->getNumChildren(); ++i)
318                            {
319                                pMatrixTransform->addChild(osgGroup->getChild(i));
320                            }
321                            pMatrixTransform->setName(osgGroup->getName());
322                        }
323                        else
324                        {
325                            pMatrixTransform->addChild(osgNode);
326                        }
327                    }
328                    osgNode = pMatrixTransform;
329                }
330
331                osgNode->setName(filenameInit);
332                return osgNode;
333            }
334        }
335    }
336    catch (...)
337    {
338    }
339
340    return ReadResult::ERROR_IN_READING_FILE;
341}
342
343osgDB::ReaderWriter::WriteResult ReaderWriterFBX::writeNode(
344    const osg::Node& node,
345    const std::string& filename,
346    const Options* options) const
347{
348    try
349    {
350        std::string ext = osgDB::getLowerCaseFileExtension(filename);
351        if (!acceptsExtension(ext)) return WriteResult::FILE_NOT_HANDLED;
352
353        osg::ref_ptr<Options> localOptions = options ?
354            static_cast<Options*>(options->clone(osg::CopyOp::SHALLOW_COPY)) : new Options;
355        localOptions->getDatabasePathList().push_front(osgDB::getFilePath(filename));
356
357        KFbxSdkManager* pSdkManager = KFbxSdkManager::Create();
358
359        if (!pSdkManager)
360        {
361            return WriteResult::ERROR_IN_WRITING_FILE;
362        }
363
364        CleanUpFbx cleanUpFbx(pSdkManager);
365
366        bool useFbxRoot = false;
367        if (options)
368        {
369            std::istringstream iss(options->getOptionString());
370            std::string opt;
371            while (iss >> opt)
372            {
373                if (opt == "Embedded")
374                {
375                    IOSREF.SetBoolProp(EXP_FBX_EMBEDDED, true);
376                    if (KFbxIOSettings::IOSettingsRef().IsIOSettingsAllocated())
377                        KFbxIOSettings::IOSettingsRef().AllocateIOSettings(*pSdkManager);
378                }
379                else if (opt == "UseFbxRoot")
380                {
381                    useFbxRoot = true;
382                }
383            }
384        }
385
386        KFbxScene* pScene = KFbxScene::Create(pSdkManager, "");
387        WriterNodeVisitor writerNodeVisitor(pScene, pSdkManager, filename,
388            options, osgDB::getFilePath(node.getName().empty() ? filename : node.getName()));
389        if (useFbxRoot && isBasicRootNode(node))
390        {
391            // If root node is a simple group, put all elements under the FBX root
392            const osg::Group * osgGroup = node.asGroup();
393            for(unsigned int child=0; child<osgGroup->getNumChildren(); ++child) {
394                const_cast<osg::Node *>(osgGroup->getChild(child))->accept(writerNodeVisitor);
395            }
396        }
397        else {
398            // Normal scene
399            const_cast<osg::Node&>(node).accept(writerNodeVisitor);
400        }
401
402        KFbxExporter* lExporter = KFbxExporter::Create(pSdkManager, "");
403        pScene->GetGlobalSettings().SetAxisSystem(KFbxAxisSystem::eOpenGL);
404
405        // Ensure the directory exists or else the FBX SDK will fail
406        if (!osgDB::makeDirectoryForFile(filename)) {
407            osg::notify(osg::NOTICE) << "Can't create directory for file '" << filename << "'. FBX SDK may fail creating the file." << std::endl;
408        }
409
410        // The FBX SDK interprets the filename as UTF-8
411#ifdef OSG_USE_UTF8_FILENAME
412        const std::string& utf8filename(filename);
413#else
414        std::string utf8filename(convertStringFromCurrentCodePageToUTF8(filename));
415#endif
416
417        if (!lExporter->Initialize(utf8filename.c_str()))
418        {
419            return std::string(lExporter->GetLastErrorString());
420        }
421        if (!lExporter->Export(pScene))
422        {
423            return std::string(lExporter->GetLastErrorString());
424        }
425
426        return WriteResult::FILE_SAVED;
427    }
428    catch (const std::string& s)
429    {
430        return s;
431    }
432    catch (const char* s)
433    {
434        return std::string(s);
435    }
436    catch (...)
437    {
438    }
439
440    return WriteResult::ERROR_IN_WRITING_FILE;
441}
442
443///////////////////////////////////////////////////////////////////////////
444// Add ourself to the Registry to instantiate the reader/writer.
445
446REGISTER_OSGPLUGIN(fbx, ReaderWriterFBX)
Note: See TracBrowser for help on using the browser.