root/OpenSceneGraph/trunk/src/osgPlugins/OpenFlight/ReaderWriterFLT.cpp @ 9890

Revision 9890, 22.4 kB (checked in by robert, 6 years ago)

Completed support for automatic detection of plugin features.

Cleaned up debug out of various plugins to ensure a clean osgconv --formats.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
RevLine 
[7748]1/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
2 *
3 * This library is open source and may be redistributed and/or modified under 
4 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
5 * (at your option) any later version.  The full license is in LICENSE file
6 * included with this distribution, and on the openscenegraph.org website.
7 *
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 * OpenSceneGraph Public License for more details.
12*/
13
[5038]14//
[7748]15// OpenFlight® loader for OpenSceneGraph
[5038]16//
[7669]17//  Copyright (C) 2005-2007  Brede Johansen
[5038]18//
19
20#include <stdexcept>
21#include <osg/Notify>
[5136]22#include <osg/ProxyNode>
[5038]23#include <osgDB/FileNameUtils>
24#include <osgDB/FileUtils>
25#include <osgDB/Registry>
26#include <osgDB/ReadFile>
[6267]27#include <OpenThreads/ReentrantMutex>
[5218]28#include <osgUtil/Optimizer>
[5038]29
30#include "Registry.h"
31#include "Document.h"
32#include "RecordInputStream.h"
[8003]33#include "DataOutputStream.h"
34#include "FltExportVisitor.h"
35#include "ExportOptions.h"
[5038]36
[6267]37#define SERIALIZER() OpenThreads::ScopedLock<OpenThreads::ReentrantMutex> lock(_serializerMutex) 
[5038]38
39using namespace flt;
40using namespace osg;
41using namespace osgDB;
42
43
[5136]44class ReadExternalsVisitor : public osg::NodeVisitor
45{
[5886]46    osg::ref_ptr<ReaderWriter::Options> _options;
[5820]47    bool _cloneExternalReferences;
[5136]48
49public:
50
51    ReadExternalsVisitor(ReaderWriter::Options* options) :
52        osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
[5820]53        _options(options),
54        _cloneExternalReferences(false)
[5136]55    {
[5820]56        if (options)
57            _cloneExternalReferences = (options->getOptionString().find("cloneExternalReferences")!=std::string::npos);
[5136]58    }
[5820]59
[5136]60    virtual ~ReadExternalsVisitor() {}
61
62    virtual void apply(ProxyNode& node)
63    {
[5281]64        // Transfer ownership of pools.
[5236]65        _options->setUserData( node.getUserData() );
[5281]66        node.setUserData(NULL);
[5236]67
[5136]68        for (unsigned int pos=0; pos<node.getNumFileNames(); pos++)
69        {
70            std::string filename = node.getFileName(pos);
71
72            // read external
[5820]73            osg::ref_ptr<osg::Node> external = osgDB::readNodeFile(filename,_options.get());
74            if (external.valid())
75            {
76                if (_cloneExternalReferences)
77                    external = dynamic_cast<osg::Node*>(external->clone(osg::CopyOp(osg::CopyOp::DEEP_COPY_NODES)));
78
79                node.addChild(external.get());
80            }
[5136]81        }
82    }
83};
84
85
[5820]86
[8003]87/*!
88
[8564]89FLTReaderWriter supports importing and exporting OSG scene graphs
[8003]90from/to OpenFlight files.
91
92<table>
93  <tr>
94    <th></th>
95    <th align="center">Node</th>
96    <th align="center">Object</th>
97    <th align="center">Image</th>
98    <th align="center">HeightField</th>
99  </tr>
100  <tr>
101    <td align="right">Read</td>
102    <td align="center"><strong>X</strong></td>
103    <td></td>
104    <td></td>
105    <td></td>
106  </tr>
107  <tr>
108    <td align="right">Write</td>
109    <td align="center"><strong>X</strong></td>
110    <td></td>
111    <td></td>
112    <td></td>
113  </tr>
114</table>
115
116*/
117
[5038]118class FLTReaderWriter : public ReaderWriter
119{
120    public:
[8003]121        FLTReaderWriter()
122          : _implicitPath( "." )
[8578]123        {
124            supportsExtension("flt","OpenFlight format");
125           
[8596]126            supportsOption("clampToEdge","Import option");
127            supportsOption("keepExternalReferences","Import option");
128            supportsOption("preserveFace","Import option");
129            supportsOption("preserveObject","Import option");
130            supportsOption("dofAnimation","Import option");
131            supportsOption("billboardCenter","Import option");
132            supportsOption("noTextureAlphaForTransparancyBinning","Import option");
133            supportsOption("readObjectRecordData","Import option");
134            supportsOption("noUnitsConversion","Import option");
135            supportsOption("convertToFeet","Import option");
136            supportsOption("convertToInches","Import option");
137            supportsOption("convertToMeters","Import option");
138            supportsOption("convertToKilometers","Import option");
139            supportsOption("convertToNauticalMiles","Import option");
[8595]140
[8596]141            supportsOption( "version=<ver>", "Export option: Specifies the version of the output OpenFlight file. Supported values include 15.7, 15.8, and 16.1. Default is 16.1. Example: \"version=15.8\"." );
142            supportsOption( "units=<units>", "Export option: Specifies the contents of the Units field of the OpenFliht header record. Valid values include INCHES, FEET, METERS, KILOMETERS, and NATICAL_MILES. Default is METERS. Example: \"units=METERS\"." );
143            supportsOption( "validate", "Export option: If present in the Options string, the plugin does not write an OpenFlight file. Instead, it returns an indication of the scene graph's suitability for OpenFlight export." );
144            supportsOption( "tempDir=<dir>", "Export option: Specifies the directory to use for creation of temporary files. If not specified, the directory is taken from the file name. If the file doesn't contain a path, the current working directory is used. Applications should set this to the name of their app-specific temp directory. If the path contains spaces, use double quotes to ensure correct parsing. Examples: \"tempDir=/tmp\", \"tempDir=\"C:\\My Temp Dir\"." );
145            supportsOption( "lighting=<ON|OFF>", "Export option: Specifies a default enable/disable state for lighting, for Nodes in the exported scene graph that don't set it explicitly. By default, the exporter assumes lighting is enabled (GL_LIGHTING ON). Set this to either ON or OFF. Example: \"lighting=OFF\"." );
146            supportsOption( "stripTextureFilePath", "Export option: If present in the Options string, the exporter strips the path from texture file names, and writes only the texture file name to the FLT Texture Palette. By default, the exporter doesn't strip the path, and the name written to the Texture Palette is taken directly from the osg::Image object referenced by the osg::Texture2D StateAttribute." );
[8578]147        }
[8003]148
[5038]149        virtual const char* className() const { return "FLT Reader/Writer"; }
150
151        virtual bool acceptsExtension(const std::string& extension) const
152        {
[7169]153            return equalCaseInsensitive(extension,"flt") || extension.empty();
[5038]154        }
155
156        virtual ReadResult readObject(const std::string& file, const Options* options) const
157        {
158            return readNode(file, options);
159        }
160       
161        virtual ReadResult readNode(const std::string& file, const Options* options) const
162        {
163            SERIALIZER();
164
165            std::string ext = osgDB::getLowerCaseFileExtension(file);
166            if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED;
167
[5136]168            std::string fileName = osgDB::findDataFile(file, options);
[5038]169            if (fileName.empty()) return ReadResult::FILE_NOT_FOUND;
170
[5136]171            // in local cache?
172            {
[5986]173                osg::Node* node = flt::Registry::instance()->getExternalFromLocalCache(fileName);
[5136]174                if (node)
175                    return ReadResult(node, ReaderWriter::ReadResult::FILE_LOADED_FROM_CACHE);
176            }
177
178            // setting up the database path so that internally referenced file are searched for on relative paths.
[5038]179            osg::ref_ptr<Options> local_opt = options ? static_cast<Options*>(options->clone(osg::CopyOp::SHALLOW_COPY)) : new Options;
180            local_opt->setDatabasePath(osgDB::getFilePath(fileName));
[5122]181
[5136]182            ReadResult rr;
[5122]183
[5136]184            // read file
185            {
[9124]186                osgDB::ifstream istream;
[5136]187                istream.imbue(std::locale::classic());
188                istream.open(fileName.c_str(), std::ios::in | std::ios::binary);
189
190                if (istream)
191                {
192                    rr = readNode(istream,local_opt.get());
193                }
194            }
195
196            static int nestedExternalsLevel = 0;
197            if (rr.success())
198            {
199                // add to local cache.
[5986]200                flt::Registry::instance()->addExternalToLocalCache(fileName,rr.getNode());
[5461]201       
202                bool keepExternalReferences = false;
203                if (options)
204                    keepExternalReferences = (options->getOptionString().find("keepExternalReferences")!=std::string::npos);
[5136]205
[5461]206
207                if ( !keepExternalReferences )
[5136]208                {
[5461]209                    osg::notify(osg::DEBUG_INFO) << "keepExternalReferences not found, so externals will be re-readed"<<std::endl;
210                    // read externals.
211                    if (rr.getNode())
212                    {
213                        nestedExternalsLevel++;
214                        ReadExternalsVisitor visitor(local_opt.get());
215                        rr.getNode()->accept(visitor);
216                        nestedExternalsLevel--;
217                    }
[5136]218                }
[5461]219                else
220                {
221                    osg::notify(osg::DEBUG_INFO) << "keepExternalReferences found, so externals will be left as ProxyNodes"<<std::endl;   
222                }
[5136]223            }
224
225            // clear local cache.
226            if (nestedExternalsLevel==0)
227                flt::Registry::instance()->clearLocalCache();
228
229            return rr;
[5038]230        }
231       
232        virtual ReadResult readObject(std::istream& fin, const Options* options) const
233        {
234            return readNode(fin, options);
235        }
236       
237        virtual ReadResult readNode(std::istream& fin, const Options* options) const
238        {
[5136]239            Document document;
240            document.setOptions(options);
241
[5236]242            // option string and parent pools
[5136]243            if (options)
[5038]244            {
[5205]245                const char readerMsg[] = "flt reader option: ";
[5461]246               
[5794]247                document.setReplaceClampWithClampToEdge((options->getOptionString().find("clampToEdge")!=std::string::npos));
248                osg::notify(osg::DEBUG_INFO) << readerMsg << "clampToEdge=" << document.getReplaceClampWithClampToEdge() << std::endl;
249
[5461]250                document.setKeepExternalReferences((options->getOptionString().find("keepExternalReferences")!=std::string::npos));
251                osg::notify(osg::DEBUG_INFO) << readerMsg << "keepExternalReferences=" << document.getKeepExternalReferences() << std::endl;
[5205]252
[5238]253                document.setPreserveFace((options->getOptionString().find("preserveFace")!=std::string::npos));
254                osg::notify(osg::DEBUG_INFO) << readerMsg << "preserveFace=" << document.getPreserveFace() << std::endl;
[5205]255
[5528]256                document.setPreserveObject((options->getOptionString().find("preserveObject")!=std::string::npos));
257                osg::notify(osg::DEBUG_INFO) << readerMsg << "preserveObject=" << document.getPreserveObject() << std::endl;
258
[5205]259                document.setDefaultDOFAnimationState((options->getOptionString().find("dofAnimation")!=std::string::npos));
260                osg::notify(osg::DEBUG_INFO) << readerMsg << "dofAnimation=" << document.getDefaultDOFAnimationState() << std::endl;
261
[5610]262                document.setUseBillboardCenter((options->getOptionString().find("billboardCenter")!=std::string::npos));
263                osg::notify(osg::DEBUG_INFO) << readerMsg << "billboardCenter=" << document.getUseBillboardCenter() << std::endl;
264
[5136]265                document.setUseTextureAlphaForTransparancyBinning(options->getOptionString().find("noTextureAlphaForTransparancyBinning")==std::string::npos);
[5610]266                osg::notify(osg::DEBUG_INFO) << readerMsg << "noTextureAlphaForTransparancyBinning=" << !document.getUseTextureAlphaForTransparancyBinning() << std::endl;
[5038]267
[7931]268                document.setReadObjectRecordData(options->getOptionString().find("readObjectRecordData")==std::string::npos);
269                osg::notify(osg::DEBUG_INFO) << readerMsg << "readObjectRecordData=" << !document.getReadObjectRecordData() << std::endl;
270
[5205]271                document.setDoUnitsConversion((options->getOptionString().find("noUnitsConversion")==std::string::npos)); // default to true, unless noUnitsConversion is specified.
[5610]272                osg::notify(osg::DEBUG_INFO) << readerMsg << "noUnitsConversion=" << !document.getDoUnitsConversion() << std::endl;
[5205]273
[5136]274                if (document.getDoUnitsConversion())
[5038]275                {
[5136]276                    if (options->getOptionString().find("convertToFeet")!=std::string::npos)
277                        document.setDesiredUnits(FEET);
278                    else if (options->getOptionString().find("convertToInches")!=std::string::npos)
279                        document.setDesiredUnits(INCHES);
280                    else if (options->getOptionString().find("convertToMeters")!=std::string::npos)
281                        document.setDesiredUnits(METERS);
282                    else if (options->getOptionString().find("convertToKilometers")!=std::string::npos)
283                        document.setDesiredUnits(KILOMETERS);
284                    else if (options->getOptionString().find("convertToNauticalMiles")!=std::string::npos)
285                        document.setDesiredUnits(NAUTICAL_MILES);
[5038]286                }
[5236]287
288                const ParentPools* pools = dynamic_cast<const ParentPools*>( options->getUserData() );
289                if (pools)
290                {
291                    // This file is an external reference. The individual pools will
292                    // be non-NULL if the parent is overriding the ext ref model's pools.
293                    if (pools->getColorPool())
294                        document.setColorPool( pools->getColorPool(), true );
295                    if (pools->getTexturePool())
296                        document.setTexturePool( pools->getTexturePool(), true );
297                    if (pools->getMaterialPool())
298                        document.setMaterialPool( pools->getMaterialPool(), true );
[6091]299                    if (pools->getLightSourcePool())
300                        document.setLightSourcePool( pools->getLightSourcePool(), true );
[5236]301                    if (pools->getLPAppearancePool())
302                        document.setLightPointAppearancePool( pools->getLPAppearancePool(), true );
[6759]303                    if (pools->getLPAnimationPool())
304                        document.setLightPointAnimationPool( pools->getLPAnimationPool(), true );
[5236]305                    if (pools->getShaderPool())
306                        document.setShaderPool( pools->getShaderPool(), true );
307                }
[5136]308            }
[5038]309
[7669]310            const int RECORD_HEADER_SIZE = 4;
[8564]311            opcode_type continuationOpcode = INVALID_OP;
[7669]312            std::string continuationBuffer;
313
314            while (fin.good() && !document.done())
[5136]315            {
[7669]316                // The continuation record complicates things a bit.
317
318                // Get current read position in stream.
319                std::istream::pos_type pos = fin.tellg();
320
321                // get opcode and size
322                flt::DataInputStream dataStream(fin.rdbuf());
323                opcode_type opcode = (opcode_type)dataStream.readUInt16();
324                size_type   size   = (size_type)dataStream.readUInt16();
325
[8564]326                // If size == 0, an EOF has probably been reached, i.e. there is nothing
327                // more to read so we must return.
[8295]328                if (size==0)
[8564]329                {
330                    // If a header was read, we return it.
331                    // This allows us handle files with empty hierarchies.
332                    if (document.getHeaderNode())
333                    {
334                        return document.getHeaderNode();
335                    }
336                    else // (no valid header)
337                    {
338                        return ReadResult::ERROR_IN_READING_FILE;
339                    }
340                }
[8295]341
[7669]342                // variable length record complete?
343                if (!continuationBuffer.empty() && opcode!=CONTINUATION_OP)
[5038]344                {
[7669]345                    // parse variable length record
346                    std::stringbuf sb(continuationBuffer);
347                    flt::RecordInputStream recordStream(&sb);
348                    recordStream.readRecordBody(continuationOpcode, continuationBuffer.length(), document);
349
[8564]350                    continuationOpcode = INVALID_OP;
[7669]351                    continuationBuffer.clear();
[5038]352                }
[7669]353
354                // variable length record use continuation buffer in case next
355                // record is a continuation record.
356                if (opcode==EXTENSION_OP ||
357                    opcode==NAME_TABLE_OP ||
358                    opcode==LOCAL_VERTEX_POOL_OP ||
359                    opcode==MESH_PRIMITIVE_OP)
360                {
361                    continuationOpcode = opcode;
362
363                    if (size > RECORD_HEADER_SIZE)
364                    {
365                        // Put record in buffer.
366                        std::string buffer((std::string::size_type)size-RECORD_HEADER_SIZE,'\0');
367                        fin.read(&buffer[0], size-RECORD_HEADER_SIZE);
368
369                        // Can't parse it until we know we have the complete record.
370                        continuationBuffer = buffer;
371                    }
372                }
373                else if (opcode==CONTINUATION_OP)
374                {
375                    if (size > RECORD_HEADER_SIZE)
376                    {
377                        std::string buffer((std::string::size_type)size-RECORD_HEADER_SIZE,'\0');
378                        fin.read(&buffer[0], size-RECORD_HEADER_SIZE);
379
380                        // The record continues.
381                        continuationBuffer.append(buffer);
382                    }
383                }
384                else if (opcode==VERTEX_PALETTE_OP)
385                {
386                    // Vertex Palette needs the file stream as it reads beyond the current record.
387                    flt::RecordInputStream recordStream(fin.rdbuf());
388                    recordStream.readRecordBody(opcode, size, document);
389                }
390                else // normal (fixed size) record.
391                {
392                    // Put record in buffer.
393                    std::string buffer((std::string::size_type)size,'\0');
394                    if (size > RECORD_HEADER_SIZE)
395                        fin.read(&buffer[0], size-RECORD_HEADER_SIZE);
396
397                    // Parse buffer.
398                    std::stringbuf sb(buffer);
399                    flt::RecordInputStream recordStream(&sb);
400                    recordStream.readRecordBody(opcode, size, document);
401                }
[5136]402            }
[5038]403
[5136]404            if (!document.getHeaderNode())
405                return ReadResult::ERROR_IN_READING_FILE;
[5461]406
[5218]407            if (!document.getPreserveFace())
408            {
409                osgUtil::Optimizer optimizer;
[6257]410                optimizer.optimize(document.getHeaderNode(),
411                    osgUtil::Optimizer::SHARE_DUPLICATE_STATE |
412                    osgUtil::Optimizer::MERGE_GEOMETRY |
413                    osgUtil::Optimizer::MERGE_GEODES |
414                    osgUtil::Optimizer::TESSELLATE_GEOMETRY |
415                    osgUtil::Optimizer::STATIC_OBJECT_DETECTION);
[5218]416            }
[5038]417
[5136]418            return document.getHeaderNode();
[5038]419        }
420
421        virtual WriteResult writeObject(const Object& object,const std::string& fileName, const osgDB::ReaderWriter::Options* options) const
422        {
423            const Node* node = dynamic_cast<const Node*>(&object);
424            if (node) return writeNode( *node, fileName, options );
425            return WriteResult::FILE_NOT_HANDLED;
426        }
427
[8003]428        virtual WriteResult writeNode( const osg::Node& node, const std::string& fileName, const Options* options ) const
[5038]429        {
[8003]430            if ( fileName.empty() )
431            {
432                return WriteResult::FILE_NOT_HANDLED;
433            }
[9890]434
[8003]435            std::string ext = osgDB::getLowerCaseFileExtension( fileName );
436            if ( !acceptsExtension(ext) )
437                return WriteResult::FILE_NOT_HANDLED;
438
439            // Get and save the implicit path name (in case a path wasn't specified in Options).
440            std::string filePath = osgDB::getFilePath( fileName );
441            if (!filePath.empty())
442                _implicitPath = filePath;
443
[9124]444            osgDB::ofstream fOut;
[8003]445            fOut.open( fileName.c_str(), std::ios::out | std::ios::binary );
446            if ( fOut.fail())
447            {
448                osg::notify( osg::FATAL ) << "fltexp: Failed to open output stream." << std::endl;
449                return WriteResult::ERROR_IN_WRITING_FILE;
450            }
451
452            WriteResult wr = WriteResult::FILE_NOT_HANDLED;
453            wr = writeNode( node, fOut, options );
454            fOut.close();
455
456            return wr;
[5038]457        }
[8003]458
459
[5038]460        virtual WriteResult writeObject(const Object& object,std::ostream& fout, const osgDB::ReaderWriter::Options* options) const
461        {
462            const Node* node = dynamic_cast<const Node*>(&object);
463            if (node) return writeNode( *node, fout, options );
464            return WriteResult::FILE_NOT_HANDLED;
465        }
466
[8003]467        virtual WriteResult writeNode( const osg::Node& node, std::ostream& fOut, const Options* options ) const
[5038]468        {
[8003]469            // Convert Options to FltOptions.
470            ExportOptions* fltOpt = new ExportOptions( options );
471            fltOpt->parseOptionsString();
472
473            // If user didn't specify a temp dir, use the output directory
474            //   that was implicit in the output file name.
475            if (fltOpt->getTempDir().empty())
476                fltOpt->setTempDir( _implicitPath );
477            if (!fltOpt->getTempDir().empty())
478            {
479                // If the temp directory doesn't already exist, make it.
480                if ( !osgDB::makeDirectory( fltOpt->getTempDir() ) )
481                {
482                    osg::notify( osg::FATAL ) << "fltexp: Error creating temp dir: " << fltOpt->getTempDir() << std::endl;
483                    return WriteResult::ERROR_IN_WRITING_FILE;
484                }
485            }
486
487            flt::DataOutputStream dos( fOut.rdbuf(), fltOpt->getValidateOnly() );
488            flt::FltExportVisitor fnv( &dos, fltOpt );
489
490            // Hm. 'node' is const, but in order to write out this scene graph,
491            //   must use Node::accept() which requires 'node' to be non-const.
492            //   Pretty much requires casting away const.
493            osg::Node* nodeNonConst = const_cast<osg::Node*>( &node );
[8010]494            if (!nodeNonConst)
495                return WriteResult::ERROR_IN_WRITING_FILE;
[8003]496            nodeNonConst->accept( fnv );
497            fnv.complete( node );
498
[8010]499            return fltOpt->getWriteResult();
[5038]500        }
501
502    protected:
[8003]503        mutable std::string _implicitPath;
[5038]504
[6267]505        mutable OpenThreads::ReentrantMutex _serializerMutex;
[5038]506};
507
508// now register with Registry to instantiate the above
509// reader/writer.
[7076]510REGISTER_OSGPLUGIN(OpenFlight, FLTReaderWriter)
Note: See TracBrowser for help on using the browser.