/* vrml.c: saves "illuminated" model in the VRML'97 file */

#include "defaults.h"
#include "mcradP.h"
#include "hierarchy.h"
#include "writevrml.h"
#include "vrml.h"
#include "render.h"
#include "geom.h"
#include "scene.h"

#define FACES_PER_SET 1000  /* keeps all arrays smaller than 30k els */
static int vid, nwrit, matidx;
static int nrcoords, nrcolors, nrcoordindices, pass;
static FILE *vrmlfp;

/* iterator adapted in order to handle only a subset of the elements
 * at a time, taking into account 'pass' and FACES_PER_SET */
static void (*elemfunc)(ELEMENT*);
static int leaf_element_count;

static void count_and_call(ELEMENT *elem)
{
  if (leaf_element_count >= pass * FACES_PER_SET &&
      leaf_element_count < (pass+1) * FACES_PER_SET)
    elemfunc(elem);
  leaf_element_count++;
}

static void GeomIterateLeafElements(GEOM *geom, void (*func)(ELEMENT*))
{
  PATCHLIST *patches = GeomPatchList(geom);
  elemfunc = func;
  leaf_element_count = 0;
  ForAllPatches(P, patches) {
    ForAllLeafElements(TOPLEVEL_ELEMENT(P), count_and_call);
  } EndForAll;
}

static void ResetVertexId(VERTEX *v)
{
  v->tmp = -1;
}

static void TriangleResetVertexIds(VERTEX *v1, VERTEX *v2, VERTEX *v3)
{
  ResetVertexId(v1);
  ResetVertexId(v2);
  ResetVertexId(v3);
}

static void QuadResetVertexIds(VERTEX *v1, VERTEX *v2, VERTEX *v3, VERTEX *v4)
{
  ResetVertexId(v1);
  ResetVertexId(v2);
  ResetVertexId(v3);
  ResetVertexId(v4);
}

/* with T-vertex elimination */
static void ResetVertexIds(ELEMENT *elem)
{
  ElementTVertexElimination(elem, TriangleResetVertexIds, QuadResetVertexIds);
}

/* without T-vertex elimination: this should do, but it doesn't do in 
 * practice. */
static void ElementResetVertexIds(ELEMENT *elem)
{
  int i;
  for (i=0; i<elem->nrvertices; i++)
    elem->vertex[i]->tmp = -1;
}

static void WriteVertexCoord(VERTEX *v)
{
  if (v->tmp == -1) {
    /* not yet written */
    if (nwrit>0)
      fprintf(vrmlfp, ", ");
    nwrit++;
    if (nwrit%4 == 0)
      fprintf(vrmlfp, "\n\t  ");
    fprintf(vrmlfp, "%g %g %g", v->point->x, v->point->y, v->point->z);
    v->tmp = vid; vid++;
  }
}

static void TriangleWriteVertexCoords(VERTEX *v1, VERTEX *v2, VERTEX *v3)
{
  WriteVertexCoord(v1);
  WriteVertexCoord(v2);
  WriteVertexCoord(v3);
}

static void QuadWriteVertexCoords(VERTEX *v1, VERTEX *v2, VERTEX *v3, VERTEX *v4)
{
  WriteVertexCoord(v1);
  WriteVertexCoord(v2);
  WriteVertexCoord(v3);
  WriteVertexCoord(v4);
}

static void WriteVertexCoords(ELEMENT *elem)
{
  ElementTVertexElimination(elem, TriangleWriteVertexCoords, QuadWriteVertexCoords);
}

static void WriteCoords(void)
{
  ForAllLeafElements(hierarchy.topcluster, ElementResetVertexIds);
  
  vid = nwrit = 0;
  fprintf(vrmlfp, "\tcoord Coordinate {\n\t  point [ ");
  ForAllLeafElements(hierarchy.topcluster, WriteVertexCoords);
  fprintf(vrmlfp, " ] ");
  fprintf(vrmlfp, "\n\t}\n");

  nrcoords = nwrit;
}

static void WritePrimitiveCoords(GEOM *geom)
{
  GeomIterateLeafElements(geom, ResetVertexIds);

  vid = nwrit = 0;
  fprintf(vrmlfp, "\tcoord Coordinate {\n\t  point [ ");
  GeomIterateLeafElements(geom, WriteVertexCoords);
  fprintf(vrmlfp, " ] ");
  fprintf(vrmlfp, "\n\t}\n");

  nrcoords = nwrit;
}

static void WriteVertexColor(VERTEX *v)
{
  if (v->tmp == -1) {
    /* not yet written */
    if (nwrit>0)
      fprintf(vrmlfp, ", ");
    nwrit++;
    if (nwrit%4 == 0)
      fprintf(vrmlfp, "\n\t  ");
    fprintf(vrmlfp, "%.3g %.3g %.3g", v->color.r, v->color.g, v->color.b);
    v->tmp = vid; vid++;
  }
}

static void TriangleWriteVertexColors(VERTEX *v1, VERTEX *v2, VERTEX *v3)
{
  WriteVertexColor(v1);
  WriteVertexColor(v2);
  WriteVertexColor(v3);
}

static void QuadWriteVertexColors(VERTEX *v1, VERTEX *v2, VERTEX *v3, VERTEX *v4)
{
  WriteVertexColor(v1);
  WriteVertexColor(v2);
  WriteVertexColor(v3);
  WriteVertexColor(v4);
}

static void ElementWriteVertexColors(ELEMENT *elem)
{
  ElementTVertexElimination(elem, TriangleWriteVertexColors, QuadWriteVertexColors);
}

static void WriteVertexColors(void)
{
  ForAllLeafElements(hierarchy.topcluster, ResetVertexIds);

  vid = nwrit = 0;
  fprintf(vrmlfp, "\tcolor Color {\n\t  color [ ");
  ForAllLeafElements(hierarchy.topcluster, ElementWriteVertexColors);
  fprintf(vrmlfp, " ] ");
  fprintf(vrmlfp, "\n\t}\n");

  nrcolors = nwrit;
}

static void WritePrimitiveColors(GEOM *geom)
{
  GeomIterateLeafElements(geom, ResetVertexIds);

  vid = nwrit = 0;
  fprintf(vrmlfp, "\tcolor Color {\n\t  color [ ");
  GeomIterateLeafElements(geom, ElementWriteVertexColors);
  fprintf(vrmlfp, " ] ");
  fprintf(vrmlfp, "\n\t}\n");

  nrcolors = nwrit;
}

static void WriteColors(void)
{
  static int wgiv=FALSE;
  if (!renderopts.smooth_shading && !wgiv) {
    Warning(NULL, "I assume you want a smooth shaded model ...");
    wgiv = TRUE;
  }
  fprintf(vrmlfp, "\tcolorPerVertex %s\n", "TRUE");
  WriteVertexColors();
}

static void WriteCoordIndex(int index)
{
  nwrit++;
  if (nwrit%20 == 0) fprintf(vrmlfp, "\n\t  ");
  fprintf(vrmlfp, "%d ", index);
}

static void TriangleWriteVertexCoordIndices(VERTEX *v1, VERTEX *v2, VERTEX *v3)
{
  WriteCoordIndex(v1->tmp);
  WriteCoordIndex(v2->tmp);
  WriteCoordIndex(v3->tmp);
  WriteCoordIndex(-1);
}

static void QuadWriteVertexCoordIndices(VERTEX *v1, VERTEX *v2, VERTEX *v3, VERTEX *v4)
{
  WriteCoordIndex(v1->tmp);
  WriteCoordIndex(v2->tmp);
  WriteCoordIndex(v3->tmp);
  WriteCoordIndex(v4->tmp);
  WriteCoordIndex(-1);
}

static void ElementWriteCoordIndices(ELEMENT *elem)
{
  ElementTVertexElimination(elem, TriangleWriteVertexCoordIndices, QuadWriteVertexCoordIndices);
}

static void WriteCoordIndices(void)
{
  nwrit = 0;
  fprintf(vrmlfp, "\tcoordIndex [ ");
  ForAllLeafElements(hierarchy.topcluster, ElementWriteCoordIndices);
  fprintf(vrmlfp, " ]\n"); 

  nrcoordindices = nwrit;
}

static void WritePrimitiveCoordIndices(GEOM *geom)
{
  nwrit = 0;
  fprintf(vrmlfp, "\tcoordIndex [ ");
  GeomIterateLeafElements(geom, ElementWriteCoordIndices);
  fprintf(vrmlfp, " ]\n");

  nrcoordindices = nwrit;
}

void McrWriteVRMLHeader(FILE *fp)
{
  TRANSFORM model_xf;
  VECTOR model_rotaxis;
  float model_rotangle;

  fprintf(fp, "#VRML V2.0 utf8\n\n");

  fprintf(fp, "WorldInfo {\n  title \"%s\"\n  info [ \"Created with RenderPark (%s) using Monte Carlo radiosty\" ]\n}\n\n",
	  "Some nice model",
	  RPKHOME);

  fprintf(fp, "NavigationInfo {\n type \"WALK\"\n headlight FALSE\n}\n\n");

  model_xf = VRMLModelTransform(&model_rotaxis, &model_rotangle);
  WriteVRMLViewPoints(fp, model_xf);

  fprintf(fp, "Transform {\n  rotation %g %g %g %g\n  children [\n",
	  model_rotaxis.x, model_rotaxis.y, model_rotaxis.z, model_rotangle);
}

void McrWriteVRMLTrailer(FILE *fp)
{
  fprintf(fp, "  ]\n}\n\n");
}

static unsigned char IdFirstChar[256], IdRestChar[256];

static void initidtranstabs(void)
{
  int i;
  char *IdSpecialChars = "!$%&()*/:;<=>?@^_`|~";
  for (i=0; i<256; i++)
    IdFirstChar[i] = '_';
  for (i='a'; i<='z'; i++)
    IdFirstChar[i] = i;
  for (i='A'; i<='Z'; i++)
    IdFirstChar[i] = i;
  for (i=0; i<strlen(IdSpecialChars); i++)
    IdFirstChar[(int)IdSpecialChars[i]] = IdSpecialChars[i];
  for (i=0; i<256; i++)
    IdRestChar[i] = IdFirstChar[i];
  IdRestChar['+'] = '+';
  IdRestChar['-'] = '-';
  for (i='0'; i<='9'; i++)
    IdRestChar[i] = i;
}

static char* make_valid_vrml_id(char *id)
{
  int idlen, i, n;
  static char buf[101];
  if (!id || id[0]=='\0')
    return "";
  idlen = strlen(id);
  if (idlen > 100) {
    Warning("make_valid_vrml_id", "id '%s' is being truncated to %d characters",
	    id, 100);
  }
  n = idlen > 100 ? 100 : idlen;  /* minimum of both */
  buf[0] = IdFirstChar[(int)id[0]];
  for (i=1; i<n; i++)
    buf[i] = IdRestChar[(int)id[i]];
  buf[n] = '\0';
  return buf;
}

static void WriteMaterial(GEOM* geom)
{
  SURFACE *surf = GeomGetSurface(geom);
  MATERIAL *mat = surf->material;
  PATCH *first_patch = (surf->faces) ? surf->faces->patch : (PATCH*)NULL;
  HITREC hit;
  COLOR Rd, Rs;
  RGB rd, rs;
  float specularity;

  if (!first_patch || !mat || !mat->bsdf)
    return;

  if (mat->radiance_data != NULL) {
    /* has been written before */
    fprintf(vrmlfp, "      appearance Appearance {\n");
    fprintf(vrmlfp, "\tmaterial USE %s\n", make_valid_vrml_id((char*)mat->radiance_data));
    fprintf(vrmlfp, "      }\n");
    return;
  }

  InitHit(&hit, first_patch, (GEOM*)NULL, &first_patch->midpoint, &first_patch->normal, mat, 0.);
  Rd = BsdfScatteredPower(mat->bsdf, &hit, &first_patch->normal, BRDF_DIFFUSE_COMPONENT);
  ColorToRGB(Rd, &rd);
  Rs = BsdfScatteredPower(mat->bsdf, &hit, &first_patch->normal, BRDF_GLOSSY_COMPONENT|BRDF_SPECULAR_COMPONENT);
  ColorToRGB(Rs, &rs);
  specularity = 128.;
  
  fprintf(vrmlfp, "      appearance Appearance {\n");
  fprintf(vrmlfp, "\tmaterial DEF %s Material {\n", make_valid_vrml_id(mat->name));
  fprintf(vrmlfp, "\t  ambientIntensity 0.\n");
  if (mat->edf)
    fprintf(vrmlfp, "\t  emissiveColor %.3g %.3g %.3g\n", rd.r, rd.g, rd.b);
  else
    fprintf(vrmlfp, "\t  emissiveColor %.3g %.3g %.3g\n", 0., 0., 0.);  
  fprintf(vrmlfp, "\t  diffuseColor %.3g %.3g %.3g \n", rd.r, rd.g, rd.b);
  fprintf(vrmlfp, "\t  specularColor %.3g %.3g %.3g \n", rs.r, rs.g, rs.b);
  fprintf(vrmlfp, "\t  shininess %g\n", specularity/128. > 1. ? 1. : specularity/128.);
  fprintf(vrmlfp, "\t  transparency 0.\n");
  fprintf(vrmlfp, "\t}\n");
  fprintf(vrmlfp, "      }\n");

  mat->radiance_data = mat->name;
}

static void BeginWritePrimitive(GEOM *geom)
{
  static int wgiv=FALSE;
  fprintf(vrmlfp, "    Shape {\n");
  if (GeomIsSurface(geom))
    WriteMaterial(geom);
  fprintf(vrmlfp, "      geometry IndexedFaceSet {\n");
  if (GeomIsSurface(geom))
    fprintf(vrmlfp, "\tsolid %s\n", GeomGetSurface(geom)->material->sided ? "TRUE" : "FALSE");
  if (!renderopts.smooth_shading && !wgiv) {
    Warning(NULL, "I assume you want a smooth shaded model ...");
    wgiv = TRUE;
  }
  fprintf(vrmlfp, "\tcolorPerVertex %s\n", "TRUE");
}

static char *PrimitiveMatName(GEOM *geom)
{
  SURFACE *surf = GeomGetSurface(geom);
  if (!surf)
    return "unknown (not a surface)";
  else
    return make_valid_vrml_id(surf->material->name);
}

static void EndWritePrimitive(GEOM *geom)
{
  fprintf(vrmlfp, "      }\n"); /* end IndexedFaceSet */
  fprintf(vrmlfp, "    },\n");  /* end Shape */

  fprintf(stderr, "Shape material %s, pass %d, %d coords, %d colors, %d coordindices\n",
	  PrimitiveMatName(geom), pass, nrcoords, nrcolors, nrcoordindices);
}

static void WritePrimitivePass(GEOM *geom)
{
  BeginWritePrimitive(geom);
  WritePrimitiveCoords(geom);
  WritePrimitiveColors(geom);
  WritePrimitiveCoordIndices(geom);
  EndWritePrimitive(geom);
}

static void WritePrimitive(GEOM *geom)
{
  pass = 0;
  WritePrimitivePass(geom);
  if (leaf_element_count > FACES_PER_SET) {
    /* large set, additional passes needed */
    for (pass=1; pass <= leaf_element_count/FACES_PER_SET; pass++)
      WritePrimitivePass(geom); /* write next batch of leaf elements */
  }
}

void IteratePrimitiveGeoms(GEOMLIST *list, void (*func)(GEOM*))
{
  ForAllGeoms(geom, list) {
    if (GeomIsAggregate(geom))
      IteratePrimitiveGeoms(GeomPrimList(geom), func);
    else
      func(geom);
  } EndForAll;
}

static void ResetMaterialData(void)
{
  ForAllMaterials(mat, MaterialLib) {
    mat->radiance_data = (void*)NULL;
  } EndForAll;
}

void McrWriteVRML(FILE *fp)
{
  initidtranstabs();

  matidx = 0;
  ResetMaterialData();

  McrWriteVRMLHeader(fp);

  vrmlfp = fp;
  IteratePrimitiveGeoms(World, WritePrimitive);
#ifdef NEVER
  WriteCoords();
  WriteColors();
  WriteCoordIndices();
#endif
  McrWriteVRMLTrailer(fp);

  ResetMaterialData();
}

