//////////////////////////////////////////////////////////////////////////////
//
//  Author      : Josh Grant
//  Date        : October 16th, 2001
//  File        : 3dgrapher.cpp
//  Description : A small Open Inventor application to interactively
//                manipulate a scalar field
//
//////////////////////////////////////////////////////////////////////////////

#include <config.h>

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

#include <X11/Intrinsic.h>
#include <X11/Xlib.h>
#include <Xm/Xm.h>
#include <Xm/PushB.h>

#include <Inventor/SoSceneManager.h>
#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/viewers/SoXtExaminerViewer.h>
#include <Inventor/Xt/SoXtMaterialEditor.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoText2.h>
#include <Inventor/nodes/SoIndexedFaceSet.h>
#include <Inventor/nodes/SoVertexProperty.h>
#include <Inventor/nodes/SoShapeHints.h>
#include <Inventor/nodes/SoTransform.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>
#include <Inventor/nodes/SoFont.h>
#include <Inventor/engines/SoCalculator.h>

#include "SFScalarField.h"
#include "SFScalarFieldBlend.h"
#include "ScalarArithmitic.h"
#include "MarchingCubes.h"
#include "ParameterEditor.h"
#include "BoundingBox.h"
#include "ExaminerViewerPlus.h"

ParameterEditor::PE_t parameters;
SoMaterial *mat;

#define rgb_width 24
#define rgb_height 24
static char rgb_bits[] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x44, 0x00, 0x00, 
  0x44, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x44, 0x00, 0x00, 0x44, 0x00, 0x00, 
  0x44, 0x78, 0x00, 0x00, 0x04, 0x00, 0x00, 0x02, 0x00, 0x00, 0xe2, 0x00, 
  0x00, 0x42, 0x00, 0x00, 0x44, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x1e, 0x00, 0x00, 0x22, 0x00, 0x00, 0x22, 0x00, 0x00, 0x1e, 
  0x00, 0x00, 0x22, 0x00, 0x00, 0x22, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 
};

#define pencil_width 24
#define pencil_height 24
static char pencil_bits[] = {
  0x00, 0x00, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x20, 0x0c, 0x00, 0x10, 0x10, 
  0x00, 0x70, 0x10, 0x00, 0x98, 0x10, 0x00, 0x08, 0x0b, 0x00, 0x0c, 0x0c, 
  0x00, 0x06, 0x04, 0x00, 0x02, 0x06, 0x00, 0x03, 0x03, 0x80, 0x01, 0x01, 
  0x80, 0x80, 0x01, 0xc0, 0x80, 0x00, 0xc0, 0xc0, 0x00, 0x40, 0x40, 0x00, 
  0x40, 0x60, 0x00, 0xc0, 0x20, 0x00, 0xc0, 0x31, 0x00, 0xc0, 0x1f, 0x00, 
  0xc0, 0x0f, 0x00, 0xc0, 0x03, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 
};

typedef struct {
  int width;
  int height;
  char *bits;
} bitmap_t;

//////////////////////////////////////////////////////////////////////////////
//
// Description:
//   creates a simple scene with an isosurface and returns the root
//
//////////////////////////////////////////////////////////////////////////////
SoSeparator *makeScene (char *equation, char *bEquation, float isovalue)
{
  SoSeparator *root = new SoSeparator();

  SoSeparator *scene = new SoSeparator();
  SoPerspectiveCamera *cam = new SoPerspectiveCamera();
  scene->addChild(cam);
  
  // create a ScalarArithmitic engine and connect the output to the
  // MarchingCubes data field
  ScalarArithmitic *arith1 = new ScalarArithmitic();
  arith1->ref();
  arith1->expression.setValue(equation);
  arith1->xDim = 20.0;
  arith1->yDim = 20.0;
  arith1->zDim = 20.0;
  
  ScalarArithmitic *arith2 = new ScalarArithmitic();
  arith2->ref();
  arith2->expression.setValue(bEquation);
  arith2->xDim.connectFrom(&arith1->xDim);
  arith2->yDim.connectFrom(&arith1->yDim);
  arith2->zDim.connectFrom(&arith1->zDim);
  arith2->minBounds.connectFrom(&arith1->minBounds);
  arith2->maxBounds.connectFrom(&arith1->maxBounds);
  
  // create a blender
  //	
  SFScalarFieldBlend *blend = new SFScalarFieldBlend();
  blend->blendValue = 0.0;
  blend->input1.connectFrom(&arith1->scalarField);
  blend->input2.connectFrom(&arith2->scalarField);
  
  // the bounding box
  BoundingBox *axis = new BoundingBox();
  axis->minBounds.connectFrom(&arith1->minBounds);
  axis->maxBounds.connectFrom(&arith1->maxBounds);
  scene->addChild(axis);

  SoSeparator *surfSep = new SoSeparator();
  
  // create a material
  mat = new SoMaterial();
  mat->diffuseColor.setValue(0.0, 0.5, 1.0);
  mat->specularColor.setValue(0.5, 0.5, 0.5);
  surfSep->addChild(mat);

  // create MarchingCubes object and connect fields
  MarchingCubes *mcubes = new MarchingCubes();
  mcubes->ref();
  mcubes->data.connectFrom(&blend->output);
  mcubes->isoValue = isovalue;

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

  // calculator used to scale the mesh coordinates to fit within the
  // BoundingBox node
  SoCalculator *transCalc = new SoCalculator();
  transCalc->ref();
  transCalc->A.connectFrom(&arith1->minBounds);
  transCalc->B.connectFrom(&arith1->maxBounds);
  transCalc->expression.setValue(SbString("oA = B - A"));

  // connecting output of calculator to the transform
  SoTransform *surfTrans = new SoTransform();
  surfTrans->translation.connectFrom(&arith1->minBounds);
  surfTrans->scaleFactor.connectFrom(&transCalc->oA);
  surfSep->addChild(surfTrans);  

  // connect the MarchingCubes points and normals to an SoVertexProperty node
  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);
  surfSep->addChild(faceSet);
  
  scene->addChild(surfSep);
  root->addChild(scene);

  SoSeparator *textSep = new SoSeparator();
  SoTransform *trans = new SoTransform();
  trans->translation.setValue(0.95, -0.95, 0.0);
  textSep->addChild(trans);
  SoFont *font = new SoFont();
  font->size.setValue(20);
  textSep->addChild(font);
  SoText2 *text = new SoText2();
  text->justification.setValue(SoText2::RIGHT);
  textSep->addChild(text);
#ifndef NO_SOTEXT2
  root->addChild(textSep);
#endif
  
  parameters.xDim       = &arith1->xDim;
  parameters.yDim       = &arith1->yDim;
  parameters.zDim       = &arith1->zDim;
  parameters.minBounds  = &arith1->minBounds;
  parameters.maxBounds  = &arith1->maxBounds;
  parameters.expression = &arith1->expression;
  parameters.blendExpression = &arith2->expression;
  parameters.blendval   = &blend->blendValue;
  parameters.text       = &text->string;
  parameters.data       = &mcubes->data;
  parameters.isoval     = &mcubes->isoValue;
  parameters.vertBlend = &mcubes->blendPercent;
  
  return root;
}

void
buttonCallback (Widget button, SoXtComponent *comp, XtPointer wdgData)
{
  comp->show();
}

void
addButton (SoXtFullViewer *viewer, SoXtComponent *comp,
	   char *name, bitmap_t *bitmap)
{
  int n, depth;
  Arg args[10];
  Pixel fg, bg;
  Pixmap pixels;
  Widget parent    = viewer->getAppPushButtonParent();
  Display *display = viewer->getDisplay();
  Screen *scr      = XtScreen(parent);
  Drawable d       = RootWindow(display, XScreenNumberOfScreen(scr));

  XtVaGetValues(parent, XtNdepth, &depth, NULL);
  
  n = 0;
  XtSetArg(args[n], XmNforeground, &fg); n++;
  XtSetArg(args[n], XmNbackground, &bg); n++;
  XtGetValues(parent, args, n);

  pixels = XCreatePixmapFromBitmapData(display, d, bitmap->bits,
				       bitmap->width, bitmap->height,
                                       fg, bg, depth);
  n = 0;
  XtSetArg(args[n], XmNmarginHeight, 0); n++;
  XtSetArg(args[n], XmNmarginWidth, 0); n++;
  XtSetArg(args[n], XmNshadowThickness, 2); n++;
  XtSetArg(args[n], XmNlabelType, XmPIXMAP); n++;
  XtSetArg(args[n], XmNlabelPixmap, pixels); n++;
  Widget button = XtCreateWidget(name,
				 xmPushButtonWidgetClass,
				 parent, args, n);
  XtAddCallback(button, XmNactivateCallback,
		(XtCallbackProc)buttonCallback, comp);
  viewer->addAppPushButton(button);
}

//////////////////////////////////////////////////////////////////////////////
//
// Description:
//   prints the program usage to stdout
//
//////////////////////////////////////////////////////////////////////////////
void printUsage (char *progname)
{
  const char *usage =
    "Usage: %s [\"equation\"]\n"
    "\n"
    "Description: A small Open Inventor application to interactively\n"
    "  manipulate a scalar field defined by \"c=equation\".  Where\n"
    "  'equation' is any valid scalar equation defined using the ANSI C\n"
    "  math libraries.\n"
    "      ex: sqrt(x*x + y*y + z*z)\n"
    "  would equal a sphere.  An interface is provided to modify\n"
    "  parameters of the scalar field.  The surface is created\n"
    "  using the Marching Cubes algorithm developed by William Lorensen.\n"
    "\n";

  printf(usage, progname);
}

int main (int argc, char **argv)
{
  float isovalue = 0.0;
  char *equation = "x*x+y*y+z*z-sqrt(x*x+y*y)-2*x*y*z+0.2";
  char *bEquation = "x*x+y*y+z*z+sin(4*x*z)*cos(4*y)-0.5";

  // check the command line parameters
  if (argc > 2 || (argc == 2 && !strcmp(argv[1], "-h"))) {
    printUsage(argv[0]);
    exit(1);
  }
  else if (argc == 2)
    equation = argv[1];
  
  // Initialize Inventor and Xt
  Widget myWindow = SoXt::init(argv[0]);
  
  // Initialize new classes
  SFScalarField::initClass();
  SFScalarFieldBlend::initClass();
  ScalarArithmitic::initClass();
  MarchingCubes::initClass();
  BoundingBox::initClass();

  // create viewer and editors
#ifdef HAVE_COIN_SOXT
  SoXtExaminerViewer *myViewer = new SoXtExaminerViewer(myWindow);
#else
  ExaminerViewerPlus *myViewer = new ExaminerViewerPlus(myWindow);
#endif

  // attach the nodes to the viewer and editors
  myViewer->setTitle("3D Grapher");
  myViewer->setSceneGraph(makeScene(equation, bEquation, isovalue));
  myViewer->viewAll();
  myViewer->saveHomePosition();

#ifndef HAVE_COIN_SOXT
  ParameterEditor *myParamEditor  = new ParameterEditor();
  myParamEditor->setTitle("Parameters");
  myParamEditor->attach(&parameters);
  bitmap_t bitmap;
  bitmap.width  = pencil_width;
  bitmap.height = pencil_height;
  bitmap.bits   = pencil_bits;
  addButton(myViewer, myParamEditor, "params", &bitmap);
  
  SoXtMaterialEditor *myMatEditor = new SoXtMaterialEditor();
  myMatEditor->attach(mat);
  bitmap.width  = rgb_width;
  bitmap.height = rgb_height;
  bitmap.bits   = rgb_bits;
  addButton(myViewer, myMatEditor, "mat", &bitmap);

#endif
  
  // show the viewer
  myViewer->show();
  SoXt::show(myWindow);
  
  SoXt::mainLoop();
}
