text: //==============================================================================
  /*
      Software License Agreement (BSD License)
      Copyright (c) 2003-2016, 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    Federico Barbagli
      \author    Chris Sewell
      \author    Francois Conti
      \version   3.2.0 $Rev: 1925 $
  */
  //==============================================================================
  
  //------------------------------------------------------------------------------
  #include "chai3d.h"
  //------------------------------------------------------------------------------
  #include <GLFW/glfw3.h>
  //------------------------------------------------------------------------------
  using namespace chai3d;
  using namespace std;
  //------------------------------------------------------------------------------
  
  //------------------------------------------------------------------------------
  // GENERAL SETTINGS
  //------------------------------------------------------------------------------
  
  // stereo Mode
  /*
      C_STEREO_DISABLED:            Stereo is disabled 
      C_STEREO_ACTIVE:              Active stereo for OpenGL NVDIA QUADRO cards
      C_STEREO_PASSIVE_LEFT_RIGHT:  Passive stereo where L/R images are rendered next to each other
      C_STEREO_PASSIVE_TOP_BOTTOM:  Passive stereo where L/R images are rendered above each other
  */
  cStereoMode stereoMode = C_STEREO_DISABLED;
  
  // fullscreen mode
  bool fullscreen = false;
  
  // mirrored display
  bool mirroredDisplay = false;
  
  
  //------------------------------------------------------------------------------
  // DECLARED VARIABLES
  //------------------------------------------------------------------------------
  
  // a world that contains all objects of the virtual environment
  cWorld* world;
  
  // a camera to render the world in a window display
  cCamera* camera;
  
  // a light source to illuminate the objects in the virtual scene
  cSpotLight *light;
  
  // a haptic device handler
  cHapticDeviceHandler* handler;
  
  // a pointer to the current haptic device
  cGenericHapticDevicePtr hapticDevice;
  
  // a virtual tool representing the haptic device in the scene
  cToolCursor* tool;
  
  // a colored background
  cBackground* background;
  
  // a font for rendering text
  cFontPtr font;
  
  // a label to display the rate [Hz] at which the simulation is running
  cLabel* labelRates;
  
  // a scope to monitor the sound signal
  cScope* scope;
  
  // a virtual turntable
  cMultiMesh* turntable;
  
  // a virtual vinyl
  cMesh* record;
  
  // audio device to play sound
  cAudioDevice* audioDevice;
  
  // audio buffer to store a sound file
  cAudioBuffer* audioBuffer;
  
  // audio source which plays the audio buffer
  cAudioSource* audioSource;
  
  // Global variables for the audio stream
  int record_direction = 1;
  unsigned int pos = 0;
  
  // angular velocity of the record
  double angVel = 0;
  
  // angular position of the record
  double angPos = 0;
  
  // a flag that indicates if the haptic simulation is currently running
  bool simulationRunning = false;
  
  // a flag that indicates if the haptic simulation has terminated
  bool simulationFinished = true;
  
  // a frequency counter to measure the simulation graphic rate
  cFrequencyCounter freqCounterGraphics;
  
  // a frequency counter to measure the simulation haptic rate
  cFrequencyCounter freqCounterHaptics;
  
  // haptic thread
  cThread* hapticsThread;
  
  // a handle to window display context
  GLFWwindow* window = NULL;
  
  // current width of window
  int width  = 0;
  
  // current height of window
  int height = 0;
  
  // swap interval for the display context (vertical synchronization)
  int swapInterval = 1;
  
  
  //------------------------------------------------------------------------------
  // 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 windowSizeCallback(GLFWwindow* a_window, int a_width, int a_height);
  
  // callback when an error GLFW occurs
  void errorCallback(int error, const char* a_description);
  
  // callback when a key is pressed
  void keyCallback(GLFWwindow* a_window, int a_key, int a_scancode, int a_action, int a_mods);
  
  // this function renders the scene
  void updateGraphics(void);
  
  // this function contains the main haptics simulation loop
  void updateHaptics(void);
  
  // this function closes the application
  void close(void);
  
  
  //==============================================================================
  /*
      DEMO:    turntable.cpp
  
      This example demonstrates the use of friction, animation, and
      sound effects.  With the haptic device you can spin the record
      back and forth and at different speeds.
  */
  //==============================================================================
  
  int main(int argc, char* argv[])
  {
      //--------------------------------------------------------------------------
      // INITIALIZATION
      //--------------------------------------------------------------------------
  
      cout << endl;
      cout << "-----------------------------------" << endl;
      cout << "CHAI3D" << endl;
      cout << "Demo: 24-turntable" << endl;
      cout << "Copyright 2003-2016" << endl;
      cout << "-----------------------------------" << endl << endl << endl;
      cout << "Keyboard Options:" << endl << endl;
      cout << "[f] - Enable/Disable full screen mode" << endl;
      cout << "[m] - Enable/Disable vertical mirroring" << endl;
      cout << "[q] - Exit application" << endl;
      cout << endl << endl;
  
      // parse first arg to try and locate resources
      string resourceRoot = string(argv[0]).substr(0,string(argv[0]).find_last_of("/\\")+1);
  
  
      //--------------------------------------------------------------------------
      // OPEN GL - WINDOW DISPLAY
      //--------------------------------------------------------------------------
  
      // initialize GLFW library
      if (!glfwInit())
      {
          cout << "failed initialization" << endl;
          cSleepMs(1000);
          return 1;
      }
  
      // set error callback
      glfwSetErrorCallback(errorCallback);
  
      // compute desired size of window
      const GLFWvidmode* mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
      int w = 0.8 * mode->height;
      int h = 0.5 * mode->height;
      int x = 0.5 * (mode->width - w);
      int y = 0.5 * (mode->height - h);
  
      // set OpenGL version
      glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
      glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
  
      // set active stereo mode
      if (stereoMode == C_STEREO_ACTIVE)
      {
          glfwWindowHint(GLFW_STEREO, GL_TRUE);
      }
      else
      {
          glfwWindowHint(GLFW_STEREO, GL_FALSE);
      }
  
      // create display context
      window = glfwCreateWindow(w, h, "CHAI3D", NULL, NULL);
      if (!window)
      {
          cout << "failed to create window" << endl;
          cSleepMs(1000);
          glfwTerminate();
          return 1;
      }
  
      // get width and height of window
      glfwGetWindowSize(window, &width, &height);
  
      // set position of window
      glfwSetWindowPos(window, x, y);
  
      // set key callback
      glfwSetKeyCallback(window, keyCallback);
  
      // set resize callback
      glfwSetWindowSizeCallback(window, windowSizeCallback);
  
      // set current display context
      glfwMakeContextCurrent(window);
  
      // sets the swap interval for the current display context
      glfwSwapInterval(swapInterval);
  
  #ifdef GLEW_VERSION
      // initialize GLEW library
      if (glewInit() != GLEW_OK)
      {
          cout << "failed to initialize GLEW library" << endl;
          glfwTerminate();
          return 1;
      }
  #endif
  
  
      //--------------------------------------------------------------------------
      // 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 orient the camera
      camera->set(cVector3d (1.70, 0.14, 1.10),    // camera position (eye)
                  cVector3d (0.20, 0.14,-0.10),    // lookat position (target)
                  cVector3d (0.00, 0.00, 1.00));   // direction of the (up) vector
  
      // set the near and far clipping planes of the camera
      // anything in front or behind these clipping planes will not be rendered
      camera->setClippingPlanes(0.01, 10.0);
  
      // set stereo mode
      camera->setStereoMode(stereoMode);
  
      // set stereo eye separation and focal length (applies only if stereo is enabled)
      camera->setStereoEyeSeparation(0.03);
      camera->setStereoFocalLength(3.0);
  
      // set vertical mirrored display mode
      camera->setMirrorVertical(mirroredDisplay);
  
      // create a light source
      light = new cSpotLight(world);
  
      // attach light to camera
      world->addChild(light);
  
      // enable light source
      light->setEnabled(true);
  
      // position the light source
      light->setLocalPos(1.5, 0.40, 1.5);
  
      // define the direction of the light beam
      light->setDir(-2.0,-0.5,-2.0);
  
      // enable this light source to generate shadows
      light->setShadowMapEnabled(true);
  
      // set the resolution of the shadow map
      //light->m_shadowMap->setQualityLow();
      light->m_shadowMap->setQualityMedium();
  
      // set light cone half angle
      light->setCutOffAngleDeg(30);
  
  
      //--------------------------------------------------------------------------
      // HAPTIC DEVICES / TOOLS
      //--------------------------------------------------------------------------
  
      // create a haptic device handler
      handler = new cHapticDeviceHandler();
  
      // get access to the first available haptic device found
      handler->getDevice(hapticDevice, 0);
  
      // retrieve information about the current haptic device
      cHapticDeviceInfo hapticDeviceInfo = hapticDevice->getSpecifications();
  
      // create a tool (cursor) and insert into the world
      tool = new cToolCursor(world);
      world->addChild(tool);
  
      // connect the haptic device to the virtual tool
      tool->setHapticDevice(hapticDevice);
  
      // define the radius of the tool (sphere)
      double toolRadius = 0.01;
  
      // define a radius for the tool
      tool->setRadius(toolRadius);
  
      // hide the device sphere. only show proxy.
      tool->setShowContactPoints(true, false);
  
      // create a white cursor
      tool->m_hapticPoint->m_sphereProxy->m_material->setWhite();
  
      // 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);
  
      // map the physical workspace of the haptic device to a larger virtual workspace.
      tool->setWorkspaceRadius(0.8);
  
      // haptic forces are enabled only if small forces are first sent to the device;
      // this mode avoids the force spike that occurs when the application starts when 
      // the tool is located inside an object for instance. 
      tool->setWaitForSmallForce(true);
  
      // start the haptic tool
      tool->start();
  
  
      //--------------------------------------------------------------------------
      // CREATE OBJECTS
      //--------------------------------------------------------------------------
  
      // read the scale factor between the physical workspace of the haptic
      // device and the virtual workspace defined for the tool
      double workspaceScaleFactor = tool->getWorkspaceScaleFactor();
  
      // stiffness properties
      double maxStiffness	= hapticDeviceInfo.m_maxLinearStiffness / workspaceScaleFactor;
  
  
      /////////////////////////////////////////////////////////////////////////
      // TURNTABLE OBJECT
      /////////////////////////////////////////////////////////////////////////
  
      // create a virtual mesh
      turntable = new cMultiMesh();
  
      // add object to world
      world->addChild(turntable);
  
      // set the position of turntable object at the center of the world
      turntable->setLocalPos(0.0, 0.0, 0.0);
      turntable->rotateAboutGlobalAxisDeg(cVector3d(0,0,1), 90);
  
      // load an object file
      bool fileload;
      fileload = turntable->loadFromFile(RESOURCE_PATH("../resources/models/turntable/turntable.obj"));
      if (!fileload)
      {
          #if defined(_MSVC)
          fileload = turntable->loadFromFile("../../../bin/resources/models/turntable/turntable.obj");
          #endif
      }
      if (!fileload)
      {
          printf("Error - 3D Model failed to load correctly.\n");
          close();
          return (-1);
      }
  
      // compute a boundary box
      turntable->computeBoundaryBox(true);
  
      // get dimensions of object
      double size = cSub(turntable->getBoundaryMax(), turntable->getBoundaryMin()).length();
  
      // resize object to screen
      if (size > 0)
      {
          turntable->scale( 2.0 / size);
      }
  
      // setup collision detection algorithm
      turntable->createAABBCollisionDetector(toolRadius);
  
      // define a default stiffness for the object
      turntable->setStiffness(0.8 * maxStiffness, true);
  
  
      /////////////////////////////////////////////////////////////////////////
      // VINYL OBJECT
      /////////////////////////////////////////////////////////////////////////
  
      // create a mesh for the vynil
      record = new cMesh();
  
      // build mesh using primitives
      cCreateCylinder(record, 0.0, 0.48, 36, 1, false, true);
      cCreateDisk(record, 0.49, 0.49, 36, cVector3d(0,0,0.04));
  
      // create texture image
      cTexture2dPtr recordImage = cTexture2d::create();
  
      // load texture image from file
      fileload = recordImage->loadFromFile(RESOURCE_PATH("../resources/models/turntable/record.jpg"));
      if (!fileload)
      {
  #if defined(_MSVC)
          fileload = recordImage->loadFromFile("../../../bin/resources/models/turntable/record.jpg");
  #endif
      }
      if (!fileload)
      {
          cout << "Error - Texture image failed to load correctly." << endl;
          close();
          return (-1);
      }
  
      // set material properties
      record->m_material->setWhite();
      record->setTexture(recordImage);
      record->setUseTexture(true, true);
      record->setUseMaterial(true, true);
  
      // create collision detector
      record->createAABBCollisionDetector(toolRadius);
  
      // add vynil to world
      world->addChild(record);
  
      // position vinyl
      record->translate(-0.0, 0, 0.02);
  
      // set stiffness properties
      record->setStiffness(maxStiffness, true);
  
      // set static and dynamic friction
      double staticFriction = (double)100 / 100.0;
      double dynamicFriction = (double)100 / 100.0;
      record->setFriction(staticFriction, dynamicFriction, true);
  
  
      //--------------------------------------------------------------------------
      // SETUP AUDIO
      //--------------------------------------------------------------------------
  
      // create an audio device to play sounds
      audioDevice = new cAudioDevice();
  
      // create an audio buffer and load audio wave file
      audioBuffer = new cAudioBuffer();
      fileload = audioBuffer->loadFromFile(RESOURCE_PATH("../resources/sounds/classic.wav"));
      if (!fileload)
      {
          #if defined(_MSVC)
          fileload = audioBuffer->loadFromFile("../../../bin/resources/sounds/classic.wav");
          #endif
      }
  
      // check for errors
      if (!fileload)
      {
          cout << "Error - Sound file failed to load or initialize correctly." << endl;
          close();
          return (-1);
      }
  
      // create audio source
      audioSource = new cAudioSource();
  
      // assign auio buffer to audio source
      audioSource->setAudioBuffer(audioBuffer);
  
      // set volume
      audioSource->setGain(10.0);
  
      // set speed at which the audio file is played. we will modulate this with the record speed.
      audioSource->setPitch(0.0);
  
      // loop audio play
      audioSource->setLoop(true);
  
      // start playing
      audioSource->play();
  
  
      //--------------------------------------------------------------------------
      // WIDGETS
      //--------------------------------------------------------------------------
  
      // create a font
      font = NEW_CFONTCALIBRI20();
      
      // create a label to display the haptic and graphic rate of the simulation
      labelRates = new cLabel(font);
      labelRates->m_fontColor.setBlack();
      camera->m_frontLayer->addChild(labelRates);
  
      // create a background
      background = new cBackground();
      camera->m_backLayer->addChild(background);
  
      // set background properties
      background->setCornerColors(cColorf(1.0f, 1.0f, 1.0f),
                                  cColorf(1.0f, 1.0f, 1.0f),
                                  cColorf(0.8f, 0.8f, 0.8f),
                                  cColorf(0.8f, 0.8f, 0.8f));
  
  
      //--------------------------------------------------------------------------
      // START SIMULATION
      //--------------------------------------------------------------------------
  
      // create a thread which starts the main haptics rendering loop
      hapticsThread = new cThread();
      hapticsThread->start(updateHaptics, CTHREAD_PRIORITY_HAPTICS);
  
      // setup callback when application exits
      atexit(close);
  
  
      //--------------------------------------------------------------------------
      // MAIN GRAPHIC LOOP
      //--------------------------------------------------------------------------
  
      // call window size callback at initialization
      windowSizeCallback(window, width, height);
  
      // main graphic loop
      while (!glfwWindowShouldClose(window))
      {
          // get width and height of window
          glfwGetWindowSize(window, &width, &height);
  
          // render graphics
          updateGraphics();
  
          // swap buffers
          glfwSwapBuffers(window);
  
          // process events
          glfwPollEvents();
  
          // signal frequency counter
          freqCounterGraphics.signal(1);
      }
  
      // close window
      glfwDestroyWindow(window);
  
      // terminate GLFW library
      glfwTerminate();
  
      // exit
      return 0;
  }
  
  //------------------------------------------------------------------------------
  
  void windowSizeCallback(GLFWwindow* a_window, int a_width, int a_height)
  {
      // update window size
      width  = a_width;
      height = a_height;
  }
  
  //------------------------------------------------------------------------------
  
  void errorCallback(int a_error, const char* a_description)
  {
      cout << "Error: " << a_description << endl;
  }
  //------------------------------------------------------------------------------
  
  void keyCallback(GLFWwindow* a_window, int a_key, int a_scancode, int a_action, int a_mods)
  {
      // filter calls that only include a key press
      if ((a_action != GLFW_PRESS) && (a_action != GLFW_REPEAT))
      {
          return;
      }
  
      // option - exit
      else if ((a_key == GLFW_KEY_ESCAPE) || (a_key == GLFW_KEY_Q))
      {
          glfwSetWindowShouldClose(a_window, GLFW_TRUE);
      }
  
      // option - toggle fullscreen
      else if (a_key == GLFW_KEY_F)
      {
          // toggle state variable
          fullscreen = !fullscreen;
  
          // get handle to monitor
          GLFWmonitor* monitor = glfwGetPrimaryMonitor();
  
          // get information about monitor
          const GLFWvidmode* mode = glfwGetVideoMode(monitor);
  
          // set fullscreen or window mode
          if (fullscreen)
          {
              glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
              glfwSwapInterval(swapInterval);
          }
          else
          {
              int w = 0.8 * mode->height;
              int h = 0.5 * mode->height;
              int x = 0.5 * (mode->width - w);
              int y = 0.5 * (mode->height - h);
              glfwSetWindowMonitor(window, NULL, x, y, w, h, mode->refreshRate);
              glfwSwapInterval(swapInterval);
          }
      }
  
      // option - toggle vertical mirroring
      else if (a_key == GLFW_KEY_M)
      {
          mirroredDisplay = !mirroredDisplay;
          camera->setMirrorVertical(mirroredDisplay);
      }
  }
  
  //------------------------------------------------------------------------------
  
  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();
  
      // delete resources
      delete hapticsThread;
      delete world;
      delete handler;
      delete audioDevice;
      delete audioBuffer;
      delete audioSource;
  }
  
  //------------------------------------------------------------------------------
  
  void updateGraphics(void)
  {
      /////////////////////////////////////////////////////////////////////
      // UPDATE WIDGETS
      /////////////////////////////////////////////////////////////////////
  
      // update haptic and graphic rate data
      labelRates->setText(cStr(freqCounterGraphics.getFrequency(), 0) + " Hz / " +
          cStr(freqCounterHaptics.getFrequency(), 0) + " Hz");
  
      // update position of label
      labelRates->setLocalPos((int)(0.5 * (width - labelRates->getWidth())), 15);
  
  
      /////////////////////////////////////////////////////////////////////
      // RENDER SCENE
      /////////////////////////////////////////////////////////////////////
  
      // update shadow maps (if any)
      world->updateShadowMaps(false, mirroredDisplay);
  
      // render world
      camera->renderView(width, height);
  
      // wait until all GL commands are completed
      glFinish();
  
      // check for any OpenGL errors
      GLenum err = glGetError();
      if (err != GL_NO_ERROR) cout << "Error: " << gluErrorString(err) << endl;
  }
  
  //------------------------------------------------------------------------------
  
  void updateHaptics(void)
  {
      // reset clock
      cPrecisionClock clock;
      clock.reset();
  
      // simulation in now running
      simulationRunning  = true;
      simulationFinished = false;
  
      // 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();
  
          // signal frequency counter
          freqCounterHaptics.signal(1);
  
  
          /////////////////////////////////////////////////////////////////////
          // HAPTIC FORCE COMPUTATION
          /////////////////////////////////////////////////////////////////////
  
          // compute global reference frames for each object
          world->computeGlobalPositions(true);
  
          // update position and orientation of tool
          tool->updateFromDevice();
  
          // compute interaction forces
          tool->computeInteractionForces();
  
          // send forces to haptic device
          tool->applyToDevice();
  
  
          /////////////////////////////////////////////////////////////////////
          // ANIMATION
          /////////////////////////////////////////////////////////////////////
  
          // init
          cVector3d torque(0,0,0);
  
          // figure out if we're touching the record
          if (tool->isInContact(record))
          {
              // get position of cursor in global coordinates
              cVector3d toolPos = tool->getDeviceGlobalPos();
  
              // get position of object in global coordinates
              cVector3d objectPos = record->getGlobalPos();
  
              // compute a vector from the center of mass of the object (point of rotation) to the tool
              cVector3d vObjectCMToTool = cSub(toolPos, objectPos);
  
              if (vObjectCMToTool.length() > 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 = -tool->getDeviceGlobalForce();
  
                  // compute effective force to take into account the fact the object
                  // can only rotate arround a its center mass and not translate
                  cVector3d effectiveForce = toolForce - cProject(toolForce, vObjectCMToTool);
  
                  // compute the resulting torque
                  torque = cMul(vObjectCMToTool.length(), cCross( cNormalize(vObjectCMToTool), effectiveForce));
              }
          }
  
          // update rotational acceleration
          const double OBJECT_INERTIA = 0.02;
          double angAcc = (1.0 / OBJECT_INERTIA) * torque.z();
  
          // update rotational velocity
          angVel = angVel + timeInterval * angAcc;
  
          // set a threshold on the rotational velocity term
          const double ANG_VEL_MAX = 20.0;
  
          if (angVel > ANG_VEL_MAX)
          {
              angVel = ANG_VEL_MAX;
          }
          else if (angVel < -ANG_VEL_MAX)
          {
              angVel = -ANG_VEL_MAX;
          }
  
          // compute the next rotation of the torus
          angPos = angPos + timeInterval * angVel;
  
          // update position of record
          cMatrix3d rot;
          rot.identity();
          rot.rotateAboutGlobalAxisRad(cVector3d(0,0,1), angPos);
          record->setLocalRot(rot);
  
          // set audio pitch  based on rotational velocity
          audioSource->setPitch(0.1 * fabs(angVel));
      }
  
      // exit haptics thread
      simulationFinished = true;
  }
  
  //------------------------------------------------------------------------------
  