//////////////////////////////////////////////////////////////////////////////
//
//  Author      : Josh Grant
//  Date        : February 12th, 2002
//  File        : main.cpp.in
//  Description : A small Open Inventor program to demonstrate how to use the
//                MarchingCubes engine.
//
//////////////////////////////////////////////////////////////////////////////

#include <iostream.h>
#include <string.h>
#include <stdlib.h>

///////////////////////////////////////////////////////////////////
//
// Q: Why are there '@' characters around the word Gui, and why doesn't it
//    say 'Xt'?
// A: This file is not your typical C++ file.  It has special macros embedded
//    in it which will be interpreted by the script 'config.status'.  All
//    characters with '@' characters wrapped around them are considered
//    macros.  When the 'config.status' script parses through the file it
//    replaces all occurrences of these strings with those determined by the
//    'configure' script.  This allows the code to be portable across
//    multiple platforms with differing windowing toolkits (SoXt, SoQt,
//    SoGtk, etc.)
//
//    The compilable main.cpp is generated by running the script
//     > config.status --file=main.cpp
//
//    This should always be the file you update.
//   
//    ***** NEVER CHANGE THE main.cpp FILE!!!!  THE config.status
//    ***** SCRIPT WILL ONLY WRITE OVER IT!!!!!
//
////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////
// To be replaced by the config.status script
#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/viewers/SoXtExaminerViewer.h>
////////////////////////////////////////////////////////////

#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoIndexedFaceSet.h>
#include <Inventor/nodes/SoVertexProperty.h>
#include <Inventor/nodes/SoShapeHints.h>
#include <Inventor/nodes/SoTransform.h>
#include <Inventor/draggers/SoTranslate1Dragger.h>
#include <Inventor/engines/SoCalculator.h>

#include "MarchingCubes.h"

void
setData (SFScalarField &sfield, int xDim, int yDim, int zDim)
{
  // set dimensions of mesh
  int index, dims[3] = {xDim, yDim, zDim};
  // allocate memory for data
  float *data = new float[dims[0]*dims[1]*dims[2]];

  // set the minimum and maximum bounds of the exponential function
  SbVec3f min(-1.0, -1.0, -1.0);
  SbVec3f max( 1.0,  1.0,  1.0);
  SbVec3f size = max - min;

  // compute the size of each voxel
  SbVec3f voxel, p;
  voxel[0] = (dims[0] > 1) ? size[0] / (float)(dims[0] - 1) : 0.0;
  voxel[1] = (dims[1] > 1) ? size[1] / (float)(dims[1] - 1) : 0.0;
  voxel[2] = (dims[2] > 1) ? size[2] / (float)(dims[2] - 1) : 0.0;

  // iterate through each grid point
  index = 0;
  for (int k = 0; k < dims[2]; k++) {
    p[2] = min[2] + voxel[2]*k;
    for (int j = 0; j < dims[1]; j++) {
      p[1] = min[1] + voxel[1]*j;
      for (int i = 0; i < dims[0]; i++) {
	p[0] = min[0] + voxel[0]*i;

	data[index] = p[0]*p[0] + p[1]*p[1] + p[2]*p[2];
	
	index++;
      }
    }
  }

  // set data, but don't copy the data since we already have a valid copy
  // allocated.  This is just an option provided by the SFScalarField class.
  // Setting it to true will have the same effect if just means the
  // SFScalarField class will allocate memory for the data and copy all the
  // values to its local space.
  sfield.setValue(dims, data, false);

}

//////////////////////////////////////////////////////////////////////////////
//
// Description:
//   creates a simple scene with a surface created by the MarchingCubes
//   engine.
//
//////////////////////////////////////////////////////////////////////////////
SoSeparator *makeScene ()
{
  SoSeparator *root = new SoSeparator();

  // create MarchingCubes engine and connect fields
  MarchingCubes *mcubes = new MarchingCubes();
  mcubes->ref();

  setData(mcubes->data, 20, 20, 20);

  // set isoValue, in this case this means find the surface of the sphere
  // where the radius is 1.0
  mcubes->isoValue = 1.0;

  // MarchingCubes gives the triangles with the vertices ordered clockwise
  SoShapeHints *hints = new SoShapeHints();
  hints->vertexOrdering.setValue(SoShapeHints::CLOCKWISE);
  root->addChild(hints);

  // Create a VertexProperty because they are more efficient the
  // SoCoordinate3 nodes.
  SoVertexProperty *vprop = new SoVertexProperty();
  vprop->ref();
  vprop->vertex.connectFrom(&mcubes->points);
  vprop->normal.connectFrom(&mcubes->normals);

  // connect the indexes from MarchingCubes
  SoIndexedFaceSet *faceSet = new SoIndexedFaceSet();
  faceSet->vertexProperty = vprop;
  faceSet->coordIndex.connectFrom(&mcubes->indexes);
  root->addChild(faceSet);

  // Translate the dragger by two in the y direction
  SoTransform *trans = new SoTransform();
  trans->translation.setValue(0, 2, 0);
  root->addChild(trans);

  // Create the dragger and connect an SoCalculator to it.
  // Why?  This allows for a slick way to make the program interactive.
  // Whenever the dragger is moved the 'isoValue' field of the MarchingCubes
  // engine will be updated, as well as the isosurface it outputs.
  SoTranslate1Dragger *dragger = new SoTranslate1Dragger();
  SoCalculator *calc = new SoCalculator();
  // translate the dragger to <1, 0, 0>. 
  dragger->translation.setValue(1, 0, 0);
  // connect the dragger translation field to an input to the calculator
  calc->A.connectFrom(&dragger->translation);
  // Now set the expression for the calculator to evaluate.  This means
  // everytime the input vector field 'A' changes assign the x component of
  // the vector to the output variable 'a' of the calculator.  In terms of
  // this program, that means...everytime the dragger moves the current x
  // position of the dragger will be the new isoValue for the MarchingCubes
  // engine to compute.
  calc->expression.setValue("oa = A[0]");
  // connect the output of the engine to the isoValue field of MarchingCubes
  mcubes->isoValue.connectFrom(&calc->oa);

  root->addChild(dragger);
 
  return root;
}

int main (int argc, char **argv)
{
  // To be replaced by the config.status script
  // Initialize Inventor and Xt
  Widget myWindow = SoXt::init(argv[0]);
  
  // Initialize new classes
  SFScalarField::initClass();
  MarchingCubes::initClass();

  // To be replaced by the config.status script
  // create viewer and editors
  SoXtExaminerViewer *myViewer = new SoXtExaminerViewer(myWindow);
  myViewer->setTitle("Marching Cubes Example");

  // attach the nodes to the viewer and editors
  myViewer->setSceneGraph(makeScene());

  // show the viewer
  myViewer->show();

  // To be replaced by the config.status script
  SoXt::show(myWindow);
  SoXt::mainLoop();
}
