//////////////////////////////////////////////////////////////////////////////
//
//  Author      : Josh Grant
//  Date        : October 16th, 2001
//  File        : ScalarArithmitic.cpp
//  Description : Implementation of the ScalarArithmitic class
//
//////////////////////////////////////////////////////////////////////////////

#include "ScalarArithmitic.h"

#include <iostream.h>

//////////////////////////////////////////////////////////////////////////////
//
// ScalarArithmitic class
//
//////////////////////////////////////////////////////////////////////////////

// required Inventor macro
SO_ENGINE_SOURCE(ScalarArithmitic);

//////////////////////////////////////////////////////////////////////////////
//
//  Description:
//    Initalizes the engine using the Inventor specific macro.
//
//////////////////////////////////////////////////////////////////////////////
void
ScalarArithmitic::initClass ()
{
  SO_ENGINE_INIT_CLASS(ScalarArithmitic, SoEngine, "Engine");
}

//////////////////////////////////////////////////////////////////////////////
//
//  Description:
//    Set up all the inputs and the output.  Also create the calculator and
//    set its inputs.
//
//////////////////////////////////////////////////////////////////////////////
ScalarArithmitic::ScalarArithmitic ()
{
  // Inventor stuff
  SO_ENGINE_CONSTRUCTOR(ScalarArithmitic);

  // Macros used to create and initialize each input field
  SO_ENGINE_ADD_INPUT(minBounds, (-1.0, -1.0, -1.0));
  SO_ENGINE_ADD_INPUT(maxBounds, ( 1.0,  1.0,  1.0));
  SO_ENGINE_ADD_INPUT(xDim,                  (10.0));
  SO_ENGINE_ADD_INPUT(yDim,                  (10.0));
  SO_ENGINE_ADD_INPUT(zDim,                  (10.0));
  SO_ENGINE_ADD_INPUT(expression,              (""));

  // A hidden input used to connect to the calculator's output field
  SO_ENGINE_ADD_INPUT(scalar,                 (0.0));

  // create the only output
  SO_ENGINE_ADD_OUTPUT(scalarField, SFScalarField);

  // creating calculator and connecting it to a field to grab the output.
  calc = new SoCalculator();
  calc->ref();
  scalar.connectFrom(&calc->oa);

  // initialize the calculator inputs
  setSizes();
  setInput();
  
  resetInput = false;
  reparse    = true;
}

//////////////////////////////////////////////////////////////////////////////
//
//  Description:
//    The calculator is not needed anymore.
//
//////////////////////////////////////////////////////////////////////////////
ScalarArithmitic::~ScalarArithmitic ()
{
  calc->unref();
}

//////////////////////////////////////////////////////////////////////////////
//
//  Description:
//    Everytime an input is modified this function is called.  Different
//    flags are set depending on which fields were changed.
//
//////////////////////////////////////////////////////////////////////////////
void
ScalarArithmitic::inputChanged (SoField *which)
{
  // expression needs to be reparsed
  if (which == &expression)
    reparse = true;
  // the input fields need to be updated
  else if (which == &xDim ||
	   which == &yDim ||
	   which == &zDim ||
           which == &minBounds ||
	   which == &maxBounds)
    resetInput = true;
}

//////////////////////////////////////////////////////////////////////////////
//
//  Description:
//    Called each time the output field is referenced with getValue()
//
//////////////////////////////////////////////////////////////////////////////
void
ScalarArithmitic::evaluate ()
{
  if (resetInput) {
    setSizes();
    setInput();
    resetInput = false;
  }

  if (reparse) {
    parseString();
    reparse = false;
  }

  // These calls are actually where the calculator will indirectly do the
  // calculations.  Once getValues() is called on scalar then evaluate() will
  // be called in the calculator and the values will be then be set to the
  // SFScalarField output.
  SO_ENGINE_OUTPUT(scalarField, SFScalarField,
		   setValue(dims, scalar.getValues(0)));
  // Don't forget to set the bounds
  SO_ENGINE_OUTPUT(scalarField, SFScalarField,
		   setBounds(minBounds.getValue(), maxBounds.getValue()));
}

//////////////////////////////////////////////////////////////////////////////
//
//  Description:
//    Sets the number of points needed by the input fields of the
//    Calculator. 
//
//////////////////////////////////////////////////////////////////////////////
void
ScalarArithmitic::setSizes ()
{
  // set up the dimensions
  dims[0] = (int)(xDim.getValue());
  if (dims[0] < 1) {
    dims[0] = 1;
    xDim.setValue(1.0);
  }
  dims[1] = (int)(yDim.getValue());
  if (dims[1] < 1) {
    dims[1] = 1;
    yDim.setValue(1.0);
  }  
  dims[2] = (int)(zDim.getValue());
  if (dims[2] < 1) {
    dims[2] = 1;
    zDim.setValue(1.0);
  }

  int numPoints = dims[0] * dims[1] * dims[2];

  // set the sizes of each input and its connected output
  calc->a.setNum(numPoints);
  calc->b.setNum(numPoints);
  calc->c.setNum(numPoints);
  scalar.setNum(numPoints);
}

//////////////////////////////////////////////////////////////////////////////
//
//  Description:
//    Setting the input consists of computing the voxel size based on the
//    current input bounds.  Then incrementing through each point in the 3D
//    box and individually setting the x,y,z input values of the calculator.
//    
//////////////////////////////////////////////////////////////////////////////
void
ScalarArithmitic::setInput ()
{
  SbVec3f min, size, voxelSize;
  float *xval, *yval, *zval;
  int i, j, k, index = 0;

  // determine the voxel size of the scalar field
  min  = minBounds.getValue();
  size = maxBounds.getValue() - min;
  
  if (dims[0] <= 1)
    voxelSize[0] = 0.0;
  else
    voxelSize[0] = size[0] / (float)(dims[0] - 1);

  if (dims[1] <= 1)
    voxelSize[1] = 0.0;
  else
    voxelSize[1] = size[1] / (float)(dims[1] - 1);

  if (dims[2] <= 1)
    voxelSize[2] = 0.0;
  else
    voxelSize[2] = size[2] / (float)(dims[2] - 1);

  // open each input for editing.  This is more efficient then calling
  // set1Value each time.
  xval = calc->a.startEditing();
  yval = calc->b.startEditing();
  zval = calc->c.startEditing();
  for (k = 0; k < dims[2]; k++) {
    for (j = 0; j < dims[1]; j++) {
      for (i = 0; i < dims[0]; i++) {
	xval[index] = min[0] + voxelSize[0]*i;
	yval[index] = min[1] + voxelSize[1]*j;
	zval[index] = min[2] + voxelSize[2]*k;
	index++;
      }
    }
  }
  // now notify the database that the field has changed
  calc->a.finishEditing();
  calc->b.finishEditing();
  calc->c.finishEditing();
}

//////////////////////////////////////////////////////////////////////////////
//
//  Description:
//    The input interface of the ScalarArithmitic engine allows for the
//    expression to be written in the conventional x,y,z notation.  However,
//    the SoCalculator interface does not have the expression set up like
//    this.  So all x->a, y->b, z->c, then the output field is appended to
//    the expression and sent to the calculator.
//    
//////////////////////////////////////////////////////////////////////////////
void
ScalarArithmitic::parseString ()
{
  char *str;
  int len, i;

  // get the length of the expression
  len = expression.getValue().getLength();
  // new expression will be 3 more than the length to account for 'oa='
  str = new char[(len + 3) + 1];

  // copy and concatinate string together
  strcpy(str, "oa=");
  strcat(str, expression.getValue().getString());

  // starting after the '=' character look at each character and replace all
  // x->a, y->b, z->c.
  for (i = 3; i < len + 3; i++) {
    // The x is tricky because we have to make sure we aren't modifing the
    // 'exp' operator.
    if (str[i] == 'x') {
      if (i == 3 || i == (len + 3) - 1)
	str[i] = 'a';
      else if (str[i - 1] != 'e' ||
	       str[i + 1] != 'p')
	str[i] = 'a';
    }
    else if (str[i] == 'y')
      str[i] = 'b';
    else if (str[i] == 'z')
      str[i] = 'c';
  }

  // set the calculator's expression
  calc->expression.setValue(str);
  // delete the string created
  delete [] str;
}
