plvViewerCmds.cc
上传用户:kellyonhid
上传日期:2013-10-12
资源大小:932k
文件大小:33k
源码类别:

3D图形编程

开发平台:

Visual C++

  1. #include <iostream.h>
  2. #include <stdlib.h>
  3. #include <limits.h>
  4. #include "plvGlobals.h"
  5. #include "plvDraw.h"
  6. #include "plvViewerCmds.h"
  7. #include "plvMeshCmds.h"
  8. #include "plvDrawCmds.h"
  9. #include "togl.h"
  10. #include "RigidScan.h"
  11. #include "DisplayMesh.h"
  12. #include "plvAnalyze.h"
  13. #include "VolCarve.h"
  14. #include "Timer.h"
  15. #include "ToglCache.h"
  16. #include "ScanFactory.h"
  17. #include "plvClipBoxCmds.h"
  18. #include "VertexFilter.h"
  19. #include "cmdassert.h"
  20. #include "TclCmdUtils.h"
  21. #include "plvScene.h"
  22. #include "GroupScan.h"
  23. static bool s_bManipulatingLocked = false;
  24. static bool s_bForceOnscreen = false;
  25. static crope s_autoResetScript;
  26. static int  s_iAutoResetTime;
  27. static void rescheduleAutoReset (void);
  28. static void lastActionTrackball (void)
  29. {
  30.   if (toglCurrent)
  31.     Tcl_Eval (Togl_Interp (toglCurrent), "autoClearSelection");
  32. }
  33. int
  34. PlvSelectScanCmd(ClientData clientData, Tcl_Interp *interp, 
  35.  int argc, char *argv[])
  36. {
  37.   if (argc < 2) {
  38.     interp->result = "Wrong number of args";
  39.     return TCL_ERROR;
  40.   }
  41.   // if argv[1] == "", the viewer is active
  42.   DisplayableMesh* scan = FindMeshDisplayInfo (argv[1]);
  43.   if (strlen(argv[1]) && scan == NULL) {
  44.     interp->result = "Could not find mesh";
  45.     return TCL_ERROR;
  46.   }
  47.   DisplayableMesh* oldActiveScan = theActiveScan;
  48.   if (argc > 2 && !strcmp (argv[2], "manipulate")) {
  49.     theActiveScan = scan;
  50.   } else {
  51.     theSelectedScan = scan;
  52.   }
  53.   if (!g_bNoUI) {
  54.     if (theActiveScan != oldActiveScan
  55. || theRenderParams->colorMode == registrationColor) {
  56.       redraw (true);
  57.     }
  58.   }
  59.   return TCL_OK;
  60. }
  61. int
  62. PlvSetVisibleCmd(ClientData clientData, Tcl_Interp *interp, 
  63.      int argc, char *argv[])
  64. {
  65.   if (argc < 3) {
  66.     interp->result = "Wrong number of args";
  67.     return TCL_ERROR;
  68.   }
  69.   DisplayableMesh *meshSet = FindMeshDisplayInfo (argv[1]);
  70.   if (meshSet == NULL) {
  71.     interp->result = "Could not find mesh";
  72.     return TCL_ERROR;
  73.   }
  74.   bool bVisible;
  75.   SetBoolFromArgIndex (2, bVisible);
  76.   meshSet->setVisible (bVisible);
  77.   
  78.   theScene->computeBBox();
  79.   redraw (true);
  80.   
  81.   return TCL_OK;
  82. }
  83. int
  84. PlvGetVisibleCmd(ClientData clientData, Tcl_Interp *interp, 
  85.  int argc, char *argv[])
  86. {
  87.   if (argc < 2) {
  88.     interp->result = "Wrong number of args";
  89.     return TCL_ERROR;
  90.   }
  91.   DisplayableMesh *meshSet = FindMeshDisplayInfo (argv[1]);
  92.   if (meshSet == NULL) {
  93.     interp->result = "Could not find mesh";
  94.     return TCL_ERROR;
  95.   }
  96.   interp->result = meshSet->getVisible() ? "1" : "0";
  97.   return TCL_OK;
  98. }
  99. static void
  100. PlvListScansHelper (Tcl_Interp *interp, vector<DisplayableMesh*> meshes, bool bLeaf);
  101. // NEW! IMPROVED!!
  102. int
  103. PlvListScansCmd(ClientData clientData, Tcl_Interp *interp, 
  104. int argc, char *argv[])
  105. {
  106.   // options:
  107.   // default,roots = only meshes with no parent
  108.   // leaves = all meshes with no children
  109.   // groups = all groups (i.e. "meshes" with children)
  110.   
  111.   bool bRoot = false;
  112.   bool bLeaf = false;
  113.   if (argc > 2) {
  114.     interp->result = "Too many arguments to PlvListScansCmd";
  115.     return TCL_ERROR;
  116.   }
  117.   
  118.   if (argc == 1) bRoot = true; // i.e. default mode
  119.   else {
  120.     if (!strcmp (argv[1], "root"))
  121.       bRoot = true;
  122.     else if (!strcmp (argv[1], "leaves"))
  123.       bLeaf = true;
  124.     else if (!strcmp (argv[1], "groups"))
  125.       bLeaf = false;
  126.     else {
  127.       interp->result = "Bad option to PlvListScansCmd";
  128.       return TCL_ERROR;
  129.     }
  130.   }
  131.   
  132.   if (bRoot) {
  133.     for (DisplayableMesh** pdm = theScene->meshSets.begin();
  134.  pdm < theScene->meshSets.end(); pdm++) {
  135.       
  136.       // Thus, since theScene->meshSets only stores the roots,
  137.       // all we need to do by default is append
  138.       Tcl_AppendElement (interp, (char*)(*pdm)->getName());
  139.     }
  140.   } else 
  141.     PlvListScansHelper (interp, theScene->meshSets, bLeaf);
  142.   return TCL_OK;
  143. }
  144. static void
  145. PlvListScansHelper (Tcl_Interp *interp, vector<DisplayableMesh*> meshes, bool bLeaf) 
  146. {
  147.   for (DisplayableMesh** pdm = meshes.begin(); pdm < meshes.end(); pdm++) {
  148.     vector<DisplayableMesh*>children;
  149.     
  150.     GroupScan *gp = dynamic_cast<GroupScan*>((*pdm)->getMeshData());
  151.     if (gp) {
  152.       bool bGroup = gp->get_children_for_display(children);
  153.       assert (bGroup);
  154.       if (!bLeaf) Tcl_AppendElement(interp, (char*) (*pdm)->getName());
  155.       PlvListScansHelper(interp, children, bLeaf);
  156.     } else {
  157.       if (bLeaf) Tcl_AppendElement(interp, (char*) (*pdm)->getName());
  158.       //      else PlvListScansHelper(interp, children, bLeaf);
  159.     }
  160.   }
  161. }
  162. int
  163. PlvLightCmd(ClientData clientData, Tcl_Interp *interp, 
  164.     int argc, char *argv[])
  165. {
  166.     char result[PATH_MAX];
  167.     if (argc != 1 && argc != 4) {
  168. interp->result = "Wrong number of args";
  169. return TCL_ERROR;
  170.     }
  171.     if (argc == 4) {
  172. theRenderParams->lightPosition[0] = atof(argv[1]);
  173. theRenderParams->lightPosition[1] = atof(argv[2]);
  174. theRenderParams->lightPosition[2] = atof(argv[3]);
  175.     } else {
  176. sprintf(result, "plv_light %f %f %f",
  177. theRenderParams->lightPosition[0],
  178. theRenderParams->lightPosition[1],
  179. theRenderParams->lightPosition[2]);
  180. Tcl_SetResult(interp, result, TCL_VOLATILE);
  181.     }
  182.     return TCL_OK;
  183. }
  184. int
  185. PlvRotateLightCmd(ClientData clientData, Tcl_Interp *interp, 
  186.    int argc, char *argv[])
  187. {
  188.   float x, y, z;
  189.   // project rectangular coordinates onto hemisphere
  190.   x = (2 * atoi(argv[2]) / (float)theWidth) - 1;
  191.   y = (-2 *atoi(argv[3]) / (float)theHeight) + 1;
  192.   if (x < -1) x = -1; else if (x > 1) x = 1;
  193.   if (y < -1) y = -1; else if (y > 1) y = 1;
  194.   z = (1 - x*x - y*y);
  195.   if (z < 0)
  196.     z = -sqrt (-z);
  197.   else if (z != 0)    // avoid sqrt(0)
  198.     z = sqrt (z);
  199.   // set theRenderParams->lightPosition
  200.   theRenderParams->lightPosition[0] = x;
  201.   theRenderParams->lightPosition[1] = y;
  202.   theRenderParams->lightPosition[2] = z;
  203.   rescheduleAutoReset();
  204.   redraw (true);
  205.   return TCL_OK;
  206. }
  207. int
  208. PlvOrthographicCmd(ClientData clientData, Tcl_Interp *interp, 
  209.    int argc, char *argv[])
  210. {
  211.   tbView->setOrthographic (true);
  212.   redraw (true);
  213.   return TCL_OK;
  214. }
  215. int
  216. PlvPerspectiveCmd(ClientData clientData, Tcl_Interp *interp, 
  217.   int argc, char *argv[])
  218. {
  219.   tbView->setOrthographic (false);
  220.   redraw (true);
  221.   return TCL_OK;
  222. }
  223. int
  224. PlvZoomAngleCmd(ClientData clientData, Tcl_Interp *interp, 
  225. int argc, char *argv[])
  226. {
  227.   tbView->changeFOV(atof(argv[1]));
  228.   redraw (true);
  229.   return TCL_OK;
  230. }
  231. int
  232. PlvObliqueCameraCmd(ClientData clientData, Tcl_Interp *interp, 
  233. int argc, char *argv[])
  234. {
  235.   if (argc > 2)
  236.    tbView->setObliqueCamera(atof(argv[1]), atof(argv[2]));
  237.   redraw (true);
  238.   return TCL_OK;
  239. }
  240. int
  241. PlvViewAllCmd(ClientData clientData, Tcl_Interp *interp, 
  242.       int argc, char *argv[])
  243. {
  244.   redraw (true);
  245.   return TCL_OK;
  246. }
  247. int
  248. PlvResetXformCmd(ClientData clientData, Tcl_Interp *interp, 
  249.  int argc, char *argv[])
  250. {
  251.   DisplayableMesh* meshDisp = NULL;
  252.   bool bMeshesOnly = false;
  253.   bool bAllMeshes = false;
  254.   if (argc > 1) {
  255.     if (!strcmp (argv[1], "all")) {
  256.       bAllMeshes = true;
  257.     } else if (!strcmp (argv[1], "allmesh")) {
  258.       bMeshesOnly = true; bAllMeshes = true;
  259.     }
  260.     
  261.     if (bAllMeshes) {
  262.       for (int i = 0; i < theScene->meshSets.size(); i++)
  263. theScene->meshSets[i]->getMeshData()->resetXform();
  264.     } else {
  265.       meshDisp = FindMeshDisplayInfo (argv[1]);
  266.     }
  267.   }
  268.   if (meshDisp != NULL)
  269.     meshDisp->getMeshData()->resetXform();
  270.   else {
  271.     if (!bMeshesOnly) {
  272.       theScene->computeBBox();
  273.       theScene->centerCamera();
  274.     }
  275.   }
  276.   
  277.   redraw (true);  
  278.   return TCL_OK;
  279. }
  280. // give me GL (lower-left origin) screen coordinates!
  281. static void
  282. resetTranslationScale (int x, int y)
  283. {
  284.   Pnt3 clickWorld, clickScreen;
  285.   if (findZBufferNeighborExt (x, y, clickWorld, toglCurrent, tbView,
  286.       100, &clickScreen, false)) {
  287.     tbView->setTransScale (clickScreen);
  288.   } else {
  289.     // do we want to fall back on old method (average-case guess for
  290.     // transScale) or keep using the last intelligently computed
  291.     // transScale?
  292.     tbView->setTransScale();
  293.     //cerr << "resetting scale" << endl;
  294.   }
  295. }
  296. static Pnt3
  297. screenRotationCenter (void)
  298. {
  299.   int x = theWidth / 2;
  300.   int y = theHeight / 2;
  301.   Pnt3 clickWorld, clickScreen;
  302.   if (!findZBufferNeighborExt (x, y, clickWorld,
  303.        toglCurrent, tbView, 100)) {
  304.     // force unproject of (x, y, z) where z is middle of scene depth
  305.     Unprojector unproject (tbView);
  306.     Pnt3 nearz = unproject.forward (theScene->worldBbox().min());
  307.     Pnt3 farz = unproject.forward (theScene->worldBbox().max());
  308.     clickWorld = unproject (x, y, (nearz[2] + farz[2]) / 2);
  309.   }
  310.   return clickWorld;
  311. }
  312. static bool
  313. passesOnscreenTest (int oldSize = -1, bool bOnlyQuery = false)
  314. {
  315.   // scene bbox must have at least 1 corner within inner 80% of screen,
  316.   // and area at least 1% of screen.
  317.   // this test is definitely not perfect; you can get the whole model
  318.   // offscreen while keeping one bbox corner on, and you can't zoom in
  319.   // very far because you'll lose all the corners pretty quick.  But it
  320.   // works pretty well.
  321.   int xmargin = theWidth / 10;
  322.   int ymargin = theHeight / 10;
  323.   TbObj tbObjIdentity;
  324.   ScreenBox sb (&tbObjIdentity,
  325. xmargin, theWidth - xmargin,
  326. ymargin, theHeight - ymargin);
  327.   theScene->computeBBox (Scene::sync);
  328.   bool onscreen = false;
  329.   int minx = INT_MAX, miny = INT_MAX, maxx = ~INT_MAX, maxy = ~INT_MAX;
  330.   Bbox bb = theScene->worldBbox();
  331.   Pnt3 center = sb.getProjector() (bb.center());
  332.   for (int i = 0; i < 8; i++) {
  333.     Pnt3 corner = bb.corner (i);
  334.     if (sb.accept (corner))
  335.       onscreen = true;
  336.     
  337.     Pnt3 screen = sb.getProjector() (corner);
  338.     minx = MIN (minx, screen[0]);
  339.     maxx = MAX (maxx, screen[0]);
  340.     miny = MIN (miny, screen[1]);
  341.     maxy = MAX (maxy, screen[1]);
  342.   }
  343.   int area = (maxx - minx) * (maxy - miny);
  344.   if (bOnlyQuery) {
  345.     oldSize = area;
  346.   } else {
  347.     if (!onscreen) { // all 8 failed
  348.       cerr << "onscreen test fails: All corners offscreen!" << endl;
  349.       return false;
  350.     }
  351.     int minsize = theWidth * theHeight * .01;
  352.     if (oldSize == -1)
  353.       oldSize = minsize;
  354.     
  355.     if (tbView->isZooming() || !tbView->isManipulating()) {
  356.       // don't let them zoom out too far.
  357.       if (area < MIN (oldSize, minsize)) {
  358. cerr << "onscreen test fails: bbox covers only " << area 
  359.      << " and it was " << oldSize << endl;
  360. return false;
  361.       }
  362.     }
  363.   }
  364.   return true;
  365. }
  366. static void
  367. passMoveToTrackball (int x, int y, int t) {
  368.   Xform<float> before;
  369.   int oldSize = -1;
  370.   if (s_bForceOnscreen) {
  371.     before = tbView->getUndoXform();
  372.     passesOnscreenTest (oldSize, true);
  373.     rescheduleAutoReset();
  374.   }
  375.   tbView->move(x, y, t, MeshData(theActiveScan));
  376.   theScene->computeBBox();
  377.   if (s_bForceOnscreen && !passesOnscreenTest (oldSize)) {
  378.     cerr << "annulling trackball action" << endl;
  379.     tbView->setUndoXform (before);
  380.   } else {
  381.     redraw (true);
  382.   }
  383. }
  384. int
  385. PlvRotateXYViewMouseCmd(ClientData clientData, Tcl_Interp *interp, 
  386.    int argc, char *argv[])
  387. {
  388.   if (!strcmp (argv[2], "stopspin")) {
  389.     tbView->stop();
  390.     return TCL_OK;
  391.   }
  392.   
  393.   int x = atoi(argv[2]);
  394.   int y = atoi(argv[3]);
  395.   int t = atoi(argv[4]);
  396.   if (argc > 5) {
  397.     prepareDrawInWin(argv[1]);
  398.     if (!strcmp (argv[5], "start")) {
  399.       lastActionTrackball();
  400.       tbView->pressButton (1, 0, x, y, t, MeshData(theActiveScan));
  401.     } else {
  402.       tbView->pressButton (1, 1, x, y, t, MeshData(theActiveScan));
  403.       if (!tbView->isManipulating()) {
  404. if (manipulatedRenderDifferent())
  405.   redraw (true);
  406.       }
  407.     }
  408.     return TCL_OK;
  409.   }
  410.   passMoveToTrackball (x, y, t);
  411.   return TCL_OK;
  412. }
  413. int
  414. PlvTransXYViewMouseCmd(ClientData clientData, Tcl_Interp *interp, 
  415.   int argc, char *argv[])
  416. {
  417.   //cout << "transxyview" << endl;
  418.   int x, y, t;
  419.   x = atoi(argv[2]);
  420.   y = atoi(argv[3]);
  421.   t = atoi(argv[4]);
  422.   if (argc > 5) {
  423.     prepareDrawInWin(argv[1]);
  424.     if (!strcmp (argv[5], "start")) {
  425.       lastActionTrackball();
  426.       tbView->pressButton (2, 0, x, y, t, MeshData(theActiveScan));
  427.       if (tbView->isPanning())
  428. resetTranslationScale (x, theHeight - 1 - y);
  429.     } else {
  430.       tbView->pressButton (2, 1, x, y, t, MeshData(theActiveScan));
  431.       if (!tbView->isManipulating()) {
  432. if (manipulatedRenderDifferent())
  433.   redraw (true);
  434.       }
  435.     }
  436.     return TCL_OK;
  437.   }
  438.   passMoveToTrackball (x, y, t);
  439.   return TCL_OK;
  440. }
  441. int
  442. PlvTranslateInPlaneCmd(ClientData clientData, Tcl_Interp *interp, 
  443.        int argc, char *argv[])
  444. {
  445.   if (argc < 3) {
  446.     interp->result = "Bad args to PlvTranslateInPlaneCmd";
  447.     return TCL_ERROR;
  448.   }
  449.   float dx = atof (argv[1]);
  450.   float dy = atof (argv[2]);
  451.   Pnt3 dt (dx, dy, 0);
  452.   TbObj* target = MeshData(theActiveScan);
  453.   //tbView->saveUndoPos (target);
  454.   tbView->translateInEyeCoords (dt, target);
  455.   theScene->computeBBox();
  456.   redraw (true);
  457.   return TCL_OK;
  458. }
  459. int
  460. PlvUndoXformCmd(ClientData clientData, Tcl_Interp *interp, 
  461. int argc, char *argv[])
  462. {
  463.   //tbView->undo_last();
  464.   TbObj::undo();
  465.   theScene->computeBBox();
  466.   redraw (true);
  467.   return TCL_OK;
  468. }
  469. int
  470. PlvRedoXformCmd(ClientData clientData, Tcl_Interp *interp, 
  471. int argc, char *argv[])
  472. {
  473.   //tbView->undo_last();
  474.   TbObj::redo();
  475.   theScene->computeBBox();
  476.   redraw (true);
  477.   return TCL_OK;
  478. }
  479. int
  480. PlvGetScreenToWorldCoords(ClientData clientData, Tcl_Interp *interp, 
  481.      int argc, char *argv[])
  482. {
  483.   if (argc != 3) {
  484.     interp->result = "Bad arguments to PlvGetScreenToWorldCoords";
  485.     return TCL_ERROR;
  486.   }
  487.   int x = atoi(argv[1]);
  488.   int y = theHeight - atoi(argv[2]);
  489.   Pnt3 coords;
  490.   char answer[200];
  491.   if (ScreenToWorldCoordinates (x, y, coords)) {
  492.     sprintf (answer, "%g %g %g", coords[0], coords[1], coords[2]);
  493.   } else if (findZBufferNeighbor (x, y, coords)) {
  494.     sprintf (answer, "Hit background; nearest point: %g %g %g",
  495.      coords[0], coords[1], coords[2]);
  496.   } else {
  497.     strcpy (answer, "Hit background");
  498.   }
  499.   
  500.   Tcl_SetResult (interp, answer, TCL_VOLATILE);
  501.   return TCL_OK;
  502. }
  503. int
  504. PlvSetThisAsCenterOfRotation(ClientData clientData, Tcl_Interp *interp, 
  505.      int argc, char *argv[])
  506. {
  507.   if (argc != 3) {
  508.     interp->result = "Bad arguments to PlvSetThisAsCenterOfRotation";
  509.     return TCL_ERROR;
  510.   }
  511.   int x = atoi(argv[1]);
  512.   int y = theHeight - atoi(argv[2]);
  513.   Pnt3 newcenter;
  514.   if (findZBufferNeighbor (x, y, newcenter))
  515.   {
  516.     tbView->newRotationCenter(newcenter, MeshData(theActiveScan));
  517.     return TCL_OK;  
  518.   }
  519.   else
  520.   {
  521.     interp->result =
  522.       "We hit a background pixel.  You must click on the mesh.n";
  523.     return TCL_ERROR;
  524.   }
  525. }
  526. int
  527. PlvResetCenterOfRotation(ClientData clientData, Tcl_Interp *interp, 
  528.  int argc, char *argv[])
  529. {
  530.   Pnt3 center;
  531.   if (!strcmp (argv[1], "screen")) {
  532.     if (theActiveScan != NULL) {
  533.       interp->result = "Don't use screen-centered rotation for moveMesh";
  534.       return TCL_ERROR;
  535.     }
  536.     center = screenRotationCenter();
  537.   } else if (!strcmp (argv[1], "object")) {
  538.     if (theActiveScan == NULL) {
  539.       theScene->computeBBox (Scene::sync);
  540.       center = theScene->worldCenter();
  541.     } else {
  542.       theActiveScan->getMeshData()->computeBBox();
  543.       center = theActiveScan->getMeshData()->worldCenter();
  544.     }
  545.   } else {
  546.     interp->result = "Must specify object|screen to PlvResetCoR";
  547.     return TCL_ERROR;
  548.   }
  549.   tbView->newRotationCenter (center, MeshData(theActiveScan));
  550.   return TCL_OK;
  551. }
  552. int
  553. PlvCameraInfoCmd(ClientData clientData, Tcl_Interp *interp, 
  554.  int argc, char *argv[])
  555. {
  556.   char transform[100];
  557.   float quat[4];
  558.   float trans[3];
  559.   float camera[3];
  560.   tbView->getXform (quat, trans);
  561.   tbView->getCameraCenter (camera);
  562.   for (int i = 0; i < 3; i++)
  563.     trans[i] += camera[i];
  564.   sprintf (transform, "%g %g %gn%g %g %g %gn",
  565.    trans[0], trans[1], trans[2],
  566.    quat[0], quat[1], quat[2], quat[3]);
  567.   if (argc > 1 && 0 == strcmp (argv[1], "graphical"))
  568.     Tcl_SetResult (interp, transform, TCL_VOLATILE);
  569.   else
  570.     printf ("%sn", transform);
  571.   return TCL_OK;
  572. }
  573. int
  574. PlvSetHomeCmd(ClientData clientData, Tcl_Interp *interp, 
  575.       int argc, char *argv[])
  576. {
  577.   if (argc != 2) {
  578.     interp->result = "Bad arguments to PlvSetHomeCmd";
  579.     return TCL_ERROR;
  580.   }
  581.   DisplayableMesh* ms;
  582.   if (0 == strcmp (argv[1], "")) {
  583.     ms = NULL;
  584.   } else {
  585.     ms = FindMeshDisplayInfo (argv[1]);
  586.     if (ms == NULL) {
  587.       interp->result = "Missing meshset in PlvSetHomeCmd";
  588.       return TCL_ERROR;
  589.     }
  590.   }
  591.   if (ms == NULL) { // viewer
  592.     theScene->setHome();
  593.   } else {
  594.     ms->setHome();
  595.   }
  596.   return TCL_OK;
  597. }
  598. int
  599. PlvGoHomeCmd(ClientData clientData, Tcl_Interp *interp, 
  600.       int argc, char *argv[])
  601. {
  602.   if (argc != 2) {
  603.     interp->result = "Bad arguments to PlvGoHomeCmd";
  604.     return TCL_ERROR;
  605.   }
  606.   DisplayableMesh* ms;
  607.   if (0 == strcmp (argv[1], "")) {
  608.     ms = NULL;
  609.   } else {
  610.     ms = FindMeshDisplayInfo (argv[1]);
  611.     if (ms == NULL) {
  612.       interp->result = "Missing meshset in PlvGoHomeCmd";
  613.       return TCL_ERROR;
  614.     }
  615.   }
  616.   if (ms == NULL) { // viewer
  617.     theScene->goHome();
  618.   } else {
  619.     ms->goHome();
  620.   }
  621.   redraw (true);
  622.   return TCL_OK;
  623. }
  624. int
  625. PlvSetOverallResCmd(ClientData clientData, Tcl_Interp *interp, 
  626.     int argc, char *argv[])
  627. {
  628.   if (argc != 2) {
  629.     interp->result = "Bad argument to PlvSetOverallResCmd";
  630.     return TCL_ERROR;
  631.   }
  632.   int res;
  633.   if (strcmp (argv[1], "temphigh") == 0)
  634.     res = Scene::resTempHigh;
  635.   else if (strcmp (argv[1], "templow") == 0)
  636.     res = Scene::resTempLow;
  637.   else if (strcmp (argv[1], "high") == 0)
  638.     res = Scene::resHigh;
  639.   else if (strcmp (argv[1], "low") == 0)
  640.     res = Scene::resLow;
  641.   else if (strcmp (argv[1], "nexthigh") == 0)
  642.     res = Scene::resNextHigh;
  643.   else if (strcmp (argv[1], "nextlow") == 0)
  644.     res = Scene::resNextLow;
  645.   else
  646.     res = Scene::resDefault;
  647.   theScene->setMeshResolution (res);
  648.   redraw (true);
  649.   return TCL_OK;
  650. }
  651. int
  652. PlvFlattenCameraXformCmd(ClientData clientData, Tcl_Interp *interp, 
  653.  int argc, char *argv[])
  654. {
  655.   if (argc != 1) {
  656.     interp->result = "Bad argument to PlvFlattenCameraXformCmd";
  657.     return TCL_ERROR;
  658.   }
  659.   theScene->flattenCameraXform();
  660.   redraw (true);
  661.   return TCL_OK;
  662. }
  663. int
  664. PlvSpaceCarveCmd(ClientData clientData, Tcl_Interp *interp, 
  665.  int argc, char *argv[])
  666. {
  667.   // space-carve all visible scans and display the result as a mesh
  668.   if (argc < 2) {
  669.     interp->result = "Bad argument to PlvSpaceCarveCmd";
  670.     return TCL_ERROR;
  671.   }
  672.   int levels = atoi (argv[1]);
  673.   if (levels <= 0) {
  674.     interp->result = "Bad levels in PlvSpaceCarveCmd";
  675.     return TCL_ERROR;
  676.   }
  677.   // build list of visible scans
  678.   vector<RigidScan*> scans;
  679.   for (int i = 0; i < theScene->meshSets.size(); i++) {
  680.     if (theScene->meshSets[i]->getVisible())
  681.       scans.push_back (theScene->meshSets[i]->getMeshData());
  682.   }
  683.   // space-carve
  684.   vector<Pnt3> pnts;
  685.   vector<int>  tris;
  686.   CubeTree* ct = new CubeTree (theScene->worldCenter(),
  687.        theScene->worldBbox().maxDim() * 1.05);
  688.   //                           2.1 * theScene->sceneRadius());
  689.   SHOW(theScene->sceneRadius());
  690.   if (argc > 2)
  691.     ct->set_leaf_depth (atoi (argv[2]));
  692.   TIMER(carve);
  693.   ct->carve (scans, levels, pnts, tris);
  694.   TIMER_STOP(carve);
  695.   delete ct;
  696.   cout << "Polygonizer results:nt"
  697.        << tris.size()/3 << " trisnt"
  698.        << pnts.size() << " points"
  699.        << endl;
  700. #if 0  // if we don't trust the points output
  701.   for (i=0; i<pnts.size(); i++) {
  702.     if (pnts[i].norm() < 5.0) continue;
  703.     SHOW(pnts[i]);
  704.     pnts[i].set(0,0,0);
  705.   }
  706. #endif
  707. #if 0  // if we don't trust the tris output
  708.   for (i=0; i<tris.size(); i++) {
  709.     if (tris[i] >= pnts.size()) {
  710.       SHOW(tris[i]);
  711.       int base = i/3;
  712.       tris[3*base] = tris[3*base+1] = tris[3*base+2] = 0;
  713.     }
  714.     assert(tris[i]>=0);
  715.     assert(tris[i]<pnts.size());
  716.   }
  717. #endif
  718.   // now add this mesh to scene, and hide all others
  719.   RigidScan* scan = CreateScanFromGeometry (pnts, tris, "polygonize");
  720.   DisplayableMesh* dm = theScene->addMeshSet (scan);
  721.   Tcl_Eval (interp, "showAllMeshes 0");
  722.   Tcl_SetResult (interp, (char*)dm->getName(), TCL_VOLATILE);
  723.   return TCL_OK;
  724. }
  725. int
  726. PlvConstrainRotationCmd(ClientData clientData, Tcl_Interp *interp, 
  727. int argc, char *argv[])
  728. {
  729.   TbConstraint constraint = CONSTRAIN_NONE;
  730.   if (argc > 1) {
  731.     if (!strcmp (argv[1], "x"))
  732.       constraint = CONSTRAIN_X;
  733.     else if (!strcmp (argv[1], "y"))
  734.       constraint = CONSTRAIN_Y;
  735.     else if (!strcmp (argv[1], "z"))
  736.       constraint = CONSTRAIN_Z;
  737.   }
  738.   tbView->constrainRotation (constraint);
  739.   return TCL_OK;
  740. }
  741. int
  742. PlvSetManipRenderModeCmd(ClientData clientData, Tcl_Interp *interp, 
  743.  int argc, char *argv[])
  744. {
  745.   if (argc == 2) { // better be lock/unlock
  746.     if (!strcmp (argv[1], "lock")) {
  747.       s_bManipulatingLocked = true;
  748.       return TCL_OK;
  749.     } else if (!strcmp (argv[1], "unlock")) {
  750.       s_bManipulatingLocked = false;
  751.       return TCL_OK;
  752.     }
  753.   }
  754.   if (argc != 7) {
  755.     interp->result = "Bad args to PlvSetManipRenderModeCmd";
  756.     return TCL_ERROR;
  757.   }
  758.   theRenderParams->bRenderManipsPoints = atoi (argv[1]);
  759.   theRenderParams->bRenderManipsTinyPoints = atoi (argv[2]);
  760.   theRenderParams->bRenderManipsUnlit = atoi (argv[3]);
  761.   theRenderParams->bRenderManipsLores = atoi (argv[4]);
  762.   theRenderParams->bRenderManipsSkipDlist = atoi (argv[5]);
  763.   theRenderParams->iFastManipsThreshold = atoi (argv[6]);
  764.   return TCL_OK;
  765. }
  766. int
  767. PlvManualRotateCmd(ClientData clientData, Tcl_Interp *interp, 
  768.    int argc, char *argv[])
  769. {
  770.   Pnt3 axis;
  771.   if (argc < 4) {
  772.     interp->result = "Bad args to PlvManualRotateCmd";
  773.     return TCL_ERROR;
  774.   }
  775.   //  tbView->saveUndoPos(MeshData(theActiveScan));
  776.   axis[0] = 1; axis[1] = axis[2] = 0;
  777.   tbView->rotateAroundAxis(axis, atof(argv[1]) * M_PI/180.0, 
  778.    MeshData(theActiveScan));
  779.   axis[0] = 0; axis[1] = 1;
  780.   tbView->rotateAroundAxis(axis, atof(argv[2]) * M_PI/180.0, 
  781.    MeshData(theActiveScan));
  782.   axis[1] = 0; axis[2] = 1;
  783.   tbView->rotateAroundAxis(axis, atof(argv[3]) * M_PI/180.0, 
  784.    MeshData(theActiveScan));
  785.   return TCL_OK;
  786. }
  787. int
  788. PlvManualTranslateCmd(ClientData clientData, Tcl_Interp *interp, 
  789.       int argc, char *argv[])
  790. {
  791.   Pnt3 tp;
  792.   if (argc < 4) {
  793.     interp->result = "Bad args to PlvManualTranslateCmd";
  794.     return TCL_ERROR;
  795.   }
  796.   tp[0] = atof(argv[1]);
  797.   tp[1] = atof(argv[2]);
  798.   tp[2] = atof(argv[3]);
  799.   //tbView->saveUndoPos(MeshData(theActiveScan));
  800.   tbView->translateInEyeCoords(tp, MeshData(theActiveScan));
  801.   
  802.   return TCL_OK;
  803. }
  804. int
  805. PlvPickScanFromPointCmd(ClientData clientData, Tcl_Interp *interp, 
  806. int argc, char *argv[])
  807. {
  808.   if (argc < 2) {
  809.     return TCL_ERROR;
  810.   }
  811.   static vector<DisplayableMesh*> ptMeshMap;
  812.   static int w;
  813.   static int h;
  814.   if (!strcmp (argv[1], "init")) { // ok to call more than once w/o exit
  815.     w = Togl_Width  (toglCurrent);
  816.     h = Togl_Height (toglCurrent);
  817.     // build pts->mesh map
  818.     cerr << "Building map from screen points to meshes ... " << flush;
  819.     GetPtMeshMap (w, h, ptMeshMap);
  820.     cerr << "done." << endl;
  821.   } else if (!strcmp (argv[1], "exit")) {
  822.     // free pts->mesh map
  823.     ptMeshMap.clear();
  824.   } else if (!strcmp (argv[1], "get")) {
  825.     if (argc < 4)
  826.       return TCL_ERROR;
  827.     int x = atoi (argv[2]);
  828.     int y = h - atoi (argv[3]) - 1;
  829.     cmdassert (x >= 0 && x < w);
  830.     cmdassert (y >= 0 && y < h);
  831.     DisplayableMesh* dm = ptMeshMap[y*w + x];
  832.     if (dm)
  833.       interp->result = (char*)dm->getName();
  834.     else
  835.       interp->result = "";
  836.   } else
  837.     return TCL_ERROR;
  838.   return TCL_OK;
  839. }
  840. int
  841. PlvGetVisiblyRenderedScans(ClientData clientData, Tcl_Interp *interp, 
  842. int argc, char *argv[])
  843. {
  844.  
  845.   static vector<DisplayableMesh*> ptMeshMap;
  846.    int w;
  847.    int h;
  848.    int startx, starty;
  849.    int fullx, fully;
  850.    fullx = Togl_Width  (toglCurrent);
  851.    fully =  Togl_Height (toglCurrent);
  852.    if (argc == 1) {
  853.      // Check whole screen
  854.      w = Togl_Width  (toglCurrent);
  855.      h = Togl_Height (toglCurrent);
  856.      startx=0;
  857.      starty=0;
  858.    } else if (argc == 2 && strcmp(argv[1],"selected")==0) {
  859.      // Find Rectangle
  860.       if (theSel.type == Selection::rect)
  861. {
  862.    startx = min (theSel[0].x, theSel[2].x);
  863.    w = abs (theSel[0].x - theSel[2].x);
  864.    starty = min (theSel[0].y, theSel[2].y);
  865.    h = abs (theSel[0].y - theSel[2].y);
  866. }
  867.       else
  868. {
  869.   interp->result="Need to have a rectangle selected";
  870.   return TCL_ERROR;
  871. }
  872.    } else if (argc == 5) {
  873.      // Check in a x,y,w,h region
  874.      startx = atoi(argv[1]);
  875.      starty = atoi(argv[2]);
  876.      w = atoi(argv[3]);
  877.      h = atoi(argv[4]);
  878.    } else {
  879.      // Usage
  880.      Tcl_SetResult(interp,"Usage: plv_getVisiblyRenderedScans [xstart ystart w h]n       plv_getVisiblyRenderedScans [selected]n",TCL_STATIC);
  881.      return TCL_ERROR;
  882.    }
  883.     // build pts->mesh map
  884.    // cerr << "Building map from screen points to meshes ... " << flush;
  885.   GetPtMeshVector (startx,starty,w, h, fullx, fully, ptMeshMap);
  886.   //cerr << "done." << endl;
  887.  
  888.   for (int i=0; i<theScene->meshSets.size(); i++) {
  889.     DisplayableMesh* dm = ptMeshMap[i];
  890.     if (dm) {
  891.       Tcl_AppendResult(interp,(char*)dm->getName()," ", (char *) NULL);
  892.     }      
  893.   }
  894.   ptMeshMap.clear();
  895.   return TCL_OK;
  896. }
  897. int
  898. PlvPositionCameraCmd(ClientData clientData, Tcl_Interp *interp, 
  899.      int argc, char *argv[])
  900. {
  901.   Pnt3 c, o, t;
  902.   float q[4];
  903.   float fov;
  904.   float oblique_x, oblique_y;
  905. #define PNT3Contents(p) p[0],p[1],p[2]
  906.   if (argc == 1) {
  907.     tbView->getState (c, o, t, q, fov, oblique_x, oblique_y);
  908.     char buf[1000];
  909.     sprintf (buf, "%g %g %g %g %g %g %g %g %g %g %g %g %g %g %g %gn",
  910.      PNT3Contents (c),
  911.      PNT3Contents (o),
  912.      PNT3Contents (t),
  913.      q[0], q[1], q[2], q[3], fov, oblique_x, oblique_y);
  914.     Tcl_SetResult (interp, buf, TCL_VOLATILE);
  915.   } else if (argc == 2) {
  916.     // Load camera from a Tsai parameters file
  917.     CameraParams *cam = CameraParams::Read(argv[1]);
  918.     if (!cam) {
  919.       interp->result = "Can't read camera file";
  920.       return TCL_ERROR;
  921.     }
  922.     tbView->getState (c, o, t, q, fov, oblique_x, oblique_y);
  923.     Xform<float> xf(cam->GLmodelmatrix());
  924.     xf.rotX(M_PI);
  925.     xf.toQuaternion(q);
  926.     Pnt3 origin, campos, tmp;
  927.     xf.apply_inv(origin, campos);
  928.     campos[0] -= o[0];  campos[1] -= o[1];  campos[2] -= o[2];
  929.     Xform<float> rot;
  930.     rot.fromQuaternion(q);
  931.     rot.apply(campos, tmp);
  932.     tmp[0] += o[0];  tmp[1] += o[1];  tmp[2] += o[2];
  933.     tmp[0] -= c[0];  tmp[1] -= c[1];  tmp[2] -= c[2];
  934.     t[0] = -tmp[0];  t[1] = -tmp[1];  t[2] = -tmp[2];
  935.     float w = cam->imgwidth, h = cam->imgheight;
  936.     float xscale = (float)theWidth / w;
  937.     float yscale = (float)theHeight / h;
  938.     if (xscale < yscale) {
  939. h *= yscale / xscale;
  940.     } else {
  941. w *= xscale / yscale;
  942.     }
  943.     float halffov = 0.5 * sqrt(w*w+h*h) / cam->pixels_per_radian;
  944.     fov = atan(halffov) * 2.0 * 180.0 / M_PI;
  945.     oblique_x = oblique_y = 0;
  946.     Pnt3 up (0, 1, 0);
  947.     tbView->setup (c, o, up, theScene->sceneRadius(), fov, oblique_x, oblique_y);
  948.     tbView->setState (t, q);
  949.     theScene->computeBBox();
  950.     tbView->newRotationCenter (theScene->worldCenter(), MeshData(NULL));
  951.     delete cam;
  952.   } else if (argc >= 15) {
  953.     for (int i = 1; i < 4; i++)
  954.       c[i-1] = atof (argv[i]);
  955.     for (; i < 7; i++)
  956.       o[i-4] = atof (argv[i]);
  957.     for (; i < 10; i++)
  958.       t[i-7] = atof (argv[i]);
  959.     for (; i < 14; i++)
  960.       q[i-10] = atof (argv[i]);
  961.     fov = atof(argv[i++]);
  962.     if (argc == 17) {
  963. oblique_x = atof(argv[i++]);
  964. oblique_y = atof(argv[i++]);
  965.     } else {
  966. oblique_x = oblique_y = 0;
  967.     }
  968.     Pnt3 up (0, 1, 0);
  969.     tbView->setup (c, o, up, theScene->sceneRadius(), fov, oblique_x, oblique_y);
  970.     tbView->setState (t, q);
  971.     theScene->computeBBox (Scene::sync);
  972.     redraw (true);
  973.   } else {
  974.     interp->result = "Bad # args";
  975.     return TCL_ERROR;
  976.   }
  977.   return TCL_OK;
  978. }
  979. int
  980. PlvSortScanListCmd (ClientData clientData, Tcl_Interp *interp, 
  981.     int argc, char *argv[])
  982. {
  983.   vector<DisplayableMesh*> sortedList;
  984.   vector<Scene::sortCriteria> criteria;
  985.   Scene::sortCriteria sc;
  986.   bool bDictionary = true;
  987.   bool bListInvisible = true;
  988.   criteria.reserve (argc - 1);
  989.   for (int i = 1; i < argc; i++) {
  990.     sc = Scene::none;
  991.     if (!strcmp (argv[i], "Name"))
  992.       sc = Scene::name;
  993.     else if (!strcmp (argv[i], "File date"))
  994.       sc = Scene::date;
  995.     else if (!strcmp (argv[i], "Loadedness"))
  996.       sc = Scene::loaded;
  997.     else if (!strcmp (argv[i], "Visibility"))
  998.       sc = Scene::visibility;
  999.     else if (!strcmp (argv[i], "PolygonCount"))
  1000.       sc = Scene::polycount;
  1001.     else if (!strcmp (argv[i], "ascii"))
  1002.       bDictionary = false;
  1003.     else if (!strcmp (argv[i], "dictionary"))
  1004.       bDictionary = true;
  1005.     else if (!strcmp (argv[i], "onlyvisible"))
  1006.       bListInvisible = false;
  1007.     else
  1008.       cerr << "Warning: unrecognized sort option " << argv[i] << endl;
  1009.     if (sc != Scene::none)
  1010.       criteria.push_back (sc);
  1011.   }
  1012.   theScene->sortSceneMeshes (sortedList, criteria,
  1013.      bDictionary, bListInvisible);
  1014.   int size = 0;
  1015.   for (i = 0; i < sortedList.size(); i++) {
  1016.     size += strlen (sortedList[i]->getName()) + 1;
  1017.   }
  1018.   if (size == 0) {
  1019.     Tcl_SetResult (interp, "", TCL_STATIC);
  1020.   } else {
  1021.     char* list = (char*)malloc (size);
  1022.     char* end = list;
  1023.     for (i = 0; i < sortedList.size(); i++) {
  1024.       strcpy (end, sortedList[i]->getName());
  1025.       end += strlen (end);
  1026.       *end++ = ' ';
  1027.     }
  1028.     end[-1] = 0;  // then terminate the whole shebang
  1029.     
  1030.     Tcl_SetResult (interp, list, (Tcl_FreeProc*)free);
  1031.   }
  1032.   return TCL_OK;
  1033. }
  1034. int
  1035. PlvZoomToRectCmd(ClientData clientData, Tcl_Interp *interp, 
  1036.  int argc, char *argv[])
  1037. {
  1038.   int x1,x2,y1,y2;
  1039.   if (argc==5) {
  1040.     // We passed in the zoom rect values
  1041.     x1 = atoi(argv[1]);
  1042.     int ty1 = theHeight - atoi(argv[2]);
  1043.     x2 = atoi(argv[3]);
  1044.     int ty2 = theHeight - atoi(argv[4]);
  1045.     y1=MIN(ty1,ty2);
  1046.     y2=MAX(ty1,ty2);
  1047.   }
  1048.   else {
  1049.     // We want to get the values from the selection
  1050.     if (theSel.type != Selection::rect) {
  1051.       return TCL_ERROR;
  1052.     }
  1053.     // points 0 and 2 are the corners
  1054.     x1 = MIN (theSel[0].x, theSel[2].x);
  1055.     x2 = MAX (theSel[0].x, theSel[2].x);
  1056.     y1 = MIN (theSel[0].y, theSel[2].y);
  1057.     y2 = MAX (theSel[0].y, theSel[2].y);
  1058.   }
  1059.   printf("zoom %d %d : %d %dn",x1,y1,x2,y2);
  1060.   // need to set translation scale before calling zoomToRect
  1061.   // BUGBUG this just looks for data near the center of selection rect;
  1062.   // normally people will probably use it this way, but we could actually
  1063.   // unproject everything in the selection, and average it.
  1064. resetTranslationScale ((x1 + x2) / 2, (y1 + y2) / 2);
  1065.   tbView->zoomToRect (x1, y1, x2, y2);
  1066.   // attempt to update selection rectangle...
  1067.   int rectW = x2 - x1;
  1068.   int rectH = y2 - y1;
  1069.   float zoom = MIN ((float)theWidth / rectW, (float)theHeight / rectH);
  1070.   x1 = (theWidth / 2) - (rectW * zoom / 2);
  1071.   x2 = (theWidth / 2) + (rectW * zoom / 2);
  1072.   y1 = (theHeight / 2) - (rectH * zoom / 2);
  1073.   y2 = (theHeight / 2) + (rectH * zoom / 2);
  1074. // update selection info only if we zoomed to a rect
  1075. if (argc!=5) {
  1076. theSel[0].x = x1; theSel[0].y = y1;
  1077. theSel[2].x = x2; theSel[2].y = y2;
  1078. theSel[1].x = x1; theSel[1].y = y2;
  1079. theSel[3].x = x2; theSel[3].y = y1;
  1080. }
  1081.   Togl_PostOverlayRedisplay (toglCurrent);
  1082.   redraw (true);
  1083.   return TCL_OK;
  1084. }
  1085. bool
  1086. isManipulatingRender (void)
  1087. {
  1088.   // return true if we render any differently (points, lores, unlit, etc.)
  1089.   // because the rendering is being trackballed
  1090.   if (s_bManipulatingLocked)
  1091.     return true;
  1092.   if (tbView->isManipulating())
  1093.       return manipulatedRenderDifferent();
  1094.   return false;
  1095. }
  1096. bool
  1097. manipulatedRenderDifferent (void)
  1098. {
  1099.   if (theRenderParams->iFastManipsThreshold < 0)
  1100.     return false;
  1101.   if (theRenderParams->bRenderManipsPoints
  1102.       || theRenderParams->bRenderManipsUnlit
  1103.       || theRenderParams->bRenderManipsLores) {
  1104.     char* pszNum = Tcl_GetVar (Togl_Interp (toglCurrent),
  1105.        "visPolyCount",
  1106.        TCL_GLOBAL_ONLY);
  1107.     int nPolys = atoi (pszNum);
  1108.     return (nPolys > theRenderParams->iFastManipsThreshold);
  1109.   }
  1110.   return false;
  1111. }
  1112. static Tk_TimerToken s_autoResetTimerToken = 0;
  1113. static void
  1114. autoResetProc (ClientData clientData = NULL)
  1115. {
  1116.   theScene->centerCamera();
  1117.   theRenderParams->lightPosition[0] = 0.0;
  1118.   theRenderParams->lightPosition[1] = 0.0;
  1119.   theRenderParams->lightPosition[2] = 1.0;
  1120.   redraw (true);
  1121.   if (!s_autoResetScript.empty()) {
  1122.     Tcl_Eval (g_tclInterp, (char*)s_autoResetScript.c_str());
  1123.   }
  1124.   rescheduleAutoReset();
  1125. }
  1126. static void
  1127. rescheduleAutoReset (void)
  1128. {
  1129.   if (s_autoResetTimerToken) {
  1130.     Tk_DeleteTimerHandler (s_autoResetTimerToken);
  1131.     s_autoResetTimerToken = 0;
  1132.   }
  1133.   if (s_iAutoResetTime) {
  1134.     s_autoResetTimerToken =
  1135.       Tk_CreateTimerHandler (s_iAutoResetTime, autoResetProc,
  1136.      NULL);
  1137.   }
  1138. }
  1139. int
  1140. PlvForceKeepOnscreenCmd(ClientData clientData, Tcl_Interp *interp, 
  1141. int argc, char *argv[])
  1142. {
  1143.   if (argc < 2) {
  1144.     interp->result = "Wrong # args";
  1145.     return TCL_ERROR;
  1146.   }
  1147.   s_bForceOnscreen = atoi (argv[1]);
  1148.   if (s_bForceOnscreen && !passesOnscreenTest()) {
  1149.     s_bForceOnscreen = false;
  1150.     interp->result = "Please GET the model onscreen "
  1151.       "before trying to KEEP it there";
  1152.     return TCL_ERROR;
  1153.   }
  1154.   if (s_bForceOnscreen && argc > 2) {
  1155.     // 2nd arg is time until auto-reset-viewer.
  1156.     if (argc > 3) {
  1157.       // 3nd arg is script to run on auto-reset.
  1158.       s_autoResetScript = crope (argv[3]);
  1159.     }
  1160.     // force message to pop up right away
  1161.     s_iAutoResetTime = 50;
  1162.     rescheduleAutoReset();
  1163.     s_iAutoResetTime = 1000 * atoi (argv[2]);
  1164.   } else {
  1165.     s_iAutoResetTime = 0;
  1166.     s_autoResetScript = crope();
  1167.     rescheduleAutoReset();
  1168.   }
  1169.   return TCL_OK;
  1170. }
  1171. void 
  1172. SpinTrackballs (ClientData clientData)
  1173. {
  1174.   if (tbView->isSpinning()) {
  1175.     Xform<float> before;
  1176.     if (s_bForceOnscreen)
  1177.       before = tbView->getUndoXform();
  1178.     redraw (true);
  1179.     if (s_bForceOnscreen && !passesOnscreenTest()) {
  1180.       cerr << "killing spin before it goes offscreen." << endl;
  1181.       tbView->stop();
  1182.       tbView->setUndoXform (before);
  1183.     }
  1184.   }
  1185.   Tk_CreateTimerHandler (30, SpinTrackballs, clientData);
  1186. }