/* * This library is open source and may be redistributed and/or modified under * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or (at * your option) any later version. The full license is in the LICENSE file * included with this distribution, and on the openscenegraph.org website. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * OpenSceneGraph Public License for more details. */ // // Copyright(c) 2008 Skew Matrix Software LLC. // #include "FltExportVisitor.h" #include "ExportOptions.h" #include "FltWriteResult.h" #include "DataOutputStream.h" #include "Opcodes.h" #include "LightSourcePaletteManager.h" #include "MaterialPaletteManager.h" #include "TexturePaletteManager.h" #include "VertexPaletteManager.h" #include "AttrData.h" #include "Utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _MSC_VER // Disable this warning. It's OK for us to use 'this' in initializer list, // as the texturePaletteManager merely stores a ref to it. #pragma warning( disable : 4355 ) #endif namespace flt { FltExportVisitor::FltExportVisitor( DataOutputStream* dos, ExportOptions* fltOpt ) : osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ), _fltOpt( fltOpt ), _dos( *dos ), _materialPalette( new MaterialPaletteManager( *fltOpt ) ), _texturePalette( new TexturePaletteManager( *this, *fltOpt ) ), _lightSourcePalette( new LightSourcePaletteManager( ) ), _vertexPalette( new VertexPaletteManager( *fltOpt ) ), _firstNode( true ) { // Init the StateSet stack. osg::StateSet* ss = new osg::StateSet; int unit; for(unit=0; unit<8; unit++) { osg::TexEnv* texenv = new osg::TexEnv; ss->setTextureAttributeAndModes( unit, texenv, osg::StateAttribute::OFF ); // TBD other texture state? } osg::Material* material = new osg::Material; ss->setAttribute( material, osg::StateAttribute::OFF ); if (fltOpt->getLightingDefault()) ss->setMode( GL_LIGHTING, osg::StateAttribute::ON ); else ss->setMode( GL_LIGHTING, osg::StateAttribute::OFF ); osg::CullFace* cf = new osg::CullFace; ss->setAttributeAndModes( cf, osg::StateAttribute::OFF ); osg::BlendFunc* bf = new osg::BlendFunc; ss->setAttributeAndModes( bf, osg::StateAttribute::OFF ); osg::PolygonOffset* po = new osg::PolygonOffset; ss->setAttributeAndModes( po, osg::StateAttribute::OFF ); _stateSetStack.push_back( ss ); // Temp file for storing records. Need a temp file because we don't // write header and palette until FltExportVisitor completes traversal. _recordsTempName = fltOpt->getTempDir() + "/ofw_temp_records"; _recordsStr.open( _recordsTempName.c_str(), std::ios::out | std::ios::binary ); _records = new DataOutputStream( _recordsStr.rdbuf(), fltOpt->getValidateOnly() ); // Always write initial push level writePush(); } FltExportVisitor::~FltExportVisitor() { // Delete our temp file. if (_recordsStr.is_open()) { OSG_WARN << "fltexp: FltExportVisitor destructor has an open temp file." << std::endl; // This should not happen. FltExportVisitor::complete should close // this file before we get to this destructor. return; } OSG_INFO << "fltexp: Deleting temp file " << _recordsTempName << std::endl; FLTEXP_DELETEFILE( _recordsTempName.c_str() ); } void FltExportVisitor::apply( osg::Group& node ) { ScopedStatePushPop guard( this, node.getStateSet() ); if (_firstNode) { // On input, a FLT header creates a Group node. // On export, we always write a Header record, but then the first Node // we export is the Group that was created from the original input Header. // On successive roundtrips, this results in increased redundant top-level Group nodes/records. // Avoid this by NOT outputting anything for a top-level Group node. _firstNode = false; traverse( node ); return; } // A Group node could indicate one of many possible records. // Header record -- Don't need to support this here. We always output a header. // Group record -- HIGH // Child of an LOD node -- HIGH Currently write out a Group record regardless. // InstanceDefinition/InstanceReference -- MED -- multiparented Group is an instance // Extension record -- MED // Object record -- MED // LightPointSystem record (if psgSim::MultiSwitch) -- LOW osgSim::MultiSwitch* multiSwitch = dynamic_cast( &node ); if (multiSwitch) { writeSwitch( multiSwitch ); } else { osgSim::ObjectRecordData* ord = dynamic_cast< osgSim::ObjectRecordData* >( node.getUserData() ); if (ord) { // This Group should write an Object Record. writeObject( node, ord ); } else { // Handle other cases here. // For now, just output a Group record. writeGroup( node ); } } writeMatrix( node.getUserData() ); writeComment( node ); writePushTraverseWritePop( node ); } void FltExportVisitor::apply( osg::Sequence& node ) { _firstNode = false; ScopedStatePushPop guard( this, node.getStateSet() ); writeSequence( node ); writeMatrix( node.getUserData() ); writeComment( node ); writePushTraverseWritePop( node ); } void FltExportVisitor::apply( osg::Switch& node ) { _firstNode = false; ScopedStatePushPop guard( this, node.getStateSet() ); writeSwitch( &node ); writeMatrix( node.getUserData() ); writeComment( node ); writePushTraverseWritePop( node ); } void FltExportVisitor::apply( osg::LOD& lodNode ) { _firstNode = false; ScopedStatePushPop guard( this, lodNode.getStateSet() ); // LOD center - same for all children osg::Vec3d center = lodNode.getCenter(); // Iterate children of the LOD and write a separate LOD record for each, // with that child's individual switchIn and switchOut properties for ( size_t i = 0; i < lodNode.getNumChildren(); ++i ) { osg::Node* lodChild = lodNode.getChild(i); // Switch-in/switch-out distances may vary per child double switchInDist = lodNode.getMaxRange(i); double switchOutDist = lodNode.getMinRange(i); writeLevelOfDetail( lodNode, center, switchInDist, switchOutDist); writeMatrix( lodNode.getUserData() ); writeComment( lodNode ); // Traverse each child of the LOD writePushTraverseChildWritePop( *lodChild ); } } void FltExportVisitor::apply( osg::MatrixTransform& node ) { // Importer reads a Matrix record and inserts a MatrixTransform above // the current node. We need to do the opposite: Write a Matrix record // as an ancillary record for each child. We do that by storing the // MatrixTransform in each child's UserData. Each child then checks // UserData and writes a Matrix record if UserData is a MatrixTransform. _firstNode = false; ScopedStatePushPop guard( this, node.getStateSet() ); osg::ref_ptr< osg::RefMatrix > m = new osg::RefMatrix; m->set( node.getMatrix() ); if (node.getUserData()) { const osg::RefMatrix* rm = dynamic_cast( node.getUserData() ); if (rm) (*m) *= *rm; } typedef std::vector< osg::ref_ptr< osg::Referenced > > UserDataList; UserDataList saveUserDataList( node.getNumChildren() ); unsigned int idx; for( idx=0; idxgetUserData(); node.getChild( idx )->setUserData( m.get() ); } traverse( (osg::Node&)node ); // Restore saved UserData. for( idx=0; idx< node.getNumChildren(); ++idx ) { node.getChild( idx )->setUserData( saveUserDataList[ idx ].get() ); } } void FltExportVisitor::apply( osg::PositionAttitudeTransform& node ) { _firstNode = false; ScopedStatePushPop guard( this, node.getStateSet() ); osg::ref_ptr m = new osg::RefMatrix( osg::Matrix::translate( -node.getPivotPoint() ) * osg::Matrix::scale( node.getScale() ) * osg::Matrix::rotate( node.getAttitude() ) * osg::Matrix::translate( node.getPosition() ) ); typedef std::vector< osg::ref_ptr< osg::Referenced > > UserDataList; UserDataList saveUserDataList( node.getNumChildren() ); unsigned int idx; for( idx=0; idxgetUserData(); node.getChild( idx )->setUserData( m.get() ); } traverse( (osg::Node&)node ); // Restore saved UserData. for( idx=0; idxsetUserData( saveUserDataList[ idx ].get() ); } } void FltExportVisitor::apply( osg::Transform& node ) { _firstNode = false; ScopedStatePushPop guard( this, node.getStateSet() ); osgSim::DOFTransform* dof = dynamic_cast( &node ); if (dof) { writeDegreeOfFreedom( dof); } writeMatrix( node.getUserData() ); writeComment( node ); writePushTraverseWritePop( node ); } void FltExportVisitor::apply( osg::LightSource& node ) { _firstNode = false; ScopedStatePushPop guard( this, node.getStateSet() ); writeLightSource( node ); writeMatrix( node.getUserData() ); writeComment( node ); writePushTraverseWritePop( node ); } // Billboards also go through this code. The Geode is passed // to writeFace and writeMesh. If those methods successfully cast // the Geode to a Billboard, then they set the template mode // bit accordingly. void FltExportVisitor::apply( osg::Geode& node ) { _firstNode = false; ScopedStatePushPop guard( this, node.getStateSet() ); unsigned int idx; for (idx=0; idxasGeometry(); if (!geom) { std::string warning( "fltexp: Non-Geometry Drawable encountered. Ignoring." ); OSG_WARN << warning << std::endl; _fltOpt->getWriteResult().warn( warning ); continue; } ScopedStatePushPop drawableGuard( this, geom->getStateSet() ); // Push and pop subfaces if polygon offset is on. SubfaceHelper subface( *this, getCurrentStateSet() ); if (atLeastOneFace( *geom )) { // If at least one record will be a Face record, then we // need to write to the vertex palette. _vertexPalette->add( *geom ); // Iterate over all PrimitiveSets and output Face records. unsigned int jdx; for (jdx=0; jdx < geom->getNumPrimitiveSets(); jdx++) { osg::PrimitiveSet* prim = geom->getPrimitiveSet( jdx ); if ( isMesh( prim->getMode() ) ) continue; if (prim->getType() == osg::PrimitiveSet::DrawArraysPrimitiveType) handleDrawArrays( dynamic_cast( prim ), *geom, node ); else if (prim->getType() == osg::PrimitiveSet::DrawArrayLengthsPrimitiveType) handleDrawArrayLengths( dynamic_cast( prim ), *geom, node ); else if ( (prim->getType() == osg::PrimitiveSet::DrawElementsUBytePrimitiveType) || (prim->getType() == osg::PrimitiveSet::DrawElementsUShortPrimitiveType) || (prim->getType() == osg::PrimitiveSet::DrawElementsUIntPrimitiveType) ) handleDrawElements( dynamic_cast( prim ), *geom, node ); else { std::string warning( "fltexp: Unknown PrimitiveSet type." ); OSG_WARN << warning << std::endl; _fltOpt->getWriteResult().warn( warning ); return; } } } if (atLeastOneMesh( *geom )) { // If at least one Mesh record, write out preamble mesh records // followed by a Mesh Primitive record per PrimitiveSet. writeMesh( node, *geom ); writeMatrix( node.getUserData() ); writeComment( node ); writeMultitexture( *geom ); writeLocalVertexPool( *geom ); writePush(); unsigned int jdx; for (jdx=0; jdx < geom->getNumPrimitiveSets(); jdx++) { osg::PrimitiveSet* prim = geom->getPrimitiveSet( jdx ); if ( !isMesh( prim->getMode() ) ) continue; if (prim->getType() == osg::PrimitiveSet::DrawArraysPrimitiveType) handleDrawArrays( dynamic_cast( prim ), *geom, node ); else if (prim->getType() == osg::PrimitiveSet::DrawArrayLengthsPrimitiveType) handleDrawArrayLengths( dynamic_cast( prim ), *geom, node ); else if ( (prim->getType() == osg::PrimitiveSet::DrawElementsUBytePrimitiveType) || (prim->getType() == osg::PrimitiveSet::DrawElementsUShortPrimitiveType) || (prim->getType() == osg::PrimitiveSet::DrawElementsUIntPrimitiveType) ) handleDrawElements( dynamic_cast( prim ), *geom, node ); else { std::string warning( "fltexp: Unknown PrimitiveSet type." ); OSG_WARN << warning << std::endl; _fltOpt->getWriteResult().warn( warning ); return; } } writePop(); } } // Would traverse here if this node could have children. // traverse( (osg::Node&)node ); } void FltExportVisitor::apply( osg::Node& node ) { _firstNode = false; ScopedStatePushPop guard( this, node.getStateSet() ); osgSim::LightPointNode* lpn = dynamic_cast< osgSim::LightPointNode* >( &node ); if (lpn) { writeLightPoint( lpn ); } else { // Unknown Node. Warn and return. // (Note, if the base class of this Node was a Group, then apply(Group&) // would export a Group record then continue traversal. Because we are // a Node, there's no way to continue traversal, so just return.) std::string warning( "fltexp: Unknown Node in OpenFlight export." ); OSG_WARN << warning << std::endl; _fltOpt->getWriteResult().warn( warning ); return; } } void FltExportVisitor::apply( osg::ProxyNode& node ) { _firstNode = false; ScopedStatePushPop guard( this, node.getStateSet() ); writeExternalReference( node ); writeMatrix( node.getUserData() ); writeComment( node ); } bool FltExportVisitor::complete( const osg::Node& node ) { // Always write final pop level writePop(); // Done writing records, close the record data temp file. _recordsStr.close(); // Write OpenFlight file front matter: header, vertex palette, etc. writeHeader( node.getName() ); writeColorPalette(); _materialPalette->write( _dos ); _texturePalette->write( _dos ); _lightSourcePalette->write( _dos ); _vertexPalette->write( _dos ); // Write Comment ancillary record and specify the _dos DataOutputStream. writeComment( node, &_dos ); // Copy record data temp file into final OpenFlight file. // Yee-uck. TBD need better stream copy routine. char buf; osgDB::ifstream recIn; recIn.open( _recordsTempName.c_str(), std::ios::in | std::ios::binary ); while (!recIn.eof() ) { recIn.read( &buf, 1 ); if (recIn.good()) _dos << buf; } recIn.close(); return true; } // // StateSet stack support void FltExportVisitor::pushStateSet( const osg::StateSet* rhs ) { osg::StateSet* ss = new osg::StateSet( *( _stateSetStack.back().get() ) ); if (rhs) ss->merge( *rhs ); _stateSetStack.push_back( ss ); } void FltExportVisitor::popStateSet() { _stateSetStack.pop_back(); } const osg::StateSet* FltExportVisitor::getCurrentStateSet() const { return _stateSetStack.back().get(); } void FltExportVisitor::clearStateSetStack() { _stateSetStack.clear(); } void FltExportVisitor::writeATTRFile( int unit, const osg::Texture2D* texture ) const { std::string name; if (_fltOpt->getStripTextureFilePath()) name = osgDB::getSimpleFileName( texture->getImage()->getFileName() ); else name = texture->getImage()->getFileName(); name += std::string( ".attr" ); if ( osgDB::findDataFile( name ).empty() ) { // No .attr file found. We should write one out. // Fill AttrData fields from current state. AttrData ad; ad.texels_u = texture->getImage()->s(); ad.texels_v = texture->getImage()->t(); switch( texture->getFilter( osg::Texture::MIN_FILTER ) ) { case osg::Texture::LINEAR: ad.minFilterMode = AttrData::MIN_FILTER_BILINEAR; break; case osg::Texture::LINEAR_MIPMAP_LINEAR: ad.minFilterMode = AttrData::MIN_FILTER_MIPMAP_TRILINEAR; break; case osg::Texture::LINEAR_MIPMAP_NEAREST: ad.minFilterMode = AttrData::MIN_FILTER_MIPMAP_BILINEAR; break; case osg::Texture::NEAREST: ad.minFilterMode = AttrData::MIN_FILTER_POINT; break; case osg::Texture::NEAREST_MIPMAP_LINEAR: ad.minFilterMode = AttrData::MIN_FILTER_MIPMAP_LINEAR; break; case osg::Texture::NEAREST_MIPMAP_NEAREST: ad.minFilterMode = AttrData::MIN_FILTER_MIPMAP_POINT; break; default: ad.minFilterMode = AttrData::MIN_FILTER_MIPMAP_TRILINEAR; break; } switch( texture->getFilter( osg::Texture::MAG_FILTER ) ) { case osg::Texture::NEAREST: ad.magFilterMode = AttrData::MAG_FILTER_POINT; break; default: ad.magFilterMode = AttrData::MAG_FILTER_BILINEAR; break; } // Repeat and Clamp switch( texture->getWrap( osg::Texture::WRAP_S ) ) { case osg::Texture::CLAMP: case osg::Texture::CLAMP_TO_EDGE: case osg::Texture::CLAMP_TO_BORDER: ad.wrapMode_u = AttrData::WRAP_CLAMP; break; case osg::Texture::REPEAT: default: ad.wrapMode_u = AttrData::WRAP_REPEAT; break; case osg::Texture::MIRROR: if (_fltOpt->getFlightFileVersionNumber() >= 1610) ad.wrapMode_u = AttrData::WRAP_MIRRORED_REPEAT; else ad.wrapMode_u = AttrData::WRAP_REPEAT; break; } switch( texture->getWrap( osg::Texture::WRAP_T ) ) { case osg::Texture::CLAMP: case osg::Texture::CLAMP_TO_EDGE: case osg::Texture::CLAMP_TO_BORDER: ad.wrapMode_v = AttrData::WRAP_CLAMP; break; case osg::Texture::REPEAT: default: ad.wrapMode_v = AttrData::WRAP_REPEAT; break; case osg::Texture::MIRROR: if (_fltOpt->getFlightFileVersionNumber() >= 1610) ad.wrapMode_v = AttrData::WRAP_MIRRORED_REPEAT; else ad.wrapMode_v = AttrData::WRAP_REPEAT; break; } const osg::StateSet* ss = getCurrentStateSet(); const osg::TexEnv* texenv = dynamic_cast( ss->getTextureAttribute( unit, osg::StateAttribute::TEXENV ) ); if (texenv) { switch( texenv->getMode()) { case osg::TexEnv::DECAL: ad.texEnvMode = AttrData::TEXENV_DECAL; break; case osg::TexEnv::MODULATE: default: ad.texEnvMode = AttrData::TEXENV_MODULATE; break; case osg::TexEnv::BLEND: ad.texEnvMode = AttrData::TEXENV_BLEND; break; case osg::TexEnv::REPLACE: ad.texEnvMode = AttrData::TEXENV_COLOR; break; case osg::TexEnv::ADD: ad.texEnvMode = AttrData::TEXENV_ADD; break; } } osgDB::writeObjectFile( ad, name, _fltOpt.get() ); } } }