text: //===========================================================================
  /*
      Software License Agreement (BSD License)
      Copyright (c) 2003-2012, CHAI3D.
      (www.chai3d.org)
  
      All rights reserved.
  
      Redistribution and use in source and binary forms, with or without
      modification, are permitted provided that the following conditions
      are met:
  
      * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
  
      * Redistributions in binary form must reproduce the above
      copyright notice, this list of conditions and the following
      disclaimer in the documentation and/or other materials provided
      with the distribution.
  
      * Neither the name of CHAI3D nor the names of its contributors may
      be used to endorse or promote products derived from this software
      without specific prior written permission.
  
      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
      FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
      COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
      INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
      BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
      LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
      CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
      LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
      ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
      POSSIBILITY OF SUCH DAMAGE. 
  
      \author    <http://www.chai3d.org>
      \author    Francois Conti
      \version   $MAJOR.$MINOR.$RELEASE $Rev: 843 $
  */
  //===========================================================================
  
  //---------------------------------------------------------------------------
  #include <assert.h>
  #include <math.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  //---------------------------------------------------------------------------
  #include "chai3d.h"
  //---------------------------------------------------------------------------
  #ifndef MACOSX
  #include "GL/glut.h"
  #else
  #include "GLUT/glut.h"
  #endif
  //---------------------------------------------------------------------------
  
  //---------------------------------------------------------------------------
  // DECLARED CONSTANTS
  //---------------------------------------------------------------------------
  
  // mouse menu options (right button)
  const int OPTION_FULLSCREEN     = 1;
  const int OPTION_WINDOWDISPLAY  = 2;
  
  // use stereo display
  const bool USE_STEREO_DISPLAY   = false;
  
  
  //---------------------------------------------------------------------------
  // DECLARED VARIABLES
  //---------------------------------------------------------------------------
  
  // width and height of the current window display
  int displayW = 0;
  int displayH = 0;
  
  // a world that contains all objects of the virtual environment
  cWorld* world;
  
  // a camera that renders the world in the window display
  cCamera* camera;
  
  // a light source to illuminate the objects in the world
  cSpotLight *light;
  
  // a virtual object
  cMultiMesh* object;
  
  // a haptic device handler
  cHapticDeviceHandler* handler;
  
  // a pointer to the current haptic device
  cGenericHapticDevice* hapticDevice;
  
  // a virtual tool representing the haptic device in the scene
  cToolCursor* tool;
  
  // a label to display the rate [Hz] at which the simulation is running
  cLabel* labelHapticRate;
  
  // indicates if the haptic simulation currently running
  bool simulationRunning = false;
  
  // indicates if the haptic simulation has terminated
  bool simulationFinished = false;
  
  // frequency counter to measure the simulation haptic rate
  cFrequencyCounter frequencyCounter;
  
  // root resource path
  string resourceRoot;
  
  
  //---------------------------------------------------------------------------
  // DECLARED MACROS
  //---------------------------------------------------------------------------
  // convert to resource path
  #define RESOURCE_PATH(p)    (char*)((resourceRoot+string(p)).c_str())
  
  
  //---------------------------------------------------------------------------
  // DECLARED FUNCTIONS
  //---------------------------------------------------------------------------
  
  // callback when the window display is resized
  void resizeWindow(int w, int h);
  
  // callback when a keyboard key is pressed
  void keySelect(unsigned char key, int x, int y);
  
  // callback when the right mouse button is pressed to select a menu item
  void menuSelect(int value);
  
  // function called before exiting the application
  void close(void);
  
  // main graphics timer and callback
  void graphicsTimer(int data);
  void updateGraphics(void);
  
  // main haptics loop
  void updateHaptics(void);
  
  
  //===========================================================================
  /*
      DEMO:    chrome.cpp
  
      This demonstration loads a 3D face by using the file loader
      functionality of the cMesh class. A finger-proxy algorithm is used to
      render the forces. Spherical mapping is then generated by loading a
      texture.
  
      In the main haptics loop function  "updateHaptics()" , the position
      of the haptic device is retrieved at each simulation iteration.
      The interaction forces are then computed and sent to the device.
      Finally, a simple dynamics model is used to simulate the behavior
      of the object.
  */
  //===========================================================================
  
  int main(int argc, char* argv[])
  {
      //-----------------------------------------------------------------------
      // INITIALIZATION
      //-----------------------------------------------------------------------
  
      printf ("\n");
      printf ("-----------------------------------\n");
      printf ("CHAI3D\n");
      printf ("Demo: 22-chrome\n");
      printf ("Copyright 2003-2012\n");
      printf ("-----------------------------------\n");
      printf ("\n\n");
      printf ("Keyboard Options:\n\n");
      printf ("[1] - Texture ON\n");
      printf ("[2] - Texture OFF\n");
      printf ("[3] - Wireframe ON\n");
      printf ("[4] - Wireframe OFF\n");
      printf ("[5] - Normals ON\n");
      printf ("[6] - Normals OFF\n");
      printf ("[x] - Exit application\n");
      printf ("\n\n>\r");
  
      // parse first arg to try and locate resources
      resourceRoot = string(argv[0]).substr(0,string(argv[0]).find_last_of("/\\")+1);
  
  
      //-----------------------------------------------------------------------
      // WORLD - CAMERA - LIGHTING
      //-----------------------------------------------------------------------
  
      // create a new world.
      world = new cWorld();
  
      // set the background color of the environment
      world->m_backgroundColor.setBlack();
  
      // create a camera and insert it into the virtual world
      camera = new cCamera(world);
      world->addChild(camera);
  
      // position and oriente the camera
      camera->set( cVector3d (3.0, 0.0, 0.6),    // camera position (eye)
                   cVector3d (0.0, 0.0, 0.0),    // lookat position (target)
                   cVector3d (0.0, 0.0, 1.0));   // direction of the (up) vector
  
      // set the near and far clipping planes of the camera
      // anything in front/behind these clipping planes will not be rendered
      camera->setClippingPlanes(0.01, 10.0);
  
      // Enable shadow casting
      camera->setUseShadowCasting(true);
  
      // create a light source
      light = new cSpotLight(world);
  
      // attach light to camera
      camera->addChild(light);    
  
      // enable light source
      light->setEnabled(true);                   
  
      // position the light source
      light->setLocalPos( 0.0, 0.5, 0.0);             
  
      // define the direction of the light beam
      light->setDir(-3.0,-0.5, 0.0);             
  
      // enable this light source to generate shadows
      light->setShadowMapEnabled(true);
  
  
      //-----------------------------------------------------------------------
      // HAPTIC DEVICES / TOOLS
      //-----------------------------------------------------------------------
  
      // create a haptic device handler
      handler = new cHapticDeviceHandler();
  
      // get access to the first available haptic device
      handler->getDevice(hapticDevice, 0);
  
      // retrieve information about the current haptic device
      cHapticDeviceInfo info = hapticDevice->getSpecifications();
  
      // if the haptic devices carries a gripper, enable it to behave like a user switch
      hapticDevice->setEnableGripperUserSwitch(true);
  
      // create a 3D tool and add it to the world
      tool = new cToolCursor(world);
      world->addChild(tool);
  
      // connect the haptic device to the tool
      tool->setHapticDevice(hapticDevice);
  
      // initialize tool by connecting to haptic device
      tool->start();
  
      // map the physical workspace of the haptic device to a larger virtual workspace.
      tool->setWorkspaceRadius(1.0);
  
      // define the radius of the tool (sphere)
      double toolRadius = 0.04;
  
      // define a radius for the tool
      tool->setRadius(toolRadius);
  
      // hide the device sphere. only show proxy.
      tool->setShowContactPoints(true, false);
  
      // enable if objects in the scene are going to rotate of translate
      // or possibly collide against the tool. If the environment
      // is entirely static, you can set this parameter to "false"
      tool->enableDynamicObjects(true);
  
      // read the scale factor between the physical workspace of the haptic
      // device and the virtual workspace defined for the tool
      double workspaceScaleFactor = tool->getWorkspaceScaleFactor();
  
      // define a maximum stiffness that can be handled by the current
      // haptic device. The value is scaled to take into account the
      // workspace scale factor
      double stiffnessMax = info.m_maxLinearStiffness / workspaceScaleFactor;
  
  
      //-----------------------------------------------------------------------
      // CREATING OBJECT
      //-----------------------------------------------------------------------
  
      // create a virtual mesh
      object = new cMultiMesh();
  
      // add object to world
      world->addChild(object);
  
      // set the position of the object at the center of the world
      object->setLocalPos(0.0, 0.0, 0.0);
  
      // rotate the object 90 degrees
      object->rotateAboutGlobalAxisDeg(cVector3d(0,0,1), 90);
  
      // load an object file
      bool fileload;
      fileload = object->loadFromFile(RESOURCE_PATH("resources/models/face/face.obj"));
      if (!fileload)
      {
          #if defined(_MSVC)
          fileload = object->loadFromFile("../../../bin/resources/models/face/face.obj");
          #endif
      }
      if (!fileload)
      {
          printf("Error - 3D Model failed to load correctly.\n");
          close();
          return (-1);
      }
  
      // compute a boundary box
      object->computeBoundaryBox(true);
  
      // get dimensions of object
      double size = cSub(object->getBoundaryMax(), object->getBoundaryMin()).length();
  
      // resize object to screen
      if (size > 0)
      {
          object->scale( 2.0 * tool->getWorkspaceRadius() / size);
      }
  
      // compute collision detection algorithm
      object->createAABBCollisionDetector(toolRadius);
  
      cMaterial mat;
      mat.setRenderTriangles(true, true);
      object->setMaterial(mat);
  
      // define some environmental texture mapping
      cTexture2d* texture = new cTexture2d();
  
      // load texture file
      fileload = texture->loadFromFile(RESOURCE_PATH("resources/images/chrome.bmp"));
      if (!fileload)
      {
          #if defined(_MSVC)
          fileload = texture->loadFromFile("../../../bin/resources/images/chrome.bmp");
          #endif
      }
      if (!fileload)
      {
          printf("Error - Texture image failed to load correctly.\n");
          close();
          return (-1);
      }
  
      // enable spherical mapping
      texture->setSphericalMappingEnabled(true);
      
      // assign texture to object
      object->setTexture(texture, true);
  
      // enable texture mapping
      object->setUseTexture(true, true);
  
      // disable culling
      object->setUseCulling(false, true);
  
      // define a default stiffness for the object
      object->setStiffness(stiffnessMax, true);
  
      // define some haptic friction properties
      object->setFriction(0.1, 0.2, true);
  
  
      //-----------------------------------------------------------------------
      // WIDGETS
      //-----------------------------------------------------------------------
  
      // create a font
      cFont *font = NEW_CFONTCALIBRI20();
      
      // create a label to display the haptic rate of the simulation
      labelHapticRate = new cLabel(font);
      labelHapticRate->m_fontColor.setWhite();
      camera->m_frontLayer->addChild(labelHapticRate);
  
     
      //-----------------------------------------------------------------------
      // OPEN GL - WINDOW DISPLAY
      //-----------------------------------------------------------------------
  
      // simulation in now running!
      simulationRunning = true;
  
      // initialize GLUT
      glutInit(&argc, argv);
  
      // retrieve the resolution of the computer display and estimate the position
      // of the GLUT window so that it is located at the center of the screen
      int screenW = glutGet(GLUT_SCREEN_WIDTH);
      int screenH = glutGet(GLUT_SCREEN_HEIGHT);
      int windowW = 0.7 * screenH;
      int windowH = 0.7 * screenH;
      int windowPosX = (screenW - windowH) / 2;
      int windowPosY = (screenH - windowW) / 2;
  
      // initialize the OpenGL GLUT window
      glutInitWindowPosition(windowPosX, windowPosY);
      glutInitWindowSize(windowW, windowH);
      if (USE_STEREO_DISPLAY)
      {
          glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE | GLUT_STEREO);
          camera->setUseStereo(true);
      }
      else
      {
          glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
          camera->setUseStereo(false);
      }
      glutCreateWindow(argv[0]);
      glutDisplayFunc(updateGraphics);
      glutKeyboardFunc(keySelect);
      glutReshapeFunc(resizeWindow);
      glutSetWindowTitle("CHAI3D");
  
      // create a mouse menu (right button)
      glutCreateMenu(menuSelect);
      glutAddMenuEntry("full screen", OPTION_FULLSCREEN);
      glutAddMenuEntry("window display", OPTION_WINDOWDISPLAY);
      glutAttachMenu(GLUT_RIGHT_BUTTON);
  
  
      //-----------------------------------------------------------------------
      // START SIMULATION
      //-----------------------------------------------------------------------
  
      // create a thread which starts the main haptics rendering loop
      cThread* hapticsThread = new cThread();
      hapticsThread->start(updateHaptics, CTHREAD_PRIORITY_HAPTICS);
  
      // start the main graphics rendering loop
      glutTimerFunc(30, graphicsTimer, 0);
      glutMainLoop();
  
      // close everything
      close();
  
      // exit
      return (0);
  }
  
  //---------------------------------------------------------------------------
  
  void resizeWindow(int w, int h)
  {
      // update the size of the viewport
      displayW = w;
      displayH = h;
      glViewport(0, 0, displayW, displayH);
  }
  
  //---------------------------------------------------------------------------
  
  void keySelect(unsigned char key, int x, int y)
  {
      // escape key
      if ((key == 27) || (key == 'x'))
      {
          // close everything
          close();
  
          // exit application
          exit(0);
      }
  
      // option 1:
      if (key == '1')
      {
          object->setUseTexture(true);
      }
  
      // option 2:
      if (key == '2')
      {
          object->setUseTexture(false);
      }
  
      // option 3:
      if (key == '3')
      {
          object->setWireMode(true);
      }
  
      // option 4:
      if (key == '4')
      {
          object->setWireMode(false);
      }
  
      // option 5:
      if (key == '5')
      {
          object->setShowNormals(true);
          object->setNormalsProperties(0.05, cColorf(1.0, 0.0, 0.0));
      }
  
      // option 6:
      if (key == '6')
      {
          object->setShowNormals(false);
      }
  }
  
  //---------------------------------------------------------------------------
  
  void menuSelect(int value)
  {
      static int _wx, _wy, _ww, _wh;
  
      switch (value)
      {
          // enable full screen display
          case OPTION_FULLSCREEN:
              _wx = glutGet(GLUT_WINDOW_X);
              _wy = glutGet(GLUT_WINDOW_Y);
              _ww = glutGet(GLUT_WINDOW_WIDTH);
              _wh = glutGet(GLUT_WINDOW_HEIGHT);
              glutFullScreen();
              break;
  
          // reshape window to original size
          case OPTION_WINDOWDISPLAY:
              glutPositionWindow(_wx, _wy);
              glutReshapeWindow(_ww, _wh);
              break;
      }
  }
  
  //---------------------------------------------------------------------------
  
  void close(void)
  {
      // stop the simulation
      simulationRunning = false;
  
      // wait for graphics and haptics loops to terminate
      while (!simulationFinished) { cSleepMs(100); }
  
      // close haptic device
      tool->stop();
  }
  
  //---------------------------------------------------------------------------
  
  void graphicsTimer(int data)
  {
      // inform the GLUT window to call updateGraphics again (next frame)
      if (simulationRunning)
      {
          glutPostRedisplay();
      }
  
      glutTimerFunc(30, graphicsTimer, 0);
  }
  
  //---------------------------------------------------------------------------
  
  void updateGraphics(void)
  {
      // update haptic rate label
      labelHapticRate->setString ("haptic rate: "+cStr(frequencyCounter.getFrequency(), 0) + " [Hz]");
  
      int px = (int)(0.5 * (displayW - labelHapticRate->getWidth()));
      labelHapticRate->setLocalPos(px, 15);
  
      // render world
      camera->renderView(displayW, displayH);
  
      // swap buffers
      glutSwapBuffers();
  
      // check for any OpenGL errors
      GLenum err;
      err = glGetError();
      if (err != GL_NO_ERROR) printf("Error:  %s\n", gluErrorString(err));
  }
  
  //---------------------------------------------------------------------------
  
  void updateHaptics(void)
  {
      // angular velocit
      cVector3d angVel(0,0,0);
  
      // reset clock
      cPrecisionClock clock;
      clock.reset();
  
      // main haptic simulation loop
      while(simulationRunning)
      {
          /////////////////////////////////////////////////////////////////////
          // SIMULATION TIME    
          /////////////////////////////////////////////////////////////////////
  
          // stop the simulation clock
          clock.stop();
  
          // read the time increment in seconds
          double timeInterval = clock.getCurrentTimeSeconds();
  
          // restart the simulation clock
          clock.reset();
          clock.start();
  
          // update frequency counter
          frequencyCounter.signal(1);
  
          /////////////////////////////////////////////////////////////////////////
          // SIMULATION
          /////////////////////////////////////////////////////////////////////////
  
          /////////////////////////////////////////////////////////////////////
          // HAPTIC FORCE COMPUTATION
          /////////////////////////////////////////////////////////////////////
  
          // compute global reference frames for each object
          world->computeGlobalPositions(true);
  
          // update position and orientation of tool
          tool->updatePose();
  
          // compute interaction forces
          tool->computeInteractionForces();
  
          // send forces to device
          tool->applyForces();
  
          // get position of cursor in global coordinates
          cVector3d toolPos = tool->getDeviceGlobalPos();
  
          // get position of object in global coordinates
          cVector3d objectPos = object->getGlobalPos();
  
          // compute a vector from the center of mass of the object (point of rotation) to the tool
          cVector3d v = cSub(toolPos, objectPos);
  
          // compute angular acceleration based on the interaction forces
          // between the tool and the object
          cVector3d angAcc(0,0,0);
          if (v.length() > 0.0)
          {
              // get the last force applied to the cursor in global coordinates
              // we negate the result to obtain the opposite force that is applied on the
              // object
              cVector3d toolForce = cNegate(tool->m_lastComputedGlobalForce);
  
              // compute the effective force that contributes to rotating the object.
              cVector3d force = toolForce - cProject(toolForce, v);
  
              // compute the resulting torque
              cVector3d torque = cMul(v.length(), cCross( cNormalize(v), force));
  
              // compute a torque to restore the face to its original position
              cVector3d dirFace = object->getLocalRot().getCol0();
              cVector3d dirTorque = cCross(dirFace, cVector3d(0, 1, 0));
              dirTorque.mul(3.0);
              torque.add(dirTorque);
  
              cVector3d upFace = object->getLocalRot().getCol2();
              cVector3d upTorque = cCross(upFace, cVector3d(0, 0, 1));
              dirTorque.mul(3.0);
              torque.add(upTorque);
  
              // update rotational acceleration
              const double INERTIA = 0.4;
              angAcc = (1.0 / INERTIA) * torque;
          }
  
          // update rotational velocity
          angVel.add(timeInterval * angAcc);
  
          // set a threshold on the rotational velocity term
          const double MAX_ANG_VEL = 10.0;
          double vel = angVel.length();
          if (vel > MAX_ANG_VEL)
          {
              angVel.mul(MAX_ANG_VEL / vel);
          }
  
          // add some damping too
          const double DAMPING = 0.1;
          angVel.mul(1.0 - DAMPING * timeInterval);
  
          // if user switch is pressed, set velocity to zero
          if (tool->getUserSwitch(0) == 1)
          {
              angVel.zero();
          }
  
          // compute the next rotation configuration of the object
          if (angVel.length() > C_SMALL)
          {
              object->rotateAboutGlobalAxisRad(cNormalize(angVel), timeInterval * angVel.length());
          }
      }
  
      // exit haptics thread
      simulationFinished = true;
  }
  
  //---------------------------------------------------------------------------
  