/* Author     : Josh Grant
   Date       : October 20th, 2000
   Description: This program uses the classes SoUnitCube and
                SoBouncingSpheres to illustrate any number of spheres
		bouncing around within a cube created by SoUnitCube.  The
		attributes of each sphere are determined randomly at
		runtime.  They include the
		  number of spheres - ranging from 1 to maxnum specified on
		                      the command line
		  radius            - range from 0.0 to 0.25
		  velocity          - each coordinate ranges from 0.0 to 0.2
		  location          - each coordinate ranges from -1.0 to 1.0
		  colors            - each component ranges from 0.0 to 1.0

		All of these parameters are then passed to the
		SoBouncingSpheres class for processing.  In order to get the
		spheres within the SoBouncingSpheres node to move an
		SoTimerSensor is created to schedule the executing of the
		call to SoBouncingSpheres::moveSpheres().  The interval of
		the Timer can be specified at the command line.

		Type -h or -help at the command line for more information.
*/

#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/viewers/SoXtExaminerViewer.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/sensors/SoTimerSensor.h>

#include <SoBouncingSpheres.h>
#include <SoUnitCube.h>
#include <iostream.h>

int sphereNum = 0;
const int maxSpheres = 10;
float timer = 0.04;

////////////////////////////////////////////////////////////
//
// The scene graph is constructed as indicated in the
// diagram below.
//
//                           root
//                             |
//                     -------------------
//                     |                 |
//                 SoUnitCube     SoBouncingSpheres

SoSeparator *makeScene (int sphereNum) {
  int numSpheres, i, j;
  float *radii, loc;
  SbVec3f *velocities, *locations;
  SbColor *colors;
  
  SoSeparator *root = new SoSeparator();

  SoUnitCube *unitCube = new SoUnitCube();
  root->addChild(unitCube);

  if (sphereNum > 0)
    numSpheres = sphereNum;
  else 
    numSpheres = (int)((double)maxSpheres * drand48() + 1.0);
  
  radii      = new float[numSpheres];
  velocities = new SbVec3f[numSpheres];
  locations  = new SbVec3f[numSpheres];
  colors     = new SbColor[numSpheres];
  
  for (i = 0; i < numSpheres; i++) {
    radii[i] = drand48() / 4.0;

    for (j = 0; j < 3; j++) {
      velocities[i][j] = (2.0 * drand48()) / 10.0;
      
      loc              = 2.0 * (drand48() - 0.5);
      if (loc < 0.0)
	locations[i][j] = loc + radii[i];
      else
	locations[i][j] = loc - radii[i];
      
      colors[i][j]     = drand48();
    }
  }
    
  SoBouncingSpheres *bouncing = new SoBouncingSpheres(numSpheres);
  bouncing->setRadii     (0, numSpheres, radii);
  bouncing->setVelocities(0, numSpheres, velocities);
  bouncing->setLocations (0, numSpheres, locations);
  bouncing->setColors    (0, numSpheres, colors);
  root->addChild(bouncing);
  
  return root;
}

////////////////////////////////////////////////////////////
//
//  This routine is repeatedly called from the Inventor
//  main loop.

void animate(void *data, SoSensor *) {
  
  //  Retrieve the sphere and its transform from the scene graph.
  SoSeparator       *root     = (SoSeparator       *)data;
  SoUnitCube        *unitCube = (SoUnitCube        *)(root->getChild(0));
  SoBouncingSpheres *bouncing = (SoBouncingSpheres *)(root->getChild(1));

  bouncing->moveSpheres(unitCube);
}

// checkArgs() checks the command line arguments and assigns all variables
// not listed on command to zero.
int checkArgs(int myargc, char **myargv) {
  int i;
  
  for (i = 1; i < myargc; i += 2) {
    if (i + 1 == myargc) return 0;
 
    if (!strcmp(myargv[i], "-spheres"))
       sphereNum = atoi(myargv[i + 1]);
    else if (!strcmp(myargv[i], "-timer"))
       timer = atof(myargv[i + 1]);
    else if (!strcmp(myargv[i], "-h"))
      return 0;
    else if (!strcmp(myargv[i], "-help"))
      return 0;
    else
      return 0;
  }
  return 1;
}

void printUsage() {
  cout << "Usage - bounce" << endl;
  cout << "  Description: Creates an interactive 3D examiner viewer with\n"
       << "               <spheres> of spheres within a unit cube bouncing\n"
       << "               around within the cube. When two or more spheres\n"
       << "               intersect their colors are changed.  \n"
       << "               The scene is updated every <timer> seconds."
       << endl;
  cout << "   -<spheres>: Maximum number of spheres to be displayed within\n"
       << "               the unit cube\n"
       << "     -<timer>: Determines number of frames per second.\n"
       << endl;
}
  

////////////////////////////////////////////////////////////
//
// The main program gets a window from X, 
// puts an interactive 3D examinerViewer in it, 
// attaches the scene graph to the viewer,
// renders the scene graph,
// and hands control over to the Inventor main loop.
void main(int argc, char **argv) {
  if (!checkArgs(argc, argv)){
    printUsage();
    exit(1);
  }
  
  // Initialize Inventor and Xt
  Widget myWindow = SoXt::init(argv[0]);
  if (myWindow == NULL) exit(1);

  srand48(getpid());
  // Create a scene graph
  SoSeparator *root = makeScene(sphereNum);

  SoTimerSensor *sensor = new SoTimerSensor(animate, root);
  sensor->setInterval(SbTime(timer)); // 50 frames per second
  sensor->schedule();

  SoXtExaminerViewer *myViewer = new SoXtExaminerViewer(myWindow);
  myViewer->setSceneGraph(root);
  myViewer->setTitle("Sphere in Cube");
  myViewer->show();

  SoXt::show(myWindow);
  SoXt::mainLoop();
}