--- geo_test-2-notimer-linear-02.html	2011-01-09 17:15:19.517797003 -0700
+++ htr16.html	2011-05-16 12:27:52.874528997 -0600
@@ -83,8 +83,23 @@
   var arrowRadius= 25; //20; // distance (in viewport pixels) from the arrow center to select arrow in the viewport
 
   var arrowRadius=20; // distance (in viewport pixels) from the arrow center to select arrow in the viewport
-
+  var maxPanoTilt=45; // maximal tilt of the panorama from vertical during HTR adjustment
   var gl;
+  var lockedDirection=0;
+  var directionIsLocked=false;
+/*
+Orto mode is implemented using the model of a weight attached with a fixed length (orthoStep==10) string to a mouse pointer.
+When the movement is just started (string was never tight), any direction is possible, as if ortho is off. As soon as the string
+becomes tight first time, the rotation angles are restored to initial (at mousedown event) and the whole mouse movement (~=orthoStep)
+is processed at once, deciding which of the two movements (vertiacal or horizontal) was farther - the other one is zeroed out.
+During the movement, if the "string was tight" (distance between current mouse coordinates and savedX, savedY is greater than orthoStep,
+the "weight" is moved towards pointer to the distance orthoStep from it, direction (vertical/horizontal) is re-calculated. If the string
+is not tight (i.e. reverse of the movement direction), the selected direction is not changed (until the string will be tight again)
+*/
+  var orthoStep=10; // use movement that far to select direction in ortho mode
+  var ortoDir=-1; // -1 - not aset, 0 - horizontal, 1  - vertical
+  var savedX, savedY, savedH, savedT, savedR, savedA, SavedE; // saved X, Y, Heading , Tilt,, roll (Azimuth, Elevation)  at saved X, Y
+  var editEnabled=true; // TOSO: use url parameters to tuern editEnabled on/off
   function initGL(canvas) {
     try {
       gl = canvas.getContext("experimental-webgl");
@@ -203,8 +218,6 @@
 
 
   function loadTexture(number) {
-//    document.title="loadTexture("+number+"), "+panoTexturesSrc[number];
-
     panoImages[number].src=panoTexturesSrc[number];
   }
 
@@ -216,14 +229,34 @@
         document.getElementById("idLongitude").innerHTML=Math.round(latLongRounding*map[0].longitude)/latLongRounding;
         document.getElementById("idLatitude").innerHTML=Math.round(latLongRounding*map[0].latitude)/latLongRounding;
         document.getElementById("idAltitude").innerHTML=Math.round(altRounding*map[0].altitude)/altRounding;
-        document.getElementById("idHeading").innerHTML=Math.round(orientRounding*map[0].heading)/orientRounding;
-        document.getElementById("idTilt").innerHTML=Math.round(orientRounding*(map[0].tilt-90))/orientRounding;
-        document.getElementById("idRoll").innerHTML=Math.round(orientRounding*map[0].roll)/orientRounding;
+        document.getElementById("idHeading").value=Math.round(orientRounding*map[0].heading)/orientRounding;
+        document.getElementById("idTilt").value=Math.round(orientRounding*(map[0].tilt-90))/orientRounding;
+        document.getElementById("idRoll").value=Math.round(orientRounding*map[0].roll)/orientRounding;
         document.getElementById("idViewAzimuth").innerHTML=Math.round(orientRounding*azimuth)/orientRounding;
         document.getElementById("idViewElevation").innerHTML=Math.round(-orientRounding*elevation)/orientRounding;
       }
     }
- 
+    function inputHTR() {
+      map[0].heading=parseInt(document.getElementById("idHeading").value);
+      map[0].tilt=parseInt(document.getElementById("idTilt").value)+90;
+      map[0].roll=parseInt(document.getElementById("idRoll").value);
+      recalcHTR();
+      showArrows=true;
+    if (showArrows) for (var i=0;i<arrows.length;i++)  drawArrow(arrows[i][0],arrows[i][1],arrows[i][2]);
+
+    }
+  function unlockAxis(){
+    directionIsLocked=false;
+    document.getElementById("idLockedAxis").style.display="none";
+  }
+  function lockAxis(direction){
+    lockedDirection=direction;
+    directionIsLocked=true;
+    document.getElementById("idLockedAxisDirection").innerHTML=lockedDirection;
+    document.getElementById("idLockedAxis").style.display="";
+  }
+
+
   function kmlGot(panos) {
      map=panos;
      showInformation();
@@ -292,12 +325,16 @@
 
 // calculate panorama rotation matrix according to heading, tilt, roll
      currentTextureNumber=map[0].textureNumber;
+     recalcHTR();
+     waitForPano=false;
+  }
 
-     var newRotationMatrix = createRotationMatrix(map[0].tilt-90, [1, 0, 0]);
-     newRotationMatrix  = newRotationMatrix.x(createRotationMatrix(-map[0].roll, [0, 0, 1]));
-     nodeRotationMatrix = newRotationMatrix.x(createRotationMatrix(-map[0].heading-90, [0, 1, 0]));
+  function recalcHTR() {
+     var newRotationMatrix = createRotationMatrix(-map[0].heading, [0, 1, 0]);
+     newRotationMatrix  = newRotationMatrix.x(createRotationMatrix(map[0].tilt-90, [1, 0, 0]));
+     newRotationMatrix =  newRotationMatrix.x(createRotationMatrix(-map[0].roll, [0, 0, 1]));
+     nodeRotationMatrix = newRotationMatrix.x(createRotationMatrix(-90, [0, 1, 0]));
      drawScene();
-     waitForPano=false;
   }
   function handleLoadedArrowTexture(texture) {
     gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
@@ -387,7 +424,10 @@
   var showArrows = true;
   var arrowsVisible=Array(arrows.length);
   var arrowsXY=     Array(arrows.length);
-  var currenArrow= -1;
+  var currentArrow= -1;
+  var shiftKey=false;
+  var ctrlKey=false;
+
   for (var i=0; i< arrows.length; i++) {
      arrowsVisible[i]=false;
      arrowsXY[i]=[0,0];
@@ -395,8 +435,10 @@
   var panoRotationMatrix = Matrix.I(4); // current view rotation matrix
   var nodeRotationMatrix = Matrix.I(4); // rotation matrix of the current panorama relative to the world
   function handleMouseDown(event) {
-    if (currenArrow>=0) {
-      var url=kmlUrl+"?latitude="+map[currenArrow+1].latitude+"&longitude="+map[currenArrow+1].longitude;
+    shiftKey= (event.shiftKey==1) && editEnabled;
+    ctrlKey=  (event.ctrlKey==1) && editEnabled;
+    if (!shiftKey && !ctrlKey && (currentArrow>=0)) {
+      var url=kmlUrl+"?latitude="+map[currentArrow+1].latitude+"&longitude="+map[currentArrow+1].longitude;
       waitForPano=true;
       document.body.style.cursor="wait";
       requestKml(url,kmlGot);
@@ -405,26 +447,43 @@
     mouseDown = true;
     lastMouseX = event.clientX;
     lastMouseY = event.clientY;
-drawScene();
+    if (ctrlKey) {
+      var direction=azimuth + ((lastMouseX-gl.viewportWidth/2) /gl.viewportHeight*fovY);
+      direction=Math.round (10*direction)/10;
+      lockAxis(direction);
+
+    }
+    drawScene();
+    savedX=event.clientX;
+    savedY=event.clientY;
+    if (!shiftKey && !ctrlKey) {
+      savedA=azimuth;
+      savedE=elevation;
+    } else {
+      savedH=map[0].heading;
+      savedT=map[0].tilt;
+      savedR=map[0].roll;
+    }
+    ortoDir=-1;
   }
 
+  var azimuth=0;
+  var elevation=0;
 
   function handleMouseUp(event) {
     mouseDown = false;
   }
 
-  var azimuth=0;
-  var elevation=0;
   function detectOverArrow(x,y) {
     var minRadius2= arrowRadius*arrowRadius;
-    currenArrow= -1;
+    currentArrow= -1;
     var dx,dy,r2;
     for (var i =0; i<arrowsVisible.length; i++) if (arrowsVisible[i]){
        dx=x-arrowsXY[i][0];
        dy=y-arrowsXY[i][1];
        r2=dx*dx+dy*dy;
        if (r2<minRadius2) {
-          currenArrow=i;
+          currentArrow=i;
           minRadius2=r2;
        }
     }
@@ -441,24 +500,83 @@
     if (!mouseDown) {
        detectOverArrow(newX,newY);
        if (waitForPano) document.body.style.cursor="wait";
-       else if (currenArrow>=0) document.body.style.cursor="pointer";
+       else if (currentArrow>=0) document.body.style.cursor="pointer";
+       else if (editEnabled && ((event.shiftKey==1) || (event.ctrlKey==1))) document.body.style.cursor="crosshair";
        else document.body.style.cursor="move";
+       lastMouseX=newX;
+       lastMouseY=newY;
       return;
     }
 
-    var deltaX = newX - lastMouseX
+    var deltaX = newX - lastMouseX;
     var deltaY = newY - lastMouseY;
-    azimuth=azimuth+(-deltaX /gl.viewportHeight*fovY);
-    elevation=elevation+(-deltaY/gl.viewportHeight*fovY);
-    while (azimuth<0)azimuth+=360;
-    while (azimuth>=360)azimuth-=360;
-    if (elevation>90) elevation=90;
-    if (elevation<-90) elevation=-90;
-    if (showInfo) showInformation();
-
-    var newRotationMatrix = createRotationMatrix(elevation, [1, 0, 0]);
-    panoRotationMatrix = newRotationMatrix.x(createRotationMatrix(azimuth, [0, 1, 0]));
+    if (document.getElementById("idOrtho").checked) {
+      var dX0 = newX - savedX
+      var dY0 = newY - savedY;
+      var k=Math.sqrt((dX0*dX0+dY0*dY0)/ (orthoStep*orthoStep));
+      if (ortoDir<0) { // undefined yet - too small travel from the mousedown point
+        lastMouseX=savedX;
+        lastMouseY=savedY;
+        deltaX = newX - lastMouseX;
+        deltaY = newY - lastMouseY;
+        if (!shiftKey && !ctrlKey) {
+          azimuth=savedA;
+          elevation=savedE;
+        } else {
+          map[0].heading=savedH;
+          map[0].tilt=savedT;
+          map[0].roll=savedR;
+        }
+        if (deltaX*deltaX>deltaY*deltaY) deltaY=0;
+        else                             deltaX=0;
+        if (k>1.0) ortoDir=(deltaY*deltaY>deltaX*deltaX)?1:0;
+       } else { // direction was set, maybe need to change
+         if (k>1.0) { // rope is tight, move "weight" towards the current mouse pointer
+            savedX=Math.round(savedX+dX0*(k-1)/k);
+            savedY=Math.round(savedY+dY0*(k-1)/k);
+            ortoDir=(dX0*dX0>dY0*dY0)?0:1; // only change direction if the "rope is tight"
+         }
+         if (ortoDir>0) deltaX=0;
+         else           deltaY=0;
+       }
+    }
+    if (!shiftKey && !ctrlKey) {
+      document.body.style.cursor="move";
+      azimuth=azimuth+(-deltaX /gl.viewportHeight*fovY);
+      elevation=elevation+(-deltaY/gl.viewportHeight*fovY);
+      while (azimuth<0)azimuth+=360;
+      while (azimuth>=360)azimuth-=360;
+      if (elevation>90) elevation=90;
+      if (elevation<-90) elevation=-90;
+      if (showInfo) showInformation();
 
+      var newRotationMatrix = createRotationMatrix(elevation, [1, 0, 0]);
+      panoRotationMatrix = newRotationMatrix.x(createRotationMatrix(azimuth, [0, 1, 0]));
+    } else if (shiftKey) {
+      document.body.style.cursor="crosshair";
+      map[0].heading=map[0].heading+(deltaX /gl.viewportHeight*fovY);
+      while (map[0].heading<0)map[0].heading+=360;
+      while (map[0].heading>=360)map[0].heading-=360;
+      var ang=deltaY/gl.viewportHeight*fovY;
+      var axisDir;
+      if (!directionIsLocked) { // rotate around horizontal axis perpendicular to view
+        axisDir=(map[0].heading-azimuth)*Math.PI/180;
+        map[0].tilt-=ang*Math.cos(axisDir);
+        map[0].roll-=ang*Math.sin(axisDir);
+      } else {  // rotate around horizontal axis at lockedDirection
+        axisDir=(map[0].heading-(lockedDirection+90))*Math.PI/180;
+        var mouseDir=azimuth + ((newX-gl.viewportWidth/2) /gl.viewportHeight*fovY);
+        ang/=Math.sin((mouseDir-lockedDirection)*Math.PI/180);
+        map[0].tilt-=ang*Math.cos(axisDir);
+        map[0].roll-=ang*Math.sin(axisDir);
+      }
+      if (map[0].tilt>(90+maxPanoTilt)) map[0].tilt=(90+maxPanoTilt);
+      if (map[0].tilt<(90-maxPanoTilt)) map[0].tilt=(90-maxPanoTilt);
+      if (map[0].roll> maxPanoTilt) map[0].roll=maxPanoTilt;
+      if (map[0].roll<-maxPanoTilt) map[0].roll=-maxPanoTilt;
+      recalcHTR();
+      showInformation();
+    }
     lastMouseX = newX
     lastMouseY = newY;
     drawScene();
@@ -868,16 +986,17 @@
 </div>
 <table id="table" border="1">
 <tr><td> Longitude:</td><td><span id="idLongitude">0000</span></td>
-    <td> Heading:</td><td><span id="idHeading">0000</span></td>
+    <td> Heading:</td><td><input id="idHeading" size="5" value="0" onChange="inputHTR();"/></td>
     <td> View azimuth:</td><td><span id="idViewAzimuth">0000</span></td>
 </tr>
 <tr><td> Latutude:</td><td><span id="idLatitude">0000</span></td>
-    <td> Tilt:</td><td><span id="idTilt">0000</span></td>
+    <td> Tilt:</td><td><input id="idTilt" size="5" value="0" onChange="inputHTR();"/></td>
     <td> View elevation:</td><td><span id="idViewElevation">0000</span></td>
 </tr>
 <tr><td> Altitude:</td><td><span id="idAltitude">0000</span></td>
-    <td> Roll:</td><td><span id="idRoll">0000</span></td>
-    <td colspan="2"> &nbsp;</td></tr>
+    <td> Roll:</td><td><input id="idRoll" size="5" value="0" onChange="inputHTR();"/></td>
+    <td><input type="checkbox" id="idOrtho" name="ortho" value="1" />Ortho</td>
+    <td> <span id="idLockedAxis" style="display:none;">Rotation axis: <span id="idLockedAxisDirection">000</span><button id="idUnlock" type="button" onclick="unlockAxis();" >unlock</button><span/></td></tr>
 <tr></tr>
 <tr></tr>
 </tr>
