//
// ivexport.C
//
// writes out a .iv file, reflecting a pf scene graph,
// to be used with SGI's OpenGL|Perfomer libpfiv
// (c)2006 Bram Stolk (bram at sara.nl)
// You may distribute this file, modified or not, as long as the
// original copyright message is maintained.
//
// SUPPORTED: 
// - LINES
// - LINESTRIPS
// - TRISTRIPS (flat / normal)
// - POLYS
// - QUADS
// - POINTS
// - Textures (rgb, jpg, gif)
// - SCS,DCS,Group,Switch
//
// LIMITATIONS:
// - No tri fans
// - No flat linestrips
// - texture bleed?
// - texture quality is not optimal? transparent texture? filtering?
// - pfSequence is written as SoSwitch, which is not the same thing.
// - Animation not supported
// - BACKMTL not supported
// - No pfLayer support
//
//  Fri Feb  3 23:20:42 CET 2006
//  Version 1.0
//

#include <sys/types.h>
#include <malloc.h>
#include <assert.h>

#include <Performer/pr/pfTexture.h>
#include <Performer/pr/pfMaterial.h>
#include <Performer/pr/pfLight.h>

#include <Performer/pf/pfGroup.h>
#include <Performer/pf/pfLOD.h>
#include <Performer/pf/pfSequence.h>
#include <Performer/pf/pfSwitch.h>
#include <Performer/pf/pfSCS.h>
#include <Performer/pf/pfGeode.h>

#define INDENT(L) \
  fwrite(indentation, (L)*2>128?128:(L)*2, 1, f);


extern "C"
{
int pfdStoreFile_iv(pfNode *root, const char *fname);
}

static const char *indentation="                                                                                                                                ";

static const char *prim_type_name(int t)
{
  if (t==PFGS_POINTS) return "PFGS_POINTS";
  if (t==PFGS_LINES) return "PFGS_LINES";
  if (t==PFGS_LINESTRIPS) return "PFGS_LINESTRIPS";
  if (t==PFGS_FLAT_LINESTRIPS) return "PFGS_FLAT_LINESTRIPS";
  if (t==PFGS_TRIS) return "PFGS_TRIS";
  if (t==PFGS_QUADS) return "PFGS_QUADS";
  if (t==PFGS_TRISTRIPS) return "PFGS_TRISTRIPS";
  if (t==PFGS_FLAT_TRISTRIPS) return "PFGS_FLAT_TRISTRIPS";
  if (t==PFGS_TRIFANS) return "PFGS_TRIFANS";
  if (t==PFGS_FLAT_TRIFANS) return "PFGS_FLAT_TRIFANS";
  if (t==PFGS_POLYS) return "PFGS_POLYS";
  return 0;
}


static const char *get_bind_name(int bd)
{
  if (bd == PFGS_OVERALL)
    return "OVERALL";
  if (bd == PFGS_PER_PRIM)
    return "PER_PART";
  if (bd == PFGS_PER_VERTEX)
    return "PER_VERTEX";
  return "OFF";
}


static const char *get_cmode_name(int cmode)
{
  if (cmode == PFMTL_CMODE_AMBIENT_AND_DIFFUSE) return "PFMTL_CMODE_AMBIENT_AND_DIFFUSE";
  if (cmode == PFMTL_CMODE_AMBIENT) return "PFMTL_CMODE_AMBIENT";
  if (cmode == PFMTL_CMODE_DIFFUSE) return "PFMTL_CMODE_DIFFUSE";
  if (cmode == PFMTL_CMODE_EMISSION) return "PFMTL_CMODE_EMISSION";
  if (cmode == PFMTL_CMODE_SPECULAR) return "PFMTL_CMODE_SPECULAR";
  if (cmode == PFMTL_CMODE_OFF) return "PFMTL_CMODE_OFF";
  if (cmode == PFMTL_CMODE_COLOR) return "PFMTL_CMODE_COLOR";
  pfNotify(PFNFY_WARN, PFNFY_USAGE, "Unsupported cmode %d", cmode);
  return 0;
}


int store_geostate(pfGeoState *gstate, FILE *f, int lvl)
{
  const char *texturename = 0;
  assert(gstate);
  int cmode=-1;

  int entexture = gstate->getMode(PFSTATE_ENTEXTURE);
  if (entexture == PF_ON)
  {
    pfTexEnv  *texenv  = (pfTexEnv*) gstate->getAttr(PFSTATE_TEXENV);
    pfTexture *texture = (pfTexture*)gstate->getAttr(PFSTATE_TEXTURE);
    if (texture)
    {
      texturename = texture->getName();
      INDENT(lvl); fprintf(f, "#Texture at %p named %s texenv at %p\n", texture, texturename, texenv);
    }
  }


  pfMaterial *fmaterial = (pfMaterial*)gstate->getAttr(PFSTATE_FRONTMTL);
  pfMaterial *bmaterial = (pfMaterial*)gstate->getAttr(PFSTATE_BACKMTL);
  INDENT(lvl); fprintf(f,"#frontmat %p backmat %p\n", fmaterial, bmaterial);
  pfLightModel *lightmodel = (pfLightModel*)gstate->getAttr(PFSTATE_LIGHTMODEL);
  if (lightmodel)
    if (lightmodel->getTwoSide() == PF_ON)
    {
      INDENT(lvl); fprintf(f, "ShapeHints { vertexOrdering COUNTERCLOCKWISE shapeType UNKNOWN_SHAPE_TYPE }\n");
    }
  bool lighting = gstate->getMode(PFSTATE_ENLIGHTING);
  if (!lighting)
  {
    INDENT(lvl); fprintf(f, "LightModel { model BASE_COLOR }\n");
  }

  pfMaterial *material = fmaterial;
  if (fmaterial)
  {
    cmode = material->getColorMode(PFMTL_FRONT);
    INDENT(lvl); fprintf(f, "Material { ");
    float r,g,b;
    material->getColor(PFMTL_DIFFUSE, &r,&g,&b);
    fprintf(f,"diffuseColor %.3f %.3f %.3f ", r,g,b);
    material->getColor(PFMTL_AMBIENT, &r,&g,&b);
    fprintf(f,"ambientColor %.3f %.3f %.3f ", r,g,b);
    material->getColor(PFMTL_EMISSION, &r,&g,&b);
    fprintf(f,"emissiveColor %.3f %.3f %.3f ", r,g,b);
    material->getColor(PFMTL_SPECULAR, &r,&g,&b);
    fprintf(f,"specularColor %.3f %.3f %.3f ", r,g,b);
    float a = material->getAlpha();
    // Yuck! Inventor does not do exponent shininess, but want a value from 0 to 1.
    float s = material->getShininess() / 128.0;
    fprintf(f,"shininess %.3f transparency %.3f ", s, 1.0-a);
    fprintf(f, "}\n");
  }

  if (texturename)
  {
    INDENT(lvl); fprintf(f, "Texture2 { filename \"%s\" }\n", texturename);
  }
  return cmode;
}


int store_geoset(pfGeoSet *gset, FILE *f, int lvl)
{
  int primtp = gset->getPrimType();
  const char *typn = prim_type_name(primtp);
  int numprims = gset->getNumPrims();
  int minc,maxc,minn,maxn,mint,maxt,minv,maxv;
  int nc = gset->getAttrRange(PFGS_COLOR4, &minc, &maxc);
  int nn = gset->getAttrRange(PFGS_NORMAL3, &minn, &maxn);
  int nt = gset->getAttrRange(PFGS_TEXCOORD2, &mint, &maxt);
  int nv = gset->getAttrRange(PFGS_COORD3, &minv, &maxv);
  INDENT(lvl); fprintf(f, "#%s with %d primitives nc=%d nn=%d nt=%d nv=%d\n", typn, numprims, nc, nn, nt, nv);
  unsigned short *idxc=0, *idxn=0, *idxt=0, *idxv=0;
  pfVec4 *lc=0;
  pfVec3 *ln=0;
  pfVec2 *lt=0;
  pfVec3 *lv=0;
  gset->getAttrLists(PFGS_COLOR4, (void**) &lc, &idxc);
  gset->getAttrLists(PFGS_NORMAL3, (void**) &ln, &idxn);
  gset->getAttrLists(PFGS_TEXCOORD2, (void**) &lt, &idxt);
  gset->getAttrLists(PFGS_COORD3, (void**) &lv, &idxv);
  int i;

  // Flatten the indexing
  if (maxv>0)
  {
    pfVec3 *flatv = new pfVec3[nv];
    for (i=0; i<nv; i++)
      flatv[i] = lv[idxv[i]];
    lv = flatv;
  }
  if (maxn>0)
  {
    pfVec3 *flatn = new pfVec3[nn];
    for (i=0; i<nn; i++)
      flatn[i] = ln[idxn[i]];
    ln = flatn;
  }
  if (maxc>0)
  {
    pfVec4 *flatc = new pfVec4[nc];
    for (i=0; i<nc; i++)
      flatc[i] = lc[idxc[i]];
    lc = flatc;
  }
  if (maxt>0)
  {
    pfVec2 *flatt = new pfVec2[nt];
    for (i=0; i<nt; i++)
      flatt[i] = lt[idxt[i]];
    lt = flatt;
  }

  bool indexed = (maxc>0) || (maxn>0) || (maxv>0) || (maxt>0);
  if (indexed)
  {
    INDENT(lvl); fprintf(f,"#indices ranges from,to: v(%d,%d), n(%d,%d), c(%d,%d), t(%d,%d)\n", minv,maxv, minn,maxn, minc,maxc, mint,maxt);
  }

  pfGeoState *gstate = 0;
  gstate = gset->getGState();
  int cmode=-1;
  if (gstate) 
  {
    INDENT(lvl); fprintf(f,"#GeoState %p\n", gstate);
    cmode = store_geostate(gstate, f, lvl);
    if (cmode != -1)
    {
      const char *cmodename = get_cmode_name(cmode);
      INDENT(lvl); fprintf(f,"#cmode: %s\n", cmodename);
    }
  }
  else
  {
    INDENT(lvl); fprintf(f,"#No geostate defined\n");
  }

  INDENT(lvl); fprintf(f, "SoVertexProperty {\n");
  int bd;
  bd = gset->getAttrBind(PFGS_COLOR4);
  const char *bindn = get_bind_name(bd);
  if (primtp == PFGS_FLAT_TRISTRIPS && bindn == "PER_VERTEX") bindn = "PER_FACE";
  if (bindn!="OFF")
  {
    INDENT(lvl+1); fprintf(f, "materialBinding %s%s\n", bindn, "");
  }
  bd = gset->getAttrBind(PFGS_NORMAL3);
  bindn = get_bind_name(bd);
  if (primtp == PFGS_FLAT_TRISTRIPS && bindn == "PER_VERTEX") bindn = "PER_FACE";
  if (bindn!="OFF")
  {
    INDENT(lvl+1); fprintf(f, "normalBinding %s%s\n", bindn, "");
  }
  bd = gset->getAttrBind(PFGS_TEXCOORD2);
  if (bd != PFGS_OFF)
    assert(bd == gset->getAttrBind(PFGS_COORD3));

  INDENT(lvl+1); fprintf(f, "vertex [");
  for (i=0; i<nv; i++) fprintf(f, "%f %f %f, ", lv[i][0], lv[i][1], lv[i][2]);
  fprintf(f, "]\n");
  if (nn)
  {
    INDENT(lvl+1); fprintf(f, "normal [");
    for (i=0; i<nn; i++) fprintf(f, "%f %f %f, ", ln[i][0], ln[i][1], ln[i][2]);
    fprintf(f, "]\n");
  }
  if (nc  && cmode != PFMTL_CMODE_OFF)
  {
    INDENT(lvl+1); fprintf(f, "orderedRGBA [");
    for (i=0; i<nc; i++) 
    {
      unsigned char r = (unsigned char)(255 * lc[i][0]);
      unsigned char g = (unsigned char)(255 * lc[i][1]);
      unsigned char b = (unsigned char)(255 * lc[i][2]);
      unsigned char a = (unsigned char)(255 * lc[i][3]);
      if (a==0) a=0xff;
      unsigned int rgba = r<<24 | g<<16 | b << 8 | a;
      fprintf(f, "0x%x, ", rgba);
    }
    fprintf(f, "]\n");
  }
  if (nt)
  {
    INDENT(lvl+1); fprintf(f, "texCoord [");
    for (i=0; i<nt; i++) fprintf(f, "%f %f, ", lt[i][0], lt[i][1]);
    fprintf(f, "]\n");
  }
  INDENT(lvl); fprintf(f, "}\n");

  if (primtp == PFGS_TRISTRIPS || primtp == PFGS_FLAT_TRISTRIPS)
  {
    INDENT(lvl); fprintf(f, "TriangleStripSet {\n");
    INDENT(lvl+1); fprintf(f, "numVertices [");
    for (i=0; i<numprims; i++)
      fprintf(f,"%d, ", gset->getPrimLength(i));
    fprintf(f,"]\n");
    INDENT(lvl); fprintf(f, "}\n");
    return true;
  }

  if (primtp == PFGS_QUADS)
  {
    INDENT(lvl); fprintf(f, "FaceSet {\n");
    INDENT(lvl+1); fprintf(f, "numVertices [");
    for (i=0; i<numprims; i++)
      fprintf(f,"%d, ", 4);
    fprintf(f,"]\n");
    INDENT(lvl); fprintf(f, "}\n");
    return true;
  }

  if (primtp == PFGS_TRIS)
  {
    INDENT(lvl); fprintf(f, "FaceSet {\n");
    INDENT(lvl+1); fprintf(f, "numVertices [");
    for (i=0; i<numprims; i++)
      fprintf(f,"%d, ", 3);
    fprintf(f,"]\n");
    INDENT(lvl); fprintf(f, "}\n");
    return true;
  }

  if (primtp == PFGS_POINTS)
  {
    float psz = gset->getPntSize();
    INDENT(lvl); fprintf(f, "DrawStyle { pointSize %f }\n", psz);
    INDENT(lvl); fprintf(f, "PointSet {\n");
    INDENT(lvl+1); fprintf(f, "numPoints %d", numprims);
    INDENT(lvl); fprintf(f, "}\n");
    return true;
  }

  if (primtp == PFGS_LINESTRIPS)
  {
    float lw = gset->getLineWidth();
    INDENT(lvl); fprintf(f, "DrawStyle { lineWidth %f }\n", lw);
    INDENT(lvl); fprintf(f, "LineSet {\n");
    INDENT(lvl+1); fprintf(f, "numVertices [");
    for (i=0; i<numprims; i++)
      fprintf(f,"%d, ", gset->getPrimLength(i));
    fprintf(f,"]\n");
    INDENT(lvl); fprintf(f, "}\n");
    return true;
  }

  if (primtp == PFGS_LINES)
  {
    float lw = gset->getLineWidth();
    INDENT(lvl); fprintf(f, "DrawStyle { lineWidth %f }\n", lw);
    INDENT(lvl); fprintf(f, "LineSet {\n");
    INDENT(lvl+1); fprintf(f, "numVertices [");
    for (i=0; i<numprims; i++)
      fprintf(f,"2, ");
    fprintf(f,"]\n");
    INDENT(lvl); fprintf(f, "}\n");
    return true;
  }

  pfNotify(PFNFY_WARN, PFNFY_USAGE, "Unsupported GeoSet type %s", typn);
  return false;
}


int store_node(pfNode *node, FILE *f, int lvl=0)
{
  assert(node);
  const char *name = node->getName();
  if (!name) name = "";
  INDENT(lvl);
  fprintf(f,"#%s %s\n", node->getTypeName(), name);

  if (node->getType() == pfGeode::getClassType())
  {
    pfGeode *geode = dynamic_cast<pfGeode*>(node);
    assert(geode);
    for (int i=0; i<geode->getNumGSets(); i++)
    {
      INDENT(lvl); fprintf(f,"Separator {\n");
      store_geoset(geode->getGSet(i), f, lvl+1);
      INDENT(lvl); fprintf(f,"}\n");
    }
    return true;
  }

  if (node->getType() == pfLOD::getClassType())
  {
    pfLOD *lod = dynamic_cast<pfLOD*>(node);
    assert(lod);
    INDENT(lvl); fprintf(f,"LOD {\n");
    int nc = lod->getNumChildren();
    for (int k=0; k<nc; k++)
      store_node(lod->getChild(k), f, lvl+1);
    INDENT(lvl); fprintf(f,"}\n");
    return true;
  }

  if (node->isOfType(pfSCS::getClassType()))
  {
    pfSCS *scs = dynamic_cast<pfSCS*>(node);
    assert(scs);
    INDENT(lvl); fprintf(f,"Separator {\n");
    INDENT(lvl+1); fprintf(f,"MatrixTransform { matrix\n");
    pfMatrix mat;
    scs->getMat(mat);
    for (int r=0; r<4; r++)
    {
      pfVec3 row;
      mat.getRow(r, row);
      INDENT(lvl+2); fprintf(f,"%f %f %f %d\n", row[0], row[1], row[2], (r==3)?1:0);
    }
    INDENT(lvl+1); fprintf(f,"}\n");
    int nc = scs->getNumChildren();
    for (int k=0; k<nc; k++)
      store_node(scs->getChild(k), f, lvl+1);
    INDENT(lvl); fprintf(f,"}\n");
    return true;
  }

  if (node->isOfType(pfSequence::getClassType()))
  {
    pfSequence *seq = dynamic_cast<pfSequence*>(node);
    assert(seq);
    INDENT(lvl); fprintf(f,"Switch {\n");
    INDENT(lvl+1); fprintf(f,"whichChild 0\n");
    int nc = seq->getNumChildren();
    for (int k=0; k<nc; k++)
      store_node(seq->getChild(k), f, lvl+1);
    INDENT(lvl); fprintf(f,"}\n");
    return true;
  }

  if (node->isOfType(pfSwitch::getClassType()))
  {
    pfSwitch *sw = dynamic_cast<pfSwitch*>(node);
    assert(sw);
    INDENT(lvl); fprintf(f,"Switch {\n");
    int v = (int)sw->getVal();
    if (v == PFSWITCH_ON)
      v = -3;
    if (v == PFSWITCH_OFF)
      v = -1;
    INDENT(lvl+1); fprintf(f,"whichChild %d\n", v);
    int nc = sw->getNumChildren();
    for (int k=0; k<nc; k++)
      store_node(sw->getChild(k), f, lvl+1);
    INDENT(lvl); fprintf(f,"}\n");
    return true;
  }

  if (node->isOfType(pfGroup::getClassType()))
  {
    pfGroup *group = dynamic_cast<pfGroup*>(node);
    assert(group);
    INDENT(lvl); fprintf(f,"Group {\n");
    int nc = group->getNumChildren();
    for (int k=0; k<nc; k++)
      store_node(group->getChild(k), f, lvl+1);
    INDENT(lvl); fprintf(f,"}\n");
    return true;
  }

  pfNotify(PFNFY_WARN, PFNFY_USAGE, "Unsupported node type %s", node->getTypeName());
  return false;
}


int pfdStoreFile_iv(pfNode *root, const char *fname)
{
  FILE *f=fopen(fname,"w");
  if (!f) return false;
  fprintf(f,"#Inventor V2.0 ascii\n");
  fprintf(f,"#Generated by pfdStoreFile_iv() by Bram Stolk\n");
  store_node(root, f);
  fclose(f);
  return true;
}

