Index: inst/WebGL/template.html
===================================================================
--- inst/WebGL/template.html	(revision 962)
+++ inst/WebGL/template.html	(working copy)
@@ -1,7 +1,7 @@
 	<html><head>
 	<TITLE>RGL model</TITLE>
     </head>
-	<body onload="webGLStart();"> 
+	<body onload="WebGL.start();"> 
     
     <div align="center">
     %WebGL%
Index: R/webGL.R
===================================================================
--- R/webGL.R	(revision 962)
+++ R/webGL.R	(working copy)
@@ -337,7 +337,29 @@
   '
 	<script type="text/javascript"> 
 
-	function getShader ( gl, id ){
+  var min = Math.min;
+  var max = Math.max;
+	var sqrt = Math.sqrt;
+	var sin = Math.sin;
+	var acos = Math.acos;
+	var tan = Math.tan;
+	var SQRT2 = Math.SQRT2;
+	var PI = Math.PI;
+	var log = Math.log;
+	var exp = Math.exp;
+
+  var %prefix%WebGLClass = function(){
+    this.prefix="%prefix%";
+    this.zoom = 1;
+    this.userMatrix = null;
+    this.panCallbacks = [];
+    this.fovCallbacks = [];
+    this.zoomCallbacks = [];
+    this.fov = 1;
+  };
+
+  (function(){
+    this.getShader = function( gl, id ){
 	   var shaderScript = document.getElementById ( id );
 	   var str = "";
 	   var k = shaderScript.firstChild;
@@ -356,20 +378,9 @@
 	   if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) == 0)
 	     alert(gl.getShaderInfoLog(shader));
 	   return shader;
-	}
+	  };
 
-	var min = Math.min;
-	var max = Math.max;
-	var sqrt = Math.sqrt;
-	var sin = Math.sin;
-	var acos = Math.acos;
-	var tan = Math.tan;
-	var SQRT2 = Math.SQRT2;
-	var PI = Math.PI;
-	var log = Math.log;
-	var exp = Math.exp;
-
-	function %prefix%webGLStart() {
+	  this.start = function(){
 	   var debug = function(msg) {
 	     document.getElementById("%prefix%debug").innerHTML = msg;
 	   }
@@ -406,10 +417,10 @@
   
   setUser <- function() c(
     subst(
-'	   var zoom = %zoom%;
-	   var fov = %fov%;', zoom = par3d("zoom"), fov = max(1, min(179, par3d("FOV")))),
-'	   var userMatrix = new CanvasMatrix4();
-	   userMatrix.load([',
+'	   this.setZoom(%zoom%);
+	   this.setFOV(%fov%);', zoom = par3d("zoom"), fov = max(1, min(179, par3d("FOV")))),
+'	   this.userMatrix = new CanvasMatrix4();
+	   this.setUserMatrix([',
     inRows(t(par3d("userMatrix")), perrow=4, leadin='	   '),
 '		]);')
   
@@ -449,7 +460,7 @@
 	       ctx.imageSmoothingEnabled = true;
 	       ctx.drawImage(image, 0, 0, canvasX, canvasY);
 	       handleLoadedTexture(texture, canvas);
-   	       drawScene();
+   	       drawScene.call(this);
 	     }
 	     image.src = filename;
 	   }  	   
@@ -540,8 +551,8 @@
     radius <- sqrt(sum(ranges^2))*1.1 # A bit bigger to handle labels
     subst(
 '	     var radius = %radius%;
-	     var s = sin(fov*PI/360);
-	     var t = tan(fov*PI/360);
+	     var s = sin(this.fov*PI/360);
+	     var t = tan(this.fov*PI/360);
 	     var distance = radius/s;
 	     var near = distance - radius;
 	     var far = distance + radius;
@@ -549,11 +560,11 @@
 	     var aspect = width/height;
 	     prMatrix.makeIdentity();
 	     if (aspect > 1)
-	       prMatrix.frustum(-hlen*aspect*zoom, hlen*aspect*zoom, 
-	                        -hlen*zoom, hlen*zoom, near, far);
+	       prMatrix.frustum(-hlen*aspect*this.zoom, hlen*aspect*this.zoom, 
+	                        -hlen*this.zoom, hlen*this.zoom, near, far);
 	     else  
-	       prMatrix.frustum(-hlen*zoom, hlen*zoom, 
-	                        -hlen*zoom/aspect, hlen*zoom/aspect, 
+	       prMatrix.frustum(-hlen*this.zoom, hlen*this.zoom, 
+	                        -hlen*this.zoom/aspect, hlen*this.zoom/aspect, 
 	                        near, far);',
             radius)
   }
@@ -566,7 +577,7 @@
 '	     mvMatrix.makeIdentity();
 	     mvMatrix.translate( %cx%, %cy%, %cz% );
 	     mvMatrix.scale( %sx%, %sy%, %sz% );   
-	     mvMatrix.multRight( userMatrix );  
+	     mvMatrix.multRight( this.userMatrix );  
 	     mvMatrix.translate(0, 0, -distance);',
      cx=-center[1], cy=-center[2], cz=-center[3],
      sx=scale[1],   sy=scale[2],   sz=scale[3])
@@ -577,7 +588,7 @@
     subst(
 '	     normMatrix.makeIdentity();
 	     normMatrix.scale( %sx%, %sy%, %sz% );   
-	     normMatrix.multRight( userMatrix );',
+	     normMatrix.multRight( this.userMatrix );',
      sx=1/scale[1], sy=1/scale[2], sz=1/scale[3])
   }
   
@@ -602,8 +613,8 @@
     if (!sprites_3d)
       result <- c(result, subst(
 '	   var prog%id%  = gl.createProgram();
-	   gl.attachShader(prog%id%, getShader( gl, "%prefix%vshader%id%" ));
-	   gl.attachShader(prog%id%, getShader( gl, "%prefix%fshader%id%" ));
+	   gl.attachShader(prog%id%, this.getShader( gl, "%prefix%vshader%id%" ));
+	   gl.attachShader(prog%id%, this.getShader( gl, "%prefix%fshader%id%" ));
 	   //  Force aPos to location 0, aCol to location 1 
 	   gl.bindAttribLocation(prog%id%, 0, "aPos");
 	   gl.bindAttribLocation(prog%id%, 1, "aCol");
@@ -1148,8 +1159,9 @@
 	   gl.clearDepth(1.0);
 	   gl.clearColor(%r%, %g%, %b%, %a%);
 	   var xOffs = yOffs = 0,  drag  = 0;
-	   drawScene();
+	   drawScene.call(this);
 
+  
 	   function drawScene(){
 	     gl.depthMask(true);
 	     gl.disable(gl.BLEND);',
@@ -1217,6 +1229,7 @@
 	   }
 
 	   var rotBase;
+     var self = this;
 '
     
     for (i in seq_along(uhandlers)) {
@@ -1225,7 +1238,7 @@
         trackball = 
 '	   var trackballdown = function(x,y) {
 	     rotBase = screenToVector(x, y);
-	     saveMat.load(userMatrix);
+	     saveMat.load(self.userMatrix);
 	   }
 	   
 	   var trackballmove = function(x,y) {
@@ -1235,9 +1248,10 @@
 	   	       rotBase[2]*rotCurrent[2];
 	     var angle = acos( dot/vlen(rotBase)/vlen(rotCurrent) )*180./PI;
 	     var axis = xprod(rotBase, rotCurrent);
-	     userMatrix.load(saveMat);
-	     userMatrix.rotate(angle, axis[0], axis[1], axis[2]);
-	     drawScene();
+	     saveMat.rotate(angle, axis[0], axis[1], axis[2]);
+       self.setUserMatrix(saveMat);	     
+       drawScene.call(self);
+       rotBase = screenToVector(x, y);
 	   }
 ',
 
@@ -1254,17 +1268,17 @@
 '
 	   var %h%down = function(x,y) {
 	     rotBase = screenToVector(x, height/2);
-	     saveMat.load(userMatrix);
+	     saveMat.load(self.userMatrix);
 	   }
 	   	   
 	   var %h%move = function(x,y) {
 	     var rotCurrent = screenToVector(x,height/2);
 	     var angle = (rotCurrent[0] - rotBase[0])*180/PI;
-	     userMatrix.load(saveMat);
+	     self.setUserMatrix(saveMat);
 	     var rotMat = new CanvasMatrix4();
 	     rotMat.rotate(angle, %h%[0], %h%[1], %h%[2]);
-	     userMatrix.multLeft(rotMat);
-	     drawScene();
+	     self.userMatrix.multLeft(rotMat);
+	     drawScene.call(self);
 	   }
 	   
 ',           h)),
@@ -1273,12 +1287,12 @@
 	   var zoom0 = 1;
 	   var zoomdown = function(x, y) {
 	     y0zoom = y;
-	     zoom0 = log(zoom);
+	     zoom0 = log(self.zoom);
 	   }
 
 	   var zoommove = function(x, y) {
-	     zoom = exp(zoom0 + (y-y0zoom)/height);
-	     drawScene();
+	     self.setZoom(exp(zoom0 + (y-y0zoom)/height));
+       drawScene.call(self);
 	   }
 ', 
       fov = 
@@ -1286,12 +1300,12 @@
 	   var fov0 = 1;
 	   var fovdown = function(x, y) {
 	     y0fov = y;
-	     fov0 = fov;
+	     fov0 = self.fov;
 	   }
 
 	   var fovmove = function(x, y) {
-	     fov = max(1, min(179, fov0 + 180*(y-y0fov)/height));
-	     drawScene();
+	     self.setFOV(max(1, min(179, fov0 + 180*(y-y0fov)/height)));
+	     drawScene.call(self);
 	   }
 '))  }
         
@@ -1306,7 +1320,7 @@
 ',    d1=down[1], d2=down[2], d3=down[3], m1=move[1], m2=move[2], m3=move[3]))
   }
 	
-  scriptEnd <- 
+  scriptEnd <- subst(
 '	   function relMouseCoords(event){
 	     var totalOffsetX = 0;
 	     var totalOffsetY = 0;
@@ -1360,16 +1374,66 @@
 	     var del = 1.1;
 	     if (ev.shiftKey) del = 1.01;
 	     var ds = ((ev.detail || ev.wheelDelta) > 0) ? del : (1 / del);
-	     zoom *= ds;
-	     drawScene();
+	     self.setZoom(self.zoom * ds);
+       drawScene.call(self);
 	     ev.preventDefault();
 	   };
 	   canvas.addEventListener("DOMMouseScroll", wheelHandler, false);
 	   canvas.addEventListener("mousewheel", wheelHandler, false);
 
+	  };
+  
+    this.setZoom = function(zoom){
+      this.zoom = zoom;
+      for (var i = 0; i < this.zoomCallbacks.length; i++){
+        this.zoomCallbacks[i](zoom);
 	}
-	</script>'
+    };
 
+    this.setUserMatrix = function(mat){
+      this.userMatrix.load(mat);
+      for (var i = 0; i < this.panCallbacks.length; i++){
+        this.panCallbacks[i](this.userMatrix);
+      }
+    };
+
+    this.setFOV = function(fov){
+      this.fov = fov;
+      for (var i = 0; i < this.panCallbacks.length; i++){
+        this.fovCallbacks[i](this.fov);
+      }
+    }
+
+    this.getZoom = function(){
+      return this.zoom;
+    };
+
+    this.getFOV = function(){
+      return this.fov;
+    }
+
+    this.getUserMatrix = function(){
+      return this.userMatrix;
+    };
+
+    this.onPan = function(callback){
+      this.panCallbacks.push(callback);
+    };
+
+    this.onZoom = function(callback){
+      this.zoomCallbacks.push(callback);
+    };
+
+    this.onFOV = function(callback){
+      this.fovCallbacks.push(callback);
+    }
+
+  }).call(%prefix%WebGLClass.prototype);
+
+  %prefix%WebGL = new %prefix%WebGLClass();
+
+	</script>', prefix)
+
   footer <- function() subst('
 	<canvas id="%prefix%canvas" width="1" height="1"></canvas> 
         <p id="%prefix%debug">
