root/OpenSceneGraph/trunk/src/osgPlugins/OpenFlight/FltExportVisitor.cpp @ 10491

Revision 10491, 21.1 kB (checked in by robert, 5 years ago)

From Fabien Lavignotte,"When exporting some models to OpenFlight?, i found a crash if the texture
unit does not contain a TexEnv? object.
Here's the small fix, just a test on the pointer."

RevLine 
[8003]1/*
2 * This library is open source and may be redistributed and/or modified under
3 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or (at
4 * your option) any later version. The full license is in the LICENSE file
5 * included with this distribution, and on the openscenegraph.org website.
6 *
7 * This library is distributed in the hope that it will be useful, but
8 * WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * OpenSceneGraph Public License for more details.
11*/
12
13//
14// Copyright(c) 2008 Skew Matrix Software LLC.
15//
16
17#include "FltExportVisitor.h"
18#include "ExportOptions.h"
19#include "FltWriteResult.h"
20#include "DataOutputStream.h"
21#include "Opcodes.h"
22#include "LightSourcePaletteManager.h"
23#include "MaterialPaletteManager.h"
24#include "TexturePaletteManager.h"
25#include "VertexPaletteManager.h"
26#include "AttrData.h"
27#include "Utils.h"
28
29#include <osgDB/FileUtils>
30#include <osgDB/WriteFile>
31#include <osg/Geode>
32#include <osg/Geometry>
33#include <osg/LightSource>
34#include <osg/LOD>
35#include <osg/MatrixTransform>
36#include <osg/PositionAttitudeTransform>
37#include <osg/PrimitiveSet>
38#include <osg/ProxyNode>
39#include <osg/Quat>
40#include <osg/Sequence>
41#include <osg/Texture2D>
42#include <osg/TexEnv>
43#include <osg/Switch>
44#include <osg/Material>
45#include <osg/CullFace>
46#include <osg/BlendFunc>
47#include <osg/PolygonOffset>
48#include <osgSim/DOFTransform>
49#include <osgSim/MultiSwitch>
50#include <osgSim/LightPointNode>
51#include <osgSim/ObjectRecordData>
52
[9959]53#ifdef _MSC_VER
[8003]54// Disable this warning. It's OK for us to use 'this' in initializer list,
55// as the texturePaletteManager merely stores a ref to it.
56#pragma warning( disable : 4355 )
57#endif
58
59namespace flt
60{
61
62
63FltExportVisitor::FltExportVisitor( DataOutputStream* dos,
64                                ExportOptions* fltOpt )
65
66  : osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ),
[9382]67    _fltOpt( fltOpt ),
[8003]68    _dos( *dos ),
69    _materialPalette( new MaterialPaletteManager( *fltOpt ) ),
70    _texturePalette( new TexturePaletteManager( *this, *fltOpt ) ),
71    _lightSourcePalette( new LightSourcePaletteManager( *fltOpt ) ),
72    _vertexPalette( new VertexPaletteManager( *fltOpt ) ),
73    _firstNode( true )
74{
75    // Init the StateSet stack.
76    osg::StateSet* ss = new osg::StateSet;
77
78    int unit;
79    for(unit=0; unit<8; unit++)
80    {
81        osg::TexEnv* texenv = new osg::TexEnv;
82        ss->setTextureAttributeAndModes( unit, texenv, osg::StateAttribute::OFF );
83        // TBD other texture state?
84    }
85
86    osg::Material* material = new osg::Material;
87    ss->setAttribute( material, osg::StateAttribute::OFF );
88    if (fltOpt->getLightingDefault())
89        ss->setMode( GL_LIGHTING, osg::StateAttribute::ON );
90    else
91        ss->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
92
93    osg::CullFace* cf = new osg::CullFace;
94    ss->setAttributeAndModes( cf, osg::StateAttribute::OFF );
95
96    osg::BlendFunc* bf = new osg::BlendFunc;
97    ss->setAttributeAndModes( bf, osg::StateAttribute::OFF );
98
99    osg::PolygonOffset* po = new osg::PolygonOffset;
100    ss->setAttributeAndModes( po, osg::StateAttribute::OFF );
101
102    _stateSetStack.push_back( ss );
103
104
[8563]105    // Temp file for storing records. Need a temp file because we don't
[8003]106    // write header and palette until FltExportVisitor completes traversal.
107    _recordsTempName = fltOpt->getTempDir() + "/ofw_temp_records";
108    _recordsStr.open( _recordsTempName.c_str(), std::ios::out | std::ios::binary );
109    _records = new DataOutputStream( _recordsStr.rdbuf(), fltOpt->getValidateOnly() );
110
111    // Always write initial push level
112    writePush();
113}
114
115FltExportVisitor::~FltExportVisitor()
116{
117    // Delete our temp file.
118    if (_recordsStr.is_open())
119    {
120        osg::notify( osg::WARN ) << "fltexp: FltExportVisitor destructor has an open temp file." << std::endl;
121        // This should not happen. FltExportVisitor::complete should close
122        // this file before we get to this destructor.
123        return;
124    }
125    osg::notify( osg::INFO ) << "fltexp: Deleting temp file " << _recordsTempName << std::endl;
126    FLTEXP_DELETEFILE( _recordsTempName.c_str() );
127}
128
129
130void
131FltExportVisitor::apply( osg::Group& node )
132{
133    ScopedStatePushPop guard( this, node.getStateSet() );
134
135    if (_firstNode)
136    {
137        // On input, a FLT header creates a Group node.
138        // On export, we always write a Header record, but then the first Node
139        //   we export is the Group that was created from the original input Header.
140        // On successive roundtrips, this results in increased redundant top-level Group nodes/records.
141        // Avoid this by NOT outputting anything for a top-level Group node.
142        _firstNode = false;
143        traverse( node );
144        return;
145    }
146
147    // A Group node could indicate one of many possible records.
148    //   Header record -- Don't need to support this here. We always output a header.
149    //   Group record -- HIGH
[8563]150    //   Child of an LOD node -- HIGH Currently write out a Group record regardless.
[8003]151    //   InstanceDefinition/InstanceReference -- MED --  multiparented Group is an instance
152    //   Extension record -- MED
153    //   Object record -- MED
154    //   LightPointSystem record (if psgSim::MultiSwitch) -- LOW
155
156    osgSim::MultiSwitch* multiSwitch = dynamic_cast<osgSim::MultiSwitch*>( &node );
157    if (multiSwitch)
158    {
159        writeSwitch( multiSwitch );
160    }
161
162    else
163    {
164        osgSim::ObjectRecordData* ord =
165            dynamic_cast< osgSim::ObjectRecordData* >( node.getUserData() );
166        if (ord)
167        {
168            // This Group should write an Object Record.
169            writeObject( node, ord );
170        }
171        else
172        {
173            // Handle other cases here.
174            // For now, just output a Group record.
175            writeGroup( node );
176        }
177    }
178
179    writeMatrix( node.getUserData() );
180    writeComment( node );
181    writePushTraverseWritePop( node );
182}
183
184void
185FltExportVisitor::apply( osg::Sequence& node )
186{
187    _firstNode = false;
188    ScopedStatePushPop guard( this, node.getStateSet() );
189
190    writeSequence( node );
191    writeMatrix( node.getUserData() );
192    writeComment( node );
193    writePushTraverseWritePop( node );
194}
195
196void
197FltExportVisitor::apply( osg::Switch& node )
198{
199    _firstNode = false;
200    ScopedStatePushPop guard( this, node.getStateSet() );
201
202    writeSwitch( &node );
203
204    writeMatrix( node.getUserData() );
205    writeComment( node );
206    writePushTraverseWritePop( node );
207}
208
209void
210FltExportVisitor::apply( osg::LOD& lodNode )
211{
212    _firstNode = false;
213    ScopedStatePushPop guard( this, lodNode.getStateSet() );
214
[8563]215    // LOD center - same for all children
[8003]216    osg::Vec3d center = lodNode.getCenter();
217
218    // Iterate children of the LOD and write a separate LOD record for each,
219    // with that child's individual switchIn and switchOut properties
220    for ( size_t i = 0; i < lodNode.getNumChildren(); ++i )
221    {
222        osg::Node* lodChild = lodNode.getChild(i);
223
224        // Switch-in/switch-out distances may vary per child
225        double switchInDist = lodNode.getMaxRange(i);
226        double switchOutDist = lodNode.getMinRange(i);
227
228        writeLevelOfDetail( lodNode, center, switchInDist, switchOutDist);
229        writeMatrix( lodNode.getUserData() );
230        writeComment( lodNode );
231
232        // Traverse each child of the LOD
[9138]233        writePushTraverseChildWritePop( *lodChild );
[8003]234    }
235
236}
237
238void
239FltExportVisitor::apply( osg::MatrixTransform& node )
240{
241    // Importer reads a Matrix record and inserts a MatrixTransform above
[8563]242    //   the current node. We need to do the opposite: Write a Matrix record
[8003]243    //   as an ancillary record for each child. We do that by storing the
244    //   MatrixTransform in each child's UserData. Each child then checks
245    //   UserData and writes a Matrix record if UserData is a MatrixTransform.
246
247    _firstNode = false;
248    ScopedStatePushPop guard( this, node.getStateSet() );
249
250    osg::ref_ptr< osg::RefMatrix > m = new osg::RefMatrix;
251    m->set( node.getMatrix() );
252    if (node.getUserData())
253    {
254        const osg::RefMatrix* rm = dynamic_cast<const osg::RefMatrix*>( node.getUserData() );
255        if (rm)
256            (*m) *= *rm;
257    }
258
[8900]259    typedef std::vector< osg::ref_ptr< osg::Referenced > > UserDataList;
260   
261    UserDataList saveUserDataList( node.getNumChildren() );
262
[8003]263    unsigned int idx;
[8900]264    for( idx=0; idx<node.getNumChildren(); ++idx )
[8003]265    {
[8900]266        saveUserDataList[ idx ] = node.getChild( idx )->getUserData();
[8003]267        node.getChild( idx )->setUserData( m.get() );
268    }
269
270    traverse( (osg::Node&)node );
271
272    // Restore saved UserData.
[8900]273    for( idx=0; idx< node.getNumChildren(); ++idx )
274    {
275      node.getChild( idx )->setUserData( saveUserDataList[ idx ].get() );
276    }
[8003]277}
278
279void
280FltExportVisitor::apply( osg::PositionAttitudeTransform& node )
281{
282    _firstNode = false;
283    ScopedStatePushPop guard( this, node.getStateSet() );
284
[8791]285    osg::ref_ptr<osg::RefMatrix> m = new osg::RefMatrix(
286        osg::Matrix::translate( -node.getPivotPoint() ) *
287        osg::Matrix::scale( node.getScale() ) *
288        osg::Matrix::rotate( node.getAttitude() ) *
289        osg::Matrix::translate( node.getPosition() ) );
[8003]290
[8900]291    typedef std::vector< osg::ref_ptr< osg::Referenced > > UserDataList;
292    UserDataList saveUserDataList( node.getNumChildren() );
293
[8003]294    unsigned int idx;
[8900]295    for( idx=0; idx<node.getNumChildren(); ++idx )
[8003]296    {
[8900]297        saveUserDataList[ idx ] = node.getChild( idx )->getUserData();
[8003]298        node.getChild( idx )->setUserData( m.get() );
299    }
300
301    traverse( (osg::Node&)node );
302
303    // Restore saved UserData.
[8900]304    for( idx=0; idx<node.getNumChildren(); ++idx )
305    {
306        node.getChild( idx )->setUserData( saveUserDataList[ idx ].get() );
307    }
[8003]308
309}
310
311
312void
313FltExportVisitor::apply( osg::Transform& node )
314{
315    _firstNode = false;
316    ScopedStatePushPop guard( this, node.getStateSet() );
317
318    osgSim::DOFTransform* dof = dynamic_cast<osgSim::DOFTransform*>( &node );
319
320    if (dof)
321    {
322        writeDegreeOfFreedom( dof);
323    }
324
325    writeMatrix( node.getUserData() );
326    writeComment( node );
327    writePushTraverseWritePop( node );
328}
329
330void
331FltExportVisitor::apply( osg::LightSource& node )
332{
333    _firstNode = false;
334    ScopedStatePushPop guard( this, node.getStateSet() );
335
336    writeLightSource( node );
337    writeMatrix( node.getUserData() );
338    writeComment( node );
339    writePushTraverseWritePop( node );
340}
341
[8301]342// Billboards also go through this code. The Geode is passed
343// to writeFace and writeMesh. If those methods successfully cast
344// the Geode to a Billboard, then they set the template mode
345// bit accordingly.
[8003]346void
347FltExportVisitor::apply( osg::Geode& node )
348{
349    _firstNode = false;
350    ScopedStatePushPop guard( this, node.getStateSet() );
351
352    unsigned int idx;
353    for (idx=0; idx<node.getNumDrawables(); idx++)
354    {
355        osg::Geometry* geom = node.getDrawable( idx )->asGeometry();
356        if (!geom)
[8018]357        {
358            std::string warning( "fltexp: Non-Geometry Drawable encountered. Ignoring." );
359            osg::notify( osg::WARN ) << warning << std::endl;
360            _fltOpt->getWriteResult().warn( warning );
[8003]361            continue;
[8018]362        }
[8003]363
364        ScopedStatePushPop drawableGuard( this, geom->getStateSet() );
365
[8286]366        // Push and pop subfaces if polygon offset is on.
367        SubfaceHelper subface( *this, getCurrentStateSet() );
368
369        if (atLeastOneFace( *geom ))
370        {
[8003]371            // If at least one record will be a Face record, then we
[8286]372            //   need to write to the vertex palette.
[8003]373            _vertexPalette->add( *geom );
374
[8286]375            // Iterate over all PrimitiveSets and output Face records.
376            unsigned int jdx;
377            for (jdx=0; jdx < geom->getNumPrimitiveSets(); jdx++)
378            {
379                osg::PrimitiveSet* prim = geom->getPrimitiveSet( jdx );
380                if ( isMesh( prim->getMode() ) )
381                    continue;
382
383                if (prim->getType() == osg::PrimitiveSet::DrawArraysPrimitiveType)
384                    handleDrawArrays( dynamic_cast<osg::DrawArrays*>( prim ), *geom, node );
385                else if (prim->getType() == osg::PrimitiveSet::DrawArrayLengthsPrimitiveType)
386                    handleDrawArrayLengths( dynamic_cast<osg::DrawArrayLengths*>( prim ), *geom, node );
387                else if ( (prim->getType() == osg::PrimitiveSet::DrawElementsUBytePrimitiveType) ||
388                        (prim->getType() == osg::PrimitiveSet::DrawElementsUShortPrimitiveType) ||
389                        (prim->getType() == osg::PrimitiveSet::DrawElementsUIntPrimitiveType) )
390                    handleDrawElements( dynamic_cast<osg::DrawElements*>( prim ), *geom, node );
391                else
392                {
393                    std::string warning( "fltexp: Unknown PrimitiveSet type." );
394                    osg::notify( osg::WARN ) << warning << std::endl;
395                    _fltOpt->getWriteResult().warn( warning );
396                    return;
397                }
398            }
399        }
400
401        if (atLeastOneMesh( *geom ))
[8003]402        {
[8286]403            // If at least one Mesh record, write out preamble mesh records
404            //   followed by a Mesh Primitive record per PrimitiveSet.
405            writeMesh( node, *geom );
406
407            writeMatrix( node.getUserData() );
408            writeComment( node );
409            writeMultitexture( *geom );
410            writeLocalVertexPool( *geom );
411
412            writePush();
413
414            unsigned int jdx;
415            for (jdx=0; jdx < geom->getNumPrimitiveSets(); jdx++)
[8003]416            {
[8286]417                osg::PrimitiveSet* prim = geom->getPrimitiveSet( jdx );
418                if ( !isMesh( prim->getMode() ) )
419                    continue;
420
421                if (prim->getType() == osg::PrimitiveSet::DrawArraysPrimitiveType)
422                    handleDrawArrays( dynamic_cast<osg::DrawArrays*>( prim ), *geom, node );
423                else if (prim->getType() == osg::PrimitiveSet::DrawArrayLengthsPrimitiveType)
424                    handleDrawArrayLengths( dynamic_cast<osg::DrawArrayLengths*>( prim ), *geom, node );
425                else if ( (prim->getType() == osg::PrimitiveSet::DrawElementsUBytePrimitiveType) ||
426                        (prim->getType() == osg::PrimitiveSet::DrawElementsUShortPrimitiveType) ||
427                        (prim->getType() == osg::PrimitiveSet::DrawElementsUIntPrimitiveType) )
428                    handleDrawElements( dynamic_cast<osg::DrawElements*>( prim ), *geom, node );
429                else
430                {
431                    std::string warning( "fltexp: Unknown PrimitiveSet type." );
432                    osg::notify( osg::WARN ) << warning << std::endl;
433                    _fltOpt->getWriteResult().warn( warning );
434                    return;
435                }
[8003]436            }
[8286]437
438            writePop();
[8003]439        }
440    }
441
442    // Would traverse here if this node could have children.
443    //   traverse( (osg::Node&)node );
444}
445
446void
447FltExportVisitor::apply( osg::Node& node )
448{
449    _firstNode = false;
450    ScopedStatePushPop guard( this, node.getStateSet() );
451
452    osgSim::LightPointNode* lpn = dynamic_cast< osgSim::LightPointNode* >( &node );
453    if (lpn)
454    {
455        writeLightPoint( lpn );
456    }
457    else
458    {
459        // Unknown Node. Warn and return.
[8563]460        // (Note, if the base class of this Node was a Group, then apply(Group&)
[8003]461        //   would export a Group record then continue traversal. Because we are
462        //   a Node, there's no way to continue traversal, so just return.)
463        std::string warning( "fltexp: Unknown Node in OpenFlight export." );
464        osg::notify( osg::WARN ) << warning << std::endl;
465        _fltOpt->getWriteResult().warn( warning );
466        return;
467    }
468}
469
470void
471FltExportVisitor::apply( osg::ProxyNode& node )
472{
473    _firstNode = false;
474    ScopedStatePushPop guard( this, node.getStateSet() );
475
476    writeExternalReference( node );
477    writeMatrix( node.getUserData() );
478    writeComment( node );
479}
480
481
482
483
484
485bool
486FltExportVisitor::complete( const osg::Node& node )
487{
488    // Always write final pop level
489    writePop();
490    // Done writing records, close the record data temp file.
491    _recordsStr.close();
492
[8563]493    // Write OpenFlight file front matter: header, vertex palette, etc.
[8003]494    writeHeader( node.getName() );
495
496    writeColorPalette();
497    _materialPalette->write( _dos );
498    _texturePalette->write( _dos );
499    _lightSourcePalette->write( _dos );
500    _vertexPalette->write( _dos );
501
502    // Write Comment ancillary record and specify the _dos DataOutputStream.
503    writeComment( node, &_dos );
504
505    // Copy record data temp file into final OpenFlight file.
506    // Yee-uck. TBD need better stream copy routine.
507    char buf;
[9124]508    osgDB::ifstream recIn;
[8003]509    recIn.open( _recordsTempName.c_str(), std::ios::in | std::ios::binary );
510    while (!recIn.eof() )
511    {
512        recIn.read( &buf, 1 );
513        if (recIn.good())
514            _dos << buf;
515    }
516    recIn.close();
517
518    return true;
519}
520
521
522
523//
524// StateSet stack support
525
526void
527FltExportVisitor::pushStateSet( const osg::StateSet* rhs )
528{
529    osg::StateSet* ss = new osg::StateSet( *( _stateSetStack.back().get() ) );
530
531    if (rhs)
532        ss->merge( *rhs );
533
534    _stateSetStack.push_back( ss );
535}
536
537void
538FltExportVisitor::popStateSet()
539{
540    _stateSetStack.pop_back();
541}
542
543const osg::StateSet*
544FltExportVisitor::getCurrentStateSet() const
545{
546    return _stateSetStack.back().get();
547}
548
549void
550FltExportVisitor::clearStateSetStack()
551{
552    _stateSetStack.clear();
553}
554
555
556void
557FltExportVisitor::writeATTRFile( int unit, const osg::Texture2D* texture ) const
558{
559    std::string name;
560    if (_fltOpt->getStripTextureFilePath())
561        name = osgDB::getSimpleFileName( texture->getImage()->getFileName() );
562    else
563        name = texture->getImage()->getFileName();
564    name += std::string( ".attr" );
565
566    if ( osgDB::findDataFile( name ).empty() )
567    {
568        // No .attr file found. We should write one out.
569        // Fill AttrData fields from current state.
570        AttrData ad;
571
572        ad.texels_u = texture->getImage()->s();
573        ad.texels_v = texture->getImage()->t();
574
575        switch( texture->getFilter( osg::Texture::MIN_FILTER ) )
576        {
577        case osg::Texture::LINEAR:
578            ad.minFilterMode = AttrData::MIN_FILTER_BILINEAR;
579            break;
580        case osg::Texture::LINEAR_MIPMAP_LINEAR:
581            ad.minFilterMode = AttrData::MIN_FILTER_MIPMAP_TRILINEAR;
582            break;
583        case osg::Texture::LINEAR_MIPMAP_NEAREST:
584            ad.minFilterMode = AttrData::MIN_FILTER_MIPMAP_BILINEAR;
585            break;
586        case osg::Texture::NEAREST:
587            ad.minFilterMode = AttrData::MIN_FILTER_POINT;
588            break;
589        case osg::Texture::NEAREST_MIPMAP_LINEAR:
590            ad.minFilterMode = AttrData::MIN_FILTER_MIPMAP_LINEAR;
591            break;
592        case osg::Texture::NEAREST_MIPMAP_NEAREST:
593            ad.minFilterMode = AttrData::MIN_FILTER_MIPMAP_POINT;
594            break;
595        default:
596            ad.minFilterMode = AttrData::MIN_FILTER_MIPMAP_TRILINEAR;
597            break;
598        }
599        switch( texture->getFilter( osg::Texture::MAG_FILTER ) )
600        {
601        case osg::Texture::NEAREST:
602            ad.magFilterMode = AttrData::MAG_FILTER_POINT;
603            break;
604        default:
605            ad.magFilterMode = AttrData::MAG_FILTER_BILINEAR;
606            break;
607        }
608
609        // Repeat and Clamp
610        switch( texture->getWrap( osg::Texture::WRAP_S ) )
611        {
612        case osg::Texture::CLAMP:
613        case osg::Texture::CLAMP_TO_EDGE:
614        case osg::Texture::CLAMP_TO_BORDER:
615            ad.wrapMode_u = AttrData::WRAP_CLAMP;
616            break;
617        case osg::Texture::REPEAT:
618        default:
619            ad.wrapMode_u = AttrData::WRAP_REPEAT;
620            break;
621        case osg::Texture::MIRROR:
622            if (_fltOpt->getFlightFileVersionNumber() >= 1610)
623                ad.wrapMode_u = AttrData::WRAP_MIRRORED_REPEAT;
624            else
625                ad.wrapMode_u = AttrData::WRAP_REPEAT;
626            break;
627        }
628        switch( texture->getWrap( osg::Texture::WRAP_T ) )
629        {
630        case osg::Texture::CLAMP:
631        case osg::Texture::CLAMP_TO_EDGE:
632        case osg::Texture::CLAMP_TO_BORDER:
633            ad.wrapMode_v = AttrData::WRAP_CLAMP;
634            break;
635        case osg::Texture::REPEAT:
636        default:
637            ad.wrapMode_v = AttrData::WRAP_REPEAT;
638            break;
639        case osg::Texture::MIRROR:
640            if (_fltOpt->getFlightFileVersionNumber() >= 1610)
641                ad.wrapMode_v = AttrData::WRAP_MIRRORED_REPEAT;
642            else
643                ad.wrapMode_v = AttrData::WRAP_REPEAT;
644            break;
645        }
646
647        const osg::StateSet* ss = getCurrentStateSet();
648        const osg::TexEnv* texenv = dynamic_cast<const osg::TexEnv*>(
649            ss->getTextureAttribute( unit, osg::StateAttribute::TEXENV ) );
[10491]650        if (texenv)
[8003]651        {
[10491]652            switch( texenv->getMode())
653            {
654            case osg::TexEnv::DECAL:
655                ad.texEnvMode = AttrData::TEXENV_DECAL;
656                break;
657            case osg::TexEnv::MODULATE:
658            default:
659                ad.texEnvMode = AttrData::TEXENV_MODULATE;
660                break;
661            case osg::TexEnv::BLEND:
662                ad.texEnvMode = AttrData::TEXENV_BLEND;
663                break;
664            case osg::TexEnv::REPLACE:
665                ad.texEnvMode = AttrData::TEXENV_COLOR;
666                break;
667            case osg::TexEnv::ADD:
668                ad.texEnvMode = AttrData::TEXENV_ADD;
669                break;
670            }
[8003]671        }
672
673        osgDB::writeObjectFile( ad, name, _fltOpt.get() );
674    }
675}
676
677
678}
Note: See TracBrowser for help on using the browser.