Files
OpenSceneGraph/src/osgPlugins/dx/DXWriter.cpp
2002-02-18 21:52:45 +00:00

2694 lines
94 KiB
C++

// This plugin writes an IBM Data Explorer (aka OpenDX) native file.
// (c) Randall Hopper, 2002.
//
// For details on the OpenDX visualization tool, its use, and its file format,
// refer to:
//
// http://www.opendx.org/
// http://www.opendx.org/support.html#docs
// http://www.research.ibm.com/dx/
// http://ftp.cs.umt.edu/DX/
//
// SUPPORTED:
//
// - Objects without color are assigned a default opaque light gray color
// - All GeoSet primitive types:
// POINTS, LINES, TRIANGLES, QUADS, LINE_STRIP, FLAT_LINE_STRIP,
// LINE_LOOP, TRIANGLE_STRIP, FLAT_TRIANGLE_STRIP, TRIANGLE_FAN,
// FLAT_TRIANGLE_FAN, QUAD_STRIP, POLYGON
// NOTE: area strips, fans, polygons, and quads are mapped to triangles
// fields; line strips and loops are mapped to polylines fields
// - OSG Node Types:
// Geode, Group, Billboards (hack), LOD (hack), Switch (hack)
// NOTES: Billboards - written as a translation Transform parenting
// the Geode Fields, so they do not rotate in DX like they should;
// LODs - only the most detailed child is written;
// Switches - only the active child is written
// - Coords, colors, normals, tcoords; with all binding types
// (per vertex/primitive/overall)
// - MATERIAL attributes -- all except ColorMode
// - Single texturing (TEXTURE_0), with RGB, RGBA, LUMINANCE, LUMINANCE_ALPHA
// textures (note: you'll need a recent DX from CVS for this to work);
// note that LUMINANCE* textures are expanded to RGB* in the conversion
// - Texture function, texture min/mag filters
// - Cull face
//
// UNSUPPORTED:
//
// - OSG Scene Graph Node Types:
// LightSource, Transform, Imposter, EarthSky,
// Switch - only active child written
// LOD - only most detailed child written
// Billboard - only static translation transform parenting child written,
// and only one Geode per Billboard is supported; DX has
// no concept just like Performer/OSG billboards that rotate
// to face the camera but scale and translate with the scene
//
// - Interleaved GeoSet data (interleaved set, no coords set)
//
// - Report just the unhandled GL modes/attributes, not all of them
//
// - DX only supports UNSIGNED_BYTE GL_RGB (and now GL_RGBA in recent CVS)
// textures, so we convert LUMINANCE* textures to RGB*
//
// - Handle other attributes in StateSet
//
// - TEXTURE_[1-3], and rest of subattributes in TEXTURE_0
// - MATERIAL - ColorMode (read but never processed); specifies that
// certain material attributes are set by glColor
// - ALPHAFUNC
// - ANTIALIAS
// - COLORTABLE
// - FOG
// - FRONTFACE
// - LIGHT, LIGHT_[0-7]
// - POINT
// - LINEWIDTH
// - POLYGONMODE
// - POLYGONOFFSET
// - TEXENV
// - TEXGEN
// - TEXMAT
// - TRANSPARENCY
// - STENCIL
// - COLORMASK
// - DEPTH
// - VIEWPORT
// - CLIPPLANE, CLIPPLANE_[0-5]
// - COLORMATRIX
//
// OTHER TO DO:
//
// - Use more meaningful names for nameless nodes (based on parent names)
// - Don't write a colormap when OSG colors aren't indexed unless num
// colors > 256 or something (see cane example)
// - Support ReaderWriter::Options for exporter switches
// - Support saving DX stream in both ASCII and binary
// - Handle normal/color/tcoord collisions (aliasing) when coords are indexed
// (See comment in DXArrayWriter::WritePerVertexNormals for details)
// Same applies for normals, colors, and tcoords
// - Handle accumulation of more than just Geodes in Groups (?)
// - Consider support for writing DX file with interleaved arrays,
// arrays at end, arrays in diff file in DX file
// - char [] -> std::string, globally
//
// DXWriter.h
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <stdio.h>
#include <math.h>
#include <assert.h>
#include <stdarg.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include <osg/Node>
#include <osg/Group>
#include <osg/LOD>
#include <osg/Geode>
#include <osg/GeoSet>
#include <osg/Notify>
#include <osg/NodeVisitor>
#include <osg/StateAttribute>
#include <osg/StateSet>
#include <osg/Material>
#include <osg/Texture>
#include <osg/TexEnv>
#include <osg/CullFace>
#include <osg/Billboard>
#include <osgDB/ReadFile>
#include "DXWriter.h"
#include "AreaGeoSetTriangulator.h"
#include "StateSetStr.h"
#if defined(__sgi) || defined(__FreeBSD__)
#include <unistd.h>
#include <ieeefp.h>
#else
#include <math.h>
#if defined(WIN32) || defined (macintosh)
#include <float.h>
#else
#include <unistd.h>
#endif
#endif
namespace dx {
//----------------------------------------------------------------------------
// COMPILATION TWEAKS
//----------------------------------------------------------------------------
//#define SKIP_SINGLE_MEMBER_DXGROUPS
//----------------------------------------------------------------------------
const osg::Vec3 INVALID_NORMAL ( -99,-99,-99 );
const osg::Vec3 INVALID_COLOR ( 0,0,0 );
const float INVALID_OPACITY = 1.0;
const float ALPHA_OPAQUE = 1.0;
#define ARRAY_LEN(a) (sizeof(a)/sizeof((a)[0]))
typedef osg::ubyte Vec4UB[4];
typedef osg::ubyte Vec3UB[3];
//----------------------------------------------------------------------------
class DXNameManager
// Generates unique object names
{
public:
std::string GetUnique( const std::string &suggestion );
protected:
std::map< std::string, int > dict;
};
std::string DXNameManager::GetUnique( const std::string &suggestion )
{
std::string name = suggestion;
int counter = 1;
char buf[30];
while ( dict.find( name ) != dict.end() ) {
sprintf( buf, " #%d", counter++ );
name = ( suggestion.empty() ? "Object" : suggestion ) + buf;
}
dict[name] = 1;
return name;
}
//----------------------------------------------------------------------------
class DXTextureManager
// Keeps track of what textures we've already written and their field names
{
public:
void Register( const osg::Image *image, std::string dx_name )
{ dict[ image ] = dx_name; }
bool IsRegistered( const osg::Image *image )
{ return dict.find( image ) != dict.end(); }
std::string Lookup( const osg::Image *image )
{ return dict[ image ]; }
protected:
std::map< const osg::Image *, std::string > dict;
};
//----------------------------------------------------------------------------
class MessageBin
{
protected:
typedef std::vector< std::string > MessageList;
MessageList msg_list;
public:
void Add( char fmt[], ... )
{
char msg[1024];
va_list args;
va_start(args, fmt);
int ret = vsnprintf(msg, sizeof(msg), fmt, args);
va_end(args);
msg_list.push_back( std::string( msg ) );
}
std::string GetPending()
{
std::string result;
for ( unsigned i = 0; i < msg_list.size(); i++ ) {
if ( !result.empty() )
result += '\n';
result += msg_list[i];
}
return result;
}
void Clear()
{ msg_list.clear(); }
};
//----------------------------------------------------------------------------
class DXField
{
public:
struct Component {
std::string name;
std::string value;
Component( const char name[], const char value[] )
: name(name), value(value) {}
};
std::vector< Component > comp;
std::string name;
DXField( DXNameManager &name_mgr, const std::string *name_suggestion )
{ if ( name_suggestion && !name_suggestion->empty() )
name = *name_suggestion;
else
name = "Field";
name = name_mgr.GetUnique( name );
}
std::string &GetName() { return name; }
void AddComponent( const char name[], const char value[] )
{ comp.push_back( Component( name, value ) ); }
void Write( FILE *fp, const char *attr=0 )
{
fprintf( fp, "object \"%s\" class field\n", name.c_str() );
for ( unsigned i = 0; i < comp.size(); i++ )
fprintf( fp, "component \"%s\" value \"%s\"\n",
comp[i].name.c_str(), comp[i].value.c_str() );
if ( attr )
fprintf( fp, "%s\n", attr );
fprintf( fp, "#\n\n" );
}
};
//----------------------------------------------------------------------------
class DXGroup
{
public:
typedef std::vector< std::string > MemberList;
MemberList members;
std::string name;
DXGroup( DXNameManager &name_mgr,
const std::string *name_suggestion = 0,
const std::string *fallback_suggestion = 0 )
{ if ( name_suggestion && !name_suggestion->empty() )
name = *name_suggestion;
else if ( fallback_suggestion )
name = fallback_suggestion->c_str();
else
name = "Group";
name = name_mgr.GetUnique( name );
}
void AddMember( const char member_name[] )
{ members.push_back( std::string( member_name ) ); }
void RemoveMember( const char member_name[] )
{
for(MemberList::iterator itr=members.begin();
itr!=members.end();
++itr)
if ( *itr == member_name ) {
members.erase(itr);
break;
}
}
int GetNumMembers() { return members.size(); }
std::string &GetName() { return name; }
void Write( FILE *fp, const char *attr=0 )
{
fprintf( fp, "object \"%s\" class group\n", GetName().c_str() );
for ( unsigned i = 0; i < members.size(); i++ )
fprintf( fp, "member %d value \"%s\"\n", i, members[i].c_str() );
if ( attr )
fprintf( fp, "%s\n", attr );
fprintf( fp, "#\n\n" );
}
};
//----------------------------------------------------------------------------
class DXArrayWriter
{
protected:
FILE *fp;
MessageBin *msg_bin;
public:
void SetFP ( FILE *_fp ) { fp = _fp; }
void SetMsgBin( MessageBin *_msg_bin ) { msg_bin = _msg_bin; }
void DeNanify( float &f, float subst );
void DeNanify( osg::Vec3 &v, const osg::Vec3 &subst );
void OSGColorToDX( const osg::Vec4 &osg_color,
osg::Vec3 &dx_color, float &dx_opacity );
void OSGColorToDX( const Vec4UB osg_color,
Vec3UB dx_color, float &dx_opacity );
int WriteMapsYN( osg::GeoSet::IndexPointer *ip, int num_colors );
void WriteAttributes( const char *ref, const char *dep, const char *attr );
void WriteFloatConstArray( float f, int len, const char name[],
const char *ref, const char *dep, const char *attr=0 );
void WriteVec3ConstArray( const osg::Vec3 &v, int len, const char name[],
const char *ref, const char *dep, const char *attr=0 );
void WriteFloatArray( const float *array, osg::GeoSet::IndexPointer *ip,
int len, const char name[],
const char *ref, const char *dep, const char *attr=0 );
void WriteVec2Array( const osg::Vec2 *array, osg::GeoSet::IndexPointer *ip,
int len, const char name[],
const char *ref, const char *dep, const char *attr=0 );
void WriteVec3Array( const osg::Vec3 *array, osg::GeoSet::IndexPointer *ip,
int len, const char name[],
const char *ref, const char *dep, const char *attr=0 );
void WriteVec4Array( const osg::Vec4 *array, osg::GeoSet::IndexPointer *ip,
int len, const char name[],
const char *ref, const char *dep, const char *attr=0 );
void WriteUByte3Array(
const Vec3UB *arrayp, osg::GeoSet::IndexPointer *ip,
int len, const char name[],
const char *ref, const char *dep, const char *attr=0 );
void WriteIndexArray( const osg::GeoSet::IndexPointer &ip, int len,
int rank, int shape, int ubyte, const char name[],
const char *ref, const char *dep, const char *attr=0 );
void WriteColors( const osg::Vec4 *colors, int num_colors,
osg::GeoSet::IndexPointer *ip, int num_cindices,
const char colors_name[], const char colormap_name[],
const char opacities_name[], const char opacitymap_name[],
int do_opacities, const char *dep, int &wrote_maps );
void WriteColors( const Vec4UB colors[], int num_colors,
osg::GeoSet::IndexPointer *ip, int num_cindices,
const char colors_name[], const char colormap_name[],
const char opacities_name[], const char opacitymap_name[],
int do_opacities, const char *dep, int &wrote_maps );
void WritePerVertexNormals( const osg::GeoSet &geoset,
const char name[], const char *attr=0 );
void WritePerVertexColors( const osg::GeoSet &geoset,
const char colors_name[],
const char colormap_name[],
const char opacities_name[],
const char opacitymap_name[],
int do_opacities, // FIXME
int &wrote_maps,
const char *attr=0 );
void WritePerVertexTCoords( const osg::GeoSet &geoset,
const char name[], const char *attr=0 );
};
//----------------------------------------------------------------------------
class MyStateSet
{
protected:
MessageBin &msg_bin;
public:
std::map< osg::StateAttribute::Type, int > attr;
// MATERIAL (osg::Material)
osg::Material::ColorMode colormode;
osg::Vec4 ambient_f, ambient_b; // Front and back colors
bool ambient_f_and_b; // Use front color for front and back
osg::Vec4 diffuse_f, diffuse_b;
bool diffuse_f_and_b;
osg::Vec4 specular_f, specular_b;
bool specular_f_and_b;
osg::Vec4 emission_f, emission_b;
bool emission_f_and_b;
float shininess_f, shininess_b;
bool shininess_f_and_b;
// TEXTURE / TEXTURE_0
const osg::Image *image;
osg::Texture::WrapMode wrap_s, wrap_t;
osg::Texture::FilterMode min_filter, mag_filter;
// TEXENV
osg::TexEnv::Mode texture_func;
MyStateSet( MessageBin &msg_bin ) : image(0), msg_bin(msg_bin) {}
// CULLFACE
osg::CullFace::Mode cullface_mode;
void Show( osg::StateSet &sset );
void AddAttribute( osg::StateAttribute::Type type )
{ attr[ type ] = 1; }
bool HasAttribute( osg::StateAttribute::Type type )
{ return attr.find( type ) != attr.end(); }
void Query( const osg::StateSet &sset );
void Write();
};
//----------------------------------------------------------------------------
class StateSetCopy : public osg::StateSet
{
public:
StateSetCopy() {}
~StateSetCopy() {}
StateSetCopy( const osg::StateSet &sset )
{
// Can't use operator = because StateSet broke it
_modeList = sset.getModeList();
_attributeList = sset.getAttributeList();
_renderingHint = sset.getRenderingHint();
_binMode = sset.getRenderBinMode();
_binNum = sset.getBinNumber();
_binName = sset.getBinName();
}
};
//----------------------------------------------------------------------------
class DXWriter
{
protected:
DXArrayWriter w;
typedef std::map<osg::StateAttribute::GLMode,int> ModeList;
typedef std::map<osg::StateAttribute::Type,int> AttrList;
ModeList unsupported_modes;
AttrList unsupported_attrs;
public:
FILE *fp;
WriterParms parms;
DXNameManager name_mgr;
DXTextureManager texture_mgr;
MessageBin msg_bin;
void SetParms( const WriterParms &p )
{ memcpy( &parms, &p, sizeof(parms) ); }
void Open();
void Close();
std::string BuildStateSetAttributes( MyStateSet &sset,
int diffuse_is_color,
int diffuse_is_opacity );
std::string WriteImage( const osg::Image &image );
void WritePolylineConnections( const std::string &field_name,
osg::GeoSet &geoset,
DXField &field );
std::string WriteGeoSetField( const std::string &field_name,
osg::GeoSet &geoset,
MyStateSet &sset );
std::string WriteGeoSet( osg::GeoSet &geoset,
const osg::StateSet &active_sset,
std::string &field_name );
std::string WriteGeode( osg::Geode &geode,
const osg::StateSet &active_sset );
void CollectUnhandledModesAndAttrs( osg::StateSet *sset );
void ReportUnhandledModesAndAttrs();
};
//----------------------------------------------------------------------------
inline int WARNING( char fmt[], ... )
{
va_list args;
va_start(args, fmt);
int ret = vfprintf(stderr, fmt, args);
va_end(args);
return ret;
}
//----------------------------------------------------------------------------
void GetParms( int argc, char *argv[],
char infile [ PATH_MAX ], WriterParms &parms )
{
WriterParms defaults;
/* Init defaults */
parms = defaults;
/* Parse user args */
int c, i, errflg = 0;
char *arg;
/* We'd use getopt(), but we have args with leading minus chars... */
for ( i = 1; i < argc && !errflg; i++ ) {
if ( argv[i][0] == '\0' ) continue;
if ( argv[i][0] != '-' )
break;
c = argv[i][1];
if ( strchr( "c", c ) ) {
if ( i+1 >= argc ) {
WARNING( "Missing argument to -%c option.\n\n", c );
errflg++;
break;
}
arg = argv[++i];
}
switch ( c ) {
case 'c': parms.set_default_color = 1;
sscanf( arg, "%g,%g,%g,%g",
&parms.default_color[0], &parms.default_color[1],
&parms.default_color[2], &parms.default_color[3] );
break;
case '?':
default :
errflg++;
}
}
if ( i != argc - 2 )
errflg++;
if (errflg) {
WARNING( "\n"
"Converts 3D model files to IBM Data Explorer format.\n\n"
"Usage: osg2dx\n"
" [-c r,g,b,a] # Give uncolored objs this color\n"
" <infile> # Input model pathname\n"
" <outfile> # Output DX pathname (- = stdout)\n"
"\n" );
exit(2);
}
infile[0] = parms.outfile[0] = '\0';
strncat( infile , argv[ i ], PATH_MAX-1 );
strncat( parms.outfile, argv[i+1], sizeof(parms.outfile)-1 );
}
//----------------------------------------------------------------------------
inline int IsNaNorInf( float f )
{
#if defined(__sgi)
int c = fpclass( double(f) );
switch (c)
{
case FP_SNAN :
case FP_QNAN :
case FP_NINF :
case FP_PINF : return 1;
default : return 0;
}
#elif defined(__FreeBSD__) || defined(__linux) || defined(__CYGWIN__)
return isnanf(f) || isinf(f);
#elif defined(WIN32)
return _isnan(f) || !_finite(f);
#else
# error Teach me how to find non-numbers on this platform.
#endif
}
void DXArrayWriter::DeNanify( float &f, float subst )
{
if ( IsNaNorInf((double)f) ) {
msg_bin->Add( "WARNING: Denanifying double.\n" );
f = subst;
}
}
void DXArrayWriter::DeNanify( osg::Vec3 &v, const osg::Vec3 &subst )
{
// An old OSG bug generated NaN normals from 0,0,0 normals;
// consider removing this someday.
// Also, Performer town has a NaN in one of the colormaps.
if ( IsNaNorInf((double)v[0]) || IsNaNorInf((double)v[1]) ||
IsNaNorInf((double)v[2]) ) {
msg_bin->Add( "WARNING: Denanifying 3D vector.\n" );
v = subst;
}
}
//----------------------------------------------------------------------------
void DXArrayWriter::OSGColorToDX( const osg::Vec4 &osg_color,
osg::Vec3 &dx_color, float &dx_opacity )
{
dx_color.set( osg_color[0], osg_color[1], osg_color[2] );
dx_opacity = osg_color[3]; // alpha==1.0 -> opaque
}
//----------------------------------------------------------------------------
void DXArrayWriter::OSGColorToDX( const Vec4UB osg_color,
Vec3UB dx_color, float &dx_opacity )
{
dx_color[0] = osg_color[0];
dx_color[1] = osg_color[1];
dx_color[2] = osg_color[2];
dx_opacity = osg_color[3] / 255.0; // alpha==1.0 -> opaque
}
//----------------------------------------------------------------------------
int DXArrayWriter::WriteMapsYN( osg::GeoSet::IndexPointer *ip, int num_colors )
{
// Yes, if we have indexed colors and the number of colors mapped to
// is <= 256.
return ( ip && ip->valid() && num_colors <= 256 );
}
//----------------------------------------------------------------------------
void DXArrayWriter::WriteAttributes( const char *ref, const char *dep,
const char *attr )
{
if ( attr && attr[0] )
fprintf( fp, "%s\n", attr );
if ( ref )
fprintf( fp, "attribute \"ref\" string \"%s\"\n", ref );
if ( dep )
fprintf( fp, "attribute \"dep\" string \"%s\"\n", dep );
}
//----------------------------------------------------------------------------
void DXArrayWriter::WriteFloatConstArray(
float f, int len, const char name[],
const char *ref, const char *dep, const char *attr )
{
fprintf( fp, "object \"%s\" class constantarray type float"
" rank 0 items %d data follows\n",
name, len );
fprintf( fp, " %g\n", f );
WriteAttributes( ref, dep, attr );
fprintf( fp, "#\n\n" );
}
//----------------------------------------------------------------------------
void DXArrayWriter::WriteVec3ConstArray(
const osg::Vec3 &v, int len, const char name[],
const char *ref, const char *dep, const char *attr )
{
fprintf( fp, "object \"%s\" class constantarray type float"
" rank 1 shape 3 items %d data follows\n",
name, len );
fprintf( fp, " %g %g %g\n", v[0], v[1], v[2] );
WriteAttributes( ref, dep, attr );
fprintf( fp, "#\n\n" );
}
//----------------------------------------------------------------------------
void DXArrayWriter::WriteFloatArray(
const float *array, osg::GeoSet::IndexPointer *ip,
int len, const char name[],
const char *ref, const char *dep, const char *attr )
// If ip && ip.valid(), use it to index; else index array directly
{
int index;
fprintf( fp, "object \"%s\" class array type float"
" rank 0 items %d data follows\n",
name, len );
for ( int i = 0; i < len; i++ ) {
index = ( ip && ip->valid() ) ? (*ip)[i] : i;
fprintf( fp, " %g\n", array[index] );
}
WriteAttributes( ref, dep, attr );
fprintf( fp, "#\n\n" );
}
//----------------------------------------------------------------------------
void DXArrayWriter::WriteVec2Array(
const osg::Vec2 *array, osg::GeoSet::IndexPointer *ip,
int len, const char name[],
const char *ref, const char *dep, const char *attr )
// If ip && ip.valid(), use it to index; else index array directly
{
int index;
fprintf( fp, "object \"%s\" class array type float"
" rank 1 shape 2 items %d data follows\n",
name, len );
for ( int i = 0; i < len; i++ ) {
index = ( ip && ip->valid() ) ? (*ip)[i] : i;
fprintf( fp, " %g %g\n", array[index][0], array[index][1] );
}
WriteAttributes( ref, dep, attr );
fprintf( fp, "#\n\n" );
}
//----------------------------------------------------------------------------
void DXArrayWriter::WriteVec3Array(
const osg::Vec3 *array, osg::GeoSet::IndexPointer *ip,
int len, const char name[],
const char *ref, const char *dep, const char *attr )
// If ip && ip.valid(), use it to index; else index array directly
{
int index;
fprintf( fp, "object \"%s\" class array type float"
" rank 1 shape 3 items %d data follows\n",
name, len );
for ( int i = 0; i < len; i++ ) {
index = ( ip && ip->valid() ) ? (*ip)[i] : i;
fprintf( fp, " %g %g %g\n",
array[index][0], array[index][1], array[index][2] );
}
WriteAttributes( ref, dep, attr );
fprintf( fp, "#\n\n" );
}
//----------------------------------------------------------------------------
void DXArrayWriter::WriteVec4Array(
const osg::Vec4 *array, osg::GeoSet::IndexPointer *ip,
int len, const char name[],
const char *ref, const char *dep, const char *attr )
// If ip && ip.valid(), use it to index; else index array directly
{
int index;
fprintf( fp, "object \"%s\" class array type float"
" rank 1 shape 4 items %d data follows\n",
name, len );
for ( int i = 0; i < len; i++ ) {
index = ( ip && ip->valid() ) ? (*ip)[i] : i;
fprintf( fp, " %g %g %g %g\n",
array[index][0], array[index][1],
array[index][2], array[index][3] );
}
WriteAttributes( ref, dep, attr );
fprintf( fp, "#\n\n" );
}
//----------------------------------------------------------------------------
void DXArrayWriter::WriteIndexArray(
const osg::GeoSet::IndexPointer &ip, int len,
int rank, int shape, int ubyte,
const char name[],
const char *ref, const char *dep, const char *attr )
// If ip.null(), write a sequential index (0,1,2,3,...)
{
const char *type_str = ( ubyte ? "unsigned byte" : "int" );
char shape_str[40];
assert( rank == 0 || rank == 1 );
if ( rank == 0 )
shape_str[0] = '\0';
else
sprintf( shape_str, "shape %d ", shape );
fprintf( fp, "object \"%s\" class array type %s"
" rank %d %sitems %d data follows\n",
name, type_str, rank, shape_str, len/shape );
for ( int i = 0; i < len; i++ ) {
if ( i % shape == 0 )
fprintf( fp, " " );
{
if ( ip.null() ) fprintf( fp, " %d", i );
else fprintf( fp, " %d", ip[i] );
}
if ( (i+1) % shape == 0 )
fprintf( fp, "\n" );
}
WriteAttributes( ref, dep, attr );
fprintf( fp, "#\n\n" );
}
//----------------------------------------------------------------------------
void DXArrayWriter::WriteUByte3Array(
const Vec3UB *array, osg::GeoSet::IndexPointer *ip,
int len, const char name[],
const char *ref, const char *dep, const char *attr )
// If ip && ip.valid(), use it to index; else index array directly
{
int index;
fprintf( fp, "object \"%s\" class array type unsigned byte"
" rank 1 shape 3 items %d data follows\n",
name, len );
for ( int i = 0; i < len; i++ ) {
index = ( ip && ip->valid() ) ? (*ip)[i] : i;
fprintf( fp, " %d %d %d\n",
array[index][0], array[index][1], array[index][2] );
}
WriteAttributes( ref, dep, attr );
fprintf( fp, "#\n\n" );
}
//----------------------------------------------------------------------------
void DXArrayWriter::WriteColors(
const osg::Vec4 *colors, int num_colors,
osg::GeoSet::IndexPointer *ip, int num_cindices,
const char colors_name[], const char colormap_name[],
const char opacities_name[], const char opacitymap_name[],
int do_opacities, const char *dep, int &wrote_maps )
{
int do_maps = WriteMapsYN( ip, num_colors );
int i;
// If should write color/opacity maps...
if ( do_maps ) {
// Separate colors and opacities
osg::Vec3 *colors2 = new osg::Vec3[256];
float *opacities2 = new float [256];
for ( i = 0; i < num_colors; i++ ) {
OSGColorToDX( colors[i], colors2[i], opacities2[i] );
DeNanify( colors2[i], INVALID_COLOR );
DeNanify( opacities2[i], INVALID_OPACITY );
}
for ( ; i < 256; i++ )
colors2[i].set( 0,0,0 ), opacities2[i] = 0;
// Write color/opacity maps
WriteVec3Array( colors2, 0, 256, colormap_name, 0, 0 );
if ( do_opacities )
WriteFloatArray( opacities2, 0, 256, opacitymap_name, 0, 0);
// Write colors/opacities
WriteIndexArray( *ip, num_cindices, 0, 1, 1, colors_name,
"color map", dep );
if ( do_opacities )
WriteIndexArray( *ip, num_cindices, 0, 1, 1, opacities_name,
"opacity map", dep );
delete [] colors2;
delete [] opacities2;
}
// Else write direct colors/opacities...
else {
int len = ip && ip->valid() ? num_cindices : num_colors;
// Separate colors and opacities
osg::Vec3 *colors2 = new osg::Vec3[ len ];
float *opacities2 = new float [ len ];
for ( i = 0; i < len; i++ ) {
int index = ip && ip->valid() ? (*ip)[i] : i;
OSGColorToDX( colors[ index ], colors2[i], opacities2[i] );
DeNanify( colors2[i], INVALID_COLOR );
DeNanify( opacities2[i], INVALID_OPACITY );
}
WriteVec3Array( colors2, 0, len, colors_name, 0, dep );
if ( do_opacities )
WriteFloatArray( opacities2, 0, len, opacities_name, 0, dep );
delete [] colors2;
delete [] opacities2;
}
wrote_maps = do_maps;
}
//----------------------------------------------------------------------------
void DXArrayWriter::WriteColors(
const Vec4UB colors[], int num_colors,
osg::GeoSet::IndexPointer *ip, int num_cindices,
const char colors_name[], const char colormap_name[],
const char opacities_name[], const char opacitymap_name[],
int do_opacities, const char *dep, int &wrote_maps )
{
int do_maps = WriteMapsYN( ip, num_colors );
int i;
// If should write color/opacity maps...
if ( do_maps ) {
// Separate colors and opacities
Vec3UB *colors2 = new Vec3UB[256];
float *opacities2 = new float [256];
for ( i = 0; i < num_colors; i++ )
OSGColorToDX( colors[i], colors2[i], opacities2[i] );
for ( ; i < 256; i++ ) {
colors2[i][0] = colors2[i][1] = colors2[i][2] = 0;
opacities2[i] = 0.0;
}
// Write color/opacity maps
WriteUByte3Array( colors2, 0, 256, colormap_name, 0, 0 );
if ( do_opacities )
WriteFloatArray( opacities2, 0, 256, opacitymap_name, 0, 0);
// Write colors/opacities
WriteIndexArray( *ip, num_cindices, 0, 1, 1, colors_name,
"color map", dep );
if ( do_opacities )
WriteIndexArray( *ip, num_cindices, 0, 1, 1, opacities_name,
"opacity map", dep );
delete [] colors2;
delete [] opacities2;
}
// Else write direct colors/opacities...
else {
int len = ip && ip->valid() ? num_cindices : num_colors;
// Separate colors and opacities
Vec3UB *colors2 = new Vec3UB[ len ];
float *opacities2 = new float [ len ];
for ( i = 0; i < len; i++ ) {
int index = ip && ip->valid() ? (*ip)[i] : i;
OSGColorToDX( colors[ index ], colors2[i], opacities2[i] );
}
WriteUByte3Array( colors2, 0, len, colors_name, 0, dep );
if ( do_opacities )
WriteFloatArray( opacities2, 0, len, opacities_name, 0, dep );
delete [] colors2;
delete [] opacities2;
}
wrote_maps = do_maps;
}
//----------------------------------------------------------------------------
void DXArrayWriter::WritePerVertexNormals(
const osg::GeoSet &geoset,
const char name[], const char *attr )
// throws 1
{
// FIXME:
// NOTE: Strictly speaking this routine is not correct, but is
// probably good enough for now.
//
// THE ISSUE:
// In OSG, PER_VERTEX normals, colors, and texture coordinates are
// stored per vertex "instance", while in DX normals, colors, and
// tcoords dep positions are stored per vertex "definition" (DX has no
// vertex instances to which normals, colors, and tcoords can be
// separately attached).
//
// (So e.g. for normals:) in OSG/Performer when coordinates (positions)
// are indexed, C coords and CI coord indices are defined to draw the
// primitives. When normals are defined per-vertex, there are N
// normals (and optionally NI normal indices).
//
// Note that CI == N (or NI, when normals are indexed). C != N (or NI).
// So, while we have one normal instance per point "instance", we may
// potentially have multiple normal instances mapped to the
// same point "definition".
//
// THE CORRECT SOLUTION:
// The right way to approach this is to pre-scan the Geodes whose
// Coords are indexed and which have either normals, colors, or tcoords
// bound to vertices, and replicate points (positions) which have
// multiple "different" normals, colors, or texture coordinates mapped
// To them.
//
// WHAT IS IMPLEMENTED BELOW:
// Below we take a short cut and map normals for vertex instances to
// vertex definitions. If a collision occurs, we just report it
// so someone will know it's a problem.
//
int num_points = geoset.getNumCoords();
int num_pindices = geoset.getNumCoordIndices();
int num_normals = geoset.getNumNormals();
int num_nindices = geoset.getNumNormalIndices();
//const osg::Vec3 *points = geoset.getCoords();
const osg::Vec3 *normals = geoset.getNormals();
const osg::GeoSet::IndexPointer &pindices = geoset.getCoordIndices();
const osg::GeoSet::IndexPointer &nindices = geoset.getNormalIndices();
// Create a list of normals per vertex "definition"
osg::Vec3 *pt_normals = new osg::Vec3[ num_points ];
int *set = new int[ num_points ];
try {
memset( set, '\0', num_points * sizeof(int) );
int num_pt_instances = num_pindices ? num_pindices : num_points;
int num_norm_instances = num_nindices ? num_nindices : num_normals;
if ( num_pt_instances != num_norm_instances ) {
msg_bin->Add( "ERROR: Incorrect number of normals found\n" );
throw 1;
}
for ( int i = 0; i < num_pt_instances; i++ )
{
int pindex = pindices.valid() ? pindices[i] : i;
int nindex = nindices.valid() ? nindices[i] : i;
osg::Vec3 normal = normals[nindex];
DeNanify( normal, INVALID_NORMAL );
if ( set[pindex] && !( pt_normals[pindex] == normal ) ) {
msg_bin->Add("ERROR: Vertex normal aliasing!!! Ask somebody to expand\n"
" the implementation of DXArrayWriter::"
"WritePerVertexNormals().\n" );
//printf( "%d: %g %g %g\n",
// nindex, normal[0], normal[1], normal[2] );
//printf( " %g %g %g\n",
// pt_normals[pindex][0],
// pt_normals[pindex][1],
// pt_normals[pindex][2] );
}
set[pindex] = 1;
pt_normals[pindex] = normal;
}
// Now write the normals
WriteVec3Array( pt_normals, 0, num_points, name, 0, "positions" );
}
catch (...) {
delete [] set;
delete [] pt_normals;
throw;
}
delete [] set;
delete [] pt_normals;
}
//----------------------------------------------------------------------------
void DXArrayWriter::WritePerVertexColors(
const osg::GeoSet &geoset,
const char colors_name[],
const char colormap_name[],
const char opacities_name[],
const char opacitymap_name[],
int do_opacities,
int &wrote_maps,
const char *attr )
// throws 1
{
// FIXME:
// NOTE: Strictly speaking this routine is not correct, but is
// probably good enough for now.
//
// THE ISSUE:
// (See the opening comment in DXArrayWriter::WritePerVertexNormals. The
// same applies here, namely that there may be multiple colors
// mapping to the same point definiton.)
int num_points = geoset.getNumCoords();
int num_pindices = geoset.getNumCoordIndices();
int num_colors = geoset.getNumColors();
int num_cindices = geoset.getNumColorIndices();
//const osg::Vec3 *points = geoset.getCoords();
const osg::Vec4 *colors = geoset.getColors();
const osg::GeoSet::IndexPointer &pindices = geoset.getCoordIndices();
const osg::GeoSet::IndexPointer &cindices = geoset.getColorIndices();
// Create a list of color indices per vertex "definition"
osg::uint *pt_cindices = new osg::uint[ num_points ];
int *set = new int [ num_points ];
try {
memset( set, '\0', num_points * sizeof(int) );
osg::uint num_pt_instances = num_pindices ? num_pindices : num_points;
osg::uint num_color_instances = num_cindices ? num_cindices : num_colors;
if ( num_pt_instances != num_color_instances ) {
msg_bin->Add( "ERROR: Incorrect number of colors found\n" );
throw 1;
}
for ( osg::uint i = 0; i < num_pt_instances; i++ )
{
osg::uint pindex = pindices.valid() ? pindices[i] : i;
osg::uint cindex = cindices.valid() ? cindices[i] : i;
if ( set[pindex] && ( pt_cindices[pindex] != cindex ) )
msg_bin->Add( "ERROR: Vertex color aliasing!!! Ask somebody to expand\n"
" the implementation of DXArrayWriter::"
"WritePerVertexColors()." );
set[pindex] = 1;
pt_cindices[pindex] = cindex;
}
// Cook an IndexPointer to write colors/opacities via std methods
osg::GeoSet::IndexPointer ip;
ip.set( num_points, pt_cindices );
// Write colors (and opacities, and possibly associated maps)
WriteColors( colors, num_colors, &ip, num_points,
colors_name, colormap_name, opacities_name, opacitymap_name,
do_opacities, "positions", wrote_maps );
}
catch (...) {
delete [] set;
delete [] pt_cindices;
throw;
}
delete [] set;
delete [] pt_cindices;
}
//----------------------------------------------------------------------------
void DXArrayWriter::WritePerVertexTCoords(
const osg::GeoSet &geoset,
const char name[], const char *attr )
// throws 1
{
// FIXME:
// NOTE: Strictly speaking this routine is not correct, but is
// probably good enough for now.
//
// THE ISSUE:
// (See the opening comment in DXArrayWriter::WritePerVertexNormals. The
// same applies here, namely that there may be multiple tcoords
// mapping to the same point definiton.)
int num_points = geoset.getNumCoords();
int num_pindices = geoset.getNumCoordIndices();
int num_tcoords = geoset.getNumTextureCoords();
int num_tindices = geoset.getNumTextureIndices();
//const osg::Vec3 *points = geoset.getCoords();
const osg::Vec2 *tcoords = geoset.getTextureCoords();
const osg::GeoSet::IndexPointer &pindices = geoset.getCoordIndices();
const osg::GeoSet::IndexPointer &tindices = geoset.getTextureIndices();
// Create a list of tcoords per vertex "definition"
osg::Vec2 *pt_tcoords = new osg::Vec2[ num_points ];
int *set = new int[ num_points ];
try {
memset( set, '\0', num_points * sizeof(int) );
int num_pt_instances = num_pindices ? num_pindices : num_points;
int num_tcoord_instances = num_tindices ? num_tindices : num_tcoords;
if ( num_pt_instances != num_tcoord_instances ) {
msg_bin->Add( "ERROR: Incorrect number of texture coordinates found\n" );
throw 1;
}
for ( int i = 0; i < num_pt_instances; i++ )
{
int pindex = pindices.valid() ? pindices[i] : i;
int tindex = tindices.valid() ? tindices[i] : i;
if ( set[pindex] && !( pt_tcoords[pindex] == tcoords[tindex] ) )
msg_bin->Add( "ERROR: Vertex texture coordinate aliasing!!!\n"
" Ask somebody to expand the implementation of\n"
" DXArrayWriter::WritePerVertexTcoords().\n" );
set[pindex] = 1;
pt_tcoords[pindex] = tcoords[tindex];
}
// Now write the tcoords
WriteVec2Array( pt_tcoords, 0, num_points, name, 0, "positions" );
}
catch (...) {
delete [] set;
delete [] pt_tcoords;
throw;
}
delete [] set;
delete [] pt_tcoords;
}
//----------------------------------------------------------------------------
inline float MAX( float x, float y )
{ return x > y ? x : y; }
inline float MAX3( float x, float y, float z )
{ return MAX( MAX( x, y ), z ); }
inline float MaxVecXYZ( const osg::Vec4 &v )
{ return MAX3( v[0], v[1], v[2] ); }
inline float Luminance( const osg::Vec4 &v )
{ return 0.2122*v[0] + 0.7013*v[1] * 0.0865*v[2]; }
inline char *Vec4AttributeString( char buf[],
char name[],
int front_and_back,
osg::Vec4 &front,
osg::Vec4 &back )
{
if ( front_and_back )
sprintf( buf, "attribute \"osg %s\" string \"%g %g %g %g\"\n",
name, front[0], front[1], front[2], front[3] );
else
sprintf( buf, "attribute \"osg front %s\" string \"%g %g %g %g\"\n"
"attribute \"osg back %s\" string \"%g %g %g %g\"\n",
name, front[0], front[1], front[2], front[3],
name, back[0], back[1], back[2], back[3] );
return buf;
}
std::string DXWriter::BuildStateSetAttributes( MyStateSet &sset,
int diffuse_is_color,
int diffuse_is_opacity )
{
std::string str;
char buf[160];
// Material
if ( sset.HasAttribute( osg::StateAttribute::MATERIAL ) ) {
// DX Lighting Model:
// I = KaAC+KdLC(n*l)+KsL(n*h)^shiny
// OpenGL:
// I = Ke+KaA+sum(stuff*KdA(n*l)+KsLs(n*h)^shiny
// NOTES:
// 1. DX specular doesn't depend on object color
// 2. DX doesn't have separate RGB color fields for specular, diffuse,
// ambient, emission
// 3. DX's material attributes are all scalars
// 4. DX lights don't separate specular and diffuse
// etc.
// Notice that the lighting models differ enough that it doesn't make
// much sense to set the DX material attributes (they're all scalars).
// So we'll just attach what the original attributes were to
// non-reserved attribute names and let the user fiddle with Shade()
// to set "shininess", "specular", "diffuse", "ambient" attributes to
// get the look they want.
if ( diffuse_is_color ) {
sprintf( buf, "attribute \"diffuse is colors component\" number 1\n" );
str += buf;
}
if ( diffuse_is_opacity ) {
sprintf( buf, "attribute \"diffuse is opacities component\" number 1\n" );
str += buf;
}
if ( sset.shininess_f_and_b )
sprintf( buf, "attribute \"osg shininess\" number %g\n",
sset.shininess_f );
else
sprintf( buf, "attribute \"osg front shininess\" number %g\n"
"attribute \"osg back shininess\" number %g\n",
sset.shininess_f, sset.shininess_b );
str += buf;
str += Vec4AttributeString( buf, "emission", sset.emission_f_and_b,
sset.emission_f, sset.emission_b );
str += Vec4AttributeString( buf, "specular", sset.specular_f_and_b,
sset.specular_f, sset.specular_b );
str += Vec4AttributeString( buf, "diffuse", sset.diffuse_f_and_b,
sset.diffuse_f, sset.diffuse_b );
str += Vec4AttributeString( buf, "ambient", sset.ambient_f_and_b,
sset.ambient_f, sset.ambient_b );
// NOTE: Didn't write "colormode"
}
// Texture
if ( sset.HasAttribute( osg::StateAttribute::TEXTURE_0 ) ) {
sprintf( buf, "attribute \"texture wrap s\" string \"%s\"\n",
sset.wrap_s == GL_CLAMP ? "clamp" : "repeat" );
str += buf;
sprintf( buf, "attribute \"texture wrap t\" string \"%s\"\n",
sset.wrap_t == GL_CLAMP ? "clamp" : "repeat" );
str += buf;
static struct
{ GLenum val; char *str; }
filter_to_str[] = {
{ GL_NEAREST , "nearest" },
{ GL_LINEAR , "linear" },
{ GL_NEAREST_MIPMAP_NEAREST, "nearest_mipmap_nearest" },
{ GL_NEAREST_MIPMAP_LINEAR , "nearest_mipmap_linear" },
{ GL_LINEAR_MIPMAP_NEAREST , "linear_mipmap_nearest" },
{ GL_LINEAR_MIPMAP_LINEAR , "linear_mipmap_linear" } };
int i;
for ( i = 0; i < ARRAY_LEN(filter_to_str); i++ )
if ( filter_to_str[i].val == sset.min_filter )
break;
if ( i >= ARRAY_LEN(filter_to_str) )
msg_bin.Add( "WARNING: Bad texture min filter: %d\n", sset.min_filter );
else {
sprintf( buf, "attribute \"texture min filter\" string \"%s\"\n",
filter_to_str[i].str );
str += buf;
}
for ( i = 0; i < ARRAY_LEN(filter_to_str); i++ )
if ( filter_to_str[i].val == sset.mag_filter )
break;
if ( i >= ARRAY_LEN(filter_to_str) )
msg_bin.Add( "WARNING: Bad texture mag filter: %d\n", sset.mag_filter );
else {
sprintf( buf, "attribute \"texture mag filter\" string \"%s\"\n",
filter_to_str[i].str );
str += buf;
}
}
// TexEnv
if ( sset.HasAttribute( osg::StateAttribute::TEXENV ) ) {
char *func = 0;
switch ( sset.texture_func ) {
case osg::TexEnv::DECAL : func = "decal" ; break;
case osg::TexEnv::MODULATE : func = "modulate"; break;
case osg::TexEnv::BLEND : func = "blend" ; break;
case osg::TexEnv::REPLACE : func = "replace" ; break;
default:
msg_bin.Add( "WARNING: Bad texture function: %d\n", sset.texture_func );
}
if ( func ) {
sprintf( buf, "attribute \"texture function\" string \"%s\"\n", func );
str += buf;
}
}
// CullFace
if ( sset.HasAttribute( osg::StateAttribute::CULLFACE ) ) {
sprintf( buf, "attribute \"cull face\" string \"%s\"\n",
sset.cullface_mode == osg::CullFace::FRONT ? "front" :
sset.cullface_mode == osg::CullFace::BACK ? "back" :
"front and back" );
str += buf;
}
else {
sprintf( buf, "attribute \"cull face\" string \"off\"\n" );
str += buf;
}
return str;
}
//----------------------------------------------------------------------------
std::string DXWriter::WriteImage( const osg::Image &image )
// Ok, so this writes a field and not just an array. Clean this up later...
// throws 1
{
// If this image has already been written, don't write it again
if ( texture_mgr.IsRegistered( &image ) )
return texture_mgr.Lookup( &image );
// Get the path to the image file, and build a uniquely named DX field
const std::string &path = image.getFileName();
const char *base = strrchr( path.c_str(), '/' );
if ( base )
base++;
else
base = path.c_str();
std::string basename( base );
DXField field( name_mgr, &basename );
std::string name = field.GetName();
// Write positions
std::string pos_name = name_mgr.GetUnique( name + " image positions" );
fprintf( fp, "object \"%s\" class gridpositions counts %d %d\n"
" origin 0 0\n"
" delta 0 1\n"
" delta 1 0\n"
"attribute \"dep\" string \"positions\"\n"
"#\n",
pos_name.c_str(), image.t(), image.s() );
field.AddComponent( "positions", pos_name.c_str() );
// Write connections
std::string conn_name = name_mgr.GetUnique( name + " image connections" );
fprintf( fp, "object \"%s\" class gridconnections counts %d %d\n"
"attribute \"element type\" string \"quads\"\n"
"attribute \"dep\" string \"connections\"\n"
"attribute \"ref\" string \"positions\"\n"
"#\n",
conn_name.c_str(), image.t(), image.s() );
field.AddComponent( "connections", conn_name.c_str() );
// Generate colors
unsigned int pixel_fmt = image.pixelFormat(); // A user-friendly int_fmt
unsigned int data_type = image.dataType(); // Sample data type
const unsigned char *data = image.data();
int r_dim = image.r();
// FIXME: Support other image formats
if ( r_dim != 1 ) {
msg_bin.Add( "ERROR: Currently only 2D textures are supported.\n" );
throw 1;
}
if ( data_type != GL_UNSIGNED_BYTE ) {
msg_bin.Add( "ERROR: Currently only ubyte textures are supported.\n" );
throw 1;
}
switch ( pixel_fmt ) {
case GL_RGB :
case GL_RGBA :
case GL_LUMINANCE :
case GL_LUMINANCE_ALPHA :
break;
default :
msg_bin.Add( "ERROR: Currently only RGB, RGBA, LUMINANCE, and \n"
" LUMINANCE_ALPHA textures are supported. Found %d.\n",
pixel_fmt );
throw 1;
}
unsigned num_pixels = image.s() * image.t();
osg::ubyte opaque = osg::ubyte( ALPHA_OPAQUE * 255.0 );
Vec4UB *colors = new Vec4UB[ num_pixels ];
unsigned i;
if ( pixel_fmt == GL_RGB || pixel_fmt == GL_RGBA )
for ( i = 0; i < num_pixels; i++, data += (pixel_fmt==GL_RGB?3:4))
colors[i][0] = data[0],
colors[i][1] = data[1],
colors[i][2] = data[2],
colors[i][3] = ( pixel_fmt == GL_RGB ? opaque : data[3] );
else
for ( i = 0; i < num_pixels;
i++, data += (pixel_fmt==GL_LUMINANCE?1:2) )
colors[i][0] = colors[i][1] = colors[i][2] = data[0],
colors[i][3] = ( pixel_fmt == GL_LUMINANCE ? opaque : data[1] );
// Write colors
int found_transparent = 0;
for ( i = 0; i < num_pixels && !found_transparent; i++ )
found_transparent = (colors[i][3] != opaque);
int do_opacities = ((pixel_fmt == GL_RGBA ||
pixel_fmt == GL_LUMINANCE_ALPHA ) &&
found_transparent);
// NOTE: DX used to only support UNSIGNED_BYTE GL_RGB textures. Added
// Jan 2002: Added support to DX for RGBA textures w/ depth sorting.
// So we always write RGBA textures as image texture maps with opacities.
std::string colors_name = name_mgr.GetUnique( name + " image colors");
std::string colormap_name = name_mgr.GetUnique( name + " color map" );
std::string opacities_name = name_mgr.GetUnique( name + " opacities" );
std::string opacitymap_name = name_mgr.GetUnique( name + " opacity map" );
int wrote_maps;
w.WriteColors( colors, num_pixels, 0, 0,
colors_name.c_str(), colormap_name.c_str(),
opacities_name.c_str(), opacitymap_name.c_str(),
do_opacities, "positions", wrote_maps );
delete [] colors;
field.AddComponent( "colors", colors_name.c_str() );
if ( do_opacities )
field.AddComponent( "opacities", opacities_name.c_str() );
if ( wrote_maps )
{
field.AddComponent( "color map", colormap_name.c_str() );
if ( do_opacities )
field.AddComponent( "opacity map", opacitymap_name.c_str() );
}
// Finally, write the image field
field.Write( fp, 0 );
// And keep track of this image to attach to future referring fields
texture_mgr.Register( &image, name );
return name;
}
//----------------------------------------------------------------------------
// FIXME: field_name never referenced
void DXWriter::WritePolylineConnections( const std::string &field_name,
osg::GeoSet &geoset,
DXField &field )
// Writes the "edges" and "polylines" field components for LINE_STRIPs,
// FLAT_LINE_STRIPs, and LINE_LOOPs.
{
assert( geoset.getPrimType() == osg::GeoSet::LINE_STRIP ||
geoset.getPrimType() == osg::GeoSet::FLAT_LINE_STRIP ||
geoset.getPrimType() == osg::GeoSet::LINE_LOOP );
int num_prims = geoset.getNumPrims();
int num_points = geoset.getNumCoords(); // FIXME: Never referenced
int num_pindices = geoset.getNumCoordIndices(); // FIXME: Never referenced
const int *prim_lengths = geoset.getPrimLengths();
int line_loops = geoset.getPrimType() == osg::GeoSet::LINE_LOOP;
int i,j;
const osg::GeoSet::IndexPointer &pindices = geoset.getCoordIndices();
// ---- Write "edges" components ----
std::string edges_name = name_mgr.GetUnique( field.GetName() + " edges" );
// Count number of points in all polylines, and set up an index pointer
int count = 0;
for ( i = 0; i < num_prims; i++ )
count += ( line_loops ? prim_lengths[i]+1 : prim_lengths[i] );
osg::uint *indices = new osg::uint[ count ];
osg::GeoSet::IndexPointer ip;
ip.set( count, indices );
// If no coordinate indices, then position indices are implicit; else explicit
int idx = 0;
for ( i = 0; i < num_prims; i++ ) {
int start_idx = idx;
for ( j = 0; j < prim_lengths[i]; j++, idx++ )
indices[ idx ] = pindices.valid() ? pindices[ idx ] : idx;
if ( line_loops ) {
indices[ idx ] = pindices.valid() ? pindices[start_idx] : start_idx;
idx++;
}
}
// Write component
w.WriteIndexArray( ip, count, 0, 1, 0, edges_name.c_str(), "positions", 0 );
field.AddComponent( "edges", edges_name.c_str() );
// ---- Write "polylines" components ----
std::string polylines_name = name_mgr.GetUnique( field.GetName() +
" polylines" );
idx = 0;
for ( i = 0; i < num_prims; i++ ) {
indices[i] = idx;
idx += ( line_loops ? prim_lengths[i]+1 : prim_lengths[i] );
}
w.WriteIndexArray( ip, num_prims, 0, 1, 0, polylines_name.c_str(),
"edges", 0 );
field.AddComponent( "polylines", polylines_name.c_str() );
delete indices;
}
//----------------------------------------------------------------------------
std::string DXWriter::WriteGeoSetField( const std::string &field_name,
osg::GeoSet &geoset,
MyStateSet &sset )
// throw 1
// Writes POINTS, LINES, TRIANGLES, LINE_STRIPs, FLAT_LINE_STRIPs, and
// LINE_LOOPs.
//
// QUADS NOTE: This routine almost supports QUADS. However, OSG/OpenGL
// quad vertices are ordered as a walk around the boundary of the polygon
// whereas DX swaps the last two vertices. So to support quads,
// we'd need to swap the 3rd and 4th coords (or coord indices) in
// each primitive, and for any normals/colors/tcoords, we'd also need
// to swap the 3rd and 4th data value (or data index) in each primitive.
// FIXME: For now, we'll just triangulate QUADS into TRIANGLES, and
// write those (see DXWriter::WriteGeoSet). Later consider swapping and
// writing quads connections if it doesn't obfuscate this routine too
// badly.
{
// No QUADS -- see above comment
assert( geoset.getPrimType() != osg::GeoSet::QUADS );
geoset.computeNumVerts(); // Update # coords/# normals from index arrays
int num_prims = geoset.getNumPrims();
int num_points = geoset.getNumCoords();
int num_pindices = geoset.getNumCoordIndices();
int num_colors = geoset.getNumColors();
int num_cindices = geoset.getNumColorIndices();
int num_normals = geoset.getNumNormals();
//int num_nindices = geoset.getNumNormalIndices();
int num_tcoords = geoset.getNumTextureCoords();
osg::GeoSet::BindingType cbinding = geoset.getColorBinding();
osg::GeoSet::BindingType nbinding = geoset.getNormalBinding();
osg::GeoSet::BindingType tbinding = geoset.getTextureBinding();
int i, prim_length, polylines = 0;
char *connection_type = 0;
// Give the file pointer to the DX array writer
w.SetFP( fp );
w.SetMsgBin( &msg_bin );
msg_bin.Clear();
// If texturing is on, write the texture as a DX image field, and give
// us back the name of field
int has_uv = num_tcoords > 0 && tbinding == osg::GeoSet::BIND_PERVERTEX;
int has_texture = sset.HasAttribute( osg::StateAttribute::TEXTURE_0 );
int has_texgen = sset.HasAttribute( osg::StateAttribute::TEXGEN );
std::string texture_name;
if ( has_texture && has_uv )
try {
texture_name = WriteImage( *sset.image );
} catch (...) { throw; }
else if ( has_texture && !has_uv && has_texgen ) {
static int been_here = 0;
if ( !been_here ) {
msg_bin.Add( "WARNING: "
"Texture/uv not written; TEXGEN not supported yet\n" );
been_here = 1;
}
}
// Create a new DX field with a unique name
DXField field( name_mgr, &field_name );
std::string name = field.GetName();
// Determine "element type" and primitive length of connections component
switch ( geoset.getPrimType() ) {
case osg::GeoSet::POINTS :
prim_length = 1, connection_type = 0 ; break;
case osg::GeoSet::LINES :
prim_length = 2, connection_type = "lines" ; break;
case osg::GeoSet::TRIANGLES :
prim_length = 3, connection_type = "triangles"; break;
case osg::GeoSet::QUADS :
prim_length = 4, connection_type = "quads" ; break;
case osg::GeoSet::LINE_STRIP :
case osg::GeoSet::FLAT_LINE_STRIP :
case osg::GeoSet::LINE_LOOP :
prim_length = 0, connection_type = "polylines";
polylines = 1;
break;
default:
msg_bin.Add( "ERROR: GeoSet passed to WriteGeoSetField is wrong type\n" );
throw 1;
}
// Ensure we "don't" have interleaved GeoSet data
// FIXME: Add support for this
if (( num_points == 0 ) && geoset.getInterleavedArray() != 0 ) {
msg_bin.Add( "ERROR: Interleaved Array GeoSets not yet supported\n" );
throw 1;
}
// Write "positions" component
std::string pos_name = name_mgr.GetUnique( name + " points" );
w.WriteVec3Array( geoset.getCoords(), 0, num_points, pos_name.c_str(),
0, "positions" );
field.AddComponent( "positions", pos_name.c_str() );
// Write "connections" component (lines, triangles, or quads); pts have none
char *conn_dep_name = 0;
if ( prim_length == 1 )
// For points, there are no connections, so don't make colors or normals
// dep on them in the OVERALL or PERPRIM cases
conn_dep_name = "positions";
else if ( polylines ) {
WritePolylineConnections( field_name, geoset, field );
conn_dep_name = "polylines";
}
else {
std::string conn_name = name_mgr.GetUnique( name + " connections" );
int len = num_pindices ? num_pindices : num_points;
char attr[80];
sprintf( attr, "attribute \"element type\" string \"%s\"", connection_type);
w.WriteIndexArray( geoset.getCoordIndices(), len, 1, prim_length, 0,
conn_name.c_str(), "positions", "connections", attr );
field.AddComponent( "connections", conn_name.c_str() );
conn_dep_name = "connections";
}
// Write "normals" component
if ( num_normals > 0 ) {
std::string normals_name = name_mgr.GetUnique( name + " normals" );
// DX doesn't support a "normal map", so expand to Vec3 if normals indexed
// Also see notes in WritePerVertexNormals.
int index;
osg::GeoSet::IndexPointer *ip;
switch( nbinding ) {
case osg::GeoSet::BIND_OVERALL :
ip = &geoset.getNormalIndices();
index = ip->valid() ? (*ip)[0] : 0;
w.WriteVec3ConstArray( geoset.getNormals()[index], num_prims,
normals_name.c_str(), 0, conn_dep_name );
break;
case osg::GeoSet::BIND_PERPRIM :
w.WriteVec3Array( geoset.getNormals(), &geoset.getNormalIndices(),
num_prims, normals_name.c_str(),
0, conn_dep_name );
break;
case osg::GeoSet::BIND_PERVERTEX :
try {
w.WritePerVertexNormals( geoset, normals_name.c_str() );
} catch (...) { throw; }
break;
case osg::GeoSet::BIND_OFF:
case osg::GeoSet::BIND_DEFAULT:
// Nonsensical cases
break;
}
field.AddComponent( "normals", normals_name.c_str() );
}
// Write "colors" (and possibly "opacities")
// If indexed colors and numcolors <= 256, create "color map"/"opacity map"
std::string colors_name = name_mgr.GetUnique( name + " colors" );
std::string colormap_name = name_mgr.GetUnique( name + " color map" );
std::string opacities_name = name_mgr.GetUnique( name + " opacities" );
std::string opacitymap_name = name_mgr.GetUnique( name + " opacity map" );
int has_maps = 0;
int diffuse_is_color = 0, diffuse_is_opacity = 0;
// 4 cases: Choose/write colors/opacities in order of preference from:
// colors, diffuse material, default color.
// If none specified, write no colors/opacities components.
if ( num_colors > 0 ) {
osg::Vec4 *colors = geoset.getColors();
osg::GeoSet::IndexPointer &ip = geoset.getColorIndices();
int index, do_opacities, wrote_maps;
// See if any non-opaque colors used
for ( i = 0; i < num_colors; i++ )
if ( colors[i][3] != ALPHA_OPAQUE )
break;
do_opacities = ( i < num_colors );
osg::Vec3 color;
float opacity;
switch( cbinding ) {
case osg::GeoSet::BIND_OVERALL :
index = ip.valid() ? ip[0] : 0;
w.OSGColorToDX( colors[index], color, opacity );
w.WriteVec3ConstArray( color, num_prims, colors_name.c_str(),
0, conn_dep_name );
if ( do_opacities )
w.WriteFloatConstArray( opacity, num_prims, opacities_name.c_str(),
0, conn_dep_name );
wrote_maps = 0;
break;
case osg::GeoSet::BIND_PERPRIM :
w.WriteColors( colors, num_colors,
&geoset.getColorIndices(), num_cindices,
colors_name.c_str(), colormap_name.c_str(),
opacities_name.c_str(), opacitymap_name.c_str(),
do_opacities, conn_dep_name, wrote_maps );
break;
case osg::GeoSet::BIND_PERVERTEX :
try {
w.WritePerVertexColors( geoset,
colors_name.c_str() , colormap_name.c_str(),
opacities_name.c_str(), opacitymap_name.c_str(),
do_opacities, wrote_maps );
} catch (...) { throw; }
break;
case osg::GeoSet::BIND_OFF:
case osg::GeoSet::BIND_DEFAULT:
// Nonsensical cases
break;
}
// Register added components
field.AddComponent( "colors", colors_name.c_str() );
if ( do_opacities )
field.AddComponent( "opacities", opacities_name.c_str() );
if ( wrote_maps )
{
field.AddComponent( "color map", colormap_name.c_str() );
if ( do_opacities )
field.AddComponent( "opacity map", opacitymap_name.c_str() );
}
}
// Else no explicit colors specific. Fall back on diffuse Material
else if ( sset.HasAttribute( osg::StateAttribute::MATERIAL ) ) {
osg::Vec3 color;
float opacity;
w.OSGColorToDX( sset.diffuse_f, color, opacity );
int do_opacities = opacity != ALPHA_OPAQUE;
// Same as the BIND_OVERALL case above, except can have front/back colors
if ( do_opacities ) {
w.WriteFloatConstArray( opacity, num_prims, opacities_name.c_str(),
0, conn_dep_name );
field.AddComponent( "opacities", opacities_name.c_str() );
}
std::string front_colors_name = name_mgr.GetUnique( name +" front colors" );
std::string back_colors_name = name_mgr.GetUnique( name +" back colors" );
if ( sset.diffuse_f_and_b ) {
w.WriteVec3ConstArray( color, num_prims, colors_name.c_str(),
0, conn_dep_name );
field.AddComponent( "colors", colors_name.c_str() );
}
else {
w.WriteVec3ConstArray( color, num_prims, front_colors_name.c_str(),
0, conn_dep_name );
field.AddComponent( "colors", front_colors_name.c_str() );
w.OSGColorToDX( sset.diffuse_b, color, opacity );
w.WriteVec3ConstArray( color, num_prims, back_colors_name.c_str(),
0, conn_dep_name );
field.AddComponent( "colors", back_colors_name.c_str() );
}
diffuse_is_color = 1;
diffuse_is_opacity = do_opacities;
}
// No explicit colors or diffuse Material color. Give user provided default.
else if ( parms.set_default_color ) {
osg::Vec3 color;
float opacity;
w.OSGColorToDX( parms.default_color, color, opacity );
int do_opacities = opacity != ALPHA_OPAQUE;
// Same as the BIND_OVERALL case above, except can have front/back colors
w.WriteVec3ConstArray( color, num_prims, colors_name.c_str(),
0, conn_dep_name );
field.AddComponent( "colors", colors_name.c_str() );
if ( do_opacities ) {
w.WriteFloatConstArray( opacity, num_prims, opacities_name.c_str(),
0, conn_dep_name );
field.AddComponent( "opacities", opacities_name.c_str() );
}
diffuse_is_color = 1;
diffuse_is_opacity = do_opacities;
}
// Write "uv" texture coordinates
std::string uv_name = name_mgr.GetUnique( name + " uv" );
if ( num_tcoords > 0 && tbinding == osg::GeoSet::BIND_PERVERTEX ) {
try {
w.WritePerVertexTCoords( geoset, uv_name.c_str() );
} catch (...) { throw; }
field.AddComponent( "uv", uv_name.c_str() );
}
// Generate field attributes
std::string attr( has_maps ? "attribute \"direct color map\" number 1\n" :"");
attr += BuildStateSetAttributes( sset, diffuse_is_color, diffuse_is_opacity );
// If texturing is on, tack on a "texture" attribute with it's field name
if ( has_uv && has_texture ) {
char buf[160];
//int size_ok = sset.image->s() >= 64 && sset.image->t() >= 64;
//char *attr_name = size_ok ? "texture" : "texture - too small";
char *attr_name = "texture";
// FIXME: DX textures must be powers of two and >= 64 on a side.
// Now that we generate MIPmaps that's no longer true, and just remove
// the 64x64 min size check from DX.
// NOTE: FIXED in DX now that we mipmap everything
//if ( !size_ok ) {
// // FIXME: When we generalize this, remove the static hack
// static int been_here = 0;
// if ( !been_here ) {
// msg_bin.Add(
// "WARNING: DX min texture size is 64x64. Renaming texture\n"
// " attribute to \"texture - too small\" for these"
// " small textures.\n" );
// been_here = 1;
// }
//}
sprintf( buf, "attribute \"%s\" value \"%s\"\n",
attr_name, texture_name.c_str() );
attr += buf;
}
if ( attr[ attr.length()-1 ] == '\n' )
attr.assign( attr, 0, attr.length()-1 );
// Finally, write the field
field.Write( fp, attr.c_str() );
return name;
// FIXME: Need to handle "texture" image attribute, GeoState colors
// storing GeoState attributes on field object, etc.
}
//----------------------------------------------------------------------------
void MyStateSet::Show( osg::StateSet &sset )
{
osg::StateSet::ModeList &mode_list = sset.getModeList();
osg::StateSet::AttributeList &attr_list = sset.getAttributeList();
//int rend_hint = sset.getRenderingHint();
//bool use_rend_bin_details = sset.useRenderBinDetails();
//int bin_num = sset.getBinNumber();
//const std::string &bin_name = sset.getBinName();
//osg::StateSet::RenderBinMode rendbin_mode = sset.getRenderBinMode();
// Iterate over all mode mappings (GLMode->GLModeValue)
for( osg::StateSet::ModeList::const_iterator mitr = mode_list.begin();
mitr != mode_list.end();
++mitr )
msg_bin.Add( " GLMode %d = GLValue %d\n", mitr->first, mitr->second );
// Iterate over all attribute mappings
// (Type -> pair< ref_ptr<StateAttribute>, OverrideValue >)
for( osg::StateSet::AttributeList::const_iterator aitr = attr_list.begin();
aitr != attr_list.end();
++aitr )
msg_bin.Add( " Attr Type %d (Attr Name \"%s\"), OverrideValue = %d\n",
aitr->first, aitr->second.first.get()->className(),
aitr->second.second );
// mgs_bin.Add( " Rendering Hint = %d\n", rend_hint );
// mgs_bin.Add( " Use Rend Bin Details = %d\n", use_rend_bin_details );
// mgs_bin.Add( " Bin Number = %d\n", bin_num );
// mgs_bin.Add( " Bin Name = %s\n", bin_name.c_str() );
// mgs_bin.Add( " Bin Mode = %d\n", rendbin_mode );
}
//----------------------------------------------------------------------------
void MyStateSet::Query( const osg::StateSet &sset )
{
const osg::StateAttribute *attr;
//MyStateSet::Show( *drawable_state );
// FIXME: Ignore override flags for now
// MATERIAL
attr = sset.getAttribute( osg::StateAttribute::MATERIAL );
if ( attr ) {
AddAttribute( osg::StateAttribute::MATERIAL );
const osg::Material &mat = (const osg::Material &) (*attr);
colormode = mat.getColorMode(); // GL_COLOR_MATERIAL
ambient_f = mat.getAmbient ( osg::Material::FRONT );
ambient_b = mat.getAmbient ( osg::Material::BACK );
diffuse_f = mat.getDiffuse ( osg::Material::FRONT );
diffuse_b = mat.getDiffuse ( osg::Material::BACK );
specular_f = mat.getSpecular ( osg::Material::FRONT );
specular_b = mat.getSpecular ( osg::Material::BACK );
emission_f = mat.getEmission ( osg::Material::FRONT );
emission_b = mat.getEmission ( osg::Material::BACK );
shininess_f = mat.getShininess( osg::Material::FRONT );
shininess_b = mat.getShininess( osg::Material::BACK );
ambient_f_and_b = mat.getAmbientFrontAndBack ();
diffuse_f_and_b = mat.getDiffuseFrontAndBack ();
specular_f_and_b = mat.getSpecularFrontAndBack ();
emission_f_and_b = mat.getEmissionFrontAndBack ();
shininess_f_and_b = mat.getShininessFrontAndBack();
}
// TEXTURE / TEXTURE_0
attr = sset.getAttribute( osg::StateAttribute::TEXTURE_0 );
if ( attr &&
( sset.getMode( GL_TEXTURE_2D ) & osg::StateAttribute::ON )) {
const osg::Texture &texture = (const osg::Texture &) (*attr);
// NOTE: If OSG failed to load the texture, we'll get a NULL right here
image = texture.getImage();
if ( image )
AddAttribute( osg::StateAttribute::TEXTURE_0 );
// NOTE: DX limitations
// Traditional DX doesn't support any texture control besides specifying
// the texture ("texture" attribute) and the texture coordinates ("uv"
// component). DX was hard-coded to use these 2D texture settings:
// GL_TEXTURE_WRAP_S = GL_CLAMP
// GL_TEXTURE_WRAP_T = GL_CLAMP
// GL_TEXTURE_MIN_FILTER = GL_NEAREST
// GL_TEXTURE_MAG_FILTER = GL_NEAREST
// GL_TEXTURE_ENV_MODE = GL_MODULATE
// DX also registers a level 0 texture
// A DX patch I recently committed (1/02) creates mipmapped textures and
// adds the following DX textured field attributes:
// attribute "texture wrap s" string ["clamp"|"repeat"]
// attribute "texture wrap t" string ["clamp"|"repeat"]
// attribute "texture min filter" string ["nearest"|"linear"|
// "nearest_mipmap_nearest"|"nearest_mipmap_linear"|
// "linear_mipmap_nearest"|"linear_mipmap_linear"]
// attribute "texture mag filter" string ["nearest"|"linear"]
// attribute "texture function" string ["decal"|"replace"|"modulate"
// "blend"]
wrap_s = texture.getWrap( osg::Texture::WRAP_S );
wrap_t = texture.getWrap( osg::Texture::WRAP_T );
min_filter = texture.getFilter( osg::Texture::MIN_FILTER );
mag_filter = texture.getFilter( osg::Texture::MAG_FILTER );
if ( texture.getInternalFormatMode() !=
osg::Texture::USE_IMAGE_DATA_FORMAT ) {
// FIXME: When we generalize this, remove the static hack
static int been_here = 0;
if ( !been_here ) {
msg_bin.Add( "WARNING: Only texture image data format supported.\n");
been_here = 1;
}
}
if ( texture.getSubloadMode() != osg::Texture::OFF ) {
// FIXME: When we generalize this, remove the static hack
static int been_here = 0;
if ( !been_here ) {
msg_bin.Add( "WARNING: Texture subloading not supported.\n" );
been_here = 1;
}
}
}
// TEXENV
attr = sset.getAttribute( osg::StateAttribute::TEXENV );
if ( attr &&
( sset.getMode( GL_TEXTURE_2D ) & osg::StateAttribute::ON )) {
AddAttribute( osg::StateAttribute::TEXENV );
const osg::TexEnv &texenv = (const osg::TexEnv &) (*attr);
texture_func = texenv.getMode();
}
// TEXGEN
attr = sset.getAttribute( osg::StateAttribute::TEXGEN );
if ( attr &&
( sset.getMode( GL_TEXTURE_2D ) & osg::StateAttribute::ON )) {
// FIXME: Just note that it is there for now. We don't actually support
// texture coordinate generation yet (i.e. generating the "uv" component
// for DX without explicit texture coordinates in the model)
AddAttribute( osg::StateAttribute::TEXGEN );
}
// CULLFACE
attr = sset.getAttribute( osg::StateAttribute::CULLFACE );
if ( attr &&
( sset.getMode( GL_CULL_FACE ) & osg::StateAttribute::ON )) {
AddAttribute( osg::StateAttribute::CULLFACE );
const osg::CullFace &cullface = (const osg::CullFace &) (*attr);
cullface_mode = cullface.getMode();
}
// FIXME: Also consider supporting:
//exenv
// ALPHAFUNC, ANTIALIAS, COLORTABLE, FOG, FRONTFACE,
// LIGHTING, POINT, POLYGONMODE, POLYGONOFFSET, TEXGEN, TEXMAT,
// TEXTURE_1, TEXTURE_2, TEXTURE_3, TRANSPARENCY,
// STENCIL, COLORMASK, CLIPPLANE, CLIPPLANE_0, CLIPPLANE_1,
// CLIPPLANE_2, CLIPPLANE_3, CLIPPLANE_4, CLIPPLANE_5, DEPTH
}
//----------------------------------------------------------------------------
void DXWriter::Open()
{
if ( strcmp( parms.outfile, "-" ) == 0 )
fp = stdout;
else
fp = fopen( parms.outfile, "wt" );
w.SetFP( fp );
w.SetMsgBin( &msg_bin );
}
//----------------------------------------------------------------------------
void DXWriter::Close()
{
if ( fp != stdout )
fclose( fp );
w.SetFP( NULL );
}
//----------------------------------------------------------------------------
std::string DXWriter::WriteGeoSet( osg::GeoSet &geoset,
const osg::StateSet &active_sset,
std::string &field_name )
// throws 1
{
std::string dx_name;
MyStateSet my_sset(msg_bin);
// Update the active state for this Drawable (GeoSet), if it has a StateSet
const osg::StateSet *sset;
StateSetCopy *new_sset = 0;
osg::StateSet *drawable_state = geoset.getStateSet();
if ( drawable_state ) {
new_sset = new StateSetCopy( active_sset );
new_sset->merge( *drawable_state );
sset = new_sset;
// Collect unhandled modes/attributes in this geoset's stateset
CollectUnhandledModesAndAttrs( drawable_state );
}
else
sset = &active_sset;
my_sset.Query( *sset );
delete new_sset;
// Now write the GeoSet to a DX field
osg::GeoSet::PrimitiveType prim_type = geoset.getPrimType();
osg::GeoSet *tri_geoset;
switch( prim_type ) {
case osg::GeoSet::POINTS :
case osg::GeoSet::LINES :
case osg::GeoSet::TRIANGLES :
case osg::GeoSet::LINE_STRIP :
case osg::GeoSet::FLAT_LINE_STRIP :
case osg::GeoSet::LINE_LOOP :
try {
dx_name = WriteGeoSetField( field_name, geoset, my_sset );
} catch (...) { throw; }
break;
case osg::GeoSet::QUADS :
case osg::GeoSet::TRIANGLE_STRIP :
case osg::GeoSet::FLAT_TRIANGLE_STRIP :
case osg::GeoSet::TRIANGLE_FAN :
case osg::GeoSet::FLAT_TRIANGLE_FAN :
case osg::GeoSet::QUAD_STRIP :
case osg::GeoSet::POLYGON :
// For all of these cases, bust up the area primitive GeoSet into
// a TRIANGLES GeoSet, and then simply write that. DX doesn't
// support any of these primitive types except POLYGON and QUADS
// anyway, and its polygon support is a bit quirky (e.g.
// $DX_NESTED_LOOPS).
tri_geoset = TriangulateAreaGeoSet( geoset );
try {
dx_name = WriteGeoSetField( field_name, *tri_geoset, my_sset );
} catch (...) {
tri_geoset->unref();
throw;
}
tri_geoset->unref();
break;
case osg::GeoSet::NO_TYPE :
// Nonsensical cases
break;
}
// Return the name of the written field, or "" if not written
return dx_name;
}
//----------------------------------------------------------------------------
std::string DXWriter::WriteGeode( osg::Geode &geode,
const osg::StateSet &active_sset )
// throws 1
{
const std::string &name = geode.getName();
std::string field_name;
DXGroup *group = 0;
std::string dx_name;
// If there are multiple GeoSets in this Geode, then we will encase them
// in a DX group
if ( geode.getNumDrawables() > 1 )
group = new DXGroup( name_mgr, &name );
// For all GeoSets in this Geode...
for ( int i = 0; i < geode.getNumDrawables(); i++ ) {
osg::GeoSet *geoset = dynamic_cast<osg::GeoSet*>( geode.getDrawable(i) );
// If we have multiple GeoSets per Geode, we need to generate different
// DX field names for each
if ( geode.getNumDrawables() > 1 ) {
char prim_num_str[20];
sprintf( prim_num_str, " %d", i+1 );
field_name = name + prim_num_str;
}
else
field_name = name;
try {
dx_name = WriteGeoSet( *geoset, active_sset, field_name );
} catch (...) { throw; }
if ( !dx_name.empty() && group )
group->AddMember( dx_name.c_str() );
}
// If collecting fields in a Group, write the Group
if ( group ) {
// If no members were written successfully, don't write this group
// or include it in any parent groups
if ( group->GetNumMembers() == 0 )
dx_name = "";
else {
group->Write( fp );
dx_name = group->GetName();
}
delete group;
}
return dx_name;
}
//----------------------------------------------------------------------------
void DXWriter::CollectUnhandledModesAndAttrs( osg::StateSet *sset )
{
if ( !sset )
return;
// FIXME: For now we're collecting all of them, not just the
// unhandled ones
osg::StateSet::ModeList &modelist = sset->getModeList();
osg::StateSet::AttributeList &attrlist = sset->getAttributeList();
// Loop through the mode settings
for( osg::StateSet::ModeList::const_iterator mitr = modelist.begin();
mitr != modelist.end();
++mitr )
unsupported_modes[ mitr->first ] = 1;
// Loop through the attribute settings
for( osg::StateSet::AttributeList::const_iterator aitr = attrlist.begin();
aitr != attrlist.end();
++aitr )
unsupported_attrs[ aitr->first ] = 1;
}
//----------------------------------------------------------------------------
#ifdef WIN32
#define snprintf _snprintf
#endif
void DXWriter::ReportUnhandledModesAndAttrs()
{
char msg[1024];
msg_bin.Add( "\n" );
// Loop through the mode settings...
msg_bin.Add( "OpenGL Modes Encounted:\n " );
msg[0] = '\0';
for( DXWriter::ModeList::const_iterator mitr = unsupported_modes.begin();
mitr != unsupported_modes.end();
++mitr ) {
const char *mode_str = GLModeToModeStr( mitr->first );
if ( mode_str )
snprintf( msg+strlen(msg), sizeof(msg)-strlen(msg),
" %s", mode_str );
else
snprintf( msg+strlen(msg), sizeof(msg)-strlen(msg),
" %d", mitr->first );
}
msg_bin.Add( msg );
msg_bin.Add( "" );
// ...and the attribute settings
msg_bin.Add( "OpenSceneGraph Attributes Encountered:\n " );
msg[0] = '\0';
for( DXWriter::AttrList::const_iterator aitr = unsupported_attrs.begin();
aitr != unsupported_attrs.end();
++aitr ) {
const char *attr_str = OSGAttrToAttrStr( aitr->first );
if ( attr_str )
snprintf( msg+strlen(msg), sizeof(msg)-strlen(msg),
" %s", attr_str );
else
snprintf( msg+strlen(msg), sizeof(msg)-strlen(msg),
" %d", aitr->first );
}
msg_bin.Add( msg );
msg_bin.Add( "" );
}
//----------------------------------------------------------------------------
// NOTES ON TRAVERSAL:
//
// So what's up with the StateSetVisitor and StateSetActionVisitor
// pair? Well it's all to make sure that when the user's apply()
// method is called for a Node, we can just hand over the current state
// (attributes and modes) without the user having to go figure it out.
//
// So why are two passes needed? One to update the state. One to
// execute the user's apply method with that state. Basically,
// StateSetVisitor is the master traversal object, and we gosub into
// the StateSetActionVisitor just long enough to invoke the apply
// method stack for a Node, and then we gosub back out to continue
// traversal on the main StateSetVisitor. StateSetVisitor's handles
// keeping the active StateSet up-to-date.
//
// Here's how it works: traversal is invoked on a StateSetVisitor (a
// NodeVisitor). It's constructor accepts a reference to a
// StateSetActionVisitor, which has the user's apply methods.
//
// StateSetVisitor: In it's constructor, we give the
// StateSetActionVisitor we've been handed a handle to ourself. Then
// during traversal when we hit a Node, we push a new StateSet onto our
// StateSet stack which is a copy of the top of the stack merged with
// this Node's StateSet. This new StateSet is then given to the
// StateSetActionVisitor instance, and a new traversal is invoked on
// this Node with the StateSetActionVisitor instance as the visitor.
// When it returns, we pop the stack.
//
// StateSetActionVisitor: When StateSetVisitor initiates a traversal on
// a Node with us, we simply call the apply() method stack for the
// current Node. At the bottom in apply(Node&), we traverse back to
// the StateSetVisitor traversal using node.traverse, which continues
// traversal on the node's children.
class StateSetVisitor;
class StateSetActionVisitor : public osg::NodeVisitor
// Second pass for StateSetVisitor. Users should override apply()
// methods on this object, pass an instance of this object to
// the StateSetVisitor constructor, and initiate traversal on the
// resulting StateSetVisitor instance.
//
// NOTE: We need two passes per Node to update the state correctly so that
// users can still override accept() and get the current state, without
// the user having to call methods to keep it current.
// The first pass updates the state for the node; the second actually
// calls the user's accept methods. In those methods, they can
// call GetActiveStateSet() to get the current state inclusive of that
// node.
// The trick that allows this all to work is that this object's
// apply(Node&) method (at the bottom of the apply stack) transfers
// traversal back over to the StateSetVisitor starting with the children
// of the current node (_traversalMode is also set to TRAVERSE_NONE,
// not that that ever comes into play). So basically we gosub
// into StateSetActionVisitor just long enough to invoke the apply
// methods and then gosub right back out to the main traverser,
// alternating back and forth as we move down the scene graph.
{
public:
friend class StateSetVisitor;
StateSetActionVisitor()
: osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_NONE ) {}
// Give first pass traversal control back to StateSetVisitor
void apply(osg::Node& node);
void apply(osg::Geode& node) { NodeVisitor::apply(node); }
void apply(osg::Billboard& node) { NodeVisitor::apply(node); }
void apply(osg::LightSource& node){ NodeVisitor::apply(node); }
void apply(osg::Group& node) { NodeVisitor::apply(node); }
void apply(osg::Transform& node) { NodeVisitor::apply(node); }
void apply(osg::Switch& node) { NodeVisitor::apply(node); }
void apply(osg::LOD& node) { NodeVisitor::apply(node); }
void apply(osg::Impostor& node) { NodeVisitor::apply(node); }
void apply(osg::EarthSky& node) { NodeVisitor::apply(node); }
const osg::StateSet &GetActiveStateSet()
{ return *_current_stateset; }
protected:
void SetStateSetVisitor( StateSetVisitor *nv )
{ _ssvisitor = nv; }
void SetActiveStateSet( osg::StateSet *sset )
{ _current_stateset = sset; }
StateSetVisitor *_ssvisitor;
osg::StateSet *_current_stateset;
};
//----------------------------------------------------------------------------
class StateSetVisitor : public osg::NodeVisitor
// First pass for StateSetVisitor. Users should override apply()
// methods on the StateSetActionVisitor object, pass an instance of it
// to this object's constructor, and initiate traversal on the
// resulting StateSetVisitor instance.
//
// See StateSetActionVisitor for more details.
{
public:
StateSetVisitor( StateSetActionVisitor *av )
// ACTIVE_CHILDREN - Only traverse the most-detailed LOD and
// active children in a Switch
: osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN ),
_actionTraversalVisitor(av)
{
_actionTraversalVisitor->SetStateSetVisitor( this );
}
void apply(osg::Node& node)
{
// In this first apply() pass, update state with node's StateSet.
osg::StateSet *node_state = node.getStateSet();
osg::ref_ptr<StateSetCopy> sset;
if ( node_state ) {
sset = new StateSetCopy( *_state_stack.back() );
sset->merge( *node_state );
}
else
if ( !_state_stack.empty() )
sset = _state_stack.back();
else
sset = new StateSetCopy();
// Now push state, traverse using action visitor, and then pop state
_state_stack.push_back( sset );
//traverse(node);
_actionTraversalVisitor->SetActiveStateSet( sset.get() );
node.accept(*_actionTraversalVisitor);
_state_stack.pop_back();
}
protected:
StateSetActionVisitor *_actionTraversalVisitor;
std::vector< osg::ref_ptr<StateSetCopy> > _state_stack;
};
//----------------------------------------------------------------------------
// Give first pass traversal control back to StateSetVisitor
void StateSetActionVisitor::apply(osg::Node& node)
{ assert( _ssvisitor );
// Flip back to StateSetVisitor and traverse node's kids
node.traverse( *_ssvisitor ); }
//----------------------------------------------------------------------------
class DXWriteVisitor : public StateSetActionVisitor
{
protected:
DXWriter &dx;
std::vector< DXGroup * > group_stack;
MessageBin &msg_bin;
enum NodeTypes { LOD, BILLBOARD, LIGHTSOURCE, TRANSFORM, SWITCH,
IMPOSTER, EARTHSKY };
typedef std::map< NodeTypes, int > StringMap;
StringMap problem_nodes;
public:
DXWriteVisitor( DXWriter &dx ) : dx(dx), msg_bin(dx.msg_bin) {}
void apply(osg::Node& node)
{
dx.CollectUnhandledModesAndAttrs( node.getStateSet() );
StateSetActionVisitor::apply( node ); // Flip back to StateSetVisitor
}
void apply(osg::Geode& node)
{
std::string name;
try {
name = dx.WriteGeode( node, GetActiveStateSet() );
} catch (...) { throw; }
// Accumulate fields (Geodes) in groups
if ( !name.empty() && group_stack.size() > 0 )
group_stack[ group_stack.size()-1 ]->AddMember( name.c_str() );
apply((osg::Node&)node);
}
void apply(osg::Group& node)
{
DXGroup *group = 0;
// Only start a new DX group if there are 2 or more kids
#ifdef SKIP_SINGLE_MEMBER_DXGROUPS
if ( node.getNumChildren() >= 2 ) {
#else
if ( 1 ) {
#endif
// New group
group = new DXGroup( dx.name_mgr, &node.getName() );
group_stack.push_back( group );
// Add to any parent group
if ( group_stack.size() >= 2 )
group_stack[ group_stack.size()-2 ]->AddMember(
group->GetName().c_str() );
}
// Process children
// (Children will be added to this DX group, or a parent DX group
// if we didn't start a new DX group above, or no DX group if
// none active right now).
apply((osg::Node&)node);
// If we started a new DX group above
if ( group ) {
// If wrote some children, write group; else remove from parent group
if ( group_stack.back()->GetNumMembers() > 0 )
group_stack.back()->Write( dx.fp );
else if ( group_stack.size() >= 2 )
group_stack[ group_stack.size()-2 ]->RemoveMember(
group->GetName().c_str() );
// Delete group
group_stack.pop_back();
delete group;
}
}
void apply(osg::LOD& node)
{ problem_nodes[ LOD ]++; apply((osg::Node&)node); }
void apply(osg::Switch& node)
{ problem_nodes[ SWITCH ]++; apply((osg::Node&)node); }
// FIXME
void apply(osg::Billboard& node)
{
// FIXME: Right now we're just treating this like a transformed Geode
// ignoring the other Billboard properties
problem_nodes[ BILLBOARD ]++;
DXGroup *group = 0;
// New dummy group (to snap up our child Geode's DX object name)
std::string fallback_name( "Billboard" );
group = new DXGroup( dx.name_mgr, &node.getName(), &fallback_name );
group_stack.push_back( group );
// Add to any parent group
if ( group_stack.size() >= 2 )
group_stack[ group_stack.size()-2 ]->AddMember(
group->GetName().c_str() );
// Process child
apply((osg::Geode&)node);
// FIXME: Currently only support one GeoSet per Billboard
if ( group->GetNumMembers() > 1 )
msg_bin.Add(
"WARNING: Currently only 1 GeoSet per Billboard is supported\n"
" Using same transform for all GeoSets.\n");
// Write billboard transform
fprintf( dx.fp, "object \"%s\" class transform of \"%s\"\n",
group->GetName().c_str(),
group->members[ group->members.size()-1 ].c_str() );
fprintf( dx.fp,
" times 1 0 0\n"
" 0 1 0\n"
" 0 0 1\n"
" plus %g %g %g\n",
node.getPos(0).x(), node.getPos(0).y(), node.getPos(0).z() );
fprintf( dx.fp, "#\n\n" );
// Delete group
group_stack.pop_back();
delete group;
}
void apply(osg::LightSource& node)
{ problem_nodes[ LIGHTSOURCE ]++; apply((osg::Node&)node); }
void apply(osg::Transform& node)
{ problem_nodes[ TRANSFORM ]++; apply((osg::Group&)node); }
void apply(osg::Impostor& node)
{ problem_nodes[ IMPOSTER ]++; apply((osg::LOD&)node); }
void apply(osg::EarthSky& node)
{ problem_nodes[ EARTHSKY ]++; apply((osg::Group&)node); }
void ReportProblems();
};
//----------------------------------------------------------------------------
void DXWriteVisitor::ReportProblems()
{
for( StringMap::const_iterator smitr = problem_nodes.begin();
smitr != problem_nodes.end();
++smitr )
switch ( smitr->first ) {
case LOD : msg_bin.Add( "WARNING: %d LOD(s) found ... "
"Traversed only the most detailed child of each.\n",
smitr->second );
break;
case SWITCH :
msg_bin.Add( "WARNING: %d Switch(s) found ... "
"Traversed only the active child of each.\n",
smitr->second );
break;
case BILLBOARD :
msg_bin.Add( "WARNING: %d Billboard(s) found ... "
"represented as simple Geodes with Transforms.\n",
smitr->second );
break;
case LIGHTSOURCE :
msg_bin.Add( "WARNING: %d LightSource(s) found ... Skipped.\n",
smitr->second );
break;
case TRANSFORM :
msg_bin.Add( "WARNING: %d Transform(s) found ... Skipped.\n",
smitr->second );
break;
case IMPOSTER :
msg_bin.Add( "WARNING: %d Imposter(s) found ... Skipped.\n",
smitr->second );
break;
case EARTHSKY :
msg_bin.Add( "WARNING: %d EarthSky(s) found ... Skipped.\n",
smitr->second );
break;
}
}
//----------------------------------------------------------------------------
bool WriteDX( const osg::Node &node, WriterParms &parms, std::string &messages )
{
messages = "";
DXWriter dx;
dx.SetParms( parms );
dx.Open();
// Yes, this looks a bit weird.
// See the comment in the StateSetActionVisitor decl for more details.
DXWriteVisitor dxvisitor(dx);
StateSetVisitor ssv(&dxvisitor);
bool success = false;
try {
// Arg. Node::accept isn't a const method.
((osg::Node &)node).accept( ssv );
success = true;
}
catch (...) {
unlink( parms.outfile );
}
dx.Close();
dx.ReportUnhandledModesAndAttrs();
dxvisitor.ReportProblems();
messages = dx.msg_bin.GetPending();
return success;
}
}; // namespace dx