//
//  ConvertToInventor converts OSG scene graph to Inventor or VRML 1 scene graph
//
//  It requires OSG and Inventor compatible library, such as Coin,
//  SGI Inventor , or TGS Inventor.
//
//
//  Autor: PCJohn (peciva _at fit.vutbr.cz)
//
//  License: public domain
//
//
//  THIS SOFTWARE IS NOT COPYRIGHTED
//
//  This source code is offered for use in the public domain.
//  You may use, modify or distribute it freely.
//
//  This source code is distributed in the hope that it will be useful but
//  WITHOUT ANY WARRANTY.  ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY
//  DISCLAIMED.  This includes but is not limited to warranties of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
//  If you find the source code useful, authors will kindly welcome
//  if you give them credit and keep their names with their source code,
//  but you are not forced to do so.
//


#include <osg/BlendFunc>
#include <osg/Billboard>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/Group>
#include <osg/LightModel>
#include <osg/LOD>
#include <osg/Material>
#include <osg/MatrixTransform>
#include <osg/Node>
#include <osg/NodeVisitor>
#include <osg/PositionAttitudeTransform>
#include <osg/PrimitiveSet>
#include <osg/ShapeDrawable>
#include <osg/TexEnv>
#include <osg/TexGen>
#include <Inventor/nodes/SoBaseColor.h>
#include <Inventor/nodes/SoCoordinate3.h>
#include <Inventor/nodes/SoCoordinate4.h>
#include <Inventor/nodes/SoCube.h>
#include <Inventor/nodes/SoCone.h>
#include <Inventor/nodes/SoCylinder.h>
#include <Inventor/nodes/SoFaceSet.h>
#include <Inventor/nodes/SoIndexedFaceSet.h>
#include <Inventor/nodes/SoIndexedLineSet.h>
#include <Inventor/nodes/SoIndexedTriangleStripSet.h>
#include <Inventor/nodes/SoLightModel.h>
#include <Inventor/nodes/SoLineSet.h>
#include <Inventor/nodes/SoLOD.h>
#include <Inventor/nodes/SoLevelOfDetail.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoMatrixTransform.h>
#include <Inventor/nodes/SoNode.h>
#include <Inventor/nodes/SoNormal.h>
#include <Inventor/nodes/SoNormalBinding.h>
#include <Inventor/nodes/SoPointSet.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoShapeHints.h>
#include <Inventor/nodes/SoSphere.h>
#include <Inventor/nodes/SoTexture2.h>
#include <Inventor/nodes/SoTextureCoordinate2.h>
#include <Inventor/nodes/SoTextureCoordinateEnvironment.h>
#include <Inventor/nodes/SoTransform.h>
#include <Inventor/nodes/SoTranslation.h>
#include <Inventor/nodes/SoTriangleStripSet.h>
#include <Inventor/fields/SoFields.h>
#ifdef __COIN__
#include <Inventor/nodes/SoTextureCoordinate3.h>
#include <Inventor/nodes/SoTransparencyType.h>
#include <Inventor/VRMLnodes/SoVRMLBillboard.h>
#endif
#include "ConvertToInventor.h"

#include <assert.h>


#define DEBUG_IV_WRITER 1


// Considerations for VRML1 support:
//
// SoSeparator:                supported (only renderCulling field)
// SoCoordinate3:              supported
// SoCoordinate4:              no -> conversion to Coordinate3 required <---- missing
// SoTextureCoordinate2:       supported
// SoTextureCoordinate3:       no - 3D textures not supported by VRML1
// SoTextureCoordinateEnvironment no <- should texturing be disabled in this case?
// SoTextureCoordinateBinding: no -> indexing is probably mandatory
// SoNormal:                   supported
// SoNormalBinding:            supported (all modes supported, including non-indexed modes)
// SoMaterial:                 supported
// SoMaterialBinding:          supported (all modes supported, including non-indexed modes)
// SoBaseColor:                no -> using SoMaterial instead
// SoPointSet:                 supported
// SoLineSet:                  no -> using SoIndexedListSet instead
// SoIndexedLineSet:           supported
// SoIndexedTriangleStripSet:  no -> using SoIndexedFaceSet instead
// SoTriangleStripSet:         no -> using SoIndexedFaceSet instead
// SoMatrixTransform:          supported
// SoTransform:                supported
// SoTransparencyType:         no   <---- missing
// SoShapeHints:               supported
// SoCone,SoCube,SoCylinder,SoSphere supported
// SoTranslation               supported
// SoLightModel:               ?
// SoLOD:                      supported
// SoLevelOfDetail:            no


// Node list from VRML1 documentation:
// shape nodes: AsciiText, Cone, Cube, Cylinder, IndexedFaceSet, IndexedLineSet, PointSet, Sphere
// properties nodes: Coordinate3, FontStyle, Info, Material, MaterialBinding, Normal, NormalBinding,
//                   Texture2, Texture2Transform, TextureCoordinate2, ShapeHints
// transformantion nodes: MatrixTransform, Rotation, Scale, Transform, Translation
// group nodes: Separator, Switch, WWWAnchor, LOD
// cameras: OrthographicCamera, PerspectiveCamera
// lights: DirectionalLight, PointLight, SpotLight
// others: WWWInline



ConvertToInventor::ConvertToInventor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
{
  // Enable/disable using of some extensions introduced by TGS and Coin.
  //
  // For instance, GL_REPLACE texturing mode is extension
  // and it will not be used if extensions are not enabled.
#ifdef __COIN__
  useIvExtensions = true;
#else
  useIvExtensions = false;
#endif
  vrml1Conversion = false;
  uniqueIdGenerator = 1;

  // Scene root
  ivRoot = new SoSeparator;
  ivRoot->ref();

  // OSG -> Inventor coordinate system conversion
  SoMatrixTransform *mt = new SoMatrixTransform;
  mt->matrix.setValue(1.f, 0.f,  0.f, 0.f,
                      0.f, 0.f, -1.f, 0.f,
                      0.f, 1.f,  0.f, 0.f,
                      0.f, 0.f,  0.f, 1.f);
  ivRoot->addChild(mt);

#if 0 // Setting everything to defaults may not be correct option
      // because the user may want to have model whose material,etc.
      // will be specified up in the scene graph and he may, for instance,
      // reuse the model with different materials.
      // However, I am not sure anyway. PCJohn-2007-08-27

  //              OSG defaults to:    Inventor defaults to:
  // GL_LIGHTING   enabled             enabled
  SoLightModel *lm = new SoLightModel;
  lm->model = SoLightModel::PHONG;
  ivRoot->addChild(lm);

  //              OSG defaults to:    Inventor defaults to:
  // lighting:     one side            one side lighting
  // frontFace:    COUNTER_CLOCKWISE   COUNTER_CLOCKWISE
  // culling:      DISABLED            DISABLED
  // faceCulling:  GL_BACK             GL_BACK
  SoShapeHints *sh = new SoShapeHints;
  sh->vertexOrdering = SoShapeHints::UNKNOWN_ORDERING;
  ivRoot->addChild(sh);

  // Make sure texturing is OFF
  ivRoot->addChild(new SoTexture2D);

  // Put default material
  ivRoot->addChild(new SoMaterial);
#endif

  // initialize Inventor state stack
  ivStack.push(InventorState::createTopLevelState(ivRoot));
}


ConvertToInventor::~ConvertToInventor()
{
  assert(ivStack.size() == 1 && "Not all InventorStates were popped from ivStack.");
  if (ivRoot)  ivRoot->unref();
}


SoNode* ConvertToInventor::getIvSceneGraph() const
{
  return ivRoot;
}


void ConvertToInventor::apply(osg::Node &node)
{
#ifdef DEBUG_IV_WRITER
  osg::notify(osg::INFO) << "IvWriter: node traversed" << std::endl;
#endif

  traverse(node);
}


template<typename fieldClass, typename ivType, typename osgType>
void osgArray2ivMField_template(const osg::Array *array, fieldClass &field, int startIndex = 0, int stopIndex = 0, int numItemsUntilMinusOne = 0)
{
  int i,num = array->getNumElements();
  if (startIndex!=0 || stopIndex!=0) {
    num = stopIndex - startIndex;
    assert(stopIndex >= startIndex);
    assert(startIndex >= 0 && stopIndex >= 0);
    assert(stopIndex <= int(array->getNumElements()));
  }

  // update num if numItemsUntilMinusOne > 0
  if (numItemsUntilMinusOne > 0 && num >= 1)
    num += (num-1) / numItemsUntilMinusOne;

  field.setNum(num);
  ivType *a = field.startEditing();

  osgType *ptr = (osgType*)array->getDataPointer() + startIndex;
  
  if (numItemsUntilMinusOne <= 0)
    for (i=0; i<num; i++, ptr++)
      a[i] = ivType(*ptr);
  else {
    int z;
    for (i=0, z=0; i<num; i++)
      if (z == numItemsUntilMinusOne) {
        a[i] = ivType(-1);
        z = 0;
      } else {
        a[i] = ivType(*ptr);
        ptr++;
        z++;
      }
  }

  field.finishEditing();
}


template<typename ivType, typename osgType, int shift>
void osgArray2ivMField_composite_template_worker(ivType *dest, osgType *src, int num, int numItemsUntilMinusOne = 0)
{
  for (int i=0; i<num; i++, src+=shift)
    dest[i] = ivType(src);
}


template<>
void osgArray2ivMField_composite_template_worker<SbColor, GLubyte, 4>(SbColor *dest, GLubyte *src, int num, int numItemsUntilMinusOne)
{
  for (int i=0; i<num; i++, src+=4)
    dest[i].setValue(src[0]/255.f, src[1]/255.f, src[2]/255.f);
}


template<>
void osgArray2ivMField_composite_template_worker<SbVec3f, float, 2>(SbVec3f *dest, float *src, int num, int numItemsUntilMinusOne)
{
  for (int i=0; i<num; i++, src+=2)
    dest[i].setValue(src[0], src[1], 0.f);
}


template<typename fieldClass, typename ivType, typename osgType, int shift>
void osgArray2ivMField_composite_template(const osg::Array *array, fieldClass &field, int startIndex = 0, int stopIndex = 0, int numItemsUntilMinusOne = 0)
{
  int num = array->getNumElements();
  if (startIndex!=0 || stopIndex!=0) {
    num = stopIndex - startIndex;
    assert(stopIndex >= startIndex);
    assert(startIndex >= 0 && stopIndex >= 0);
    assert(stopIndex <= int(array->getNumElements()));
  }
  assert(numItemsUntilMinusOne <= 0 && "Composite template must have numItemsUntilMinusOne set to 0.");
  field.setNum(num);
  ivType *a = field.startEditing();

  osgType *ptr = (osgType*)array->getDataPointer() + startIndex;
  osgArray2ivMField_composite_template_worker<ivType, osgType, shift>(a, ptr, num, 0);

  field.finishEditing();
}


template<typename fieldClass, typename ivType, typename osgType, int numComponents>
void osgArray2ivMField_pack_template(const osg::Array *array, fieldClass &field,
                                            osgType mul, osgType max, osgType min,
                                            int startIndex = 0, int stopIndex = 0, int numItemsUntilMinusOne = 0)
{
  int i,num = array->getNumElements();
  if (startIndex!=0 || stopIndex!=0) {
    num = stopIndex - startIndex;
    assert(stopIndex >= startIndex);
    assert(startIndex >= 0 && stopIndex >= 0);
    assert(stopIndex <= int(array->getNumElements()));
  }
  assert(numItemsUntilMinusOne <= 0 && "Pack template must have numItemsUntilMinusOne set to 0.");
  field.setNum(num);
  ivType *a = field.startEditing();

  osgType *ptr = (osgType*)array->getDataPointer() + startIndex;
  for (i=0; i<num; i++, ptr++) {
    a[i] = ivType(0);
    for (int j=0; j<numComponents; j++) {
      osgType tmp = ptr[j]*mul;
      if (tmp > max)  tmp = max;
      if (tmp < min)  tmp = min;
      a[i] |= ivType(tmp) << (((numComponents-1)*8)-(j*8));
    }
  }

  field.finishEditing();
}


template<typename fieldClass, typename fieldItemType>
bool ivApplicateIntType(const osg::Array *array, fieldClass &field, int startIndex, int stopIndex, int numItemsUntilMinusOne)
{
  if (field.isOfType(fieldClass::getClassTypeId()))
  {
    switch (array->getType())
    {
      case osg::Array::ByteArrayType:   osgArray2ivMField_template<fieldClass, fieldItemType, int8_t>
                                          (array, field, startIndex, stopIndex, numItemsUntilMinusOne); return true;
      case osg::Array::UByteArrayType:  osgArray2ivMField_template<fieldClass, fieldItemType, uint8_t>
                                          (array, field, startIndex, stopIndex, numItemsUntilMinusOne); return true;
      case osg::Array::ShortArrayType:  osgArray2ivMField_template<fieldClass, fieldItemType, int16_t>
                                          (array, field, startIndex, stopIndex, numItemsUntilMinusOne); return true;
      case osg::Array::UShortArrayType: osgArray2ivMField_template<fieldClass, fieldItemType, uint16_t>
                                          (array, field, startIndex, stopIndex, numItemsUntilMinusOne); return true;
      case osg::Array::IntArrayType:    osgArray2ivMField_template<fieldClass, fieldItemType, int32_t>
                                          (array, field, startIndex, stopIndex, numItemsUntilMinusOne); return true;
      case osg::Array::UIntArrayType:   osgArray2ivMField_template<fieldClass, fieldItemType, uint32_t>
                                          (array, field, startIndex, stopIndex, numItemsUntilMinusOne); return true;
      case osg::Array::FloatArrayType:  osgArray2ivMField_template<fieldClass, fieldItemType, float>
                                          (array, field, startIndex, stopIndex, numItemsUntilMinusOne); return true;
      case osg::Array::Vec4bArrayType:  // FIXME: should signed values be handled differently? Like -128..127?
      case osg::Array::Vec4ubArrayType: osgArray2ivMField_pack_template<fieldClass, fieldItemType, GLubyte, 4>
                                          (array, field, 1, 255, 0, startIndex, stopIndex, numItemsUntilMinusOne); return true;
      case osg::Array::Vec4ArrayType:   osgArray2ivMField_pack_template<fieldClass, fieldItemType, float, 4>
                                          (array, field, 255.f, 255.f, 0.f, startIndex, stopIndex, numItemsUntilMinusOne); return true;
      default: break;
    }
  }
  return false;
}


static void osgArray2ivMField(const osg::Array *array, SoMField &field, int startIndex = 0, int stopIndex = 0, int numItemsUntilMinusOne = 0)
{
  if (field.isOfType(SoMFFloat::getClassTypeId()))
  {
    switch (array->getType())
    {
      case osg::Array::FloatArrayType:  osgArray2ivMField_template<SoMFFloat, float, float>
                                          (array, (SoMFFloat&)field, startIndex, stopIndex, numItemsUntilMinusOne); return;
      default: break;
    }
  }
  else if (ivApplicateIntType<SoMFInt32,  int32_t>(array, (SoMFInt32&) field, startIndex, stopIndex, numItemsUntilMinusOne)) return; 
  else if (ivApplicateIntType<SoMFUInt32,uint32_t>(array, (SoMFUInt32&)field, startIndex, stopIndex, numItemsUntilMinusOne)) return;
  else if (ivApplicateIntType<SoMFShort,  int16_t>(array, (SoMFShort&) field, startIndex, stopIndex, numItemsUntilMinusOne)) return; 
  else if (ivApplicateIntType<SoMFUShort,uint16_t>(array, (SoMFUShort&)field, startIndex, stopIndex, numItemsUntilMinusOne)) return;
  else if (field.isOfType(SoMFVec2f::getClassTypeId()))
  {
    switch (array->getType())
    {
      case osg::Array::Vec2ArrayType:   osgArray2ivMField_composite_template<SoMFVec2f, SbVec2f, float, 2>
                                          (array, (SoMFVec2f&)field, startIndex, stopIndex, numItemsUntilMinusOne); return;
      default: break;
    }
  }
  else if (field.isOfType(SoMFVec3f::getClassTypeId()))
  {
    switch (array->getType())
    {
      case osg::Array::Vec3ArrayType:   osgArray2ivMField_composite_template<SoMFVec3f, SbVec3f, float, 3>
                                          (array, (SoMFVec3f&)field, startIndex, stopIndex, numItemsUntilMinusOne); return;
      case osg::Array::Vec2ArrayType:   osgArray2ivMField_composite_template<SoMFVec3f, SbVec3f, float, 2>
                                          (array, (SoMFVec3f&)field, startIndex, stopIndex, numItemsUntilMinusOne); return;
      default: break;
    }
  }
  else if (field.isOfType(SoMFVec4f::getClassTypeId()))
  {
    switch (array->getType()) {
      case osg::Array::Vec4ArrayType:   osgArray2ivMField_composite_template<SoMFVec4f, SbVec4f, float, 4>
                                          (array, (SoMFVec4f&)field, startIndex, stopIndex, numItemsUntilMinusOne); return;
      default: break;
    }
  }
  else if (field.isOfType(SoMFColor::getClassTypeId()))
  {
    switch (array->getType())
    {
      case osg::Array::Vec3ArrayType:   osgArray2ivMField_composite_template<SoMFColor, SbColor, float, 3>
                                          (array, (SoMFColor&)field, startIndex, stopIndex, numItemsUntilMinusOne); return;
      case osg::Array::Vec4ArrayType:   osgArray2ivMField_composite_template<SoMFColor, SbColor, float, 4>
                                          (array, (SoMFColor&)field, startIndex, stopIndex, numItemsUntilMinusOne); return;
      case osg::Array::Vec4ubArrayType: osgArray2ivMField_composite_template<SoMFColor, SbColor, GLubyte, 4>
                                          (array, (SoMFColor&)field, startIndex, stopIndex, numItemsUntilMinusOne); return;
      default: break;
    }
  };

  osg::notify(osg::WARN) << "IvWriter: No direct conversion for array. "
    << "The file may be broken." << std::endl;
}


template<typename variableType, typename indexType>
bool ivDeindex(variableType *dest, const variableType *src, const int srcNum,
                    const indexType *indices, const int numToProcess)
{
  for (int i=0; i<numToProcess; i++) {
    int index = indices[i];
    if (index<0 || index>=srcNum)  return false;
    dest[i] = src[index];
  }
  return true;
}


template<typename variableType>
bool ivDeindex(variableType *dest, const variableType *src, const int srcNum,
                    const osg::Array *indices, const int numToProcess)
{
  if (int(indices->getNumElements()) < numToProcess) {
    assert(0 && "Something is wrong: indices array is shorter than numToProcess.");
    return false;
  }

  switch (indices->getType()) {
  case osg::Array::ByteArrayType:
  case osg::Array::UByteArrayType:
      return ivDeindex<variableType, GLbyte>(dest, src, srcNum,
                                           (GLbyte*)indices->getDataPointer(), numToProcess);
      break;
  case osg::Array::ShortArrayType:
  case osg::Array::UShortArrayType:
      return ivDeindex<variableType, GLshort>(dest, src, srcNum,
                                            (GLshort*)indices->getDataPointer(), numToProcess);
      break;
  case osg::Array::IntArrayType:
  case osg::Array::UIntArrayType:
      return ivDeindex<variableType, GLint>(dest, src, srcNum,
                                          (GLint*)indices->getDataPointer(), numToProcess);
      break;
  default:
      assert(0 && "Index of strange type.");
      return false;
  }
}


template<typename variableType, typename fieldType>
bool ivProcessArray(const osg::Array *indices, const osg::Array *drawElemIndices,
                         fieldType *destField, const fieldType *srcField,
                         int startIndex, int numToProcess)
{
  bool ok = true;

  if (indices || drawElemIndices) {
    
    // "deindex" original data
    if (indices && !drawElemIndices)
      ok = ivDeindex<variableType>(destField->startEditing(),
                                 srcField->getValues(startIndex),
                                 srcField->getNum(), indices, numToProcess); else
    if (!indices && drawElemIndices)
      ok = ivDeindex<variableType>(destField->startEditing(),
                                 srcField->getValues(startIndex),
                                 srcField->getNum(), drawElemIndices, numToProcess);
    else {
      osg::notify(osg::WARN) << "IvWriter: NOT IMPLEMENTED" << std::endl;
      assert(0); // FIXME:
    }

    destField->finishEditing();
    if (!ok)
      osg::notify(osg::WARN) << "IvWriter: Can not deindex - bug in model: index out of range." << std::endl;

  } else {

    // copy required part of original data
    const variableType *src = srcField->getValues(startIndex);
    assert(startIndex+numToProcess <= srcField->getNum() && "Index out of bounds.");
    variableType *dest = destField->startEditing();
    for (int i=0; i<numToProcess; i++)
      *(dest++) = *(src++);
    destField->finishEditing();
  }

  return ok;
}


static void processIndices(const osg::Array *indices, const osg::Array *drawElemIndices,
                           SoMFInt32 &ivIndices,
                           int startIndex, int stopIndex, int numItemsUntilMinusOne)
{
  if (indices || drawElemIndices) {
    if (indices && !drawElemIndices)
      osgArray2ivMField(indices, ivIndices, startIndex, stopIndex, numItemsUntilMinusOne); else
    if (!indices && drawElemIndices)
      osgArray2ivMField(drawElemIndices, ivIndices, startIndex, stopIndex, numItemsUntilMinusOne);
    else {
      osg::notify(osg::WARN) << "IvWriter: NOT IMPLEMENTED" << std::endl;
      assert(0); // FIXME:
    }

  } else {
    int num = stopIndex-startIndex;
    if (numItemsUntilMinusOne != 0 && num >= 1)
      num += (num-1)/numItemsUntilMinusOne;
    ivIndices.setNum(num);
    int32_t *a = ivIndices.startEditing();
    if (numItemsUntilMinusOne <= 0)
      for (int i=0,j=startIndex; j<stopIndex; i++,j++)
        a[i] = j;
    else
      for (int i=0,j=startIndex,k=0; j<stopIndex; i++)
        if (k==numItemsUntilMinusOne) {
          a[i]=-1;
          k=0;
        } else {
          a[i] = j;
          j++;
          k++;
        }
    ivIndices.finishEditing();
  }
}


static void postProcessDrawArrayLengths(const osg::DrawArrayLengths *drawArrayLengths, SoMFInt32 *field)
{
  int origNum = field->getNum();
  int newNum = origNum + drawArrayLengths->size()-1;
  field->setNum(newNum);
  int32_t *a = field->startEditing();
  int32_t *src = a + origNum;
  int32_t *dst = a + newNum;
  for (osg::DrawArrayLengths::const_reverse_iterator primItr = drawArrayLengths->rbegin();
      primItr!=drawArrayLengths->rend()-1;
      ++primItr) {
    int c = *primItr;
    src -= c;
    dst -= c;
    memmove(dst, src, sizeof(int32_t)*c);
    dst--;
    *dst = -1;
  }
  field->finishEditing();
}


static void postProcessField(const SbIntList &runLengths, osg::PrimitiveSet::Mode primType,
                             SoMFInt32 *field, osg::Geometry::AttributeBinding binding)
{
  if (binding==osg::Geometry::BIND_OFF || binding==osg::Geometry::BIND_OVERALL ||
      binding==osg::Geometry::BIND_PER_PRIMITIVE_SET)
    return;

  // make copy of array
  const int32_t *fieldArray = field->getValues(0);
  int origNum = field->getNum();
  int32_t *tmpArray = new int32_t[origNum];
  memcpy(tmpArray, fieldArray, origNum*sizeof(int32_t));

  // compute new number of indices
  int newNum = origNum;
  const int l = runLengths.getLength();
  switch (binding) {
    case osg::Geometry::BIND_PER_VERTEX:
      for (int i=0; i<l; i++)
        newNum += (runLengths[i]-3)*3;
      break;
    case osg::Geometry::BIND_PER_PRIMITIVE:
      for (int i=0; i<l; i++)
        newNum += runLengths[i]-3;
      break;
    default:
      assert(0);
  }

  // process indices
  field->setNum(newNum);
  int32_t *src = tmpArray;
  int32_t *dst = field->startEditing();
  // int32_t *dst2 = dst;
  switch (binding) {
    case osg::Geometry::BIND_PER_VERTEX:
      for (int i=0; i<l; i++) {
        int c = runLengths[i];
        *(dst++) = *(src++);
        *(dst++) = *(src++);
        *(dst++) = *(src++);
        bool even = true;
        int32_t first = *(src-3);
        for (int j=3; j<c; j++) {
          *(dst++) = -1;
          if (primType==osg::PrimitiveSet::TRIANGLE_STRIP) {
            if (even) {
              *(dst++) = *(src-1);
              *(dst++) = *(src-2);
            } else {
              *(dst++) = *(src-2);
              *(dst++) = *(src-1);
            }
            even = !even;
          } else
          if (primType==osg::PrimitiveSet::TRIANGLE_FAN) {
            *(dst++) = first;
            *(dst++) = *(src-1);
          } // FIXME: are QUADS, QUAD_STRIP, and POLYGON requiring some handling here as well? PCJohn-2007-08-25
          else {
            *(dst++) = *(src-2);
            *(dst++) = *(src-1);
          }
          *(dst++) = *(src++);
        }
        src++; // skip -1
        if (i != l-1)
          *(dst++) = -1;
      }
      break;

    case osg::Geometry::BIND_PER_PRIMITIVE:
      for (int i=0; i<l; i++,src++) {
        int c = runLengths[i];
        *(dst++) = *(src);
        for (int j=3; j<c; j++)
          *(dst++) = *(src);
      }
      break;

    default:
      assert(0);
  }
  field->finishEditing();

  // free resources
  delete tmpArray;
}


static void postProcessTriangleSeparation(SoIndexedShape *shape, osg::PrimitiveSet::Mode primType,
                                          osg::Geometry::AttributeBinding normalBinding,
                                          osg::Geometry::AttributeBinding colorBinding)
{
  // compute runLengths
  SbIntList runLengths;
  const int32_t *a = shape->coordIndex.getValues(0);
  int origNum = shape->coordIndex.getNum();
  int l = 0;
  for (int i=0; i<origNum; i++,a++) {
    if (*a == -1) {
      runLengths.append(l);
      l = 0;
    } else
      l++;
  }
  if (l != 0) // append final l if field is not finished by -1
    runLengths.append(l);

  postProcessField(runLengths, primType, &shape->coordIndex,        osg::Geometry::BIND_PER_VERTEX);
  postProcessField(runLengths, primType, &shape->normalIndex,       normalBinding);
  postProcessField(runLengths, primType, &shape->materialIndex,     colorBinding);
  bool notUseTexCoords = shape->textureCoordIndex.getNum()==0 ||
                         (shape->textureCoordIndex.getNum()==1 && shape->textureCoordIndex[0] == -1);
  if (!notUseTexCoords)
    postProcessField(runLengths, primType, &shape->textureCoordIndex, osg::Geometry::BIND_PER_VERTEX);

}


static SoMaterialBinding* createMaterialBinding(const osg::Geometry *g, bool isMaterialIndexed)
{
  SoMaterialBinding *materialBinding = new SoMaterialBinding;
  switch (g->getColorBinding()) {
  case osg::Geometry::BIND_OFF: // OFF means use material from state set (if any) for whole geometry
  case osg::Geometry::BIND_OVERALL:
  case osg::Geometry::BIND_PER_PRIMITIVE_SET: materialBinding->value = SoMaterialBinding::OVERALL; break;
  case osg::Geometry::BIND_PER_PRIMITIVE:     materialBinding->value = (isMaterialIndexed) ? SoMaterialBinding::PER_PART_INDEXED   : SoMaterialBinding::PER_PART; break;
  case osg::Geometry::BIND_PER_VERTEX:        materialBinding->value = (isMaterialIndexed) ? SoMaterialBinding::PER_VERTEX_INDEXED : SoMaterialBinding::PER_VERTEX; break;
  default: assert(0);
  }
  return materialBinding;
}


static SoNormalBinding* createNormalBinding(const osg::Geometry *g, bool areNormalsIndexed)
{
  // Convert normal binding
  SoNormalBinding *normalBinding = new SoNormalBinding;
  switch (g->getNormalBinding()) {
  case osg::Geometry::BIND_OFF: // FIXME: what to do with BIND_OFF value?
  case osg::Geometry::BIND_OVERALL:
  case osg::Geometry::BIND_PER_PRIMITIVE_SET: normalBinding->value = SoNormalBinding::OVERALL; break;
  case osg::Geometry::BIND_PER_PRIMITIVE:     normalBinding->value = (areNormalsIndexed) ? SoNormalBinding::PER_PART_INDEXED   : SoNormalBinding::PER_PART; break;
  case osg::Geometry::BIND_PER_VERTEX:        normalBinding->value = (areNormalsIndexed) ? SoNormalBinding::PER_VERTEX_INDEXED : SoNormalBinding::PER_VERTEX; break;
  default: assert(0);
  }
  return normalBinding;
}


static SoTextureCoordinateBinding* createTexCoordBinding(SbBool useIndexing)
{
  SoTextureCoordinateBinding *b = new SoTextureCoordinateBinding;
  b->value.setValue(useIndexing ? SoTextureCoordinateBinding::PER_VERTEX_INDEXED :
                    SoTextureCoordinateBinding::PER_VERTEX);
  return b;
}


static SoTexture2::Model convertTexEnvMode(osg::TexEnv::Mode osgMode, bool useIvExtensions)
{
  switch (osgMode) {
  case GL_MODULATE: return SoTexture2::MODULATE;
  case GL_REPLACE:  return (SoTexture2::Model)(useIvExtensions ? GL_REPLACE : GL_MODULATE);
  case GL_BLEND:    return SoTexture2::BLEND;
  case GL_DECAL:    return SoTexture2::DECAL;
  default: assert(0); return SoTexture2::MODULATE;
  }
}



static SoTexture2::Wrap convertTextureWrap(osg::Texture::WrapMode osgWrap)
{
  // notes on support of CLAMP_TO_BORDER, CLAMP_TO_EDGE, and MIRRORED_REPEAT:
  // original SGI Inventor: no
  // Coin: no (until current version Coin 2.5.0b3)
  // TGS Inventor: introduced in TGS Inventor 5.0 (available in SoTexture class)
  
  // note: Coin (since 2.0) uses CLAMP_TO_EDGE for rendering if SoTexture2::CLAMP is specified.

  switch (osgWrap) {
    case osg::Texture::CLAMP:
    case osg::Texture::CLAMP_TO_BORDER:
    case osg::Texture::CLAMP_TO_EDGE: return SoTexture2::CLAMP;
    case osg::Texture::REPEAT:
    case osg::Texture::MIRROR: return SoTexture2::REPEAT;
    default: assert(0); return SoTexture2::REPEAT;
  }
}


static void setSoTransform(SoTransform *tr, const osg::Vec3 &translation, const osg::Quat &rotation,
                           const osg::Vec3 &scale = osg::Vec3(1.,1.,1.))
{
  tr->translation.setValue(translation.ptr());
  tr->rotation.setValue(rotation.x(), rotation.y(), rotation.z(), rotation.w());
  tr->scaleFactor.setValue(scale.ptr());
  //tr->scaleCenter.setValue(osg::Vec3f(node.getPivotPoint())); <- testing required on this line
}


static bool updateMode(bool &flag, const osg::StateAttribute::GLModeValue value)
{
  if (value & osg::StateAttribute::INHERIT)  return flag;
  else  return (flag = (value & osg::StateAttribute::ON));
}


ConvertToInventor::InventorState* ConvertToInventor::createInventorState(const osg::StateSet *ss)
{
  // Push on stack
  const InventorState *ivPrevState = &ivStack.top();
  ivStack.push(*ivPrevState);
  InventorState *ivState = &ivStack.top();

  // Inventor graph
  ivState->ivHead = new SoSeparator;
  ivPrevState->ivHead->addChild(ivState->ivHead);

  if (ss) {
    
    //
    // Lighting
    //
    
    // enable/disable lighting
    updateMode(ivState->osgLighting, ss->getMode(GL_LIGHTING));
    if (ivState->osgLighting != ivPrevState->osgLighting) {
      SoLightModel *lm = new SoLightModel;
      lm->model = (ivState->osgLighting) ? SoLightModel::PHONG : SoLightModel::BASE_COLOR;
      ivState->ivHead->addChild(lm);
    }

    // two-sided lighting
    const osg::LightModel *osgLM = dynamic_cast<const osg::LightModel*>(ss->getAttribute(osg::StateAttribute::LIGHTMODEL));
    if (osgLM)
      ivState->osgTwoSided = osgLM->getTwoSided();

    // front face
    const osg::FrontFace *osgFF = dynamic_cast<const osg::FrontFace*>(ss->getAttribute(osg::StateAttribute::FRONTFACE));
    if (osgFF)
      ivState->osgFrontFace = osgFF->getMode();

    // face culling
    updateMode(ivState->osgCullFaceEnabled, ss->getMode(GL_CULL_FACE));
    const osg::CullFace *osgCF = dynamic_cast<const osg::CullFace*>(ss->getAttribute(osg::StateAttribute::CULLFACE));
    if (osgCF)
      ivState->osgCullFace = osgCF->getMode();

    // detect state change
    if (ivState->osgTwoSided != ivPrevState->osgTwoSided ||
        ivState->osgFrontFace != ivPrevState->osgFrontFace ||
        ivState->osgCullFaceEnabled != ivPrevState->osgCullFaceEnabled ||
        ivState->osgCullFace != ivPrevState->osgCullFace) {

      // new SoShapeHints
      SoShapeHints *sh = new SoShapeHints;
      if (ivState->osgTwoSided) {
        // warn if face culling is on
        if (ivState->osgCullFaceEnabled)
          osg::notify(osg::WARN) << "IvWriter: Using face culling and two-sided lighting together! "
                                    "Ignoring face culling." << std::endl;

        // set two-sided lighting and backface culling off
        sh->vertexOrdering = ivState->osgFrontFace==osg::FrontFace::COUNTER_CLOCKWISE ? 
                             SoShapeHints::COUNTERCLOCKWISE : SoShapeHints::CLOCKWISE;
        sh->shapeType = SoShapeHints::UNKNOWN_SHAPE_TYPE;
      }
      else {
        // set one-sided lighting and backface optionally
        if (ivState->osgCullFaceEnabled) {

          // determine vertex ordering
          bool ccw = ivState->osgFrontFace==osg::FrontFace::COUNTER_CLOCKWISE;
          if (ivState->osgCullFace != osg::CullFace::BACK)  ccw = !ccw;

          if (ccw)
            // Warn if culling the lit faces while rendering unlit faces.
            // Inventor does not support this setup and it lits the unculled faces only.
            osg::notify(osg::WARN) << "IvWriter: Culling was set in a way that one-sided lighting will lit the culled sides of faces. "
                                      "Using lighting on correct faces." << std::endl; 

          // face culling on
          sh->vertexOrdering = ccw ? SoShapeHints::COUNTERCLOCKWISE : SoShapeHints::CLOCKWISE;
          sh->shapeType = SoShapeHints::SOLID;
        }
        else
          // no face culling
          sh->shapeType = SoShapeHints::UNKNOWN_SHAPE_TYPE;
      }
      ivState->ivHead->addChild(sh);
    }

    //
    // Texturing
    //
    // FIXME: handle 1D and 3D textures

    // get OSG state
    ivState->osgTexture = dynamic_cast<const osg::Texture*>(ss->getTextureAttribute(0, osg::StateAttribute::TEXTURE));
    ivState->osgTexEnv = dynamic_cast<const osg::TexEnv*>(ss->getTextureAttribute(0, osg::StateAttribute::TEXENV));
    updateMode(ivState->osgTexture2Enabled, ss->getTextureMode(0, GL_TEXTURE_2D));

    // detect state changes
    if (ivState->osgTexture2Enabled != ivPrevState->osgTexture2Enabled ||
        ivState->osgTexture != ivPrevState->osgTexture ||
        ivState->osgTexEnv != ivPrevState->osgTexEnv) {

      if (!ivState->osgTexture2Enabled ||
          ivState->osgTexture==NULL || ivState->osgTexture->getImage(0)==NULL)
        
        // empty texture disables texturing
        ivState->ivTexture = new SoTexture2;
      
      else {
        
        // reuse texture if possible
        ivState->ivTexture = ivTexturesMap[ivState->osgTexture][ivState->osgTexEnv];

        // if nothing for reusing, create new
        if (!ivState->ivTexture) {

          // create texture
          ivState->ivTexture = new SoTexture2;
          ivTexturesMap[ivState->osgTexture][ivState->osgTexEnv] = ivState->ivTexture;

          // texture file name
          const std::string &textureName = ivState->osgTexture->getImage(0)->getFileName();
          ivState->ivTexture->filename.setValue(textureName.c_str()); // FIXME: handle inlined texture data in the files
          
          // wrap
          ivState->ivTexture->wrapS.setValue(convertTextureWrap(
                                             ivState->osgTexture->getWrap(osg::Texture::WRAP_S)));
          ivState->ivTexture->wrapT.setValue(convertTextureWrap(
                                             ivState->osgTexture->getWrap(osg::Texture::WRAP_T)));

          // texture environment
          if (ivState->osgTexEnv) {
            ivState->ivTexture->model.setValue(convertTexEnvMode(
                                               ivState->osgTexEnv->getMode(), useIvExtensions));
            osg::Vec4 color = ivState->osgTexEnv->getColor();
            ivState->ivTexture->blendColor.setValue(color.r(), color.g(), color.b());
          }

          // notes on support of borderColor and borderWidth:
          // original SGI Inventor: no
          // Coin: no (until current version 2.5.0b3)
          // TGS Inventor: introduced in version 5.0 (as SoTexture::borderColor)

          // FIXME: implement support for texture filtering
        }
      }
    }

    // Texture coordinate generation
    updateMode(ivState->osgTexGenS, ss->getTextureMode(0, GL_TEXTURE_GEN_S));
    updateMode(ivState->osgTexGenT, ss->getTextureMode(0, GL_TEXTURE_GEN_T));
    ivState->osgTexGen = dynamic_cast<const osg::TexGen*>(ss->getTextureAttribute(0, osg::StateAttribute::TEXGEN));

    // Material parameters
    const osg::Material *osgMaterial = dynamic_cast<const osg::Material*>(ss->getAttribute(osg::StateAttribute::MATERIAL));
    if (osgMaterial)
      ivState->osgMaterial = osgMaterial;

    if (ivState->osgMaterial != ivPrevState->osgMaterial) {

      ivState->ivMaterial = new SoMaterial;
      assert(ivState->osgMaterial);

      // Warn if using two side materials
      // FIXME: The geometry can be probably doubled, or some estimation can be made
      // whether only front or back faces are used.
      if (ivState->osgMaterial->getAmbientFrontAndBack() == false ||
          ivState->osgMaterial->getDiffuseFrontAndBack() == false ||
          ivState->osgMaterial->getSpecularFrontAndBack() == false ||
          ivState->osgMaterial->getEmissionFrontAndBack() == false ||
          ivState->osgMaterial->getShininessFrontAndBack() == false)
        osg::notify(osg::WARN) << "IvWriter: Model contains different materials for front and "
                                  "back faces. This is not handled properly. Using front material only." << std::endl;

    
      // Convert colors
      // OSG represents colors by: Vec3, Vec4,Vec4ub
      // Inventor by: uint32 (RGBA, by SoPackedColor), SbColor (Vec3f, by SoMaterial and SoBaseColor)
      // note: Inventor can use DIFFUSE component inside a shape only. Material is set for whole shape.
      // Although SoMaterial is used only, SoPackedColor may bring some possibilities on per-vertex
      // alpha and SoBaseColor may be useful on pre-lit scene.
      if (ivState->osgMaterial->getColorMode() != osg::Material::DIFFUSE &&
          ivState->osgMaterial->getColorMode() != osg::Material::OFF) {

      if (ivState->osgMaterial->getColorMode() == osg::Material::AMBIENT_AND_DIFFUSE)
        osg::notify(osg::WARN) << "IvWriter: The model is using AMBIENT_AND_DIFFUSE material "
                                  "mode while Inventor supports DIFFUSE mode only. "
                                  "The model colors may not much exactly." << std::endl;
      else
        osg::notify(osg::WARN) << "IvWriter: The model is not using DIFFUSE material mode and "
                                  "Inventor supports DIFFUSE mode only. "
                                  "The model colors may not be correct." << std::endl;
      }

      // Convert material components
      // FIXME: Transparency can be specified for each component in OSG
      // and just globally in Inventor.
      // Solutions? It can be averaged or just diffuse can be used.
      ((SoMaterial*)ivState->ivMaterial)->ambientColor.setValue(osgMaterial->getAmbient(
        osgMaterial->getAmbientFrontAndBack()   ? osg::Material::FRONT_AND_BACK : osg::Material::FRONT).ptr());
      ((SoMaterial*)ivState->ivMaterial)->diffuseColor.setValue(osgMaterial->getDiffuse(
        osgMaterial->getDiffuseFrontAndBack()   ? osg::Material::FRONT_AND_BACK : osg::Material::FRONT).ptr());
      ((SoMaterial*)ivState->ivMaterial)->specularColor.setValue(osgMaterial->getSpecular(
        osgMaterial->getSpecularFrontAndBack()  ? osg::Material::FRONT_AND_BACK : osg::Material::FRONT).ptr());
      ((SoMaterial*)ivState->ivMaterial)->emissiveColor.setValue(osgMaterial->getEmission(
        osgMaterial->getEmissionFrontAndBack()  ? osg::Material::FRONT_AND_BACK : osg::Material::FRONT).ptr());
      ((SoMaterial*)ivState->ivMaterial)->shininess.setValue(osgMaterial->getShininess(
        osgMaterial->getShininessFrontAndBack() ? osg::Material::FRONT_AND_BACK : osg::Material::FRONT));
      ((SoMaterial*)ivState->ivMaterial)->transparency.setValue(1.f - osgMaterial->getDiffuse(
        osgMaterial->getDiffuseFrontAndBack()   ? osg::Material::FRONT_AND_BACK : osg::Material::FRONT).a());
    }

    // note on "headlight":
    // OSG is using HEADLIGHT or SKY_LIGHT. In both cases it sets these defaults:
    // osg::LightModel::ambientIntensity(0.1, 0.1, 0.1, 1.0);
    // osg::Light::num(0)
    // osg::Light::ambient(0,0,0,1)
    // osg::Light::diffuse(0.8,0.8,0.8, 1)
    // osg::Light::specular(1,1,1,1)
    //
    // Inventor uses different settings:
    // SoEnvironment::ambientIntensity(0.2)
    // SoEnvironment::ambientColor(1,1,1)
    // SoDirectionalLight::intensity(1)
    // SoDirectionalLight::color(1,1,1)
    //
    // note on Inventor light handling:
    // ambient is set to 0,0,0,1
    // diffuse to color*intensity
    // specular to color*intensity


  #ifdef __COIN__
    //
    // Handle transparency
    //
    // OSG supports GL_BLEND and GL_ALPHA_TEST
    // Related classes: BlendFunc, BlendEquation, BlendColor, AlphaFunc
    //
    // Inventor is more difficult and not so robust. According to Inventor 2.1 standard,
    // just SoMaterial::transparency, SoTexture2 with alpha channel carry transparency information
    // that is controlled by SoGLRenderAction::transparency type that is set to SCREEN_DOOR by default
    // (dither pattern). So, if the user does not select better transparency type, there is no
    // possiblity to control transparency type from file format.
    //
    // However, SoTransparencyType node was introduced to overcome this historical limitation
    // because transparency was performance expensive long time ago.
    // Support by different Inventor branches:
    //
    // SGI Inventor: no
    // Coin: since 2.0
    // TGS Inventor: since 5.0
    //
    // Alpha test was introduced in TGS 4.0, but as SoGLRenderArea::setAlphaTest. So, no direct
    // control in the file format.
    //
    // Conclusion:
    // - Alpha test is not supported and all pixels will be drawn
    // - Blending - current strategy is following:
    //     ADD x BLEND - ADD is used if destination-blending-factor is GL_ONE
    //     DELAYED rendering is used if transparency bin is used by OSG
    //     SORTED_OBJECT is used if ...
    //

    updateMode(ivState->osgBlendEnabled, ss->getMode(GL_BLEND));
    ivState->osgBlendFunc = dynamic_cast<const osg::BlendFunc*>(ss->getAttribute(osg::StateAttribute::BLENDFUNC));

    if (useIvExtensions)
      if (ivState->osgBlendEnabled != ivPrevState->osgBlendEnabled ||
          ivState->osgBlendFunc != ivPrevState->osgBlendFunc ||
          (ivState->osgBlendFunc && ivPrevState->osgBlendFunc &&
          ivState->osgBlendFunc->getDestinationRGB() != ivPrevState->osgBlendFunc->getDestinationRGB())) {

        const SoTransparencyType::Type transparencyTable[8] = {
          SoTransparencyType::BLEND,         SoTransparencyType::ADD,
          SoTransparencyType::DELAYED_BLEND, SoTransparencyType::DELAYED_ADD,
          // FIXME: add sorted modes and test previous four
        };

        int index = 0;
        if (!ivState->osgBlendFunc)  index |= 0;
        else  index = (ivState->osgBlendFunc->getDestinationRGB() == osg::BlendFunc::ONE) ? 1 : 0;
        index |= (ss->getRenderingHint() == osg::StateSet::TRANSPARENT_BIN) ? 2 : 0;

        SoTransparencyType *ivTransparencyType = new SoTransparencyType;
        ivTransparencyType->value = transparencyTable[index];
        ivState->ivHead->addChild(ivTransparencyType);
      }
  #endif

  }

  // ref Inventor nodes that are required when processing Drawables
  if (ivState->ivTexture)
    ivState->ivTexture->ref();
  if (ivState->ivMaterial)
    ivState->ivMaterial->ref();

  return ivState;
}


void ConvertToInventor::popInventorState()
{
  InventorState *ivState = &ivStack.top();

  // unref Inventor nodes
  if (ivState->ivTexture)
    ivState->ivTexture->unref();
  if (ivState->ivMaterial)
    ivState->ivMaterial->unref();

  ivStack.pop();
}


static bool processPrimitiveSet(const osg::Geometry *g, const osg::PrimitiveSet *pset,
                                osg::UIntArray *drawElemIndices, bool needSeparateTriangles,
                                int elementsCount, int primSize, const int startIndex, int stopIndex,
                                int &normalIndex, int &colorIndex,
                                SoNode *ivCoords, SoNormal *ivNormals, SoNode *ivMaterial,
                                SoNode *ivTexCoords, SoNode *ivTexture, SoShape *shape,
                                SoSeparator *&indexedRoot, SoSeparator *&nonIndexedRoot)
{
  bool ok = true;
  const osg::DrawArrayLengths *drawArrayLengths =
    (elementsCount == -1) ? dynamic_cast<const osg::DrawArrayLengths*>(pset) : NULL;

  int drawArrayLengthsElems = 0;

  if (drawArrayLengths) {

    // compute elementsCount
    elementsCount = 0;
    drawArrayLengthsElems = 0;
    for (osg::DrawArrayLengths::const_iterator primItr=drawArrayLengths->begin();
        primItr!=drawArrayLengths->end();
        ++primItr, drawArrayLengthsElems++)
      elementsCount += *primItr;

    // update stopIndex
    stopIndex = startIndex + elementsCount;
  }

  // NonIndexed data for nonIndexed shapes
  SoNode *nonIndexedCoords = NULL;
  SoNode *nonIndexedTexCoords = NULL;
  SoNormal *nonIndexedNormals = NULL;
  SoNode *nonIndexedMaterial = NULL;

  // Normal indexing
  int normalStart = g->getNormalBinding() == osg::Geometry::BIND_PER_VERTEX ? startIndex : normalIndex;
  int numNormalsUsed = 0;
  switch (g->getNormalBinding()) {
  case osg::Geometry::BIND_OFF: // FIXME: what is meaning of OFF value?
  case osg::Geometry::BIND_OVERALL:           numNormalsUsed = 0; break;
  case osg::Geometry::BIND_PER_PRIMITIVE_SET: numNormalsUsed = 1; break;
  case osg::Geometry::BIND_PER_PRIMITIVE:     numNormalsUsed = primSize!=0 ? (stopIndex-startIndex)/primSize :
                                                (drawArrayLengths ? drawArrayLengths->size() : 1); break;
  case osg::Geometry::BIND_PER_VERTEX:        numNormalsUsed = stopIndex-startIndex; break;
  }
  normalIndex += numNormalsUsed;

  // Color indexing
  int colorStart = g->getColorBinding() == osg::Geometry::BIND_PER_VERTEX ? startIndex : colorIndex;
  int numColorsUsed = 0;
  switch (g->getColorBinding()) {
  case osg::Geometry::BIND_OFF:
  case osg::Geometry::BIND_OVERALL:           numColorsUsed = 0; break;
  case osg::Geometry::BIND_PER_PRIMITIVE_SET: numColorsUsed = 1; break;
  case osg::Geometry::BIND_PER_PRIMITIVE:     numColorsUsed = primSize!=0 ? (stopIndex-startIndex)/primSize :
                                                (drawArrayLengths ? drawArrayLengths->size() : 1); break;
  case osg::Geometry::BIND_PER_VERTEX:        numColorsUsed = stopIndex-startIndex; break;
  }
  colorIndex += numColorsUsed;

  if (shape->isOfType(SoIndexedShape::getClassTypeId())) {

    // Convert to SoIndexedShape
    processIndices(g->getVertexIndices(), drawElemIndices, ((SoIndexedShape*)shape)->coordIndex,
                   startIndex, stopIndex, primSize);

    if (ivNormals)
      processIndices(g->getNormalIndices(), drawElemIndices, ((SoIndexedShape*)shape)->normalIndex,
                     normalStart, normalStart+(numNormalsUsed==0 ? 1 : numNormalsUsed),
                     g->getNormalBinding()==osg::Geometry::BIND_PER_VERTEX ? primSize : 0);

    if (ivMaterial)
      processIndices(g->getColorIndices(), drawElemIndices, ((SoIndexedShape*)shape)->materialIndex,
                     colorStart, colorStart+(numColorsUsed==0 ? 1 : numColorsUsed),
                     g->getColorBinding()==osg::Geometry::BIND_PER_VERTEX ? primSize : 0);

    if (ivTexCoords && !ivTexCoords->isOfType(SoTextureCoordinateFunction::getClassTypeId()))
      processIndices(g->getTexCoordIndices(0), drawElemIndices, ((SoIndexedShape*)shape)->textureCoordIndex,
                     startIndex, stopIndex, primSize);

    // Post-processing for DrawArrayLengths
    if (drawArrayLengths && primSize==0 && drawArrayLengths->size()>=2) {
      
      postProcessDrawArrayLengths(drawArrayLengths, &((SoIndexedShape*)shape)->coordIndex);
      
      if (ivNormals && g->getNormalBinding()==osg::Geometry::BIND_PER_VERTEX)
        postProcessDrawArrayLengths(drawArrayLengths, &((SoIndexedShape*)shape)->normalIndex);
      
      if (ivMaterial && g->getColorBinding()==osg::Geometry::BIND_PER_VERTEX)
        postProcessDrawArrayLengths(drawArrayLengths, &((SoIndexedShape*)shape)->materialIndex);
      
      if (ivTexCoords && !ivTexCoords->isOfType(SoTextureCoordinateFunction::getClassTypeId()))
        postProcessDrawArrayLengths(drawArrayLengths, &((SoIndexedShape*)shape)->textureCoordIndex);
    }

    if (needSeparateTriangles)
      postProcessTriangleSeparation((SoIndexedShape*)shape, (osg::PrimitiveSet::Mode)pset->getMode(),
                                    g->getNormalBinding(), g->getColorBinding());

  } else {

    // Convert to SoNonIndexedShape

    assert(shape->isOfType(SoNonIndexedShape::getClassTypeId()) && "Shape must be non-indexed.");

    int i,n = stopIndex-startIndex;

    // create alternate coordinates
    if (ivCoords->isOfType(SoCoordinate4::getClassTypeId()))
    {
      nonIndexedCoords = new SoCoordinate4;
      if (ok) {
        ((SoCoordinate4*)nonIndexedCoords)->point.setNum(n);
        ok = ivProcessArray<SbVec4f,SoMFVec4f>(g->getVertexIndices(),
                                             drawElemIndices,
                                             &((SoCoordinate4*)nonIndexedCoords)->point,
                                             &((SoCoordinate4*)ivCoords)->point,
                                             startIndex, n);
      }
    } else {
      nonIndexedCoords = new SoCoordinate3;
      if (ok) {
        ((SoCoordinate3*)nonIndexedCoords)->point.setNum(n);
        ok = ivProcessArray<SbVec3f,SoMFVec3f>(g->getVertexIndices(),
                                             drawElemIndices,
                                             &((SoCoordinate3*)nonIndexedCoords)->point,
                                             &((SoCoordinate3*)ivCoords)->point,
                                             startIndex, n);
      }
    }

    // create alternate texture coordinates
    if (ivTexCoords)
    {
      if (ivTexCoords->isOfType(SoTextureCoordinate2::getClassTypeId()))
      {
        nonIndexedTexCoords = new SoTextureCoordinate2;
        if (ok)
        {
          ((SoTextureCoordinate2*)nonIndexedTexCoords)->point.setNum(n);
          ok = ivProcessArray<SbVec2f,SoMFVec2f>(g->getTexCoordIndices(0),
                                               drawElemIndices,
                                               &((SoTextureCoordinate2*)nonIndexedTexCoords)->point,
                                               &((SoTextureCoordinate2*)ivTexCoords)->point,
                                               startIndex, n);
        }
      } else
#ifdef __COIN__
      if (ivTexCoords->isOfType(SoTextureCoordinate3::getClassTypeId()))
      {
        nonIndexedTexCoords = new SoTextureCoordinate3;
        if (ok)
        {
          ((SoTextureCoordinate3*)nonIndexedTexCoords)->point.setNum(n);
          ok = ivProcessArray<SbVec3f,SoMFVec3f>(g->getTexCoordIndices(0),
                                               drawElemIndices,
                                               &((SoTextureCoordinate3*)nonIndexedTexCoords)->point,
                                               &((SoTextureCoordinate3*)ivCoords)->point,
                                               startIndex, n);
        }
      } 
      else
#endif  // __COIN__
        nonIndexedTexCoords = ivTexCoords;
    }
    
    // create alternate normals
    if (ivNormals) {
      nonIndexedNormals = new SoNormal;
      if (ok) {
        nonIndexedNormals->vector.setNum(numNormalsUsed==0 ? 1 : numNormalsUsed);
        ok = ivProcessArray<SbVec3f,SoMFVec3f>(g->getNormalIndices(),
                                             g->getNormalBinding()==osg::Geometry::BIND_PER_VERTEX ? drawElemIndices : NULL,
                                             &nonIndexedNormals->vector, &ivNormals->vector,
                                             normalStart, numNormalsUsed==0 ? 1 : numNormalsUsed);
      }
    }

    // create alternate material
    if (ivMaterial) {
      SoMFColor *dstColorField;
      if (ivMaterial->isOfType(SoMaterial::getClassTypeId())) {
        nonIndexedMaterial = new SoMaterial;
        dstColorField = &((SoMaterial*)nonIndexedMaterial)->diffuseColor;
      } else {
        nonIndexedMaterial = new SoBaseColor;
        dstColorField = &((SoBaseColor*)nonIndexedMaterial)->rgb;
      }
      if (ok) {
        // FIXME: diffuse only?
        SoMFColor *srcColorField = (ivMaterial->isOfType(SoMaterial::getClassTypeId())) ?
                                   &((SoMaterial*)ivMaterial)->diffuseColor :
                                   &((SoBaseColor*)ivMaterial)->rgb;
        dstColorField->setNum(numColorsUsed==0 ? 1 : numColorsUsed);
        ok = ivProcessArray<SbColor,SoMFColor>(g->getColorIndices(),
                                            g->getColorBinding()==osg::Geometry::BIND_PER_VERTEX ? drawElemIndices : NULL,
                                            dstColorField, srcColorField,
                                            colorStart, numColorsUsed==0 ? 1 : numColorsUsed);
      }
    }

    if (shape->isOfType(SoPointSet::getClassTypeId()))
      ((SoPointSet*)shape)->numPoints.setValue(elementsCount); else
    
    if (shape->isOfType(SoLineSet::getClassTypeId())) {
      switch (pset->getMode()) {
      case GL_LINES:
          assert(elementsCount % 2 == 0 && "elementsCount is not multiple of 2.");
          n = elementsCount/2;
          ((SoLineSet*)shape)->numVertices.setNum(n);
          for (i=0; i<n; i++)
            ((SoLineSet*)shape)->numVertices.set1Value(i, 2);
          break;
      case GL_LINE_STRIP:
          if (drawArrayLengths) {
            ((SoLineSet*)shape)->numVertices.setNum(drawArrayLengthsElems);
            int i=0;
            for (osg::DrawArrayLengths::const_iterator primItr=drawArrayLengths->begin();
                primItr!=drawArrayLengths->end();
                ++primItr, i++)
              ((SoLineSet*)shape)->numVertices.set1Value(i, *primItr);
          } else {
            ((SoLineSet*)shape)->numVertices.setNum(1);
            ((SoLineSet*)shape)->numVertices.set1Value(0, elementsCount);
          }
          break;
      default:
          osg::notify(osg::WARN) << "IvWriter: NOT IMPLEMENTED" << std::endl;
          assert(0);
      }
    } else

    if (shape->isOfType(SoTriangleStripSet::getClassTypeId())) {
      switch (pset->getMode()) {
      case GL_TRIANGLES:
          n = elementsCount/3;
          assert(n*3 == elementsCount && "elementsCount is not multiple of 3.");
          ((SoTriangleStripSet*)shape)->numVertices.setNum(n);
          for (i=0; i<n; i++)
            ((SoTriangleStripSet*)shape)->numVertices.set1Value(i, 3);
          break;
      case GL_TRIANGLE_STRIP:
          if (drawArrayLengths) {
            ((SoTriangleStripSet*)shape)->numVertices.setNum(drawArrayLengthsElems);
            int i=0;
            for (osg::DrawArrayLengths::const_iterator primItr=drawArrayLengths->begin();
                primItr!=drawArrayLengths->end();
                ++primItr, i++)
              ((SoTriangleStripSet*)shape)->numVertices.set1Value(i, *primItr);
          } else {
            ((SoTriangleStripSet*)shape)->numVertices.setNum(1);
            ((SoTriangleStripSet*)shape)->numVertices.set1Value(0, elementsCount);
          }
          break;
      case GL_TRIANGLE_FAN:
          osg::notify(osg::WARN) << "IvWriter: GL_TRIANGLE_FAN NOT IMPLEMENTED" << std::endl;
          ((SoTriangleStripSet*)shape)->numVertices.setNum(1);
          ((SoTriangleStripSet*)shape)->numVertices.set1Value(0, elementsCount);
          break;
      case GL_QUAD_STRIP:
          assert(elementsCount % 2 == 0 && "elementsCount is not multiple of 2.");
          if (drawArrayLengths) {
            ((SoTriangleStripSet*)shape)->numVertices.setNum(drawArrayLengthsElems);
            int i=0;
            for (osg::DrawArrayLengths::const_iterator primItr=drawArrayLengths->begin();
                primItr!=drawArrayLengths->end();
                ++primItr, i++)
              ((SoTriangleStripSet*)shape)->numVertices.set1Value(i, *primItr);
          } else {
            ((SoTriangleStripSet*)shape)->numVertices.setNum(1);
            ((SoTriangleStripSet*)shape)->numVertices.set1Value(0, elementsCount);
          }
          break;
      default:
          osg::notify(osg::WARN) << "IvWriter: NOT IMPLEMENTED" << std::endl;
          assert(0);
      }
    } else

    if (shape->isOfType(SoFaceSet::getClassTypeId())) {
      switch (pset->getMode()) {
      case GL_QUADS:
          n = elementsCount/4;
          assert(n*4 == elementsCount && "elementsCount is not multiple of 4.");
          ((SoFaceSet*)shape)->numVertices.setNum(n);
          for (i=0; i<n; i++)
            ((SoFaceSet*)shape)->numVertices.set1Value(i, 4);
          break;
      case GL_POLYGON:
          if (drawArrayLengths) {
            ((SoFaceSet*)shape)->numVertices.setNum(drawArrayLengthsElems);
            int i=0;
            for (osg::DrawArrayLengths::const_iterator primItr=drawArrayLengths->begin();
                primItr!=drawArrayLengths->end();
                ++primItr, i++)
              ((SoFaceSet*)shape)->numVertices.set1Value(i, *primItr);
          } else {
            ((SoFaceSet*)shape)->numVertices.setNum(1);
            ((SoFaceSet*)shape)->numVertices.set1Value(0, elementsCount);
          }
          break;
      default:
          osg::notify(osg::WARN) << "IvWriter: NOT IMPLEMENTED" << std::endl;
          assert(0);
      }
    } else {
      osg::notify(osg::WARN) << "IvWriter: NOT IMPLEMENTED" << std::endl;
      assert(0 && "Unknown non-indexed shape type.");
    }
  }

  // Construct graph

  // Each osg::Drawable will have its own SoSeparator (render caching, culling, etc.)
  SoSeparator *sep = new SoSeparator;
  if (nonIndexedCoords) {
    assert(shape->isOfType(SoNonIndexedShape::getClassTypeId()) && "Not nonIndexed shape.");

    if (!ok) {

      // handle errors
      nonIndexedCoords->ref();
      nonIndexedCoords->unref();
      if (nonIndexedTexCoords) { nonIndexedTexCoords->ref(); nonIndexedTexCoords->unref(); }
      if (nonIndexedNormals) { nonIndexedNormals->ref(); nonIndexedNormals->unref(); }
      nonIndexedMaterial->ref();
      nonIndexedMaterial->unref();
      shape->ref();
      shape->unref();
      sep->ref();
      sep->unref();
    } else {

      // make scene graph
      if (nonIndexedRoot == NULL) {
        nonIndexedRoot = new SoSeparator;
        if (ivTexture)  nonIndexedRoot->addChild(ivTexture);
        if (ivTexture)  nonIndexedRoot->addChild(createTexCoordBinding(FALSE));
        nonIndexedRoot->addChild(createMaterialBinding(g, FALSE));
        if (ivNormals)  nonIndexedRoot->addChild(createNormalBinding(g, FALSE));
      }
      if (nonIndexedMaterial)  sep->addChild(nonIndexedMaterial);
      sep->addChild(nonIndexedCoords);
      if (nonIndexedNormals)  sep->addChild(nonIndexedNormals);
      if (nonIndexedTexCoords)  sep->addChild(nonIndexedTexCoords);
      sep->addChild(shape);
      nonIndexedRoot->addChild(sep);
    }
  } else {
    assert(shape->isOfType(SoIndexedShape::getClassTypeId()) && "Not indexed shape.");
    assert(nonIndexedCoords==NULL && nonIndexedNormals==NULL && nonIndexedMaterial==NULL);
    if (indexedRoot == NULL) {
      indexedRoot = new SoSeparator;
      if (ivTexture)  indexedRoot->addChild(ivTexture);
      if (ivTexture)  indexedRoot->addChild(createTexCoordBinding(TRUE));
      if (ivMaterial)  indexedRoot->addChild(ivMaterial);
      indexedRoot->addChild(createMaterialBinding(g, TRUE));
      indexedRoot->addChild(ivCoords);
      if (ivNormals)  indexedRoot->addChild(ivNormals);
      if (ivNormals)  indexedRoot->addChild(createNormalBinding(g, TRUE));
      if (ivTexCoords)  indexedRoot->addChild(ivTexCoords);
    }
    sep->addChild(shape);
    indexedRoot->addChild(sep);
  }

  return ok;
}


void ConvertToInventor::processGeometry(const osg::Geometry *g, InventorState *ivState)
{
  int normalIndex = 0;
  int colorIndex = 0;

  // Inventor scene graph roots
  SoSeparator *indexedRoot = NULL;
  SoSeparator *nonIndexedRoot = NULL;

  // Active material
  SoMaterial *ivStateMaterial = ivState->ivMaterial;
  SoNode *ivMaterial = NULL;
  if (ivState->osgLighting || vrml1Conversion)
    // SoMaterial
    if (g->getColorArray())
      if (ivStateMaterial)  ivMaterial = ivStateMaterial->copy();
      else  ivMaterial = new SoMaterial; // FIXME: check default values of SoMaterial and OSG lighting
    else
      if (ivStateMaterial)  ivMaterial = ivStateMaterial;
      else  ivMaterial = NULL;
  else
    // SoBaseColor
    if (g->getColorArray())
      if (ivStateMaterial) {
        ivMaterial = new SoBaseColor;
        ((SoBaseColor*)ivMaterial)->rgb.setValue(ivStateMaterial->diffuseColor[0]); // copy first value
      } else
        ivMaterial = new SoBaseColor; // FIXME: check default values of SoBaseColor and OSG pre-lit scene
    else
      if (ivStateMaterial) {
        ivMaterial = new SoBaseColor;
        ((SoBaseColor*)ivMaterial)->rgb.setValue(ivStateMaterial->diffuseColor[0]); // copy first value
      } else
        ivMaterial = NULL;

  // Convert color array into the SoMaterial
  if (g->getColorArray()) {
    assert(ivMaterial);

    // Choose correct color field
    SoMFColor *colorField;
    if (ivMaterial->isOfType(SoMaterial::getClassTypeId())) {
      if (vrml1Conversion && ivState->osgLighting==false) {

        // special case of pre-lit VRML1 scene
        ((SoMaterial*)ivMaterial)->ambientColor.setValue(0.f,0.f,0.f);
        ((SoMaterial*)ivMaterial)->diffuseColor.setValue(0.f,0.f,0.f);
        ((SoMaterial*)ivMaterial)->specularColor.setValue(0.f,0.f,0.f);
        colorField = &((SoMaterial*)ivMaterial)->emissiveColor;
      } else
        // regular diffuse color
        colorField = &((SoMaterial*)ivMaterial)->diffuseColor;
    } else
      // Using of SoBaseColor
      colorField = &((SoBaseColor*)ivMaterial)->rgb;


    // Color array with material
    if (ivState->osgMaterial == NULL ||
        ivState->osgMaterial->getColorMode() == osg::Material::DIFFUSE ||
        ivState->osgMaterial->getColorMode() == osg::Material::AMBIENT_AND_DIFFUSE)
      osgArray2ivMField(g->getColorArray(), *colorField);
    else; // FIXME: implement some workaround for non-diffuse cases?
          // note: Warning was already shown in createInventorState().
          // note2: There is no effect to convert SoMaterial::[ambient|specular|emissive]color
          // here because Inventor does not set them per-vertex (performance reasons). See
          // Inventor documentation for more details.
  }


  // Convert coordinates
  // OSG represents coordinates by: Vec2, Vec3, Vec4
  // Inventor by: SbVec3f, SbVec4f
  SoNode *coords;
  if (g->getVertexArray()->getDataSize() == 4) {
    coords = new SoCoordinate4;
    osgArray2ivMField(g->getVertexArray(), ((SoCoordinate4*)coords)->point);
  } else {
    coords = new SoCoordinate3;
    osgArray2ivMField(g->getVertexArray(), ((SoCoordinate3*)coords)->point);
  }
  coords->ref();

  // Convert texture coordinates
  SoNode *texCoords = NULL;
  if (ivState->ivTexture) {
    if (ivState->osgTexGenS && ivState->osgTexGenT &&
        ivState->osgTexGen && ivState->osgTexGen->getMode()==osg::TexGen::SPHERE_MAP)
      texCoords = new SoTextureCoordinateEnvironment;
    else
    if (g->getTexCoordArray(0)) {
      if (g->getTexCoordArray(0)->getDataSize() <= 2) {
        texCoords = new SoTextureCoordinate2;
        osgArray2ivMField(g->getTexCoordArray(0), ((SoTextureCoordinate2*)texCoords)->point);
      } 
#ifdef __COIN__
      else {
        texCoords = new SoTextureCoordinate3;
        osgArray2ivMField(g->getTexCoordArray(0), ((SoTextureCoordinate3*)texCoords)->point);
      }
#endif   // __COIN__
    }
    if (texCoords)
      texCoords->ref();
  }

  // Convert normals
  // OSG represents normals by: Vec3,Vec3s,Vec3b
  // and can handle: Vec4s,Vec4b by truncating them to three components
  // Inventor by: SbVec3f
  SoNormal *normals = NULL;
  if (g->getNormalArray()) {
    normals = new SoNormal;
    osgArray2ivMField(g->getNormalArray(), normals->vector);
    normals->ref();
  }

  // Convert osg::PrimitiveSets to Inventor's SoShapes
  int psetIndex,numPsets = g->getNumPrimitiveSets();
  for (psetIndex=0; psetIndex<numPsets; psetIndex++) {

    // Get PrimitiveSet
    const osg::PrimitiveSet *pset = g->getPrimitiveSet(psetIndex);
    osg::PrimitiveSet::Type type = pset->getType();
    GLenum mode = pset->getMode();

    // Create appropriate SoShape
    bool useIndices = g->getVertexIndices() != NULL || vrml1Conversion;
    bool needSeparateTriangles = false;
    SoVertexShape *shape = NULL;
    switch (mode) {
      case GL_POINTS:         shape = new SoPointSet; break;
      case GL_LINES:
      case GL_LINE_STRIP:
      case GL_LINE_LOOP:      if (useIndices) shape = new SoIndexedLineSet;
                              else shape = new SoLineSet;
                              break;
      case GL_TRIANGLES:
      case GL_TRIANGLE_STRIP:
      case GL_QUAD_STRIP:     if (useIndices) 
                                if (vrml1Conversion) {
                                  shape = new SoIndexedFaceSet;
                                  needSeparateTriangles = true;
                                } else
                                  shape = new SoIndexedTriangleStripSet;
                              else
                                shape = new SoTriangleStripSet;
                              break;
      case GL_TRIANGLE_FAN:   needSeparateTriangles = true;
                              shape = (vrml1Conversion) ? (SoVertexShape*)new SoIndexedFaceSet :
                                                          new SoIndexedTriangleStripSet;
                              break;
      case GL_QUADS:
      case GL_POLYGON:        if (useIndices) shape = new SoIndexedFaceSet;
                              else shape = new SoFaceSet;
                              break;
      default: assert(0);
    }

    // Size of single geometric primitive
    int primSize;
    switch (mode) {
    case GL_LINES:          primSize = 2; break;
    case GL_TRIANGLES:      primSize = 3; break;
    case GL_QUADS:          primSize = 4; break;
    default: primSize = 0;
    };


    bool ok = true;

    switch (type) {

      case osg::PrimitiveSet::DrawArraysPrimitiveType:
      {
        const osg::DrawArrays *drawArrays = dynamic_cast<const osg::DrawArrays*>(pset);

        int startIndex = drawArrays->getFirst();
        int stopIndex = startIndex + drawArrays->getCount();

        // FIXME: Am I using startIndex for all bundles that are PER_VERTEX?
        ok = processPrimitiveSet(g, pset, NULL, needSeparateTriangles,
                                  drawArrays->getCount(), primSize,
                                  startIndex, stopIndex, normalIndex, colorIndex,
                                  coords, normals, ivMaterial, texCoords,
                                  ivState->ivTexture, shape, indexedRoot, nonIndexedRoot);
        break;
      }

      case osg::PrimitiveSet::DrawArrayLengthsPrimitiveType:
      {
        const osg::DrawArrayLengths *drawArrayLengths =
          dynamic_cast<const osg::DrawArrayLengths*>(pset);
        
        int startIndex = drawArrayLengths->getFirst();
          
        ok = processPrimitiveSet(g, pset, NULL, needSeparateTriangles,
                                  -1, primSize, startIndex, -1, normalIndex, colorIndex,
                                  coords, normals, ivMaterial, texCoords,
                                  ivState->ivTexture, shape, indexedRoot, nonIndexedRoot);

        break;
      }
      
      case osg::PrimitiveSet::DrawElementsUBytePrimitiveType:
      case osg::PrimitiveSet::DrawElementsUShortPrimitiveType:
      case osg::PrimitiveSet::DrawElementsUIntPrimitiveType:
      {
        osg::ref_ptr<osg::UIntArray> drawElemIndices = new osg::UIntArray;

        switch (type) {
        case osg::PrimitiveSet::DrawElementsUBytePrimitiveType:
          {
            const osg::DrawElementsUByte *drawElements =
              dynamic_cast<const osg::DrawElementsUByte*>(pset);
            for(osg::DrawElementsUByte::const_iterator primItr = drawElements->begin();
                primItr!=drawElements->end();
                ++primItr)
              drawElemIndices->push_back(*primItr);
            break;
          }
        case osg::PrimitiveSet::DrawElementsUShortPrimitiveType:
          {
            const osg::DrawElementsUShort *drawElements =
              dynamic_cast<const osg::DrawElementsUShort*>(pset);
            for(osg::DrawElementsUShort::const_iterator primItr = drawElements->begin();
                primItr!=drawElements->end();
                ++primItr)
              drawElemIndices->push_back(*primItr);
            break;
          }
        case osg::PrimitiveSet::DrawElementsUIntPrimitiveType:
          {
            const osg::DrawElementsUInt *drawElements =
              dynamic_cast<const osg::DrawElementsUInt*>(pset);
            for(osg::DrawElementsUInt::const_iterator primItr = drawElements->begin();
                primItr!=drawElements->end();
                ++primItr)
              drawElemIndices->push_back(*primItr);
            break;
          }
        default: assert(0);
        }

        ok = processPrimitiveSet(g, pset, drawElemIndices.get(), needSeparateTriangles,
                                  drawElemIndices->getNumElements(), primSize,
                                  0, drawElemIndices->getNumElements(), normalIndex, colorIndex,
                                  coords, normals, ivMaterial, texCoords,
                                  ivState->ivTexture, shape, indexedRoot, nonIndexedRoot);
        break;
      }

      default:
        osg::notify(osg::WARN) << "IvWriter: NOT IMPLEMENTED" << std::endl;
    }
  }

  if (indexedRoot)  ivState->ivHead->addChild(indexedRoot);
  if (nonIndexedRoot)  ivState->ivHead->addChild(nonIndexedRoot);

  coords->unref();
  if (texCoords)  texCoords->unref();
  if (normals)  normals->unref();
}


void ConvertToInventor::processShapeDrawable(const osg::ShapeDrawable *d, InventorState *ivState)
{
  // visitor for converting ShapeDrawables
  class MyShapeVisitor : public osg::ConstShapeVisitor {
  public:
    void processNode(SoNode *ivNode, const osg::Vec3& center, osg::Quat rotation,
                      SoGroup *root) {
      // convert rotation
      rotation = osg::Quat(-M_PI_2, osg::Vec3(0.,1.,0.)) * osg::Quat(M_PI_2, osg::Vec3(1.,0.,0.)) * rotation;
      
      if (center.length2()==0. && rotation.zeroRotation() && ivState->ivTexture==NULL)

        // optimized handling of single node
        root->addChild(ivNode);
      
      else {
        SoSeparator *root2 = new SoSeparator;
        
        // handle transformation
        if (center.length2()!=0. || !rotation.zeroRotation()) {
          SoTransform *ivTransform = new SoTransform;
          setSoTransform(ivTransform, center, rotation);
          root2->addChild(ivTransform);
        }

        // handle texture
        if (ivState->ivTexture)
          root2->addChild(ivState->ivTexture);
        
        // build graph
        root2->addChild(ivNode);
        root->addChild(root2);
      }
    }
    
    virtual void apply(const osg::Sphere &s) { 
      SoSphere *ivSphere = new SoSphere;
      ivSphere->radius.setValue(s.getRadius());
      processNode(ivSphere, s.getCenter(), osg::Quat(0., osg::Vec3(1.,0.,0.)), ivState->ivHead);
    }
    virtual void apply(const osg::Box &b) {
      SoCube *ivCube = new SoCube;
      ivCube->width  = 2 * b.getHalfLengths().y();
      ivCube->height = 2 * b.getHalfLengths().z();
      ivCube->depth  = 2 * b.getHalfLengths().x();
      processNode(ivCube, b.getCenter(), b.getRotation(), ivState->ivHead);
    }
    virtual void apply(const osg::Cone &c) {
      SoCone *ivCone = new SoCone;
      ivCone->bottomRadius = c.getRadius();
      ivCone->height = c.getHeight();
      osg::Vec3 newCenter(c.getCenter());
      newCenter.ptr()[2] -= c.getBaseOffset();
      processNode(ivCone, newCenter, c.getRotation(), ivState->ivHead);
    }
    virtual void apply(const osg::Cylinder &c) {
      SoCylinder *ivCylinder = new SoCylinder;
      ivCylinder->radius = c.getRadius();
      ivCylinder->height = c.getHeight();
      processNode(ivCylinder, c.getCenter(), c.getRotation(), ivState->ivHead);
    }
    
    void warnNonSupported() {
      osg::notify(osg::WARN) << "IvWriter: Not supported ShapeDrawable found. Skipping it." << std::endl;
    }
    virtual void apply(const osg::Capsule&)        { warnNonSupported(); }
    virtual void apply(const osg::InfinitePlane&)  { warnNonSupported(); }
    virtual void apply(const osg::TriangleMesh&)   { warnNonSupported(); }
    virtual void apply(const osg::ConvexHull&)     { warnNonSupported(); }
    virtual void apply(const osg::HeightField&)    { warnNonSupported(); }
    virtual void apply(const osg::CompositeShape&) { warnNonSupported(); }
  
    InventorState *ivState;
    MyShapeVisitor(InventorState *ivState) { this->ivState = ivState; }
  } shapeVisitor(ivState);

  // convert ShapeDrawable
  const osg::Shape *shape = d->getShape();
  if (shape)
    shape->accept(shapeVisitor);
}


void ConvertToInventor::processDrawable(osg::Drawable *d)
{
  osg::Geometry *g = d->asGeometry(); // FIXME: other drawables have to be handled also
  osg::ShapeDrawable *sd;

  // Create SoSeparator and convert StateSet for Drawable
  InventorState *ivDrawableState = createInventorState(d->getStateSet());

  if (g != NULL)
    processGeometry(g, ivDrawableState);
  else
  
  if ((sd = dynamic_cast<osg::ShapeDrawable*>(d)) != NULL) {
    processShapeDrawable(sd, ivDrawableState);
  }
  else
    osg::notify(osg::WARN) << "IvWriter: Unsupported drawable found: \"" << d->className() <<
                              "\". Skipping it." << std::endl;

  // Restore state
  popInventorState();
}


void ConvertToInventor::apply(osg::Geode &node)
{
#ifdef DEBUG_IV_WRITER
  osg::notify(osg::INFO) << "IvWriter: Geode traversed" << std::endl;
#endif

  // Create SoSeparator and convert StateSet for Geode
  /*InventorState *ivGeodeState = */createInventorState(node.getStateSet());

  // Convert drawables
  const int numDrawables = node.getNumDrawables();
  for (int i=0; i<numDrawables; i++)
    processDrawable(node.getDrawable(i));

  traverse(node);

  // Restore state
  popInventorState();
}


void ConvertToInventor::apply(osg::Group &node)
{
#ifdef DEBUG_IV_WRITER
  osg::notify(osg::INFO) << "IvWriter: Group traversed" << std::endl;
#endif

  // Create SoSeparator and convert StateSet
  /*InventorState *ivState = */createInventorState(node.getStateSet());

  traverse(node);

  popInventorState();
}


void ConvertToInventor::apply(osg::Billboard& node)
{
#ifdef DEBUG_IV_WRITER
  osg::notify(osg::INFO) << "IvWriter: Billboard traversed" << std::endl;
#endif

#ifdef __COIN__

  if (useIvExtensions) {
    
    // Create SoSeparator and convert StateSet
    InventorState *ivState = createInventorState(node.getStateSet());
    SoGroup *root = ivState->ivHead;

    // Process drawables
    const int numDrawables = node.getNumDrawables();
    for (int i=0; i<numDrawables; i++) {

      SoVRMLBillboard *billboard = new SoVRMLBillboard;

      // SoVRMLBillboard is VRML 2.0 node supported by Coin (?since 2.0?)
      // However, I am seeing bug in my Coin 2.4.5 so that if
      // SoVRMLBillboard::axisOfRotation is not 0,0,0, the billboard behaviour is strange.
      // As long as it is set to 0,0,0, POINT_ROT_EYE-style billboard works perfectly.
      // AXIAL_ROT seems not possible with the bug. And POINT_ROT_WORLD was not
      // investigated by me until now.
      // There is also billboard culling bug in Coin, so the billboards may not be
      // rendered properly from time to time. PCJohn-2007-09-08
    #if 0
      SbVec3f axis;
      switch (node.getMode()) {
        case osg::Billboard::POINT_ROT_EYE:   axis = SbVec3f(0.f,0.f,0.f); break;
        case osg::Billboard::POINT_ROT_WORLD: axis = SbVec3f(0.f,0.f,0.f); break;
        case osg::Billboard::AXIAL_ROT:       axis = node.getAxis().ptr(); break;
        default:
          axis = SbVec3f(0.f,0.f,0.f);
      };
      billboard->axisOfRotation.setValue(axis);
    #else
      
      billboard->axisOfRotation.setValue(SbVec3f(0.f,0.f,0.f));

    #endif

      SoTranslation *translation = new SoTranslation;
      translation->translation.setValue(node.getPosition(i).ptr());

      // Rotate billboard correctly (OSG->IV conversion)
      // Note: use SoTransform instead of SoRotation because SoRotation is not supported by VRML1.
      SoTransform *transform = new SoTransform;
      transform->rotation = SbRotation(SbVec3f(1.f,0.f,0.f), float(-M_PI_2));

      SoSeparator *separator = new SoSeparator;
      separator->addChild(translation);
      separator->addChild(billboard);
      billboard->addChild(transform);

      root->addChild(separator);
      ivState->ivHead = billboard;

      processDrawable(node.getDrawable(i));

      traverse((osg::Node&)node);
    }

    popInventorState();
  
  } else
    apply((osg::Geode&)node);

#else

  apply((osg::Geode&)node);

#endif
}


void ConvertToInventor::apply(osg::MatrixTransform& node)
{
#ifdef DEBUG_IV_WRITER
  osg::notify(osg::INFO) << "IvWriter: MatrixTransform traversed" << std::endl;
#endif

  // Convert matrix
  SoMatrixTransform *ivTransform = new SoMatrixTransform;
  SbMatrix ivMatrix;
  const osg::Matrix::value_type *src = node.getMatrix().ptr();
  float *dest = ivMatrix[0];
  for (int i=0; i<16; i++,dest++,src++)
    *dest = *src;
  ivTransform->matrix.setValue(ivMatrix);

  // Create SoSeparator and convert StateSet
  InventorState *ivState = createInventorState(node.getStateSet());
  ivState->ivHead->addChild(ivTransform);

  traverse((osg::Node&)node);

  popInventorState();
}


void ConvertToInventor::apply(osg::PositionAttitudeTransform& node)
{
#ifdef DEBUG_IV_WRITER
  osg::notify(osg::INFO) << "IvWriter: PositionAttitudeTransform traversed" << std::endl;
#endif

  // Convert matrix
  SoTransform *ivTransform = new SoTransform;
  setSoTransform(ivTransform, node.getPosition(), node.getAttitude(), node.getScale());

  // Create SoSeparator and convert StateSet
  InventorState *ivState = createInventorState(node.getStateSet());
  ivState->ivHead->addChild(ivTransform);

  traverse((osg::Node&)node);

  popInventorState();
}


void ConvertToInventor::apply(osg::LOD& node)
{
#ifdef DEBUG_IV_WRITER
  osg::notify(osg::INFO) << "IvWriter: LOD traversed" << std::endl;
#endif

  // Convert LOD
  SoGroup *ivLOD = NULL;
  osg::LOD::RangeMode rangeMode = node.getRangeMode();
  if (rangeMode == osg::LOD::DISTANCE_FROM_EYE_POINT) {
    
    // use SoLOD for DISTANCE_FROM_EYE_POINT
    SoLOD *lod = new SoLOD;

    // copy ranges
    int i,c=node.getNumRanges();
    for (i=0; i<c; i++)
       lod->range.set1Value(i, node.getMaxRange(i));

    // set center
    osg::Vec3f center(node.getCenter());
    lod->center.setValue(center.ptr());

    ivLOD = lod;

  } else
  if (rangeMode == osg::LOD::PIXEL_SIZE_ON_SCREEN) {

    // use SoLevelOfDetail for PIXEL_SIZE_ON_SCREEN
    SoLevelOfDetail *lod = new SoLevelOfDetail;

    // copy ranges
    int i,c=node.getNumRanges();
    for (i=0; i<c; i++)
       lod->screenArea.set1Value(i, node.getMaxRange(i));

    ivLOD = lod;

  } else {
    
    // undefined mode -> put warning
    osg::notify(osg::WARN) << "IvWriter: Undefined LOD::RangeMode value." << std::endl;
    ivLOD = new SoGroup;
  }

  // Create SoSeparator and convert StateSet
  InventorState *ivState = createInventorState(node.getStateSet());
  ivState->ivHead->addChild(ivLOD);
  ivState->ivHead = ivLOD;

  traverse((osg::Node&)node);

  popInventorState();
}

