R/webGL.R

subst <- function(strings, ..., digits=7) {
  substitutions <- list(...)
  names <- names(substitutions)
  if (is.null(names)) names <- rep("", length(substitutions))
  for (i in seq_along(names)) {
    if ((n <- names[i]) == "")
      n <- as.character(sys.call()[[i+2]])
    value <- substitutions[[i]]
    if (is.numeric(value)) 
      value <- formatC(value, digits=digits, width=1)
    strings <- gsub(paste("%", n, "%", sep=""), value, strings)
  }
  strings
}

addDP <- function(value, digits=7) {
  if (is.numeric(value)) {
    value <- formatC(value, digits=digits, width=1)
    noDP <- !grepl("[.]", value)
    value[noDP] <- paste(value[noDP], ".", sep="")
  }
  value
}

inRows <- function(values, perrow, leadin = '	', digits=7) {
  if (is.matrix(values)) values <- t(values)
  values <- c(values)
  if (is.numeric(values))
    values <- formatC(values, digits=digits, width=1)
  len <- length(values)
  if (len %% perrow != 0)
    values <- c(values, rep("PADDING", perrow - len %% perrow))
  values <- matrix(values, ncol=perrow, byrow=TRUE)
  
  lines <- paste(leadin, apply(values, 1,
                     function(row) paste(row, collapse=", ")))
  lines[length(lines)] <- gsub(", PADDING", "", lines[length(lines)])
  paste(lines, collapse=",\n")
}

convertBBox <- function(id) {
  verts <- rgl.attrib(id, "vertices")
  text <- rgl.attrib(id, "text")
  if (!length(text))
    text <- rep("", NROW(verts))
  mat <- rgl.getmaterial(id = id)
  if (length(mat$color) > 1)
    mat$color <- mat$color[2] # We ignore the "box" colour
  
  if(any(missing <- text == "")) 
    text[missing] <- apply(verts[missing,], 1, function(row) format(row[!is.na(row)]))
    
  res <- integer(0)
  if (any(inds <- is.na(verts[,2]) & is.na(verts[,3]))) 
    res <- c(res, do.call(axis3d, c(list(edge = "x", at = verts[inds, 1], labels = text[inds]), mat)))
  if (any(inds <- is.na(verts[,1]) & is.na(verts[,3]))) 
    res <- c(res, do.call(axis3d, c(list(edge = "y", at = verts[inds, 2], labels = text[inds]), mat)))
  if (any(inds <- is.na(verts[,1]) & is.na(verts[,2]))) 
    res <- c(res, do.call(axis3d, c(list(edge = "z", at = verts[inds, 3], labels = text[inds]), mat)))
  res <- c(res, do.call(box3d, mat))
  res
}

convertBBoxes <- function (id) {
  result <- NULL
  if (NROW(bboxes <- rgl.ids(type = "bboxdeco", subscene = id))) {
    save <- currentSubscene3d()
    on.exit(useSubscene3d(save))
    useSubscene3d(id)
    for (i in bboxes$id) 
      result <- c(result, convertBBox(i))
  }
  children <- subsceneInfo(id)$children
  for (i in children)
    result <- c(result, convertBBoxes(i))
  result
}

rootSubscene <- function() {
  id <- currentSubscene3d()
  repeat {
    info <- subsceneInfo(id)
    if (is.null(info$parent)) return(id)
    else id <- info$parent
  }
}    

# This gets all the clipping planes in a particular subscene
getClipplanes <- function(subscene) {
  shapes <- rgl.ids(subscene=subscene)
  shapes$id[shapes$type == "clipplanes"]
}

# This counts how many clipping planes might affect a particular object
countClipplanes <- function(id) {
  recurse <- function(subscene) {
    result <- 0
    subids <- rgl.ids(c("shapes", "bboxdeco"), subscene=subscene)
    ids <- subids$id
    for (spriteid in ids[subids$type == "sprites"]) {
    	ids <- c(ids, rgl.attrib(spriteid, "ids"))  
    }
    if (id %in% ids) {
      clipids <- getClipplanes(subscene)
      for (clipid in clipids)
        result <- result + rgl.attrib.count(clipid, "offsets")
    }
    subscenes <- rgl.ids("subscene", subscene=subscene)$id
    for (sub in subscenes) {
      if (result >= bound)
        break
      result <- max(result, recurse(sub))
    }
    result
  }
  bound <- length(getClipplanes(0))
  if (!bound) return(0)
  recurse(rootSubscene())
}  

writeWebGL <- function(dir="webGL", filename=file.path(dir, "index.html"), 
                       template = system.file(file.path("WebGL", "template.html"), package = "rgl"),
                       prefix = "",
                       snapshot = TRUE, commonParts = TRUE, reuse = NULL,
		       font="Arial",
                       width, height) {
 
  # Lots of utility functions and constants defined first; execution starts way down there...
  

  vec2vec3 <- function(vec) {
    vec <- addDP(vec)
    sprintf("vec3(%s, %s, %s)", vec[1], vec[2], vec[3])
  }
  
  col2rgba <- function(col) as.numeric(col2rgb(col, alpha=TRUE))/255
  
  col2vec3 <- function(col) vec2vec3(col2rgba(col))
  
  vec2vec4 <- function(vec) {
    vec <- addDP(vec)
    sprintf("vec4(%s, %s, %s, %s)", vec[1], vec[2], vec[3], vec[4])
  }
  
  header <- function() c(
  	if (commonParts) 
'	<script src="CanvasMatrix.js" type="text/javascript"></script>',
        subst(
'	<canvas id="%prefix%textureCanvas" style="display: none;" width="256" height="256">
        %snapshotimg%
	Your browser does not support the HTML5 canvas element.</canvas>
', prefix, snapshotimg))

  getPrefix <- function(id) 
    prefixes$prefix[prefixes$id == id][1]

  shaders <- function(id, type, flags) {
    if (type == "clipplanes" || flags["reuse"]) return(NULL)
    mat <- rgl.getmaterial(id=id)
    is_lit <- flags["is_lit"]
    is_smooth <- flags["is_smooth"]
    has_texture <- flags["has_texture"]
    fixed_quads <- flags["fixed_quads"]
    sprites_3d <- flags["sprites_3d"]
    sprite_3d <- flags["sprite_3d"]
    clipplanes <- countClipplanes(id)
    
    if (has_texture)
      texture_format <- mat$textype

    if (is_lit) {
      lights <- rgl.ids("lights")
      if (is.na(lights$id[1])) {
        # no lights
        is_lit <- FALSE
      }
      else {
        lAmbient <- list()
        lDiffuse <- list()
        lSpecular <- list()
        lightxyz <- list()
        lighttype <- matrix(NA, length(lights$id), 2)
        colnames(lighttype) <- c("viewpoint", "finite")
        for (i in seq_along(lights$id)) {
            lightid <- lights$id[[i]]
            lightcols <- rgl.attrib(lightid, "colors")
            lAmbient[[i]] <- lightcols[1,]
            lDiffuse[[i]] <- lightcols[2,]
            lSpecular[[i]] <- lightcols[3,]
            lightxyz[[i]] <- rgl.attrib(lightid, "vertices")
            lighttype[i,] <- t(rgl.attrib(lightid, "flags"))
        }
      }
    }
    vertex <- subst(
'	<!-- ****** %type% object %id% ****** -->',
      type, id)
    if (sprites_3d)
      return(c(vertex, 
'	<!-- 3d sprite, no shader -->
'            ))

    vertex <- c(vertex, subst(
'	<script id="%prefix%vshader%id%" type="x-shader/x-vertex">', 
      prefix, id),
	
'	attribute vec3 aPos;
	attribute vec4 aCol;
	uniform mat4 mvMatrix;
	uniform mat4 prMatrix;
	varying vec4 vCol;
	varying vec4 vPosition;',
	
      if (is_lit && !fixed_quads) 
'	attribute vec3 aNorm;
	uniform mat4 normMatrix;
	varying vec3 vNormal;',
		  
      if (has_texture || type == "text")
'	attribute vec2 aTexcoord;
	varying vec2 vTexcoord;',
	
      if (type == "text")
'	uniform vec2 textScale;',

      if (fixed_quads)
'	attribute vec2 aOfs;'
      else if (sprite_3d)
'	uniform vec3 uOrig;
	uniform float uSize;
	uniform mat4 usermat;',
	
'	void main(void) {',

      if (clipplanes || (!fixed_quads && !sprite_3d))
'	  vPosition = mvMatrix * vec4(aPos, 1.);',

      if (!fixed_quads && !sprite_3d)
'	  gl_Position = prMatrix * vPosition;',
	  
      if (type == "points") subst(
'	  gl_PointSize = %size%;', size=addDP(mat$size)),

'	  vCol = aCol;',

      if (is_lit && !fixed_quads && !sprite_3d)
'	  vNormal = normalize((normMatrix * vec4(aNorm, 1.)).xyz);',

      if (has_texture || type == "text")
'	  vTexcoord = aTexcoord;',

      if (type == "text") 
'	  vec4 pos = prMatrix * mvMatrix * vec4(aPos, 1.);
	  pos = pos/pos.w;
	  gl_Position = pos + vec4(aOfs*textScale, 0.,0.);',
	  
      if (type == "sprites") 
'	  vec4 pos = mvMatrix * vec4(aPos, 1.);
	  pos = pos/pos.w + vec4(aOfs, 0., 0.);
	  gl_Position = prMatrix*pos;',
	  
      if (sprite_3d)
'	  vNormal = normalize((vec4(aNorm, 1.)*normMatrix).xyz);	  
	  vec4 pos = mvMatrix * vec4(uOrig, 1.);
	  vPosition = pos/pos.w + vec4(uSize*(vec4(aPos, 1.)*usermat).xyz,0.);
	  gl_Position = prMatrix * vPosition;',
	  	  
'	}
	</script>
')

    # Important:  in some implementations (e.g. ANGLE) declarations that involve computing must be local (inside main()), not global
    fragment <- c(subst(
'	<script id="%prefix%fshader%id%" type="x-shader/x-fragment"> 
	#ifdef GL_ES
	precision highp float;
	#endif
	varying vec4 vCol; // carries alpha
	varying vec4 vPosition;',
      prefix, id),
      
      if (has_texture || type == "text") 
'	varying vec2 vTexcoord;
	uniform sampler2D uSampler;',
  
      if (is_lit && !fixed_quads)
'	varying vec3 vNormal;',

      if (clipplanes) paste0(
'	uniform vec4 vClipplane', seq_len(clipplanes), ';'),

      if (is_lit && !all(lighttype[,"viewpoint"]))
'	uniform mat4 mvMatrix;',

'	void main(void) {',

      if (clipplanes) paste0(
'	  if (dot(vPosition, vClipplane', seq_len(clipplanes), ') < 0.0) discard;'),

      if (is_lit)
'	  vec3 eye = normalize(-vPosition.xyz);',      

   # collect lighting information   
      if (is_lit) {
        res <- subst(
'	  const vec3 emission = %emission%;',
          emission = vec2vec3(col2rgba(mat$emission)))

        for (idn in seq_along(lights$id)) { 
          finite <- lighttype[idn,"finite"]
          viewpoint <- lighttype[idn, "viewpoint"]
          res <- c(res, subst(
'	  const vec3 ambient%idn% = %ambient%;
	  const vec3 specular%idn% = %specular%;// light*material
	  const float shininess%idn% = %shininess%;
	  vec4 colDiff%idn% = vec4(vCol.rgb * %diffuse%, vCol.a);',
          ambient = vec2vec3(col2rgba(mat$ambient)*lAmbient[[idn]] + col2rgba(mat$emission)), #FIXME : Materialemission wird bei mehreren Lichtquellen mehrfach genutzt 
          specular = vec2vec3(col2rgba(mat$specular)*lSpecular[[idn]]), 
          shininess = addDP(mat$shininess),
          diffuse = vec2vec3(lDiffuse[[idn]]),
          idn = idn),
     
          {
            lightdir <- lightxyz[[idn]]
            if (!finite)
              lightdir <- normalize(lightdir)
            if (viewpoint)
              lightdir <- vec2vec3(lightdir)
            else
              lightdir <- subst('(mvMatrix * %lightdir%).xyz', lightdir=vec2vec4(c(lightdir,1)))
            # directional light
            if (!finite) {
              subst(
'	  const vec3 lightDir%idn% = %lightdir%;
	  vec3 halfVec%idn% = normalize(lightDir%idn% + eye);',
              lightdir = lightdir,
              idn = idn)
            }
            else { # point-light
              subst(
'	  vec3 lightDir%idn% = normalize(%lightdir% - vPosition.xyz);
	  vec3 halfVec%idn% = normalize(lightDir%idn% + eye);',
              lightdir = lightdir,
              idn = idn)
            }
          })
        }
        res
      }
      else {
'      vec4 colDiff = vCol;'
      },

      if (is_lit) {
        res <- c(
'      vec4 lighteffect = vec4(emission, 0.);')
        if (fixed_quads) {
          res <- c(res,
'	  vec3 n = vec3(0., 0., -1.);')
        }
        else {
          res <- c(res,
'	  vec3 n = normalize(vNormal);
	  n = -faceforward(n, n, eye);')
        }
        for (idn in seq_along(lights$id)) {
          res <- c(res, subst(
'	  vec3 col%idn% = ambient%idn%;
	  float nDotL%idn% = dot(n, lightDir%idn%);
	  col%idn% = col%idn% + max(nDotL%idn%, 0.) * colDiff%idn%.rgb;
	  col%idn% = col%idn% + pow(max(dot(halfVec%idn%, n), 0.), shininess%idn%) * specular%idn%;
	  lighteffect = lighteffect + vec4(col%idn%, colDiff%idn%.a);',
          idn = idn))
        }
        res
      }
      else { 
'	  vec4 lighteffect = colDiff;'
      },

      if ((has_texture && texture_format == "rgba") || type == "text")
'	  vec4 textureColor = lighteffect*texture2D(uSampler, vTexcoord);',

      if (has_texture) switch(texture_format,
         rgb = 
'	  vec4 textureColor = lighteffect*vec4(texture2D(uSampler, vTexcoord).rgb, 1.);',
	 alpha =
'	  vec4 textureColor = texture2D(uSampler, vTexcoord);
	  float luminance = dot(vec3(1.,1.,1.), textureColor.rgb)/3.;
	  textureColor =  vec4(lighteffect.rgb, lighteffect.a*luminance);',
         luminance =
'	  vec4 textureColor = vec4(lighteffect.rgb*dot(texture2D(uSampler, vTexcoord).rgb, vec3(1.,1.,1.))/3.,
                                   lighteffect.a);',
         luminance.alpha =
'	  vec4 textureColor = texture2D(uSampler, vTexcoord);
	  float luminance = dot(vec3(1.,1.,1.),textureColor.rgb)/3.;
	  textureColor = vec4(lighteffect.rgb*luminance, lighteffect.a*textureColor.a);'),
           
      if (has_texture)
'	  gl_FragColor = textureColor;'
      else if (type == "text")
'	  if (textureColor.a < 0.1)
	    discard;
	  else
	    gl_FragColor = textureColor;'
        else
'	  gl_FragColor = lighteffect;',

'	}
	</script> 
'     )
    c(vertex, fragment)    
  }

  scriptheader <- function() c(
  '
	<script type="text/javascript">',
  
  if (commonParts) c(
'
	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 rglClass = function() {
	  this.zoom = new Array();
	  this.FOV  = new Array();
	  this.userMatrix = new Array();
	  this.viewport = new Array();
	  this.listeners = new Array();
	  this.clipplanes = new Array();
	  this.opaque = new Array();
	  this.transparent = new Array();
	  this.subscenes = new Array();

	  this.flags = new Array();
	  this.prog = new Array();
	  this.ofsLoc = new Array();
	  this.origLoc = new Array();
	  this.sizeLoc = new Array();
	  this.usermatLoc = new Array();
	  this.vClipplane = new Array();
	  this.texture = new Array();
	  this.texLoc = new Array();
	  this.sampler = new Array();
	  this.origsize = new Array();
	  this.values = new Array();
	  this.normLoc = new Array();
	  this.clipLoc = new Array();
	  this.centers = new Array();
	  this.f = new Array();
	  this.buf = new Array();
	  this.ibuf = new Array();
	  this.mvMatLoc = new Array();
	  this.prMatLoc = new Array();
	  this.textScaleLoc = new Array();
	  this.normMatLoc = new Array();
	  this.IMVClip = new Array();

	  this.drawFns = new Array();
	  this.clipFns = new Array();

	  this.prMatrix = new CanvasMatrix4();
	  this.mvMatrix = new CanvasMatrix4();
	  this.vp = null;
	  this.prmvMatrix = null;
	  this.origs = null;
	};
	  
	(function() {
	  this.getShader = function( gl, id ){
	    var shaderScript = document.getElementById ( id );
	    var str = "";
	    var k = shaderScript.firstChild;
	    while ( k ){
	      if ( k.nodeType == 3 ) str += k.textContent;
	      k = k.nextSibling;
	    }
	    var shader;
	    if ( shaderScript.type == "x-shader/x-fragment" )
	      shader = gl.createShader ( gl.FRAGMENT_SHADER );
	    else if ( shaderScript.type == "x-shader/x-vertex" )
	      shader = gl.createShader(gl.VERTEX_SHADER);
	    else return null;
	    gl.shaderSource(shader, str);
	    gl.compileShader(shader);
	    if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) == 0)
	      alert(gl.getShaderInfoLog(shader));
	    return shader;
	  }
	  this.multMV = function(M, v) {
	     return [M.m11*v[0] + M.m12*v[1] + M.m13*v[2] + M.m14*v[3],
		     M.m21*v[0] + M.m22*v[1] + M.m23*v[2] + M.m24*v[3],
		     M.m31*v[0] + M.m32*v[1] + M.m33*v[2] + M.m34*v[3],
		     M.m41*v[0] + M.m42*v[1] + M.m43*v[2] + M.m44*v[3]];
	  }',
	  paste0(
'	  this.f_', flagnames, ' = ', 2^(seq_along(flagnames)-1), ';'),
'	  this.whichList = function(id) {
	    if (this.flags[id] & this.f_is_subscene)
	      return "subscenes";
	    else if (this.flags[id] & this.f_is_clipplanes)
	      return "clipplanes";
	    else if (this.flags[id] & this.f_is_transparent)
	      return "transparent";
	    else
	      return "opaque"; 
          }
	  this.inSubscene = function(id, subscene) {
	    var thelist = this.whichList(id);
	    return this[thelist][subscene].indexOf(id) > -1;
	  }
          this.addToSubscene = function(id, subscene) {
            var thelist = this.whichList(id);
	    if (this[thelist][subscene].indexOf(id) == -1)
	      this[thelist][subscene].push(id);
	  }
	  this.delFromSubscene = function(id, subscene) {
	    var thelist = this.whichList(id);
	    var i = this[thelist][subscene].indexOf(id);
	    if (i > -1)
	      this[thelist][subscene].splice(i, 1);
	  }
	  this.setSubsceneEntries = function(ids, subscene) {
	    this.subscenes[subscene] = [];
	    this.clipplanes[subscene] = [];
	    this.transparent[subscene] = [];
	    this.opaque[subscene] = [];
	    for (var i = 0; i < ids.length; i++)
	      this.addToSubscene(ids[i], subscene);
	  }
    }).call(rglClass.prototype);
'),
  subst(
'
	var %prefix%rgl = new rglClass();
	%prefix%rgl.start = function() {
	   var debug = function(msg) {
	     document.getElementById("%prefix%debug").innerHTML = msg;
	   }
	   debug("");

	   var canvas = document.getElementById("%prefix%canvas");
	   if (!window.WebGLRenderingContext){
	     debug("%snapshotimg2% Your browser does not support WebGL. See <a href=\\\"http://get.webgl.org\\\">http://get.webgl.org</a>");
	     return;
	   }
	   var gl;
	   try {
	     // Try to grab the standard context. If it fails, fallback to experimental.
	     gl = canvas.getContext("webgl") 
	       || canvas.getContext("experimental-webgl");
	   }
	   catch(e) {}
	   if ( !gl ) {
	     debug("%snapshotimg2% Your browser appears to support WebGL, but did not create a WebGL context.  See <a href=\\\"http://get.webgl.org\\\">http://get.webgl.org</a>");
	     return;
	   }
	   var width = %width%;  var height = %height%;
	   canvas.width = width;   canvas.height = height;
	   var normMatrix = new CanvasMatrix4();
	   var saveMat = new Object();
	   var distance;
	   var posLoc = 0;
	   var colLoc = 1;
', prefix, snapshotimg2, width, height))
  
  setUser <- function() {
    subsceneids <- rgl.ids("subscene", subscene = 0)$id
    save <- currentSubscene3d()
    on.exit(useSubscene3d(save))
    result <- subst(
'       var activeSubscene = %root%;', root=rootSubscene(), prefix)
    
    for (id in subsceneids) {
      useSubscene3d(id)
      info <- subsceneInfo(id)
      result <- c(result, subst(
'	   this.flags[%id%] = %flags%;', id, flags = numericFlags(getSubsceneFlags(id))))
      if (info$embeddings["projection"] != "inherit") {
        useSubscene3d(id)
        result <- c(result, subst(
'	   this.zoom[%id%] = %zoom%;
	   this.FOV[%id%] = %fov%;', id, zoom = par3d("zoom"), fov = max(1, min(179, par3d("FOV")))))
      }
      viewport <- par3d("viewport")*c(wfactor, hfactor)
      result <- c(result, subst(
'	   this.viewport[%id%] = [%v1%, %v2%, %v3%, %v4%];',
      	  id, v1 = viewport[1], v2 = viewport[2], v3 = viewport[3], v4 = viewport[4]))
      if (info$embeddings["model"] != "inherit") {
        result <- c(result, subst(
'	   this.userMatrix[%id%] = new CanvasMatrix4();
	   this.userMatrix[%id%].load([', id),
    inRows(t(par3d("userMatrix")), perrow=4, leadin='	   '),
'		]);')
      }
      
      clipplanes <- getClipplanes(id)
      subids <- which( ids %in% rgl.ids()$id )
      opaque <- ids[subids[!flags[subids,"sprite_3d"] & !flags[subids,"is_transparent"] & types[subids] != "clipplanes"]]
      transparent <- ids[subids[!flags[subids,"sprite_3d"] & flags[subids,"is_transparent"] & types[subids] != "clipplanes"]]
      subscenes <- as.integer(info$children)
      
      result <- c(result, subst(
'	   this.clipplanes[%id%] = [%clipplanes%];
	   this.opaque[%id%] = [%opaque%];
	   this.transparent[%id%] = [%transparent%];
	   this.subscenes[%id%] = [%subscenes%];
',      id, clipplanes = paste(clipplanes, collapse=","),
	opaque = paste(opaque, collapse=","),
	transparent = paste(transparent, collapse=","),
	subscenes = paste(subscenes, collapse=",")))
    }    
    result
  }
  
  textureSupport <- subst(
'	   function getPowerOfTwo(value) {
	     var pow = 1;
	     while(pow<value) {
	       pow *= 2;
	     }
	     return pow;
	   }

	   function handleLoadedTexture(texture, textureCanvas) {
	     gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);

	     gl.bindTexture(gl.TEXTURE_2D, texture);
	     gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textureCanvas);
	     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
	     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
	     gl.generateMipmap(gl.TEXTURE_2D);

	     gl.bindTexture(gl.TEXTURE_2D, null);
	   }
	   
	   function loadImageToTexture(filename, texture) {   
	     var canvas = document.getElementById("%prefix%textureCanvas");
	     var ctx = canvas.getContext("2d");
	     var image = new Image();
	     
	     image.onload = function() {
	       var w = image.width;
	       var h = image.height;
	       var canvasX = getPowerOfTwo(w);
	       var canvasY = getPowerOfTwo(h);
	       canvas.width = canvasX;
	       canvas.height = canvasY;
	       ctx.imageSmoothingEnabled = true;
	       ctx.drawImage(image, 0, 0, canvasX, canvasY);
	       handleLoadedTexture(texture, canvas);
	       %prefix%rgl.drawScene();
	     }
	     image.src = filename;
	   }  	   
', prefix)

  textSupport <- subst( 
'	   function drawTextToCanvas(text, cex) {
	     var canvasX, canvasY;
	     var textX, textY;

	     var textHeight = 20 * cex;
	     var textColour = "white";
	     var fontFamily = "%font%";

	     var backgroundColour = "rgba(0,0,0,0)";

	     var canvas = document.getElementById("%prefix%textureCanvas");
	     var ctx = canvas.getContext("2d");

	     ctx.font = textHeight+"px "+fontFamily;

	     canvasX = 1;
	     var widths = [];
	     for (var i = 0; i < text.length; i++)  {
	       widths[i] = ctx.measureText(text[i]).width;
	       canvasX = (widths[i] > canvasX) ? widths[i] : canvasX;
	     }	  
	     canvasX = getPowerOfTwo(canvasX);

	     var offset = 2*textHeight; // offset to first baseline
	     var skip = 2*textHeight;   // skip between baselines	  
	     canvasY = getPowerOfTwo(offset + text.length*skip);
	     
	     canvas.width = canvasX;
	     canvas.height = canvasY;

	     ctx.fillStyle = backgroundColour;
	     ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);

	     ctx.fillStyle = textColour;
	     ctx.textAlign = "left";

	     ctx.textBaseline = "alphabetic";
	     ctx.font = textHeight+"px "+fontFamily;

	     for(var i = 0; i < text.length; i++) {
	       textY = i*skip + offset;
	       ctx.fillText(text[i], 0,  textY);
	     }
	     return {canvasX:canvasX, canvasY:canvasY,
	             widths:widths, textHeight:textHeight,
	             offset:offset, skip:skip};
	   }
', font, prefix)

  sphereCount <- 0
  sphereStride <- 0
  
  sphereSupport <- function() {
    # Use -1 as a fake ID of the sphere data          
    if ((-1) %in% prefixes$id) {
      reuse <- TRUE
      thisprefix <- getPrefix(-1)
    } else {
      reuse <- FALSE
      thisprefix <- prefix
      prefixes <<- rbind(prefixes, data.frame(id = -1, prefix = thisprefix, 
      					texture = "", stringsAsFactors = FALSE))
      x <- subdivision3d(octahedron3d(),2)
      x$vb[4,] <- 1
      r <- sqrt(x$vb[1,]^2 + x$vb[2,]^2 + x$vb[3,]^2)
      values <- t(x$vb[1:3,])/r
    }
    sphereCount <<- 384 # length(x$it)
    sphereStride <<- 12
    c(
'	   // ****** sphere object ******',
      if (!reuse) subst(
'	   this.sphereverts = new Float32Array([
%values%
           ]);
	   this.spherefaces=new Uint16Array([
%faces%
	   ]);', values = inRows(values, perrow=3, '	   '),
	   faces = inRows(t(x$it)-1, perrow=3, '	   ')),
      subst(
'	   var sphereBuf = gl.createBuffer();
	   gl.bindBuffer(gl.ARRAY_BUFFER, sphereBuf);
	   gl.bufferData(gl.ARRAY_BUFFER, %thisprefix%rgl.sphereverts, gl.STATIC_DRAW);
	   var sphereIbuf = gl.createBuffer();
	   gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, sphereIbuf);
	   gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, %thisprefix%rgl.spherefaces, gl.STATIC_DRAW);
',         thisprefix))
}    

  setViewport <- function() 
'	     this.vp = this.viewport[id];
	     gl.viewport(this.vp[0], this.vp[1], this.vp[2], this.vp[3]);
	     gl.scissor(this.vp[0], this.vp[1], this.vp[2], this.vp[3]);'
	     
  setprMatrix <- function(subsceneid) {
    info <- subsceneInfo(subsceneid)
    embedding <- info$embeddings["projection"]
    if (embedding == "replace")
      result <-
'	     this.prMatrix.makeIdentity();'
    else
      result <- setprMatrix(info$parent);
    if (embedding == "inherit")
      return(result)
    
    save <- currentSubscene3d()
    on.exit(useSubscene3d(save))
    
    useSubscene3d(subsceneid)
    
    # This is based on the Frustum::enclose code from geom.cpp
    bbox <- par3d("bbox")
    scale <- par3d("scale")
    ranges <- c(bbox[2]-bbox[1], bbox[4]-bbox[3], bbox[6]-bbox[5])*scale/2
    radius <- sqrt(sum(ranges^2))*1.1 # A bit bigger to handle labels
    if (radius <= 0) radius <- 1
    observer <- par3d("observer")
    distance <- observer[3]
    c(result, subst(
'	     var radius = %radius%;
	     var distance = %distance%;
	     var t = tan(this.FOV[%id%]*PI/360);
	     var near = distance - radius;
	     var far = distance + radius;
	     var hlen = t*near;
	     var aspect = this.vp[2]/this.vp[3];
	     var z = this.zoom[%id%];
	     if (aspect > 1) 
	       this.prMatrix.frustum(-hlen*aspect*z, hlen*aspect*z, 
	                        -hlen*z, hlen*z, near, far);
	     else  
	       this.prMatrix.frustum(-hlen*z, hlen*z, 
	                        -hlen*z/aspect, hlen*z/aspect, 
	                        near, far);',
            prefix, id = subsceneid, radius, distance))
  }

  setmvMatrix <- function(subsceneid) {
    save <- currentSubscene3d()
    on.exit(useSubscene3d(save))
    
    useSubscene3d(subsceneid)
    observer <- par3d("observer")
    
    c('
         this.mvMatrix.makeIdentity();',
      setmodelMatrix(subsceneid),
      subst(
'         this.mvMatrix.translate(%x%, %y%, %z%);',
        x = -observer[1], y = -observer[2], z = -observer[3]))
  }      
	     
  setmodelMatrix <- function(subsceneid) {
    info <- subsceneInfo(subsceneid)
    embedding <- info$embeddings["model"]
    
    save <- currentSubscene3d()
    on.exit(useSubscene3d(save))
    
    useSubscene3d(subsceneid)

    if (embedding != "inherit") {
      scale <- par3d("scale")
      bbox <- par3d("bbox")
      center <- c(bbox[1]+bbox[2], bbox[3]+bbox[4], bbox[5]+bbox[6])/2
      result <- subst(
'	     this.mvMatrix.translate( %cx%, %cy%, %cz% );
	     this.mvMatrix.scale( %sx%, %sy%, %sz% );   
	     this.mvMatrix.multRight( %prefix%rgl.userMatrix[%id%] );',
     prefix, id = subsceneid,
     cx=-center[1], cy=-center[2], cz=-center[3],
     sx=scale[1],   sy=scale[2],   sz=scale[3])
    } else result <- character(0)
   
    if (embedding != "replace") 
      result <- c(result, setmodelMatrix(info$parent))
     
    result
  }
  
  setnormMatrix <- function(subsceneid) {
    save <- currentSubscene3d()
    on.exit(useSubscene3d(save))
    
    recurse <- function(subsceneid) {
      info <- subsceneInfo(subsceneid)
      embedding <- info$embeddings["model"]
    
      useSubscene3d(subsceneid)
      if (embedding != "inherit") {    
        scale <- par3d("scale")
        result <- subst(
'	     normMatrix.scale( %sx%, %sy%, %sz% );   
	     normMatrix.multRight( %prefix%rgl.userMatrix[%id%] );',
         prefix, id = subsceneid,
         sx=1/scale[1], sy=1/scale[2], sz=1/scale[3])
      } else result <- character(0)
    
      if (embedding != "replace")
        result <- c(result, recurse(info$parent))
      result
    }
    c('
         normMatrix.makeIdentity();',
      recurse(subsceneid))
  }
  
  setprmvMatrix <-
'	     this.prmvMatrix = new CanvasMatrix4( this.mvMatrix );
	     this.prmvMatrix.multRight( this.prMatrix );'

  init <- function(id, type, flags) {
    is_indexed <- flags["is_indexed"]
    mat <- rgl.getmaterial(id=id)
    is_lit <- flags["is_lit"]
    has_texture <- flags["has_texture"]
    fixed_quads <- flags["fixed_quads"]
    depth_sort <- flags["depth_sort"]
    sprites_3d <- flags["sprites_3d"]
    sprite_3d <- flags["sprite_3d"]
    is_clipplanes <- type == "clipplanes"
    clipplanes <- countClipplanes(id)
    thisprefix <- getPrefix(id)
    
    result <- subst(
'
	   // ****** %type% object %id% ******
	   this.flags[%id%] = %flags%;', type, id, flags = numericFlags(flags))
    if (!sprites_3d && !is_clipplanes)
      result <- c(result, subst(
'	   this.prog[%id%]  = gl.createProgram();
	   gl.attachShader(this.prog[%id%], this.getShader( gl, "%thisprefix%vshader%id%" ));
	   gl.attachShader(this.prog[%id%], this.getShader( gl, "%thisprefix%fshader%id%" ));
	   //  Force aPos to location 0, aCol to location 1 
	   gl.bindAttribLocation(this.prog[%id%], 0, "aPos");
	   gl.bindAttribLocation(this.prog[%id%], 1, "aCol");
	   gl.linkProgram(this.prog[%id%]);', thisprefix, id))
   
    nv <- rgl.attrib.count(id, "vertices")
    if (nv)
      values <- rgl.attrib(id, "vertices")
    else
      values <- NULL
    if (nv > 65535)
    	warning("Object ", id, " has ", nv, " vertices.  Some browsers support only 65535.")
    
    nc <- rgl.attrib.count(id, "colors")
    colors <- rgl.attrib(id, "colors")
    if (nc > 1) {
      if (nc != nv) {
        rows <- rep(seq_len(nc), length.out=nv)
        colors <- colors[rows,,drop=FALSE]
      }
      values <- cbind(values, colors)
    }
    
    nn <- rgl.attrib.count(id, "normals")
    if (nn > 0) {
      normals <- rgl.attrib(id, "normals")
      values <- cbind(values, normals)
    } 
    
    if (type == "spheres") {
      radii <- rgl.attrib(id, "radii")
      if (length(radii) == 1)
        radii <- rep(radii, NROW(values))
      values <- cbind(values, radii)
    }
    
    if (type == "clipplanes") {
      offsets <- rgl.attrib(id, "offsets")
      stopifnot(NCOL(values) == 3)
      values <- cbind(values, offsets)
      result <- c(result,subst(
'	   this.vClipplane[%id%]=[', id),
      inRows(values, 4, leadin='	   '),
'	   ];
')
      return(result)
    }
    
    if (type == "surface") { # Compute indices of triangles
      dim <- rgl.attrib(id, "dim")
      nx <- dim[1]
      nz <- dim[2]
      f <- NULL
      for (j in seq_len(nx-1)-1) {
        v1 <- j + nx*(seq_len(nz) - 1)
        v2 <- v1 + 1
        f <- cbind(f, rbind(v1[-nz],
                            v1[-1],
                            v2[-1],
                            v1[-nz],
                            v2[-1],
                            v2[-nz]))
      }
      frowsize <- 6
    }  
    
    if (type == "text") {
      adj <- rgl.attrib(id, "adj") # Should query scene...
      texts <- rgl.attrib(id, "texts")
      cex <- rgl.attrib(id, "cex")
      if (min(cex) < max(cex))
      	warning("Only the first value of cex used")
      
      values <- values[rep(seq_len(nv), each=4),]
      
      # String heights and widths need to be multiplied in here
      texcoords <- matrix(rep(c(0,-0.5,1,-0.5,1,1.5,0,1.5),nv), 4*nv, 2, byrow=TRUE)
      refs <- matrix(adj, 4*nv, 2, byrow=TRUE)
      tofs <- NCOL(values)
      values <- cbind(values, texcoords)
      oofs <- NCOL(values)
      values <- cbind(values, refs)
      nv <- nv*4
      result <- c(result, 
'	   var texts = [',
      	paste('	    "', texts, '"', sep="", collapse=",\n"),
'	   ];',

        subst(
'	   var texinfo = drawTextToCanvas(texts, %cex%);',
	  cex=cex[1], id))
    }

    if (type == "sprites") {
      oofs <- NCOL(values)
      if (sprites_3d) 
        values <- cbind(values, rep(rgl.attrib(id, "radii")/2, len=nv))
      else {
        size <- rep(rgl.attrib(id, "radii"), len=4*nv)
        values <- values[rep(seq_len(nv), each=4),]
        texcoords <- matrix(rep(c(0,0,1,0,1,1,0,1),nv), 4*nv, 2, byrow=TRUE)
        values <- cbind(values, (texcoords - 0.5)*size)
        nv <- nv*4
      }
    }

    if (fixed_quads && !sprites_3d) result <- c(result, subst(
'	   this.ofsLoc[%id%] = gl.getAttribLocation(this.prog[%id%], "aOfs");',
          id))
          
    if (sprite_3d) result <- c(result, subst(
'	   this.origLoc[%id%] = gl.getUniformLocation(this.prog[%id%], "uOrig");
	   this.sizeLoc[%id%] = gl.getUniformLocation(this.prog[%id%], "uSize");
	   this.usermatLoc[%id%] = gl.getUniformLocation(this.prog[%id%], "usermat");',
	  id))

    if (has_texture) {
        tofs <- NCOL(values)
        if (type != "sprites")
            texcoords <- rgl.attrib(id, "texcoords")
        if (!sprites_3d)
            values <- cbind(values, texcoords)
        if (mat$texture %in% prefixes$texture) {
            i <- which(mat$texture == prefixes$texture)[1]
            texprefix <- prefixes$prefix[i]
            texid <- prefixes$id[i]
        } else {
            texprefix <- prefix
            texid <- id
            file.copy(mat$texture, file.path(dir, paste(texprefix, "texture", texid, ".png", sep="")))
        }
        i <- which(prefixes$id == id & prefixes$prefix == prefix)      
        prefixes$texture[i] <<- mat$texture
        i <- which(mat$texture == prefixes$texture & prefix == prefixes$prefix)
        load_texture <- length(i) < 2 # first time loaded in this scene
        if (!load_texture)
            texid <- prefixes$id[i[1]]
    } else
        load_texture <- FALSE
    
    if (load_texture || type == "text") result <- c(result, subst(
'	   this.texture[%id%] = gl.createTexture();
	   this.texLoc[%id%] = gl.getAttribLocation(this.prog[%id%], "aTexcoord");
	   this.sampler[%id%] = gl.getUniformLocation(this.prog[%id%],"uSampler");',
	  id))

    if (load_texture)	  
        result <- c(result, subst(
'	   loadImageToTexture("%texprefix%texture%texid%.png", this.texture[%id%]);',
          id, texprefix, texid))
    else if (has_texture)  # just reuse the existing texture
        result <- c(result, subst(
'	   this.texture[%id%] = this.texture[%texid%];',
                      id, texid))              
    
    if (type == "text") result <- c(result, subst(
'	   handleLoadedTexture(this.texture[%id%], document.getElementById("%prefix%textureCanvas"));',
        prefix, id))
      
    stride <- NCOL(values)

    result <- c(result,
      if (sprites_3d) subst(
'	   this.origsize[%id%]=new Float32Array([', id)
      else if (!flags["reuse"])
'	   var v=new Float32Array([',
      if (!flags["reuse"]) 
        c(inRows(values, stride, leadin='	   '),
'	   ]);'),

      if (sprites_3d) c(subst(
'	   this.userMatrix[%id%] = new Float32Array([', id),
        inRows(rgl.attrib(id, "usermatrix"), 4, leadin='	   '),
'	   ]);'),
        
      if (type == "text" && !flags["reuse"]) subst(
'	   for (var i=0; i<%len%; i++) 
	     for (var j=0; j<4; j++) {
	         var ind = %stride%*(4*i + j) + %tofs%;
	         v[ind+2] = 2*(v[ind]-v[ind+2])*texinfo.widths[i];
	         v[ind+3] = 2*(v[ind+1]-v[ind+3])*texinfo.textHeight;
	         v[ind] *= texinfo.widths[i]/texinfo.canvasX;
	         v[ind+1] = 1.0-(texinfo.offset + i*texinfo.skip 
	           - v[ind+1]*texinfo.textHeight)/texinfo.canvasY;
	     }', len=length(texts), stride, tofs),
	     
      if (!sprites_3d && !flags["reuse"]) subst(
'	   this.values[%id%] = v;', 
                  id),
	   
      if (is_lit && !fixed_quads && !sprites_3d) subst(
'	   this.normLoc[%id%] = gl.getAttribLocation(this.prog[%id%], "aNorm");', 
        id),
      if (clipplanes && !sprites_3d) c(subst(
'	   this.clipLoc[%id%] = new Array();', id),
      	subst(paste0(
'	   this.clipLoc[%id%][', seq_len(clipplanes)-1, '] = gl.getUniformLocation(this.prog[%id%], "vClipplane', seq_len(clipplanes), '");'),
           id))
	)
	

    if (is_indexed) {
      if (type %in% c("quads", "text", "sprites") && !sprites_3d) {
        v1 <- 4*(seq_len(nv/4) - 1)
        v2 <- v1 + 1
        v3 <- v2 + 1
        v4 <- v3 + 1
        f <- rbind(v1, v2, v3, v1, v3, v4)
        frowsize <- 6
      } else if (type == "triangles") {
        v1 <- 3*(seq_len(nv/3) - 1)
        v2 <- v1 + 1
        v3 <- v2 + 1
        f <- rbind(v1, v2, v3)
        frowsize <- 3
      } else if (type == "spheres") {
        f <- seq_len(nv)-1
        frowsize <- 8 # not used for depth sorting, just for display
      }  
        
      if (depth_sort) {
        result <- c(result, subst(
'	   this.centers[%id%] = new Float32Array([', id),
        inRows(rgl.attrib(id, "centers"), 3, leadin='	   '),
'	   ]);')

        fname <- subst("this.f[%id%]", id)
        var <- ""
        drawtype <- "DYNAMIC_DRAW"
      } else {
        fname <- "f"
        var <- "var "
        drawtype <- "STATIC_DRAW"
      }
      
      result <- c(result, subst(
'	   %var%%fname%=new Uint16Array([', 
          var, fname),
	inRows(c(f), frowsize, leadin='	   '),
'	   ]);')
    }
    result <- c(result,
      if (type != "spheres" && !sprites_3d) subst(
'	   this.buf[%id%] = gl.createBuffer();
	   gl.bindBuffer(gl.ARRAY_BUFFER, this.buf[%id%]);
	   gl.bufferData(gl.ARRAY_BUFFER, %thisprefix%rgl.values[%id%], gl.STATIC_DRAW);',
   		thisprefix, id),
      if (is_indexed && type != "spheres" && !sprites_3d) subst(
'	   this.ibuf[%id%] = gl.createBuffer();
	   gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ibuf[%id%]);
	   gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, %fname%, gl.%drawtype%);',
   		id, fname, drawtype),
      if (!sprites_3d && !is_clipplanes) subst(
'	   this.mvMatLoc[%id%] = gl.getUniformLocation(this.prog[%id%],"mvMatrix");
	   this.prMatLoc[%id%] = gl.getUniformLocation(this.prog[%id%],"prMatrix");',
   		  id),
      if (type == "text") subst(
'	   this.textScaleLoc[%id%] = gl.getUniformLocation(this.prog[%id%],"textScale");',
		  id),
      if (is_lit && !sprites_3d) subst(
'	   this.normMatLoc[%id%] = gl.getUniformLocation(this.prog[%id%],"normMatrix");',
		  id)
   		 ) 
    c(result, '')
  }
  
  draw <- function(id, type, flags) {
    mat <- rgl.getmaterial(id=id)
    is_lit <- flags["is_lit"]
    is_indexed <- flags["is_indexed"]
    depth_sort <- flags["depth_sort"]
    has_texture <- flags["has_texture"]
    fixed_quads <- flags["fixed_quads"]
    is_transparent <- flags["is_transparent"]
    sprites_3d <- flags["sprites_3d"]
    sprite_3d <- flags["sprite_3d"]
    thisprefix <- getPrefix(id)
    
    result <- subst(
'
       // ****** %type% object %id% *******
       this.drawFns[%id%] = function(id, clipplanes) {',
       type, id)
  
    if (type == "clipplanes") {
      count <- rgl.attrib.count(id, "offsets")
      result <- c(result, subst(
'	     this.IMVClip[id] = new Array();', id))       	
      for (i in seq_len(count) - 1) {
        result <- c(result, subst(
'	     this.IMVClip[id][%i%] = this.multMV(this.invMatrix, this.vClipplane[id]%slice%);',
	id, i, slice = subst(".slice(%first%, %stop%)", first = 4*i, stop = 4*(i+1))))
      }
      result <- c(result, subst(
'       }
       this.clipFns[%id%] = function(id, objid, count) {', id))
      count <- rgl.attrib.count(id, "offsets")
      result <- c(result, 
      	    if (count == 1)
'	     gl.uniform4fv(this.clipLoc[objid][count], this.IMVClip[id][0]);
	     return(count + 1);
	   }'
	    else if (count > 1)
      	    	subst(
'	     for (var i=0; i<%count%; i++)   
	       gl.uniform4fv(this.clipLoc[objid][count + i], this.IMVClip[id][i]);
	     return(count + %count%);
       }', id, count))
      return(result)
    }

    if (sprites_3d) {
      norigs <- rgl.attrib.count(id, "vertices")
      result <- c(result, subst(
'	     this.origs = this.origsize[id];
	     this.usermat = this.userMatrix[id];
	     for (iOrig=0; iOrig < %norigs%; iOrig++) {',
        id, norigs))
      spriteids <- rgl.attrib(id, "ids")
      for (i in seq_along(spriteids)) 
        result <- c(result, subst(
'	       this.drawFns[%spriteid%].call(this, %spriteid%, clipplanes);',
	  spriteid = spriteids[i]))
      result <- c(result, 
'	     }')
    } else {
      result <- c(result, subst(	     
'	     gl.useProgram(this.prog[id]);', id),
       
        if (sprite_3d) subst(
'	     gl.uniform3f(this.origLoc[id], this.origs[4*iOrig], 
	                                this.origs[4*iOrig+1],
	                                this.origs[4*iOrig+2]);
	     gl.uniform1f(this.sizeLoc[id], this.origs[4*iOrig+3]);
	     gl.uniformMatrix4fv(this.usermatLoc[id], false, this.usermat);',
	  id),
	  
        if (type == "spheres") 
'	     gl.bindBuffer(gl.ARRAY_BUFFER, sphereBuf);'
        else subst(
'	     gl.bindBuffer(gl.ARRAY_BUFFER, this.buf[id]);',
         id),
       
        if (is_indexed && type != "spheres") subst(    
'	     gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ibuf[id]);', id)
        else if (type == "spheres")
'	     gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, sphereIbuf);',

        subst(
'	     gl.uniformMatrix4fv( this.prMatLoc[id], false, new Float32Array(this.prMatrix.getAsArray()) );
	     gl.uniformMatrix4fv( this.mvMatLoc[id], false, new Float32Array(this.mvMatrix.getAsArray()) );',
         id))
         
      result <- c(result, 
'	     var clipcheck = 0;
	     for (var i=0; i < clipplanes.length; i++)
	       clipcheck = this.clipFns[clipplanes[i]].call(this, clipplanes[i], id, clipcheck);')           
      if (is_lit && !sprite_3d)
        result <- c(result, subst(
'	     gl.uniformMatrix4fv( this.normMatLoc[id], false, new Float32Array(normMatrix.getAsArray()) );',
          id))
          
      if (is_lit && sprite_3d)
        result <- c(result, subst(
'	     gl.uniformMatrix4fv( this.normMatLoc[id], false, this.usermat);',
          id))
	  
      if (type == "text") 
        result <- c(result, 
'	     gl.uniform2f( this.textScaleLoc[id], 0.75/this.vp[2], 0.75/this.vp[3]);')	

      result <- c(result, 
'	     gl.enableVertexAttribArray( posLoc );')

      count <- rgl.attrib.count(id, "vertices")
      stride <- 12 
      nc <- rgl.attrib.count(id, "colors")
      if (nc > 1) {
        cofs <- stride
        stride <- stride + 16
      }
    
      nn <- rgl.attrib.count(id, "normals")
      if (nn > 0) {
        nofs <- stride
        stride <- stride + 12
      }
    
      if (type == "spheres") {
        radofs <- stride
        stride <- stride + 4
        scount <- count
      }
    
      if (type == "sprites" && !sprites_3d) {
        oofs <- stride
        stride <- stride + 8
      }
    
      if (has_texture || type == "text") {
        tofs <- stride
        stride <- stride + 8
      }
    
      if (type == "text") {
        oofs <- stride
        stride <- stride + 8
      }

      if (depth_sort) {
        nfaces <- rgl.attrib.count(id, "centers")
        frowsize <- if (sprites_3d) 1 else
          switch(type,
            quads =,
            text =,
            surface =,
            sprites = 6,
            triangles = 3)
        
        result <- c(result, subst(
'	     var depths = new Float32Array(%nfaces%);
	     var faces = new Array(%nfaces%);
	     for(var i=0; i<%nfaces%; i++) {
	       var z = this.prmvMatrix.m13*this.centers[id][3*i] 
	             + this.prmvMatrix.m23*this.centers[id][3*i+1]
	             + this.prmvMatrix.m33*this.centers[id][3*i+2]
	             + this.prmvMatrix.m43;
	       var w = this.prmvMatrix.m14*this.centers[id][3*i] 
	             + this.prmvMatrix.m24*this.centers[id][3*i+1]
	             + this.prmvMatrix.m34*this.centers[id][3*i+2]
	             + this.prmvMatrix.m44;
	       depths[i] = z/w;
	       faces[i] = i;
	     }
	     var depthsort = function(i,j) { return depths[j] - depths[i] }
	     faces.sort(depthsort);',
           nfaces, id),
           if (type != "spheres") subst(
'	     var f = new Uint16Array(this.f[id].length);
	     for (var i=0; i<%nfaces%; i++) {
	       for (var j=0; j<%frowsize%; j++) {
	         f[%frowsize%*i + j] = this.f[id][%frowsize%*faces[i] + j];
	       }
	     }	     
	     gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, f, gl.DYNAMIC_DRAW);',
	   nfaces, id, frowsize))
      }
    
      if (type == "spheres") {
        scale <- par3d("scale")
        sx <- 1/scale[1]
        sy <- 1/scale[2]
        sz <- 1/scale[3]
        result <- c(result, subst(
'	     gl.vertexAttribPointer(posLoc,  3, gl.FLOAT, false, %sphereStride%,  0);
	     gl.enableVertexAttribArray(this.normLoc[id] );
	     gl.vertexAttribPointer(this.normLoc[id],  3, gl.FLOAT, false, %sphereStride%,  0);
	     gl.disableVertexAttribArray( colLoc );
	     var sphereNorm = new CanvasMatrix4();
	     sphereNorm.scale(%sx%, %sy%, %sz%);
	     sphereNorm.multRight(normMatrix);
	     gl.uniformMatrix4fv( this.normMatLoc[id], false, new Float32Array(sphereNorm.getAsArray()) );', 
	    id, sphereStride, sx=1/sx, sy=1/sy, sz=1/sz),

          if (nc == 1) {
            colors <- rgl.attrib(id, "colors")
            subst(
'	     gl.vertexAttrib4f( colLoc, %r%, %g%, %b%, %a%);',
	    r=colors[1], g=colors[2], b=colors[3], a=colors[4])
	  },
	
          subst(
'	     for (var i = 0; i < %scount%; i++) {
	       var sphereMV = new CanvasMatrix4();', scount),
	       
	  if (depth_sort) subst(
'	       var baseofs = faces[i]*%stride%', stride=stride/4)
          else subst(
'	       var baseofs = i*%stride%', stride=stride/4),

	  subst(
'	       var ofs = baseofs + %radofs%;	       
	       var scale = %thisprefix%rgl.values[id][ofs];
	       sphereMV.scale(%sx%*scale, %sy%*scale, %sz%*scale);
	       sphereMV.translate(%thisprefix%rgl.values[id][baseofs], 
	       			  %thisprefix%rgl.values[id][baseofs+1], 
	       			  %thisprefix%rgl.values[id][baseofs+2]);
	       sphereMV.multRight(this.mvMatrix);
	       gl.uniformMatrix4fv( this.mvMatLoc[id], false, new Float32Array(sphereMV.getAsArray()) );',
	     radofs=radofs/4, stride=stride/4, id, sx, sy, sz, thisprefix),
	 
	   if (nc > 1) subst(
'	       ofs = baseofs + %cofs%;       
	       gl.vertexAttrib4f( colLoc, %thisprefix%rgl.values[id][ofs], 
					  %thisprefix%rgl.values[id][ofs+1], 
					  %thisprefix%rgl.values[id][ofs+2],
					  %thisprefix%rgl.values[id][ofs+3] );',
	     cofs=cofs/4, id, thisprefix),
	   
           subst(
'	       gl.drawElements(gl.TRIANGLES, %sphereCount%, gl.UNSIGNED_SHORT, 0);
	     }', sphereCount))

      } else {
        if (nc == 1) {
          colors <- rgl.attrib(id, "colors")
          result <- c(result, subst(
'	     gl.disableVertexAttribArray( colLoc );
	     gl.vertexAttrib4f( colLoc, %r%, %g%, %b%, %a% );', 
            r=colors[1], g=colors[2], b=colors[3], a=colors[4]))
        } else {
          result <- c(result, subst(
'	     gl.enableVertexAttribArray( colLoc );
	     gl.vertexAttribPointer(colLoc, 4, gl.FLOAT, false, %stride%, %cofs%);',
            stride, cofs))
        }
    
        if (is_lit && nn > 0) {
          result <- c(result, subst(
'	     gl.enableVertexAttribArray( this.normLoc[id] );
	     gl.vertexAttribPointer(this.normLoc[id], 3, gl.FLOAT, false, %stride%, %nofs%);',
            id, stride, nofs))
        }
    
        if (has_texture || type == "text") {
          result <- c(result, subst(
'	     gl.enableVertexAttribArray( this.texLoc[id] );
	     gl.vertexAttribPointer(this.texLoc[id], 2, gl.FLOAT, false, %stride%, %tofs%);
	     gl.activeTexture(gl.TEXTURE0);
	     gl.bindTexture(gl.TEXTURE_2D, this.texture[id]);
	     gl.uniform1i( this.sampler[id], 0);',
            id, stride, tofs))
        }
      
        if (fixed_quads) {
          result <- c(result, subst(
'	     gl.enableVertexAttribArray( this.ofsLoc[id] );
	     gl.vertexAttribPointer(this.ofsLoc[id], 2, gl.FLOAT, false, %stride%, %ofs%);',
            id, stride, ofs=oofs))
        }
	     
        mode <- switch(type,
          points = "POINTS",
          linestrip = "LINE_STRIP",
          abclines =,
          lines = "LINES",
          sprites =,
          planes =,
          text =,
          quads =,
          surface =,
          triangles = "TRIANGLES",
          stop("unsupported mode") )
      
        switch(type,
          sprites =,
          text = count <- count*6,
          quads = count <- count*6/4,
          surface = {
            dim <- rgl.attrib(id, "dim")
            nx <- dim[1]
            nz <- dim[2]
            count <- (nx - 1)*(nz - 1)*6
          })
    
        if (flags["is_lines"]) {
          lwd <- mat$lwd
          result <- c(result,subst(
'	     gl.lineWidth( %lwd% );',
            lwd))
        }
      
        result <- c(result, subst(
'	     gl.vertexAttribPointer(posLoc,  3, gl.FLOAT, false, %stride%,  0);',
            stride),
      	
          if (is_indexed) subst(
'	     gl.drawElements(gl.%mode%, %count%, gl.UNSIGNED_SHORT, 0);',
              mode, count)
          else
            subst(
'	     gl.drawArrays(gl.%mode%, 0, %count%);',
              mode, count))
      }
    }    
    result <- c(result,
'       }')
    result
  }
    
  scriptMiddle <- function() {
    rootid <- rootSubscene()
    subst(
'	   gl.enable(gl.DEPTH_TEST);
	   gl.depthFunc(gl.LEQUAL);
	   gl.clearDepth(1.0);
	   gl.clearColor(1,1,1,1);
	   var drag  = 0;
	   
	   this.drawScene = function() {
	     gl.depthMask(true);
	     gl.disable(gl.BLEND);
	     gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
	     this.drawFns[%rootid%].call(this, %rootid%)
	     gl.flush ();
	   }
', rootid)
  }
  
  drawSubscene <- function(subsceneid) {
    useSubscene3d(subsceneid)  
    subids <- which( ids %in% rgl.ids()$id )
      
    subscene_has_faces <- any(flags[subids,"is_lit"] & !flags[subids,"fixed_quads"])
    subscene_needs_sorting <- any(flags[subids,"depth_sort"])

    bgid <- rgl.ids("background")$id
    if (!length(bgid) || !length(bg <- rgl.attrib(bgid, "colors")))
      bg <- c(1,1,1,1)
      
    result <- c(subst('
       // ***** subscene %subsceneid% ****
       this.drawFns[%subsceneid%] = function(id) {',
	subsceneid),

      setViewport(),    
      
      if (length(bgid)) subst(
'	     gl.clearColor(%r%, %g%, %b%, %a%);
	     gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);',
         r=bg[1], g=bg[2], b=bg[3], a=bg[4]),
              
      if (length(subids)) 
        c(setprMatrix(subsceneid),
          setmvMatrix(subsceneid),
      
          if (subscene_has_faces) setnormMatrix(subsceneid),
      
          if (subscene_needs_sorting) setprmvMatrix), 

'	     var clipids = this.clipplanes[id];
	     if (clipids.length > 0) {
	       this.invMatrix = new CanvasMatrix4(this.mvMatrix);
	       this.invMatrix.invert();
	       for (var i = 0; i < this.clipplanes[id].length; i++) 
	         this.drawFns[clipids[i]].call(this, clipids[i]);
	     }
	     var subids = this.opaque[id];
	     for (var i = 0; i < subids.length; i++) 
	       this.drawFns[subids[i]].call(this, subids[i], clipids);
	     subids = this.transparent[id];
	     if (subids.length > 0) {
	       gl.depthMask(false);
	       gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA,
	                          gl.ONE, gl.ONE);
	       gl.enable(gl.BLEND);
	       for (var i = 0; i < subids.length; i++) 
	         this.drawFns[subids[i]].call(this, subids[i], clipids);
	     }
	     subids = this.subscenes[id];
	     for (var i = 0; i < subids.length; i++)
	       this.drawFns[subids[i]].call(this, subids[i]);
      }')
    result
  }
	  
  drawEnd <- '
	   this.drawScene();
'	   
  mouseHandlers <- function() {
    save <- currentSubscene3d()
    on.exit(useSubscene3d(save))
    
    x0 <- y0 <- widths <- heights <- tests <- models <- 
            projections <- listeners <- character(0)
    
    useid <- function(id, type="model") {
      info <- subsceneInfo(id)
      if (info$embeddings[type] == "inherit")
        useid(info$parent, type)
      else
        id
    }
    
    rects <- function(parent) {
      useSubscene3d(parent)
      info <- subsceneInfo(parent)      
      for (id in rev(info$children))
        rects(id)

      viewport <- par3d("viewport")*c(wfactor, hfactor)
      x0 <<- c(x0, subst("%id%: %x0%", id=parent, x0=viewport[1]))
      y0 <<- c(y0, subst("%id%: %y0%", id=parent, y0=viewport[2]))
      widths <<- c(widths, subst("%id%: %width%", id=parent, width=viewport[3]))
      heights <<- c(heights, subst("%id%: %height%", id=parent, height=viewport[4]))

      tests <<- c(tests, subst(
'         if (%x0% <= coords.x && coords.x <= %x1% && %y0% <= coords.y && coords.y <= %y1%) return(%id%);',
             id=parent, x0=viewport[1], y0=viewport[2], x1=viewport[1]+viewport[3],
             y1=viewport[2]+viewport[4]))
      models <<- c(models, subst("%id%: %model%", id=parent, model=useid(parent, "model")))
      projections <<- c(projections, subst("%id%: %projection%", id=parent, projection=useid(parent, "projection")))
      l <- par3d("listeners", subscene=parent)
      listeners <<- c(listeners, subst("%id%: [ %ids% ]", id = parent, 
                                       ids = paste(unique(l), collapse=",")))
    }

    rootid <- rootSubscene()      
    rects(rootid)
    
    result <- c(
'  	   var vpx0 = {',
  	     inRows(x0, perrow=6, "          "),
'  	     };
	   var vpy0 = {',  	     
  	     inRows(y0, perrow=6, "          "),
'  	     };
	   var vpWidths = {',
	inRows(widths, perrow=6, "         "),
'  	     };
	   var vpHeights = {',
	inRows(heights, perrow=6, "          "),
'  	     };
	   var activeModel = {',
	inRows(models, perrow=6, "         "),
'  	     };
	   var activeProjection = {',
	inRows(projections, perrow=6, "         "), subst(
'  	     };
	   %prefix%rgl.listeners = {', prefix),
        inRows(listeners, perrow=2, "         "),
'  	     };
',
'  	   var whichSubscene = function(coords){',
  	   tests, subst(
'         return(%id%);
       }
',	id=rootid)
    )
  
    handlers <- par3d("mouseMode")
    if (any(notdone <- handlers %in% c("polar", "selecting"))) {
      warning("Mouse mode(s) '", handlers[notdone], "' not supported.  'trackball' used.")
      handlers[notdone] <- "trackball"
    }

    # User handlers are different than others, so do them first
    for (i in which(handlers == "user")) {
      handlers[i] <- paste0("user", i)
      handlerfns <- rgl.getMouseCallbacks(i)
      actions <- c("down", "move", "end")
      defaults <- c("trackball", "zoom", "fov")
      for (j in 1:3) 
        if (!is.null(handlerfns[[j]])) {
          fn <- attr(handlerfns[[j]], "javascript")
          if (!is.null(fn)) {
            result <- c(result, subst(
'           var %handler%%action% = %fn%', 
              handler = handlers[i], action = actions[j], fn = fn))
          } else if (!is.null(name <- attr(handlerfns[[j]], "jsName"))) {
            result <- c(result, subst(
'           var %handler%%action% = function(x, y) {
             %javascript%(activeSubscene%args%)
	   }', handler = handlers[i], action = actions[j], javascript = name,
                          args = if (j < 3) ", x, y" else ""))
          } else {                  
            warning("No \"javascript\" or \"jsName\" attribute found on user handler.  Default used instead.")
            handlers[i] <- defaults[i]
          }
        }
    }
    uhandlers <- setdiff(unique(handlers), "none")
    result <- c(result,
'       var translateCoords = function(subsceneid, coords){
         return {x:coords.x - vpx0[subsceneid], y:coords.y - vpy0[subsceneid]};
       }
       
       var vlen = function(v) {
	     return sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2])
	   }
	   
	   var xprod = function(a, b) {
	     return [a[1]*b[2] - a[2]*b[1],
	             a[2]*b[0] - a[0]*b[2],
	             a[0]*b[1] - a[1]*b[0]];
	   }

	   var screenToVector = function(x, y) {
	     var width = vpWidths[activeSubscene];
	     var height = vpHeights[activeSubscene];
	     var radius = max(width, height)/2.0;
	     var cx = width/2.0;
	     var cy = height/2.0;
	     var px = (x-cx)/radius;
	     var py = (y-cy)/radius;
	     var plen = sqrt(px*px+py*py);
	     if (plen > 1.e-6) { 
	       px = px/plen;
	       py = py/plen;
	     }

	     var angle = (SQRT2 - plen)/SQRT2*PI/2;
	     var z = sin(angle);
	     var zlen = sqrt(1.0 - z*z);
	     px = px * zlen;
	     py = py * zlen;
	     return [px, py, z];
	   }

	   var rotBase;
')
    
    for (i in seq_along(uhandlers)) {
      h <- uhandlers[i]
      result <- c(result, switch(h,
        trackball = subst(
'	   var trackballdown = function(x,y) {
	     rotBase = screenToVector(x, y);
	     var l = %prefix%rgl.listeners[activeModel[activeSubscene]];
	     saveMat = new Object();
	     for (var i = 0; i < l.length; i++) 
	       saveMat[l[i]] = new CanvasMatrix4(%prefix%rgl.userMatrix[l[i]]);
	   }
	   
	   var trackballmove = function(x,y) {
	     var rotCurrent = screenToVector(x,y);
	     var dot = rotBase[0]*rotCurrent[0] + 
	   	       rotBase[1]*rotCurrent[1] + 
	   	       rotBase[2]*rotCurrent[2];
	     var angle = acos( dot/vlen(rotBase)/vlen(rotCurrent) )*180./PI;
	     var axis = xprod(rotBase, rotCurrent);
	     var l = %prefix%rgl.listeners[activeModel[activeSubscene]];
	     for (i = 0; i < l.length; i++) {
	       %prefix%rgl.userMatrix[l[i]].load(saveMat[l[i]]);
	       %prefix%rgl.userMatrix[l[i]].rotate(angle, axis[0], axis[1], axis[2]);
	     }
	     %prefix%rgl.drawScene();
	   }
	   var trackballend = 0;
', prefix),

        xAxis =,
        yAxis =,
        zAxis = c(
          if (h == "xAxis") 
'	   var xAxis = [1.0, 0.0, 0.0];'
          else if (h == "yAxis")
'	   var yAxis = [0.0, 1.0, 0.0];'
          else
'	   var zAxis = [0.0, 0.0, 1.0];',
          subst(
'
	   var %h%down = function(x,y) {
	     rotBase = screenToVector(x, height/2);
	     var l = %prefix%rgl.listeners[activeModel[activeSubscene]];
	     saveMat = new Object();
	     for (var i = 0; i < l.length; i++) 
	     saveMat[l[i]] = new CanvasMatrix4(%prefix%rgl.userMatrix[l[i]]);
	   }
	   	   
	   var %h%move = function(x,y) {
	     var rotCurrent = screenToVector(x,height/2);
	     var angle = (rotCurrent[0] - rotBase[0])*180/PI;
	     var rotMat = new CanvasMatrix4();
	     rotMat.rotate(angle, %h%[0], %h%[1], %h%[2]);
	     var l = %prefix%rgl.listeners[activeModel[activeSubscene]];
	     for (i = 0; i < l.length; i++) {
	       %prefix%rgl.userMatrix[l[i]].load(saveMat[l[i]]);
	       %prefix%rgl.userMatrix[l[i]].multLeft(rotMat);
	     }
	     %prefix%rgl.drawScene();
	   }
	   
	   var %h%end = 0;
	   
',           h, prefix)),
      zoom = subst(
'	   var y0zoom = 0;
	   var zoom0 = 0;
	   var zoomdown = function(x, y) {
	     y0zoom = y;
	     zoom0 = new Object();
	     l = %prefix%rgl.listeners[activeProjection[activeSubscene]];
	     for (i = 0; i < l.length; i++)
	     zoom0[l[i]] = log(%prefix%rgl.zoom[l[i]]);
	   }

	   var zoommove = function(x, y) {
	     l = %prefix%rgl.listeners[activeProjection[activeSubscene]];
	     for (i = 0; i < l.length; i++)
	       %prefix%rgl.zoom[l[i]] = exp(zoom0[l[i]] + (y-y0zoom)/height);
	     %prefix%rgl.drawScene();
	   }
	   
	   var zoomend = 0;
',      prefix),
      fov = subst(
'	   var y0fov = 0;
	   var fov0 = 0;
	   var fovdown = function(x, y) {
	     y0fov = y;
	     fov0 = new Object();
	     l = %prefix%rgl.listeners[activeProjection[activeSubscene]];
	     for (i = 0; i < l.length; i++)
	       fov0[l[i]] = %prefix%rgl.FOV[l[i]];
	   }

	   var fovmove = function(x, y) {
	     l = %prefix%rgl.listeners[activeProjection[activeSubscene]];
	     for (i = 0; i < l.length; i++)
	       %prefix%rgl.FOV[l[i]] = max(1, min(179, fov0[l[i]] + 180*(y-y0fov)/height));
	     %prefix%rgl.drawScene();
	   }
	   
	   var fovend = 0;
',      prefix)))  }
        
    down <- paste(handlers, "down", sep="")
    move <- paste(handlers, "move", sep="")
    end <-  paste(handlers, "end", sep="")
    none <- handlers == "none"
    down[none] <- "0"
    move[none] <- "0"
    end[none] <- "0"
    c(result, subst(
'	   var mousedown = [%d1%, %d2%, %d3%];
	   var mousemove = [%m1%, %m2%, %m3%];
	   var mouseend = [%e1%, %e2%, %e3%];
',    d1=down[1], d2=down[2], d3=down[3], m1=move[1], m2=move[2], m3=move[3],
      e1=end[1],  e2=end[2],  e3=end[3]))
  }
	
  scriptEnd <- subst(
'	   function relMouseCoords(event){
	     var totalOffsetX = 0;
	     var totalOffsetY = 0;
	     var currentElement = canvas;
	   
	     do{
	       totalOffsetX += currentElement.offsetLeft;
	       totalOffsetY += currentElement.offsetTop;
	       currentElement = currentElement.offsetParent;
	     }
	     while(currentElement)
	   
	     var canvasX = event.pageX - totalOffsetX;
	     var canvasY = event.pageY - totalOffsetY;
	   
	     return {x:canvasX, y:canvasY}
	   }
	   
	   canvas.onmousedown = function ( ev ){
	     if (!ev.which) // Use w3c defns in preference to MS
	       switch (ev.button) {
	       case 0: ev.which = 1; break;
	       case 1: 
	       case 4: ev.which = 2; break;
	       case 2: ev.which = 3;
	     }
	     drag = ev.which;
	     var f = mousedown[drag-1];
	     if (f) {
	       var coords = relMouseCoords(ev);
	       coords.y = height-coords.y;
	       activeSubscene = whichSubscene(coords);
	       coords = translateCoords(activeSubscene, coords);
	       f(coords.x, coords.y); 
	       ev.preventDefault();
	     }
	   }    

	   canvas.onmouseup = function ( ev ){	
	     if ( drag == 0 ) return;
	     var f = mouseend[drag-1];
	     if (f) 
	       f();
	     drag = 0;
	   }
	   
	   canvas.onmouseout = canvas.onmouseup;

	   canvas.onmousemove = function ( ev ){
	     if ( drag == 0 ) return;
	     var f = mousemove[drag-1];
	     if (f) {
	       var coords = relMouseCoords(ev);
	       coords.y = height - coords.y;
	       coords = translateCoords(activeSubscene, coords);
	       f(coords.x, coords.y);
	     }
	   }

	   var wheelHandler = function(ev) {
	     var del = 1.1;
	     if (ev.shiftKey) del = 1.01;
	     var ds = ((ev.detail || ev.wheelDelta) > 0) ? del : (1 / del);
	     l = %prefix%rgl.listeners[activeProjection[activeSubscene]];
	     for (i = 0; i < l.length; i++)
	       %prefix%rgl.zoom[l[i]] *= ds;
	     %prefix%rgl.drawScene();
	     ev.preventDefault();
	   };
	   canvas.addEventListener("DOMMouseScroll", wheelHandler, false);
	   canvas.addEventListener("mousewheel", wheelHandler, false);

	}
	</script>', prefix)

  footer <- function() subst('
	<canvas id="%prefix%canvas" class="rglWebGL" width="1" height="1"></canvas> 
	<p id="%prefix%debug">
	%snapshotimg%
	You must enable Javascript to view this page properly.</p>',
    prefix, snapshotimg)

  flagnames <- c("is_lit", "is_smooth", "has_texture", "is_indexed", 
		 "depth_sort", "fixed_quads", "is_transparent", 
		 "is_lines", "sprites_3d", "sprite_3d", 
		 "is_subscene", "is_clipplanes", "reuse")
		 
  getFlags <- function(id, type) {    
    if (type == "subscene")
      return(getSubsceneFlags(id))
      
    result <- structure(rep(FALSE, length(flagnames)), names = flagnames)
    if (type == "clipplanes") {
      result["is_clipplanes"] <- TRUE
      return(result)
    }
    
    mat <- rgl.getmaterial(id=id)
    result["is_lit"] <- mat$lit && type %in% c("triangles", "quads", "surface", "planes", 
                                     "spheres", "sprites")
                                     
    result["is_smooth"] <- mat$smooth && type %in% c("triangles", "quads", "surface", "planes", 
                                     "spheres")
                                     
    result["has_texture"] <- !is.null(mat$texture) && length(rgl.attrib.count(id, "texcoords"))
    
    result["is_transparent"] <- is_transparent <- any(rgl.attrib(id, "colors")[,"a"] < 1)
    
    result["depth_sort"] <- depth_sort <- is_transparent && type %in% c("triangles", "quads", "surface",
                                                  "spheres", "sprites", "text")

    result["sprites_3d"] <- sprites_3d <- type == "sprites" && rgl.attrib.count(id, "ids")
    
    result["is_indexed"] <- (depth_sort || type %in% c("quads", "surface", "text", "sprites")) && !sprites_3d
    
    result["fixed_quads"] <- type %in% c("text", "sprites") && !sprites_3d
    result["is_lines"]    <- type %in% c("lines", "linestrip", "abclines")
    
    result
  }
  
  getSubsceneFlags <- function(id) {
     result <- structure(rep(FALSE, length(flagnames)), names = flagnames)
     result["is_subscene"] <- TRUE
     subs <- rgl.ids(subscene = id)
     for (i in seq_len(nrow(subs))) 
	result <- result | getFlags(subs[i, "id"], subs[i, "type"])
     return(result)
  }
  
  numericFlags <- function(flags) {
    if (is.matrix(flags))
      n <- ncol(flags)
    else
      n <- length(flags)
    flags %*% 2^(seq_len(n)-1)
  }
  
  flagConstants <- 

  knowntypes <- c("points", "linestrip", "lines", "triangles", "quads",
                  "surface", "text", "abclines", "planes", "spheres",
                  "sprites", "clipplanes")
  
  #  Execution starts here!
  
  
  # Do a few checks first
    
  if (!file.exists(dir))
    dir.create(dir)
  if (!file.info(dir)$isdir)
    stop("'", dir, "' is not a directory.")
  
  if (commonParts)
    file.copy(system.file(file.path("doc", "CanvasMatrix.js"), package = "rgl"), 
                          file.path(dir, "CanvasMatrix.js"))

  if (is.null(reuse) || isTRUE(reuse)) 
    prefixes <- data.frame(id = integer(), prefix = character(), texture = character(),
    		           stringsAsFactors = FALSE)
  else {
    if (!is.data.frame(reuse) || !all(c("id", "prefix", "texture") %in% names(reuse)))
      stop(dQuote("reuse"), " should be a dataframe with columns ", dQuote("id"), ", ", dQuote("prefix"),
           ", ", dQuote("texture"))
    prefixes <- reuse[,c("id", "prefix", "texture")]
    prefixes$texture <- as.character(prefixes$texture)
  }
  
  rect <- par3d("windowRect")
  rwidth <- rect[3] - rect[1] + 1
  rheight <- rect[4] - rect[2] + 1
  if (missing(width)) {
    if (missing(height)) {
      wfactor <- hfactor <- 1  # width = wfactor*rwidth, height = hfactor*rheight
    } else 
      wfactor <- hfactor <- height/rheight
  } else {
    if (missing(height)) {
      wfactor <- hfactor <- width/rwidth
    } else {
      wfactor <- width/rwidth;
      hfactor <- height/rheight;
    }
  }
  width <- wfactor*rwidth;
  height <- hfactor*rheight;
      
  if (snapshot) {
    snapshot3d(file.path(dir, paste(prefix, "snapshot.png", sep="")))
    snapshotimg <- subst('<img src="%prefix%snapshot.png" alt="%prefix%snapshot" width=%width%/><br>', prefix, width)
    snapshotimg2 <- gsub('"', '\\\\\\\\"', snapshotimg)
  } else snapshotimg2 <- snapshotimg <- ""
  
  if (!is.null(template)) {
    templatelines <- readLines(template)
    templatelines <- subst(templatelines, rglVersion = packageVersion("rgl"), prefix = prefix)

    target <- paste("%", prefix, "WebGL%", sep="")
    replace <- grep( target, templatelines, fixed=TRUE)
    if (length(replace) != 1) 
      stop("template ", sQuote(template), " does not contain ", target)
  
    result <- c(templatelines[seq_len(replace-1)], header())
  } else
    result <- header()

  if (NROW(rgl.ids("bboxdeco", subscene = 0))) {
    saveredraw <- par3d(skipRedraw = TRUE)
    temp <- convertBBoxes(rootSubscene())
    on.exit({ rgl.pop(id=temp); par3d(saveredraw) })
  }
    
  ids <- rgl.ids(subscene = 0)
  types <- as.character(ids$type)
  ids <- ids$id
  flags <- matrix(FALSE, nrow = length(ids), ncol=length(flagnames), 
		  dimnames = list(NULL, flagnames))

  i <- 0
  while (i < length(ids)) {
    i <- i + 1
    flags[i,] <- getFlags(ids[i], types[i])
    if (flags[i, "sprites_3d"]) {
      subids <- rgl.attrib(ids[i], "ids")
      flags[ids %in% subids, "sprite_3d"] <- TRUE
    } 
  }   
  flags[ids %in% prefixes$id, "reuse"] <- TRUE
  
  unknowntypes <- setdiff(types, knowntypes)
  if (length(unknowntypes))
    warning("Object type(s) ", 
      paste("'", unknowntypes, "'", sep="", collapse=", "), " not handled.")

  keep <- types %in% knowntypes
  ids <- ids[keep]
  flags <- flags[keep,,drop=FALSE]
  types <- types[keep]

  if (length(ids))
    prefixes <- rbind(prefixes, data.frame(id = ids, 
                      prefix = prefix, texture = "", 
    		      stringsAsFactors = FALSE))

  texnums <- -1
  
  scene_has_faces <- any(flags[,"is_lit"] & !flags[,"fixed_quads"])
  scene_needs_sorting <- any(flags[,"depth_sort"])
  
  for (i in seq_along(ids))
    result <- c(result, shaders(ids[i], types[i], flags[i,]))
    
  result <- c(result, scriptheader(), setUser(),
    textureSupport,
    if ("text" %in% types) textSupport,
    if ("spheres" %in% types) sphereSupport())

  for (i in seq_along(ids)) 
    result <- c(result, init(ids[i], types[i], flags[i,]))
   
  result <- c(result, scriptMiddle())
  
  for (i in seq_along(ids))
    result <- c(result, draw(ids[i], types[i], flags[i,]))
  
  subscenes <- rgl.ids("subscene", subscene = 0)$id
  for (i in seq_along(subscenes))
    result <- c(result, drawSubscene(subscenes[i]))

  result <- c(result, drawEnd, mouseHandlers(), scriptEnd, footer(),
              if (!is.null(template)) 
              	templatelines[replace + seq_len(length(templatelines)-replace)]
              else
              	subst("<script>%prefix%rgl.start();</script>", prefix = prefix)
             )
              	
  cat(result, file=filename, sep="\n")
  if (!is.null(reuse)) {
    prefixes <- prefixes[!duplicated(prefixes$id),]
    attr(filename, "reuse") <- prefixes
  }  
  invisible(filename)
}

# This displays an HTML5 input widget to show a subset of objects.  It assigns a random id
# and returns that invisibly.

subsetSlider <- function(subsets, labels = names(subsets), 
			    prefix = "", subscene = currentSubscene3d(), 
			    init = 1,
			    id = paste0(basename(tempfile("input"))), name = id
) {
	if (is.null(labels)) labels <- seq_along(subsets)
	cat(subst(
'<input type="range" min="0" max="%max%" step="1" value="%init%" id="%id%" name="%name%"
oninput = "(function(value) {
  var ids = [%vals%]; 
  var labels = [%labels%];
  %prefix%rgl.setSubsceneEntries(ids[value], %subscene%); 
  document.getElementById(\'%id%text\').value = labels[value];
  %prefix%rgl.drawScene();
})(this.valueAsNumber)"></input><output id="%id%text">%label%</output>', 
    max = length(subsets)-1, init = init-1, name, id,
    vals = paste(paste0("[", sapply(subsets, 
    				function(i) paste(i, collapse=",")), 
    				"]"), collapse=","), prefix, subscene,
    labels = paste0("'", labels, "'", collapse=","),
    label = labels[init]
	))
	invisible(id)
}

toggleButton <- function(subset, label = deparse(substitute(subset)), 
			 prefix = "", subscene = currentSubscene3d(), 
			 id = paste0(basename(tempfile("input"))), name = id) {
  cat(subst(
'<button type="button" id="%id%" name="%name%" onclick = "(function(){
  var subset = [%subset%];
  if (%prefix%rgl.inSubscene(subset[0], %subscene%)) {
    for (var i=0; i<subset.length; i++)
      %prefix%rgl.delFromSubscene(subset[i], %subscene%);
  } else {
    for (var i=0; i<subset.length; i++)
      %prefix%rgl.addToSubscene(subset[i], %subscene%);
  }
  %prefix%rgl.drawScene();
})()">%label%</button>', 
  name, id, subset = paste(subset, collapse=","),
  prefix, subscene, label))
  invisible(id)
}
trestletech/rgl documentation built on May 31, 2019, 7:49 p.m.