text: // JSmolGLmol.js -- Jmol GLmol  extension	 author: Bob Hanson, hansonr@stolaf.edu	4/16/2012
  //                                                       biochem_fan 6/12/2012
  
  // BH 2/9/2014 6:34:17 PM updated for integration of org.jmol.exportjs into org.jmol.export
  
  // This library requires
  //
  //  JSmol.min.js
  //  JSmolThree.js
  //
  // prior to JmolGlmol.js
  
  /*
  
  BH: added drawSphere and getMat methods
  
  
   GLmol - Molecular Viewer on WebGL/Javascript (0.44)
  	(C) Copyright 2011-2012, biochem_fan
  		License: dual license of MIT or LGPL3
  
  	 This program uses
  		Three.js 
  			https://github.com/mrdoob/three.js
  			Copyright (c) 2010-2012 three.js Authors. All rights reserved.
  		jQuery
  			http://jquery.org/
  			Copyright (c) 2011 John Resig
  
  	// ParseXYZ by Bob Hanson hansonr@stolaf.edu 6/12/2012
  
   */
  
  (function(THREE) {
  // Workaround for Intel GMA series (gl_FrontFacing causes compilation error)
  THREE.ShaderLib.lambert.fragmentShader = THREE.ShaderLib.lambert.fragmentShader.replace("gl_FrontFacing", "true");
  THREE.ShaderLib.lambert.vertexShader = THREE.ShaderLib.lambert.vertexShader.replace(/\}$/, "#ifdef DOUBLE_SIDED\n if (transformedNormal.z < 0.0) vLightFront = vLightBack;\n #endif\n }");
  })(THREE);
  
  
  ;(function(Jmol) {
  
  Jmol._Canvas3D = function(id, Info, type, checkOnly){
  	this._uniqueId = ("" + Math.random()).substring(3);
  	this._id = id;
  	this._is2D = false;
  	this._isJava = false;
  	this._jmolType = "Jmol._Canvas3D (Jmol/GLmol)";
  	this._platform = "J.awtjs.Platform";
  	if (checkOnly)
  		return this;
  	window[id] = this;
  	this._createCanvas(id, Info, new Jmol.GLmol);
  	if (!Jmol._document || this._deferApplet)
  		return this;
  	this._init();
  	return this;
  };
  
  Jmol.GLmol = function() {
  	return this;
  };
  
  ;(function(GLmol) {
  
  
  GLmol.extendApplet = function(applet) {
  
  	applet._refresh = function() {
  	
  		// Called by org.jmol.Viewer.viewer.refresh
  			
  		// pixelsPerAngstrom can be used to calculate the new camera position.
  		// modelRadius is half the distance across the screen. 
  		// 100% implies that camera Z position is 3.5 * modelRadius, with a field of view of 16.24 degrees (2 * atan(1/7))
  		
  		if (!this._applet || !this._applet.viewer)
  			return
  			
  		var view = this._applet.viewer.getGLmolView();
  		var gl = this._GLmol;
  		var rg = gl.rotationGroup;
  		if (!rg)
  			return
  		var mg = gl.modelGroup;
  		var rQ = view.quaternion;
  		rg.quaternion = new THREE.Quaternion(-rQ.q3, -rQ.q0, rQ.q1, rQ.q2);
  		// cameraDistance is in units of screenPixelCount; distance is to front of scene, not to the center.
  		// scaled linearly by zoom
  		var sppa = view.scale;
  		rg.position.z = gl.CAMERA_Z + (view.cameraDistance+view.pixelCount*0.5)/sppa;
  		//model "position" is moved such that {0 0 0} is the fixedRotationCenter 
  		mg.position.x = -view.center.x; 
  		mg.position.y = -view.center.y;
  		mg.position.z = -view.center.z; 
  		//there is also the fixedTranslation to worry about (from CTRL-ALT-drag)
  		if (view.perspective) {
  			gl.camera = gl.perspectiveCamera;
  			gl.camera.fov = gl.fov;
  		} else {
  			gl.camera = gl.orthoscopicCamera;
  			gl.camera.right = view.width * 0.5 / sppa;
  			gl.camera.left = -gl.camera.right;
  			gl.camera.top = view.height * 0.5 / sppa;
  			gl.camera.bottom = -gl.camera.top;
  		}
  		gl.camera.updateProjectionMatrix();
  		var me = this;
  		requestAnimationFrame(function() {me._GLmol.show();});
  	}
  	return applet;
  }
  
  GLmol.extendJSExporter = function(exporter){
  
  	// This method will be called just after org.jmol.export.JSExporter has loaded,
  	//  as one of its static calls.  
  	
  	// exporter is org.jmol.export.JSExporter.protothpe
  	
  	// What we are doing here is overriding methods of org.jmol.export.JSExporter.
  	// These methods are called by that general class and implemened here usring
  	// GLmol and THREE.  
  	
  				
  	exporter.jsInitExport = function(applet) {
  		applet._GLmol.initializeJmolExport();
  	}
  
  	exporter.jsSphere = function(applet, id, found, pt, o) {
  		applet._GLmol.addJmolSphere(pt, o[0], o[1]);
  	}
  	exporter.jsCylinder = function(applet, id, found, pt1, pt2, o) {
  		applet._GLmol.addJmolCylinder(pt1, pt2, o[0], o[1], o[2])
  	}
  
  	exporter.jsTriangle = function(applet, color, pt1, pt2, pt3) {
  		applet._GLmol.addJmolTriangle(color, pt1, pt2, pt3)
  	}
  
  	exporter.jsEndExport = function(applet) {
  		applet._GLmol.finalizeJmolExport();
  		applet._refresh();
  	}
  
  	exporter.jsSurface = function(applet, vertices, normals, indices, 
  			nVertices, nPolygons, nFaces, bsPolygons, faceVertexMax,
  			color, vertexColors, polygonColors) {
  	// notes: Color is only used if both vertexColors and polygonColors are null.
  	//        Only one of vertexColors or polygonColors will NOT be null.
  	//        Int facevertexMax is either 3 or 4; indices may have MORE than that number
  	//        of vertex indices, because the last one may be a flag indicating which 
  	//        edges to display when just showing mesh edges. When there are quadrilaterals,
  	//        then nPolygons != nFaces, and you need to create both 3-sides and 4-sided faces
  	//				based on the length of the individual indices[i] array.  
  
  	// nFaces was determined as follows:
  
  	//    boolean isAll = (bsPolygons == null);
  	//    if (isAll) {
  	//      for (int i = nPolygons; --i >= 0;)
  	//        nFaces += (faceVertexMax == 4 && indices[i].length == 4 ? 2 : 1);    
  	//    } else {
  	//      for (int i = bsPolygons.nextSetBit(0); i >= 0; i = bsPolygons.nextSetBit(i + 1))
  	//        nFaces += (faceVertexMax == 4 && indices[i].length == 4 ? 2 : 1);      
  
  
  		var params = {};
  		if (vertexColors != null) {
  			params.vertexColors = THREE.VertexColors;
  			var vc = new Array(vertexColors.length);
  			for (var i = vertexColors.length; --i >= 0;)
  				vc[i] = new THREE.Color(vertexColors[i]);
  		} else if (polygonColors != null) {
  			params.vertexColors = THREE.FaceColors;
  		} else {
  			params.color = color;
  		}
  		var geo = new THREE.Geometry();
  		for (var i = 0; i < nVertices; i++)
  			geo.vertices.push(new THREE.Vector3(vertices[i].x, vertices[i].y, vertices[i].z));
  		for (var i = 0; i < nPolygons; i++) {
  			var h = indices[i][0], k = indices[i][1], l = indices[i][2];
  			var m = indices[i][3];
  			var is3 = (faceVertexMax == 3 || indices[i].length == 3);
  			var f = (is3 ? new THREE.Face3(h, k, l) : new THREE.Face4(h, k, l, m));
  			// we can use the normals themselves, because they have .x .y .z
  			f.vertexNormals[0] = normals[h];
  			f.vertexNormals[1] = normals[k];
  			f.vertexNormals[2] = normals[l];
  			if (is3) {
  				if (vertexColors != null)
  					f.vertexColors = [vc[h], vc[k], vc[l]];
  			} else {
  				f.vertexNormals[3] = normals[m];
  			}
  
  			if (polygonColors != null)
  				f.color = new THREE.Color(polygonColors[i]);
  			geo.faces.push(f);
  		}
  
  		var obj = new THREE.Mesh(geo, new THREE.MeshLambertMaterial(params));
  		obj.doubleSided = true; // generally?
  	  //obj.material.wireframe = true;
  		applet._GLmol.modelGroup.add(obj);
  	}
  
  }
  
  GLmol.getMat = function(me, color) {
  	if (!me._mat || me._matColor != color) {
  		me._matColor = color;
  		me._mat = new THREE.MeshLambertMaterial({color: color, specular:0xFFFFFF });
  	}
  	return me._mat;
  };
  
  GLmol.setPt = function(p,q) {p.x = q.x;p.y=q.y;p.z=q.z;};
  
  
  // The GLmol object is defined by the following functions: 
  
  ;(function(gp){
  
  gp.create = function() {
  	this.container = Jmol.$(this.applet, "appletdiv");
  	this.WIDTH = this.container.width(), this.HEIGHT = this.container.height();
  	this.ASPECT = this.WIDTH / this.HEIGHT;
  	this.NEAR = 1, FAR = 800;
  	this.CAMERA_Z = -150;
  
  	this.applet.aaScale = 2;
  	this.renderer = new THREE.WebGLRenderer({antialias: true});
  	var canvas = this.renderer.domElement;
  	canvas.width = this.container.width();
  	canvas.height = this.container.height();
  	canvas.applet = this.applet;
  	Jmol._jsSetMouse(canvas);
  	this.applet._canvas = canvas;
  	canvas.style.width = "100%";
  	canvas.style.height = "100%";
  	canvas.id = this.id+"_canvas";
  	this.container.append(canvas);
  	this.renderer.setSize(this.WIDTH * this.applet.aaScale, this.HEIGHT * this.applet.aaScale); // fake of full scene antialiasing
  	this.renderer.sortObjects = false; // hopefully improve performance
  	this.camera = new THREE.PerspectiveCamera(16.26, this.ASPECT, 1, 800); // will be updated anyway
  	this.camera.position = new THREE.Vector3(0, 0, this.CAMERA_Z);
  	this.camera.lookAt(new THREE.Vector3(0, 0, 0));
  	this.perspectiveCamera = this.camera;
  	this.orthoscopicCamera = new THREE.OrthographicCamera();
  	this.orthoscopicCamera.position.z = this.CAMERA_Z;
  	this.orthoscopicCamera.lookAt(new THREE.Vector3(0, 0, 0));
  
  	var me = this;
  	Jmol.$resize(function() { // only window can capture resize event
  		me.WIDTH = me.container.width();
  		me.HEIGHT = me.container.height();
  		me.ASPECT = me.WIDTH / me.HEIGHT;
  		me.renderer.setSize(me.WIDTH, me.HEIGHT);
  		me.camera.aspect = me.ASPECT;
  		me.camera.updateProjectionMatrix();
  		me.show();
  	});
  
  	this.scene = null;
  	this.rotationGroup = null; // which contains modelGroup
  	this.modelGroup = null;
  
  	this.bgColor = 0x000000;
  	this.fov = 16.26;
  	this.fogStart = 0.4;
  	this.slabNear = -50; // relative to the center of rotationGroup
  	this.slabFar = 50;
  
  	// Default values
  	this.sphereQuality = 16; //16;
  	this.cylinderQuality = 16; //8;
   
  };
  
  gp.getView = function() {
  	if (!this.modelGroup) return [0, 0, 0, 0, 0, 0, 0, 1];
  	var pos = this.modelGroup.position;
  	var q = this.rotationGroup.quaternion;
  	return [pos.x, pos.y, pos.z, this.rotationGroup.position.z, q.x, q.y, q.z, q.w];
  };
  
  gp.setView = function(arg) {
  	if (!this.modelGroup || !this.rotationGroup) return;
  	this.modelGroup.position.x = arg[0];
  	this.modelGroup.position.y = arg[1];
  	this.modelGroup.position.z = arg[2];
  	this.rotationGroup.position.z = arg[3];
  	this.rotationGroup.quaternion.x = arg[4];
  	this.rotationGroup.quaternion.y = arg[5];
  	this.rotationGroup.quaternion.z = arg[6];
  	this.rotationGroup.quaternion.w = arg[7];
  	this.show();
  };
  
  gp.setBackground = function(hex, a) {
  	a = a | 1.0;
  	this.bgColor = hex;
  	this.renderer.setClearColorHex(hex, a);
  	this.scene.fog.color = new THREE.Color(hex);
  };
  
  gp.setupLights = function(scene) {
  	var directionalLight =  new THREE.DirectionalLight(0xFFFFFF);
  	directionalLight.position = new THREE.Vector3(1, 1, -2.5).normalize();
  	directionalLight.intensity = 1.0;
  	scene.add(directionalLight);
  	var ambientLight = new THREE.AmbientLight(0x101010);
  	scene.add(ambientLight);
  };
  
  gp.setSlabAndFog = function() {
  	var center = this.rotationGroup.position.z - this.camera.position.z;
  	if (center < 1) center = 1;
  	this.camera.near = center + this.slabNear;
  	if (this.camera.near < 1) this.camera.near = 1;
  	this.camera.far = center + this.slabFar;
  	if (this.camera.near + 1 > this.camera.far) this.camera.far = this.camera.near + 1;
  	this.scene.fog.near = this.camera.near + this.fogStart * (this.camera.far - this.camera.near);
  //   if (this.scene.fog.near > center) this.scene.fog.near = center;
  	this.scene.fog.far = this.camera.far;
  	this.camera.updateProjectionMatrix();
  };
  
  gp.initializeScene = function() {
  	// CHECK: Should I explicitly call scene.deallocateObject?
  	this.scene = new THREE.Scene();
  	this.scene.fog = new THREE.Fog(this.bgColor, 100, 200);
  
  	this.modelGroup = new THREE.Object3D();
  	this.rotationGroup = new THREE.Object3D();
  	this.rotationGroup.useQuaternion = true;
  	this.rotationGroup.quaternion = new THREE.Quaternion(0, -1, 0, 0); // was 1 0 0 0
  	this.rotationGroup.add(this.modelGroup);
  
  	this.scene.add(this.rotationGroup);
  	this.setupLights(this.scene);
  };
  
  gp.show = function() {
  	if (!this.scene) return;
  	var time = new Date();
  	this.setSlabAndFog();
  	this.renderer.render(this.scene, this.camera);
  	//console.log("rendered in " + (+new Date() - time) + "ms");
  };
  
  gp.initializeJmolExport = function() {
  	this._view = this.getView();
  	this.initializeScene();
  	this.tpt1 = new THREE.Vector3();
  	this.tpt2 = new THREE.Vector3();
  	this._m = new THREE.Matrix4();
  	this._geoTriangles = null;	
  	if (!this.sphereGeometry) {
  		this.sphereGeometry = new THREE.SphereGeometry(1, this.sphereQuality, this.sphereQuality); // r, seg, ring
  		this.sphereGeometry0 = new THREE.SphereGeometry(1, this.sphereQuality / 4, this.sphereQuality / 4); // r, seg, ring
  		var cg = this.cylinderGeometry = {};
  		cg['c'] = new THREE.CylinderGeometry(1, 1, 1, this.cylinderQuality, 1, true);
  		cg['c'].faceUvs = [];
  		cg['c'].faceVertexUvs = [];
  		cg['c0'] = new THREE.CylinderGeometry(1, 1, 1, this.cylinderQuality/4, 1, true);
  		cg['c0'].faceUvs = [];
  		cg['c0'].faceVertexUvs = [];
  		cg['n'] = new THREE.CylinderGeometry(1, 1, 1, this.cylinderQuality, 1, false);
  		cg['n'].faceUvs = [];
  		cg['n'].faceVertexUvs = [];
  		cg['n0'] = new THREE.CylinderGeometry(1, 1, 1, this.cylinderQuality/4, 1, false);
  		cg['n0'].faceUvs = [];
  		cg['n0'].faceVertexUvs = [];
  	}
  };
  
  gp.drawSphere = function(group, pt, color, radius) {
  	var isRough = (radius < 0.05); // typical mesh dot
  	var geom = (isRough ? this.sphereGeometry0 : this.sphereGeometry);
  	var sphere = new THREE.Mesh(geom, GLmol.getMat(this, color));
  	group.add(sphere);
  	sphere.scale.x = sphere.scale.y = sphere.scale.z = radius;
  	sphere.position.x = pt.x;
  	sphere.position.y = pt.y;
  	sphere.position.z = pt.z;
  }
  
  gp.addJmolSphere = function(pt, color, radius) {
  	this.drawSphere(this.modelGroup, pt, color, radius);
  }
  
  gp.addJmolCylinder = function(pt1, pt2, color, length, radius) {
  	GLmol.setPt(this.tpt1,pt1);
  	GLmol.setPt(this.tpt2,pt2);
  	this.drawCylinder(this.modelGroup, this.tpt1, this.tpt2, 0 + radius, color, true);
  };
  
  gp.addJmolTriangle = function(color, pt1, pt2, pt3) {
  	var geo = this._geoTriangles;
  	if (!geo)
  		geo = this._geoTriangles = new THREE.Geometry();
  	var n = geo.vertices.length;
  	geo.vertices.push(new THREE.Vector3(pt1.x, pt1.y, pt1.z));	
  	geo.vertices.push(new THREE.Vector3(pt2.x, pt2.y, pt2.z));	
  	geo.vertices.push(new THREE.Vector3(pt3.x, pt3.y, pt3.z));	
  	var f = new THREE.Face3(n, n+1, n+2, null, new THREE.Color(color));
  	geo.faces.push(f);
  };
  
  gp.finalizeJmolExport = function() {	
  
  	if (this._geoTriangles) {
  		var geo = this._geoTriangles;
  		geo.computeFaceNormals();
  		var params = {vertexColors: THREE.FaceColors};
  		var obj = new THREE.Mesh(geo, new THREE.MeshLambertMaterial(params));
  		obj.doubleSided = true; // generally?
  //			    obj.material.wireframe = true;
  		this.modelGroup.add(obj);
  	}
  	this.setView(this._view);
  };
  
  gp.drawCylinder = function(group, from, to, radius, color, cap) {
  	if (!from || !to) return;
  
  	midpoint = new THREE.Vector3().add(from, to).multiplyScalar(0.5);
  
  	var isRough = (radius < 0.05);
  	var geom = this.cylinderGeometry[(cap ? "c" : "n") + (isRough ? "0" : "")];
  	var cylinder = new THREE.Mesh(geom, GLmol.getMat(this, color));
  	cylinder.position = midpoint;
  	cylinder.lookAt(from);
  	cylinder.updateMatrix();
  	cylinder.matrixAutoUpdate = false;
  	cylinder.matrix.multiplySelf(this._m.makeScale(radius, radius, from.distanceTo(to)).rotateX(Math.PI / 2));
  	group.add(cylinder);
  };
  
  })(GLmol.prototype);
  
  })(Jmol.GLmol);
  
  })(Jmol);
  
   