airavata-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From scnakand...@apache.org
Subject [3/8] airavata-php-gateway git commit: adding datacat web browser initial files
Date Wed, 03 Feb 2016 13:50:34 GMT
adding datacat web browser initial files


Project: http://git-wip-us.apache.org/repos/asf/airavata-php-gateway/repo
Commit: http://git-wip-us.apache.org/repos/asf/airavata-php-gateway/commit/bac76ae5
Tree: http://git-wip-us.apache.org/repos/asf/airavata-php-gateway/tree/bac76ae5
Diff: http://git-wip-us.apache.org/repos/asf/airavata-php-gateway/diff/bac76ae5

Branch: refs/heads/develop
Commit: bac76ae50087fe8c66604ee6e54628d2da9a884c
Parents: e675d4d
Author: scnakandala <supun.nakandala@gmail.com>
Authored: Wed Jan 27 15:08:16 2016 -0500
Committer: scnakandala <supun.nakandala@gmail.com>
Committed: Wed Jan 27 15:08:16 2016 -0500

----------------------------------------------------------------------
 app/controllers/DataCatController.php |   16 +
 app/routes.php                        |    5 +
 app/views/datacat/select.blade.php    |   80 ++
 public/js/datacat/GLmol.js            | 1708 ++++++++++++++++++++++++++++
 public/js/datacat/Three.js            |  382 +++++++
 public/js/datacat/jquery-1.7.min.js   |    4 +
 6 files changed, 2195 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/airavata-php-gateway/blob/bac76ae5/app/controllers/DataCatController.php
----------------------------------------------------------------------
diff --git a/app/controllers/DataCatController.php b/app/controllers/DataCatController.php
new file mode 100755
index 0000000..fb821f6
--- /dev/null
+++ b/app/controllers/DataCatController.php
@@ -0,0 +1,16 @@
+<?php
+
+class DataCatController extends BaseController
+{
+
+    public function select()
+    {
+        $results = json_decode(file_get_contents('http://localhost:9000/query-api/select?q=sddslfnlsdf'), true);
+        if(!isset($results) || empty($results)){
+            $results = array();
+        }
+//        var_dump($results);exit;
+        return View::make('datacat/select', array("results" => $results));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/airavata-php-gateway/blob/bac76ae5/app/routes.php
----------------------------------------------------------------------
diff --git a/app/routes.php b/app/routes.php
index a180cba..e183220 100755
--- a/app/routes.php
+++ b/app/routes.php
@@ -204,6 +204,11 @@ Route::post("gp/remove-sr", "GatewayprofileController@delete");
 
 Route::post("gp/credential-store-token-change", "GatewayprofileController@cstChange");
 
+
+//DataCat
+Route::get("datacat/select", "DataCatController@select");
+
+
 //Management Dashboard
 
 Route::get("admin/dashboard", "AdminController@dashboard");

http://git-wip-us.apache.org/repos/asf/airavata-php-gateway/blob/bac76ae5/app/views/datacat/select.blade.php
----------------------------------------------------------------------
diff --git a/app/views/datacat/select.blade.php b/app/views/datacat/select.blade.php
new file mode 100755
index 0000000..ada2902
--- /dev/null
+++ b/app/views/datacat/select.blade.php
@@ -0,0 +1,80 @@
+@extends('layout.basic')
+{{ HTML::script('js/datacat/jquery-1.7.min.js')}}
+{{ HTML::script('js/datacat/Three.js')}}
+{{ HTML::script('js/datacat/GLmol.js')}}
+
+@section('page-header')
+    @parent
+@stop
+@section('content')
+    <div class="container" style="max-width: 80%;">
+    @if ( isset($results))
+        @if (sizeof($results) != 0)
+            <div id="re" class="table-responsive">
+                <table class="table">
+                    <tr>
+                        <th>FinalGeom</th>
+                        <th>Formula</th>
+                        <th>Energy</th>
+                        <th>ZPE</th>
+                        <th>CalcType</th>
+                        <th>Methods</th>
+                        <th>Basis</th>
+                        <th>Enthalpy</th>
+                        <th>Gibbs</th>
+                        <th>NImag</th>
+                        <th>CodeVersion</th>
+                    </tr>
+                    @foreach($results as $key=>$result)
+                        @if(isset($result['Formula']))
+                            <tr>
+                                <td>
+                                    <div id="mol_{{$key}}" style="width: 100px; height: 100px; background-color: black;"></div>
+                                    @if(isset($result['PDB']))
+                                        <textarea id="mol_{{$key}}_src" style="display: none;">{{$result['PDB']}}</textarea>
+                                        <script type="text/javascript">
+                                            $( document ).ready(function() {
+                                                var mol_{{$key}}  = new GLmol('mol_{{$key}}', true);
+
+                                                mol_{{$key}}.defineRepresentation = function () {
+                                                    var all = this.getAllAtoms();
+                                                    var hetatm = this.removeSolvents(this.getHetatms(all));
+                                                    this.colorByAtom(all, {});
+                                                    this.colorByChain(all);
+                                                    var asu = new THREE.Object3D();
+
+                                                    this.drawBondsAsStick(asu, hetatm, this.cylinderRadius, this.cylinderRadius);
+                                                    this.drawBondsAsStick(asu, this.getResiduesById(this.getSidechains(this.getChain(all, ['A'])), [58, 87]),
+                                                            this.cylinderRadius, this.cylinderRadius);
+                                                    this.drawBondsAsStick(asu, this.getResiduesById(this.getSidechains(this.getChain(all, ['B'])), [63, 92]),
+                                                            this.cylinderRadius, this.cylinderRadius);
+                                                    this.drawCartoon(asu, all, this.curveWidth, this.thickness);
+
+                                                    this.drawSymmetryMates2(this.modelGroup, asu, this.protein.biomtMatrices);
+                                                    this.modelGroup.add(asu);
+                                                };
+
+                                                mol_{{$key}}.loadMolecule();
+                                            });
+                                        </script>
+                                    @endif
+                                </td>
+                                <td>@if(isset($result['Formula']))<a>{{$result['Formula']}}</a>@endif</td>
+                                <td>@if(isset($result['Energy'])){{$result['Energy']}}@endif</td>
+                                <td>@if(isset($result['ZPE'])){{$result['ZPE']}}@endif</td>
+                                <td>@if(isset($result['CalcType'])){{$result['CalcType']}}@endif</td>
+                                <td>@if(isset($result['Methods'])){{$result['Methods']}}@endif</td>
+                                <td>@if(isset($result['Basis'])){{$result['Basis']}}@endif</td>
+                                <td>@if(isset($result['Enthalpy'])){{$result['Enthalpy']}}@endif</td>
+                                <td>@if(isset($result['Gibbs'])){{$result['Gibbs']}}@endif</td>
+                                <td>@if(isset($result['NImag'])){{$result['NImag']}}@endif</td>
+                                <td>@if(isset($result['CodeVersion'])){{$result['CodeVersion']}}@endif</td>
+                            </tr>
+                        @endif
+                    @endforeach
+                </table>
+            </div>
+        @endif
+    @endif
+    </div>
+@stop
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/airavata-php-gateway/blob/bac76ae5/public/js/datacat/GLmol.js
----------------------------------------------------------------------
diff --git a/public/js/datacat/GLmol.js b/public/js/datacat/GLmol.js
new file mode 100644
index 0000000..9af612b
--- /dev/null
+++ b/public/js/datacat/GLmol.js
@@ -0,0 +1,1708 @@
+/*
+ GLmol - Molecular Viewer on WebGL/Javascript (0.47)
+  (C) Copyright 2011-2012, biochem_fan
+      License: dual license of MIT or LGPL3
+
+  Contributors:
+    Robert Hanson for parseXYZ, deferred instantiation
+
+  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
+ */
+
+// 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 }");
+
+var TV3 = THREE.Vector3, TF3 = THREE.Face3, TCo = THREE.Color;
+
+THREE.Geometry.prototype.colorAll = function (color) {
+   for (var i = 0; i < this.faces.length; i++) {
+      this.faces[i].color = color;
+   }
+};
+
+THREE.Matrix4.prototype.isIdentity = function() {
+   for (var i = 0; i < 4; i++)
+      for (var j = 0; j < 4; j++) 
+         if (this.elements[i * 4 + j] != (i == j) ? 1 : 0) return false;
+   return true;
+};
+
+var GLmol = (function() {
+function GLmol(id, suppressAutoload) {
+   if (id) this.create(id, suppressAutoload);
+   return true;
+}
+
+GLmol.prototype.create = function(id, suppressAutoload) {
+   this.Nucleotides = ['  G', '  A', '  T', '  C', '  U', ' DG', ' DA', ' DT', ' DC', ' DU'];
+   this.ElementColors = {"H": 0xCCCCCC, "C": 0xAAAAAA, "O": 0xCC0000, "N": 0x0000CC, "S": 0xCCCC00, "P": 0x6622CC,
+                         "F": 0x00CC00, "CL": 0x00CC00, "BR": 0x882200, "I": 0x6600AA,
+                         "FE": 0xCC6600, "CA": 0x8888AA};
+// Reference: A. Bondi, J. Phys. Chem., 1964, 68, 441.
+   this.vdwRadii = {"H": 1.2, "Li": 1.82, "Na": 2.27, "K": 2.75, "C": 1.7, "N": 1.55, "O": 1.52,
+                   "F": 1.47, "P": 1.80, "S": 1.80, "CL": 1.75, "BR": 1.85, "SE": 1.90,
+                   "ZN": 1.39, "CU": 1.4, "NI": 1.63};
+
+   this.id = id;
+   this.aaScale = 1; // or 2
+
+   this.container = $('#' + this.id);
+   this.WIDTH = this.container.width() * this.aaScale, this.HEIGHT = this.container.height() * this.aaScale;
+   this.ASPECT = this.WIDTH / this.HEIGHT;
+   this.NEAR = 1, FAR = 800;
+   this.CAMERA_Z = -150;
+   this.renderer = new THREE.WebGLRenderer({antialias: true});
+   this.renderer.sortObjects = false; // hopefully improve performance
+   // 'antialias: true' now works in Firefox too!
+   // setting this.aaScale = 2 will enable antialias in older Firefox but GPU load increases.
+   this.renderer.domElement.style.width = "100%";
+   this.renderer.domElement.style.height = "100%";
+   this.container.append(this.renderer.domElement);
+   this.renderer.setSize(this.WIDTH, this.HEIGHT);
+
+   this.camera = new THREE.PerspectiveCamera(20, this.ASPECT, 1, 800); // will be updated anyway
+   this.camera.position = new TV3(0, 0, this.CAMERA_Z);
+   this.camera.lookAt(new TV3(0, 0, 0));
+   this.perspectiveCamera = this.camera;
+   this.orthoscopicCamera = new THREE.OrthographicCamera();
+   this.orthoscopicCamera.position.z = this.CAMERA_Z;
+   this.orthoscopicCamera.lookAt(new TV3(0, 0, 0));
+
+   var self = this;
+   $(window).resize(function() { // only window can capture resize event
+      self.WIDTH = self.container.width() * self.aaScale;
+      self.HEIGHT = self.container.height() * self.aaScale;
+      self.ASPECT = self.WIDTH / self.HEIGHT;
+      self.renderer.setSize(self.WIDTH, self.HEIGHT);
+      self.camera.aspect = self.ASPECT;
+      self.camera.updateProjectionMatrix();
+      self.show();
+   });
+
+   this.scene = null;
+   this.rotationGroup = null; // which contains modelGroup
+   this.modelGroup = null;
+
+   this.bgColor = 0x000000;
+   this.fov = 20;
+   this.fogStart = 0.4;
+   this.slabNear = -50; // relative to the center of rotationGroup
+   this.slabFar = +50;
+
+   // Default values
+   this.sphereRadius = 1.5; 
+   this.cylinderRadius = 0.4;
+   this.lineWidth = 1.5 * this.aaScale;
+   this.curveWidth = 3 * this.aaScale;
+   this.defaultColor = 0xCCCCCC;
+   this.sphereQuality = 16; //16;
+   this.cylinderQuality = 16; //8;
+   this.axisDIV = 5; // 3 still gives acceptable quality
+   this.strandDIV = 6;
+   this.nucleicAcidStrandDIV = 4;
+   this.tubeDIV = 8;
+   this.coilWidth = 0.3;
+   this.helixSheetWidth = 1.3;
+   this.nucleicAcidWidth = 0.8;
+   this.thickness = 0.4;
+ 
+   // UI variables
+   this.cq = new THREE.Quaternion(1, 0, 0, 0);
+   this.dq = new THREE.Quaternion(1, 0, 0, 0);
+   this.isDragging = false;
+   this.mouseStartX = 0;
+   this.mouseStartY = 0;
+   this.currentModelPos = 0;
+   this.cz = 0;
+   this.enableMouse();
+
+   if (suppressAutoload) return;
+   this.loadMolecule();
+}
+
+GLmol.prototype.setupLights = function(scene) {
+   var directionalLight =  new THREE.DirectionalLight(0xFFFFFF);
+   directionalLight.position = new TV3(0.2, 0.2, -1).normalize();
+   directionalLight.intensity = 1.2;
+   scene.add(directionalLight);
+   var ambientLight = new THREE.AmbientLight(0x202020);
+   scene.add(ambientLight);
+};
+
+GLmol.prototype.parseSDF = function(str) {
+   var atoms = this.atoms;
+   var protein = this.protein;
+
+   var lines = str.split("\n");
+   if (lines.length < 4) return;
+   var atomCount = parseInt(lines[3].substr(0, 3));
+   if (isNaN(atomCount) || atomCount <= 0) return;
+   var bondCount = parseInt(lines[3].substr(3, 3));
+   var offset = 4;
+   if (lines.length < 4 + atomCount + bondCount) return;
+   for (var i = 1; i <= atomCount; i++) {
+      var line = lines[offset];
+      offset++;
+      var atom = {};
+      atom.serial = i;
+      atom.x = parseFloat(line.substr(0, 10));
+      atom.y = parseFloat(line.substr(10, 10));
+      atom.z = parseFloat(line.substr(20, 10));
+      atom.hetflag = true;
+      atom.atom = atom.elem = line.substr(31, 3).replace(/ /g, "");
+      atom.bonds = [];
+      atom.bondOrder = [];
+      atoms[i] = atom;
+   }
+   for (i = 1; i <= bondCount; i++) {
+      var line = lines[offset];
+      offset++;
+      var from = parseInt(line.substr(0, 3));
+      var to = parseInt(line.substr(3, 3));
+      var order = parseInt(line.substr(6, 3));
+      atoms[from].bonds.push(to);
+      atoms[from].bondOrder.push(order);
+      atoms[to].bonds.push(from);
+      atoms[to].bondOrder.push(order);
+   }
+
+   protein.smallMolecule = true;
+   return true;
+};
+
+GLmol.prototype.parseXYZ = function(str) {
+   var atoms = this.atoms;
+   var protein = this.protein;
+
+   var lines = str.split("\n");
+   if (lines.length < 3) return;
+   var atomCount = parseInt(lines[0].substr(0, 3));
+   if (isNaN(atomCount) || atomCount <= 0) return;
+   if (lines.length < atomCount + 2) return;
+   var offset = 2;
+   for (var i = 1; i <= atomCount; i++) {
+      var line = lines[offset++];
+      var tokens = line.replace(/^\s+/, "").replace(/\s+/g," ").split(" ");
+      console.log(tokens);
+      var atom = {};
+      atom.serial = i;
+      atom.atom = atom.elem = tokens[0];
+      atom.x = parseFloat(tokens[1]);
+      atom.y = parseFloat(tokens[2]);
+      atom.z = parseFloat(tokens[3]);
+      atom.hetflag = true;
+      atom.bonds = [];
+      atom.bondOrder = [];
+      atoms[i] = atom;
+   }
+   for (var i = 1; i < atomCount; i++) // hopefully XYZ is small enough
+      for (var j = i + 1; j <= atomCount; j++)
+         if (this.isConnected(atoms[i], atoms[j])) {
+	    atoms[i].bonds.push(j);
+	    atoms[i].bondOrder.push(1);
+    	    atoms[j].bonds.push(i);
+     	    atoms[j].bondOrder.push(1);
+         }
+   protein.smallMolecule = true;
+   return true;
+};
+
+GLmol.prototype.parsePDB2 = function(str) {
+   var atoms = this.atoms;
+   var protein = this.protein;
+   var molID;
+
+   var atoms_cnt = 0;
+   lines = str.split("\n");
+   for (var i = 0; i < lines.length; i++) {
+      line = lines[i].replace(/^\s*/, ''); // remove indent
+      var recordName = line.substr(0, 6);
+      if (recordName == 'ATOM  ' || recordName == 'HETATM') {
+         var atom, resn, chain, resi, x, y, z, hetflag, elem, serial, altLoc, b;
+         altLoc = line.substr(16, 1);
+         if (altLoc != ' ' && altLoc != 'A') continue; // FIXME: ad hoc
+         serial = parseInt(line.substr(6, 5));
+         atom = line.substr(12, 4).replace(/ /g, "");
+         resn = line.substr(17, 3);
+         chain = line.substr(21, 1);
+         resi = parseInt(line.substr(22, 5)); 
+         x = parseFloat(line.substr(30, 8));
+         y = parseFloat(line.substr(38, 8));
+         z = parseFloat(line.substr(46, 8));
+         b = parseFloat(line.substr(60, 8));
+         elem = line.substr(76, 2).replace(/ /g, "");
+         if (elem == '') { // for some incorrect PDB files
+            elem = line.substr(12, 4).replace(/ /g,"");
+         }
+         if (line[0] == 'H') hetflag = true;
+         else hetflag = false;
+         atoms[serial] = {'resn': resn, 'x': x, 'y': y, 'z': z, 'elem': elem,
+  'hetflag': hetflag, 'chain': chain, 'resi': resi, 'serial': serial, 'atom': atom,
+  'bonds': [], 'ss': 'c', 'color': 0xFFFFFF, 'bonds': [], 'bondOrder': [], 'b': b /*', altLoc': altLoc*/};
+      } else if (recordName == 'SHEET ') {
+         var startChain = line.substr(21, 1);
+         var startResi = parseInt(line.substr(22, 4));
+         var endChain = line.substr(32, 1);
+         var endResi = parseInt(line.substr(33, 4));
+         protein.sheet.push([startChain, startResi, endChain, endResi]);
+     } else if (recordName == 'CONECT') {
+// MEMO: We don't have to parse SSBOND, LINK because both are also 
+// described in CONECT. But what about 2JYT???
+         var from = parseInt(line.substr(6, 5));
+         for (var j = 0; j < 4; j++) {
+            var to = parseInt(line.substr([11, 16, 21, 26][j], 5));
+            if (isNaN(to)) continue;
+            if (atoms[from] != undefined) {
+               atoms[from].bonds.push(to);
+               atoms[from].bondOrder.push(1);
+            }
+         }
+     } else if (recordName == 'HELIX ') {
+         var startChain = line.substr(19, 1);
+         var startResi = parseInt(line.substr(21, 4));
+         var endChain = line.substr(31, 1);
+         var endResi = parseInt(line.substr(33, 4));
+         protein.helix.push([startChain, startResi, endChain, endResi]);
+     } else if (recordName == 'CRYST1') {
+         protein.a = parseFloat(line.substr(6, 9));
+         protein.b = parseFloat(line.substr(15, 9));
+         protein.c = parseFloat(line.substr(24, 9));
+         protein.alpha = parseFloat(line.substr(33, 7));
+         protein.beta = parseFloat(line.substr(40, 7));
+         protein.gamma = parseFloat(line.substr(47, 7));
+         protein.spacegroup = line.substr(55, 11);
+         this.defineCell();
+      } else if (recordName == 'REMARK') {
+         var type = parseInt(line.substr(7, 3));
+         if (type == 290 && line.substr(13, 5) == 'SMTRY') {
+            var n = parseInt(line[18]) - 1;
+            var m = parseInt(line.substr(21, 2));
+            if (protein.symMat[m] == undefined) protein.symMat[m] = new THREE.Matrix4().identity();
+            protein.symMat[m].elements[n] = parseFloat(line.substr(24, 9));
+            protein.symMat[m].elements[n + 4] = parseFloat(line.substr(34, 9));
+            protein.symMat[m].elements[n + 8] = parseFloat(line.substr(44, 9));
+            protein.symMat[m].elements[n + 12] = parseFloat(line.substr(54, 10));
+         } else if (type == 350 && line.substr(13, 5) == 'BIOMT') {
+            var n = parseInt(line[18]) - 1;
+            var m = parseInt(line.substr(21, 2));
+            if (protein.biomtMatrices[m] == undefined) protein.biomtMatrices[m] = new THREE.Matrix4().identity();
+            protein.biomtMatrices[m].elements[n] = parseFloat(line.substr(24, 9));
+            protein.biomtMatrices[m].elements[n + 4] = parseFloat(line.substr(34, 9));
+            protein.biomtMatrices[m].elements[n + 8] = parseFloat(line.substr(44, 9));
+            protein.biomtMatrices[m].elements[n + 12] = parseFloat(line.substr(54, 10));
+         } else if (type == 350 && line.substr(11, 11) == 'BIOMOLECULE') {
+             protein.biomtMatrices = []; protein.biomtChains = '';
+         } else if (type == 350 && line.substr(34, 6) == 'CHAINS') {
+             protein.biomtChains += line.substr(41, 40);
+         }
+      } else if (recordName == 'HEADER') {
+         protein.pdbID = line.substr(62, 4);
+      } else if (recordName == 'TITLE ') {
+         if (protein.title == undefined) protein.title = "";
+            protein.title += line.substr(10, 70) + "\n"; // CHECK: why 60 is not enough???
+      } else if (recordName == 'COMPND') {
+              // TODO: Implement me!
+      }
+   }
+
+   // Assign secondary structures 
+   for (i = 0; i < atoms.length; i++) {
+      atom = atoms[i]; if (atom == undefined) continue;
+
+      var found = false;
+      // MEMO: Can start chain and end chain differ?
+      for (j = 0; j < protein.sheet.length; j++) {
+         if (atom.chain != protein.sheet[j][0]) continue;
+         if (atom.resi < protein.sheet[j][1]) continue;
+         if (atom.resi > protein.sheet[j][3]) continue;
+         atom.ss = 's';
+         if (atom.resi == protein.sheet[j][1]) atom.ssbegin = true;
+         if (atom.resi == protein.sheet[j][3]) atom.ssend = true;
+      }
+      for (j = 0; j < protein.helix.length; j++) {
+         if (atom.chain != protein.helix[j][0]) continue;
+         if (atom.resi < protein.helix[j][1]) continue;
+         if (atom.resi > protein.helix[j][3]) continue;
+         atom.ss = 'h';
+         if (atom.resi == protein.helix[j][1]) atom.ssbegin = true;
+         else if (atom.resi == protein.helix[j][3]) atom.ssend = true;
+      }
+   }
+   protein.smallMolecule = false;
+   return true;
+};
+
+// Catmull-Rom subdivision
+GLmol.prototype.subdivide = function(_points, DIV) { // points as Vector3
+   var ret = [];
+   var points = _points;
+   points = new Array(); // Smoothing test
+   points.push(_points[0]);
+   for (var i = 1, lim = _points.length - 1; i < lim; i++) {
+      var p1 = _points[i], p2 = _points[i + 1];
+      if (p1.smoothen) points.push(new TV3((p1.x + p2.x) / 2, (p1.y + p2.y) / 2, (p1.z + p2.z) / 2));
+      else points.push(p1);
+   }
+   points.push(_points[_points.length - 1]);
+
+   for (var i = -1, size = points.length; i <= size - 3; i++) {
+      var p0 = points[(i == -1) ? 0 : i];
+      var p1 = points[i + 1], p2 = points[i + 2];
+      var p3 = points[(i == size - 3) ? size - 1 : i + 3];
+      var v0 = new TV3().sub(p2, p0).multiplyScalar(0.5);
+      var v1 = new TV3().sub(p3, p1).multiplyScalar(0.5);
+      for (var j = 0; j < DIV; j++) {
+         var t = 1.0 / DIV * j;
+         var x = p1.x + t * v0.x 
+                  + t * t * (-3 * p1.x + 3 * p2.x - 2 * v0.x - v1.x)
+                  + t * t * t * (2 * p1.x - 2 * p2.x + v0.x + v1.x);
+         var y = p1.y + t * v0.y 
+                  + t * t * (-3 * p1.y + 3 * p2.y - 2 * v0.y - v1.y)
+                  + t * t * t * (2 * p1.y - 2 * p2.y + v0.y + v1.y);
+         var z = p1.z + t * v0.z 
+                  + t * t * (-3 * p1.z + 3 * p2.z - 2 * v0.z - v1.z)
+                  + t * t * t * (2 * p1.z - 2 * p2.z + v0.z + v1.z);
+         ret.push(new TV3(x, y, z));
+      }
+   }
+   ret.push(points[points.length - 1]);
+   return ret;
+};
+
+GLmol.prototype.drawAtomsAsSphere = function(group, atomlist, defaultRadius, forceDefault, scale) {
+   var sphereGeometry = new THREE.SphereGeometry(1, this.sphereQuality, this.sphereQuality); // r, seg, ring
+   for (var i = 0; i < atomlist.length; i++) {
+      var atom = this.atoms[atomlist[i]];
+      if (atom == undefined) continue;
+
+      var sphereMaterial = new THREE.MeshLambertMaterial({color: atom.color});
+      var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
+      group.add(sphere);
+      var r = (!forceDefault && this.vdwRadii[atom.elem] != undefined) ? this.vdwRadii[atom.elem] : defaultRadius;
+      if (!forceDefault && scale) r *= scale;
+      sphere.scale.x = sphere.scale.y = sphere.scale.z = r;
+      sphere.position.x = atom.x;
+      sphere.position.y = atom.y;
+      sphere.position.z = atom.z;
+   }
+};
+
+// about two times faster than sphere when div = 2
+GLmol.prototype.drawAtomsAsIcosahedron = function(group, atomlist, defaultRadius, forceDefault) {
+   var geo = this.IcosahedronGeometry();
+   for (var i = 0; i < atomlist.length; i++) {
+      var atom = this.atoms[atomlist[i]];
+      if (atom == undefined) continue;
+
+      var mat = new THREE.MeshLambertMaterial({color: atom.color});
+      var sphere = new THREE.Mesh(geo, mat);
+      sphere.scale.x = sphere.scale.y = sphere.scale.z = (!forceDefault && this.vdwRadii[atom.elem] != undefined) ? this.vdwRadii[atom.elem] : defaultRadius;
+      group.add(sphere);
+      sphere.position.x = atom.x;
+      sphere.position.y = atom.y;
+      sphere.position.z = atom.z;
+   }
+};
+
+GLmol.prototype.isConnected = function(atom1, atom2) {
+   var s = atom1.bonds.indexOf(atom2.serial);
+   if (s != -1) return atom1.bondOrder[s];
+
+   if (this.protein.smallMolecule && (atom1.hetflag || atom2.hetflag)) return 0; // CHECK: or should I ?
+
+   var distSquared = (atom1.x - atom2.x) * (atom1.x - atom2.x) + 
+                     (atom1.y - atom2.y) * (atom1.y - atom2.y) + 
+                     (atom1.z - atom2.z) * (atom1.z - atom2.z);
+
+//   if (atom1.altLoc != atom2.altLoc) return false;
+   if (isNaN(distSquared)) return 0;
+   if (distSquared < 0.5) return 0; // maybe duplicate position.
+
+   if (distSquared > 1.3 && (atom1.elem == 'H' || atom2.elem == 'H' || atom1.elem == 'D' || atom2.elem == 'D')) return 0;
+   if (distSquared < 3.42 && (atom1.elem == 'S' || atom2.elem == 'S')) return 1;
+   if (distSquared > 2.78) return 0;
+   return 1;
+};
+
+GLmol.prototype.drawBondAsStickSub = function(group, atom1, atom2, bondR, order) {
+   var delta, tmp;
+   if (order > 1) delta = this.calcBondDelta(atom1, atom2, bondR * 2.3);
+   var p1 = new TV3(atom1.x, atom1.y, atom1.z);
+   var p2 = new TV3(atom2.x, atom2.y, atom2.z);
+   var mp = p1.clone().addSelf(p2).multiplyScalar(0.5);
+
+   var c1 = new TCo(atom1.color), c2 = new TCo(atom2.color);
+   if (order == 1 || order == 3) {
+      this.drawCylinder(group, p1, mp, bondR, atom1.color);
+      this.drawCylinder(group, p2, mp, bondR, atom2.color);
+   }
+   if (order > 1) {
+      tmp = mp.clone().addSelf(delta);
+      this.drawCylinder(group, p1.clone().addSelf(delta), tmp, bondR, atom1.color);
+      this.drawCylinder(group, p2.clone().addSelf(delta), tmp, bondR, atom2.color);
+      tmp = mp.clone().subSelf(delta);
+      this.drawCylinder(group, p1.clone().subSelf(delta), tmp, bondR, atom1.color);
+      this.drawCylinder(group, p2.clone().subSelf(delta), tmp, bondR, atom2.color);
+   }
+};
+
+GLmol.prototype.drawBondsAsStick = function(group, atomlist, bondR, atomR, ignoreNonbonded, multipleBonds, scale) {
+   var sphereGeometry = new THREE.SphereGeometry(1, this.sphereQuality, this.sphereQuality);
+   var nAtoms = atomlist.length, mp;
+   var forSpheres = [];
+   if (!!multipleBonds) bondR /= 2.5;
+   for (var _i = 0; _i < nAtoms; _i++) {
+      var i = atomlist[_i];
+      var atom1 = this.atoms[i];
+      if (atom1 == undefined) continue;
+      for (var _j = _i + 1; _j < _i + 30 && _j < nAtoms; _j++) {
+         var j = atomlist[_j];
+         var atom2 = this.atoms[j];
+         if (atom2 == undefined) continue;
+         var order = this.isConnected(atom1, atom2);
+         if (order == 0) continue;
+         atom1.connected = atom2.connected = true;
+         this.drawBondAsStickSub(group, atom1, atom2, bondR, (!!multipleBonds) ? order : 1);
+      }
+      for (var _j = 0; _j < atom1.bonds.length; _j++) {
+         var j = atom1.bonds[_j];
+         if (j < i + 30) continue; // be conservative!
+         if (atomlist.indexOf(j) == -1) continue;
+         var atom2 = this.atoms[j];
+         if (atom2 == undefined) continue;
+         atom1.connected = atom2.connected = true;
+         this.drawBondAsStickSub(group, atom1, atom2, bondR, (!!multipleBonds) ? atom1.bondOrder[_j] : 1);
+      }
+      if (atom1.connected) forSpheres.push(i);
+   }
+   this.drawAtomsAsSphere(group, forSpheres, atomR, !scale, scale);
+};
+
+GLmol.prototype.defineCell = function() {
+    var p = this.protein;
+    if (p.a == undefined) return;
+
+    p.ax = p.a;
+    p.ay = 0;
+    p.az = 0;
+    p.bx = p.b * Math.cos(Math.PI / 180.0 * p.gamma);
+    p.by = p.b * Math.sin(Math.PI / 180.0 * p.gamma);
+    p.bz = 0;
+    p.cx = p.c * Math.cos(Math.PI / 180.0 * p.beta);
+    p.cy = p.c * (Math.cos(Math.PI / 180.0 * p.alpha) - 
+               Math.cos(Math.PI / 180.0 * p.gamma) 
+             * Math.cos(Math.PI / 180.0 * p.beta)
+             / Math.sin(Math.PI / 180.0 * p.gamma));
+    p.cz = Math.sqrt(p.c * p.c * Math.sin(Math.PI / 180.0 * p.beta)
+               * Math.sin(Math.PI / 180.0 * p.beta) - p.cy * p.cy);
+};
+
+GLmol.prototype.drawUnitcell = function(group) {
+    var p = this.protein;
+    if (p.a == undefined) return;
+
+    var vertices = [[0, 0, 0], [p.ax, p.ay, p.az], [p.bx, p.by, p.bz], [p.ax + p.bx, p.ay + p.by, p.az + p.bz],
+          [p.cx, p.cy, p.cz], [p.cx + p.ax, p.cy + p.ay,  p.cz + p.az], [p.cx + p.bx, p.cy + p.by, p.cz + p.bz], [p.cx + p.ax + p.bx, p.cy + p.ay + p.by, p.cz + p.az + p.bz]];
+    var edges = [0, 1, 0, 2, 1, 3, 2, 3, 4, 5, 4, 6, 5, 7, 6, 7, 0, 4, 1, 5, 2, 6, 3, 7];    
+
+    var geo = new THREE.Geometry();
+    for (var i = 0; i < edges.length; i++) {
+       geo.vertices.push(new TV3(vertices[edges[i]][0], vertices[edges[i]][1], vertices[edges[i]][2]));
+    }
+   var lineMaterial = new THREE.LineBasicMaterial({linewidth: 1, color: 0xcccccc});
+   var line = new THREE.Line(geo, lineMaterial);
+   line.type = THREE.LinePieces;
+   group.add(line);
+};
+
+// TODO: Find inner side of a ring
+GLmol.prototype.calcBondDelta = function(atom1, atom2, sep) {
+   var dot;
+   var axis = new TV3(atom1.x - atom2.x, atom1.y - atom2.y, atom1.z - atom2.z).normalize();
+   var found = null;
+   for (var i = 0; i < atom1.bonds.length && !found; i++) {
+      var atom = this.atoms[atom1.bonds[i]]; if (!atom) continue;
+      if (atom.serial != atom2.serial && atom.elem != 'H') found = atom;
+   }
+   for (var i = 0; i < atom2.bonds.length && !found; i++) {
+      var atom = this.atoms[atom2.bonds[i]]; if (!atom) continue;
+      if (atom.serial != atom1.serial && atom.elem != 'H') found = atom;
+   }
+   if (found) {
+      var tmp = new TV3(atom1.x - found.x, atom1.y - found.y, atom1.z - found.z).normalize();
+      dot = tmp.dot(axis);
+      delta = new TV3(tmp.x - axis.x * dot, tmp.y - axis.y * dot, tmp.z - axis.z * dot);
+   }
+   if (!found || Math.abs(dot - 1) < 0.001 || Math.abs(dot + 1) < 0.001) {
+      if (axis.x < 0.01 && axis.y < 0.01) {
+         delta = new TV3(0, -axis.z, axis.y);
+      } else {
+         delta = new TV3(-axis.y, axis.x, 0);
+      }
+   }
+   delta.normalize().multiplyScalar(sep);
+   return delta;
+};
+
+GLmol.prototype.drawBondsAsLineSub = function(geo, atom1, atom2, order) {
+   var delta, tmp, vs = geo.vertices, cs = geo.colors;
+   if (order > 1) delta = this.calcBondDelta(atom1, atom2, 0.15);
+   var p1 = new TV3(atom1.x, atom1.y, atom1.z);
+   var p2 = new TV3(atom2.x, atom2.y, atom2.z);
+   var mp = p1.clone().addSelf(p2).multiplyScalar(0.5);
+
+   var c1 = new TCo(atom1.color), c2 = new TCo(atom2.color);
+   if (order == 1 || order == 3) {
+      vs.push(p1); cs.push(c1); vs.push(mp); cs.push(c1);
+      vs.push(p2); cs.push(c2); vs.push(mp); cs.push(c2);
+   }
+   if (order > 1) {
+      vs.push(p1.clone().addSelf(delta)); cs.push(c1);
+      vs.push(tmp = mp.clone().addSelf(delta)); cs.push(c1);
+      vs.push(p2.clone().addSelf(delta)); cs.push(c2);
+      vs.push(tmp); cs.push(c2);
+      vs.push(p1.clone().subSelf(delta)); cs.push(c1);
+      vs.push(tmp = mp.clone().subSelf(delta)); cs.push(c1);
+      vs.push(p2.clone().subSelf(delta)); cs.push(c2);
+      vs.push(tmp); cs.push(c2);
+   }
+};
+
+GLmol.prototype.drawBondsAsLine = function(group, atomlist, lineWidth) {
+   var geo = new THREE.Geometry();   
+   var nAtoms = atomlist.length;
+
+   for (var _i = 0; _i < nAtoms; _i++) {
+      var i = atomlist[_i];
+      var  atom1 = this.atoms[i];
+      if (atom1 == undefined) continue;
+      for (var _j = _i + 1; _j < _i + 30 && _j < nAtoms; _j++) {
+         var j = atomlist[_j];
+         var atom2 = this.atoms[j];
+         if (atom2 == undefined) continue;
+         var order = this.isConnected(atom1, atom2);
+         if (order == 0) continue;
+
+         this.drawBondsAsLineSub(geo, atom1, atom2, order);
+      }
+      for (var _j = 0; _j < atom1.bonds.length; _j++) {
+          var j = atom1.bonds[_j];
+          if (j < i + 30) continue; // be conservative!
+          if (atomlist.indexOf(j) == -1) continue;
+          var atom2 = this.atoms[j];
+          if (atom2 == undefined) continue;
+          this.drawBondsAsLineSub(geo, atom1, atom2, atom1.bondOrder[_j]);
+      }
+    }
+   var lineMaterial = new THREE.LineBasicMaterial({linewidth: lineWidth});
+   lineMaterial.vertexColors = true;
+
+   var line = new THREE.Line(geo, lineMaterial);
+   line.type = THREE.LinePieces;
+   group.add(line);
+};
+
+GLmol.prototype.drawSmoothCurve = function(group, _points, width, colors, div) {
+   if (_points.length == 0) return;
+
+   div = (div == undefined) ? 5 : div;
+
+   var geo = new THREE.Geometry();
+   var points = this.subdivide(_points, div);
+
+   for (var i = 0; i < points.length; i++) {
+      geo.vertices.push(points[i]);
+      geo.colors.push(new TCo(colors[(i == 0) ? 0 : Math.round((i - 1) / div)]));
+  }
+  var lineMaterial = new THREE.LineBasicMaterial({linewidth: width});
+  lineMaterial.vertexColors = true;
+  var line = new THREE.Line(geo, lineMaterial);
+  line.type = THREE.LineStrip;
+  group.add(line);
+};
+
+GLmol.prototype.drawAsCross = function(group, atomlist, delta) {
+   var geo = new THREE.Geometry();
+   var points = [[delta, 0, 0], [-delta, 0, 0], [0, delta, 0], [0, -delta, 0], [0, 0, delta], [0, 0, -delta]];
+ 
+   for (var i = 0, lim = atomlist.length; i < lim; i++) {
+      var atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+
+      var c = new TCo(atom.color);
+      for (var j = 0; j < 6; j++) {
+         geo.vertices.push(new TV3(atom.x + points[j][0], atom.y + points[j][1], atom.z + points[j][2]));
+         geo.colors.push(c);
+      }
+  }
+  var lineMaterial = new THREE.LineBasicMaterial({linewidth: this.lineWidth});
+  lineMaterial.vertexColors = true;
+  var line = new THREE.Line(geo, lineMaterial, THREE.LinePieces);
+  group.add(line);
+};
+
+// FIXME: Winkled...
+GLmol.prototype.drawSmoothTube = function(group, _points, colors, radii) {
+   if (_points.length < 2) return;
+
+   var circleDiv = this.tubeDIV, axisDiv = this.axisDIV;
+   var geo = new THREE.Geometry();
+   var points = this.subdivide(_points, axisDiv);
+   var prevAxis1 = new TV3(), prevAxis2;
+
+   for (var i = 0, lim = points.length; i < lim; i++) {
+      var r, idx = (i - 1) / axisDiv;
+      if (i == 0) r = radii[0];
+      else { 
+         if (idx % 1 == 0) r = radii[idx];
+         else {
+            var floored = Math.floor(idx);
+            var tmp = idx - floored;
+            r = radii[floored] * tmp + radii[floored + 1] * (1 - tmp);
+         }
+      }
+      var delta, axis1, axis2;
+
+      if (i < lim - 1) {
+         delta = new TV3().sub(points[i], points[i + 1]);
+         axis1 = new TV3(0, - delta.z, delta.y).normalize().multiplyScalar(r);
+         axis2 = new TV3().cross(delta, axis1).normalize().multiplyScalar(r);
+//      var dir = 1, offset = 0;
+         if (prevAxis1.dot(axis1) < 0) {
+                 axis1.negate(); axis2.negate();  //dir = -1;//offset = 2 * Math.PI / axisDiv;
+         }
+         prevAxis1 = axis1; prevAxis2 = axis2;
+      } else {
+         axis1 = prevAxis1; axis2 = prevAxis2;
+      }
+
+      for (var j = 0; j < circleDiv; j++) {
+         var angle = 2 * Math.PI / circleDiv * j; //* dir  + offset;
+         var c = Math.cos(angle), s = Math.sin(angle);
+         geo.vertices.push(new TV3(
+         points[i].x + c * axis1.x + s * axis2.x,
+         points[i].y + c * axis1.y + s * axis2.y, 
+         points[i].z + c * axis1.z + s * axis2.z));
+      }
+   }
+
+   var offset = 0;
+   for (var i = 0, lim = points.length - 1; i < lim; i++) {
+      var c =  new TCo(colors[Math.round((i - 1)/ axisDiv)]);
+
+      var reg = 0;
+      var r1 = new TV3().sub(geo.vertices[offset], geo.vertices[offset + circleDiv]).lengthSq();
+      var r2 = new TV3().sub(geo.vertices[offset], geo.vertices[offset + circleDiv + 1]).lengthSq();
+      if (r1 > r2) {r1 = r2; reg = 1;};
+      for (var j = 0; j < circleDiv; j++) {
+          geo.faces.push(new TF3(offset + j, offset + (j + reg) % circleDiv + circleDiv, offset + (j + 1) % circleDiv));
+          geo.faces.push(new TF3(offset + (j + 1) % circleDiv, offset + (j + reg) % circleDiv + circleDiv, offset + (j + reg + 1) % circleDiv + circleDiv));
+          geo.faces[geo.faces.length -2].color = c;
+          geo.faces[geo.faces.length -1].color = c;
+      }
+      offset += circleDiv;
+   }
+   geo.computeFaceNormals();
+   geo.computeVertexNormals(false);
+   var mat = new THREE.MeshLambertMaterial();
+   mat.vertexColors = THREE.FaceColors;
+   var mesh = new THREE.Mesh(geo, mat);
+   mesh.doubleSided = true;
+   group.add(mesh);
+};
+
+
+GLmol.prototype.drawMainchainCurve = function(group, atomlist, curveWidth, atomName, div) {
+   var points = [], colors = [];
+   var currentChain, currentResi;
+   if (div == undefined) div = 5;
+
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]];
+      if (atom == undefined) continue;
+
+      if ((atom.atom == atomName) && !atom.hetflag) {
+         if (currentChain != atom.chain || currentResi + 1 != atom.resi) {
+            this.drawSmoothCurve(group, points, curveWidth, colors, div);
+            points = [];
+            colors = [];
+         }
+         points.push(new TV3(atom.x, atom.y, atom.z));
+         colors.push(atom.color);
+         currentChain = atom.chain;
+         currentResi = atom.resi;
+      }
+   }
+    this.drawSmoothCurve(group, points, curveWidth, colors, div);
+};
+
+GLmol.prototype.drawMainchainTube = function(group, atomlist, atomName, radius) {
+   var points = [], colors = [], radii = [];
+   var currentChain, currentResi;
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]];
+      if (atom == undefined) continue;
+
+      if ((atom.atom == atomName) && !atom.hetflag) {
+         if (currentChain != atom.chain || currentResi + 1 != atom.resi) {
+            this.drawSmoothTube(group, points, colors, radii);
+            points = []; colors = []; radii = [];
+         }
+         points.push(new TV3(atom.x, atom.y, atom.z));
+         if (radius == undefined) {
+            radii.push((atom.b > 0) ? atom.b / 100 : 0.3);
+         } else {
+            radii.push(radius);
+         }
+         colors.push(atom.color);
+         currentChain = atom.chain;
+         currentResi = atom.resi;
+      }
+   }
+   this.drawSmoothTube(group, points, colors, radii);
+};
+
+GLmol.prototype.drawStrip = function(group, p1, p2, colors, div, thickness) {
+   if ((p1.length) < 2) return;
+   div = div || this.axisDIV;
+   p1 = this.subdivide(p1, div);
+   p2 = this.subdivide(p2, div);
+   if (!thickness) return this.drawThinStrip(group, p1, p2, colors, div);
+
+   var geo = new THREE.Geometry();
+   var vs = geo.vertices, fs = geo.faces;
+   var axis, p1v, p2v, a1v, a2v;
+   for (var i = 0, lim = p1.length; i < lim; i++) {
+      vs.push(p1v = p1[i]); // 0
+      vs.push(p1v); // 1
+      vs.push(p2v = p2[i]); // 2
+      vs.push(p2v); // 3
+      if (i < lim - 1) {
+         var toNext = p1[i + 1].clone().subSelf(p1[i]);
+         var toSide = p2[i].clone().subSelf(p1[i]);
+         axis = toSide.crossSelf(toNext).normalize().multiplyScalar(thickness);
+      }
+      vs.push(a1v = p1[i].clone().addSelf(axis)); // 4
+      vs.push(a1v); // 5
+      vs.push(a2v = p2[i].clone().addSelf(axis)); // 6
+      vs.push(a2v); // 7
+   }
+   var faces = [[0, 2, -6, -8], [-4, -2, 6, 4], [7, 3, -5, -1], [-3, -7, 1, 5]];
+   for (var i = 1, lim = p1.length; i < lim; i++) {
+      var offset = 8 * i, color = new TCo(colors[Math.round((i - 1)/ div)]);
+      for (var j = 0; j < 4; j++) {
+         var f = new THREE.Face4(offset + faces[j][0], offset + faces[j][1], offset + faces[j][2], offset + faces[j][3], undefined, color);
+         fs.push(f);
+      }
+   }
+   var vsize = vs.length - 8; // Cap
+   for (var i = 0; i < 4; i++) {vs.push(vs[i * 2]); vs.push(vs[vsize + i * 2])};
+   vsize += 8;
+   fs.push(new THREE.Face4(vsize, vsize + 2, vsize + 6, vsize + 4, undefined, fs[0].color));
+   fs.push(new THREE.Face4(vsize + 1, vsize + 5, vsize + 7, vsize + 3, undefined, fs[fs.length - 3].color));
+   geo.computeFaceNormals();
+   geo.computeVertexNormals(false);
+   var material =  new THREE.MeshLambertMaterial();
+   material.vertexColors = THREE.FaceColors;
+   var mesh = new THREE.Mesh(geo, material);
+   mesh.doubleSided = true;
+   group.add(mesh);
+};
+
+
+GLmol.prototype.drawThinStrip = function(group, p1, p2, colors, div) {
+   var geo = new THREE.Geometry();
+   for (var i = 0, lim = p1.length; i < lim; i++) {
+      geo.vertices.push(p1[i]); // 2i
+      geo.vertices.push(p2[i]); // 2i + 1
+   }
+   for (var i = 1, lim = p1.length; i < lim; i++) {
+      var f = new THREE.Face4(2 * i, 2 * i + 1, 2 * i - 1, 2 * i - 2);
+      f.color = new TCo(colors[Math.round((i - 1)/ div)]);
+      geo.faces.push(f);
+   }
+   geo.computeFaceNormals();
+   geo.computeVertexNormals(false);
+   var material =  new THREE.MeshLambertMaterial();
+   material.vertexColors = THREE.FaceColors;
+   var mesh = new THREE.Mesh(geo, material);
+   mesh.doubleSided = true;
+   group.add(mesh);
+};
+
+
+GLmol.prototype.IcosahedronGeometry = function() {
+   if (!this.icosahedron) this.icosahedron = new THREE.IcosahedronGeometry(1);
+   return this.icosahedron;
+};
+
+GLmol.prototype.drawCylinder = function(group, from, to, radius, color, cap) {
+   if (!from || !to) return;
+
+   var midpoint = new TV3().add(from, to).multiplyScalar(0.5);
+   var color = new TCo(color);
+
+   if (!this.cylinderGeometry) {
+      this.cylinderGeometry = new THREE.CylinderGeometry(1, 1, 1, this.cylinderQuality, 1, !cap);
+      this.cylinderGeometry.faceUvs = [];
+      this.faceVertexUvs = [];
+   }
+   var cylinderMaterial = new THREE.MeshLambertMaterial({color: color.getHex()});
+   var cylinder = new THREE.Mesh(this.cylinderGeometry, cylinderMaterial);
+   cylinder.position = midpoint;
+   cylinder.lookAt(from);
+   cylinder.updateMatrix();
+   cylinder.matrixAutoUpdate = false;
+   var m = new THREE.Matrix4().makeScale(radius, radius, from.distanceTo(to));
+   m.rotateX(Math.PI / 2);
+   cylinder.matrix.multiplySelf(m);
+   group.add(cylinder);
+};
+
+// FIXME: transition!
+GLmol.prototype.drawHelixAsCylinder = function(group, atomlist, radius) {
+   var start = null;
+   var currentChain, currentResi;
+
+   var others = [], beta = [];
+
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]];
+      if (atom == undefined || atom.hetflag) continue;
+      if ((atom.ss != 'h' && atom.ss != 's') || atom.ssend || atom.ssbegin) others.push(atom.serial);
+      if (atom.ss == 's') beta.push(atom.serial);
+      if (atom.atom != 'CA') continue;
+
+      if (atom.ss == 'h' && atom.ssend) {
+         if (start != null) this.drawCylinder(group, new TV3(start.x, start.y, start.z), new TV3(atom.x, atom.y, atom.z), radius, atom.color, true);
+         start = null;
+      }
+      currentChain = atom.chain;
+      currentResi = atom.resi;
+      if (start == null && atom.ss == 'h' && atom.ssbegin) start = atom;
+   }
+   if (start != null) this.drawCylinder(group, new TV3(start.x, start.y, start.z), new TV3(atom.x, atom.y, atom.z), radius, atom.color);
+   this.drawMainchainTube(group, others, "CA", 0.3);
+   this.drawStrand(group, beta, undefined, undefined, true,  0, this.helixSheetWidth, false, this.thickness * 2);
+};
+
+GLmol.prototype.drawCartoon = function(group, atomlist, doNotSmoothen, thickness) {
+   this.drawStrand(group, atomlist, 2, undefined, true, undefined, undefined, doNotSmoothen, thickness);
+};
+
+GLmol.prototype.drawStrand = function(group, atomlist, num, div, fill, coilWidth, helixSheetWidth, doNotSmoothen, thickness) {
+   num = num || this.strandDIV;
+   div = div || this.axisDIV;
+   coilWidth = coilWidth || this.coilWidth;
+   doNotSmoothen == (doNotSmoothen == undefined) ? false : doNotSmoothen;
+   helixSheetWidth = helixSheetWidth || this.helixSheetWidth;
+   var points = []; for (var k = 0; k < num; k++) points[k] = [];
+   var colors = [];
+   var currentChain, currentResi, currentCA;
+   var prevCO = null, ss=null, ssborder = false;
+
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]];
+      if (atom == undefined) continue;
+
+      if ((atom.atom == 'O' || atom.atom == 'CA') && !atom.hetflag) {
+         if (atom.atom == 'CA') {
+            if (currentChain != atom.chain || currentResi + 1 != atom.resi) {
+               for (var j = 0; !thickness && j < num; j++)
+                  this.drawSmoothCurve(group, points[j], 1 ,colors, div);
+               if (fill) this.drawStrip(group, points[0], points[num - 1], colors, div, thickness);
+               var points = []; for (var k = 0; k < num; k++) points[k] = [];
+               colors = [];
+               prevCO = null; ss = null; ssborder = false;
+            }
+            currentCA = new TV3(atom.x, atom.y, atom.z);
+            currentChain = atom.chain;
+            currentResi = atom.resi;
+            ss = atom.ss; ssborder = atom.ssstart || atom.ssend;
+            colors.push(atom.color);
+         } else { // O
+            var O = new TV3(atom.x, atom.y, atom.z);
+            O.subSelf(currentCA);
+            O.normalize(); // can be omitted for performance
+            O.multiplyScalar((ss == 'c') ? coilWidth : helixSheetWidth); 
+            if (prevCO != undefined && O.dot(prevCO) < 0) O.negate();
+            prevCO = O;
+            for (var j = 0; j < num; j++) {
+               var delta = -1 + 2 / (num - 1) * j;
+               var v = new TV3(currentCA.x + prevCO.x * delta, 
+                               currentCA.y + prevCO.y * delta, currentCA.z + prevCO.z * delta);
+               if (!doNotSmoothen && ss == 's') v.smoothen = true;
+               points[j].push(v);
+            }                         
+         }
+      }
+   }
+   for (var j = 0; !thickness && j < num; j++)
+      this.drawSmoothCurve(group, points[j], 1 ,colors, div);
+   if (fill) this.drawStrip(group, points[0], points[num - 1], colors, div, thickness);
+};
+
+GLmol.prototype.drawNucleicAcidLadderSub = function(geo, lineGeo, atoms, color) {
+//        color.r *= 0.9; color.g *= 0.9; color.b *= 0.9;
+   if (atoms[0] != undefined && atoms[1] != undefined && atoms[2] != undefined &&
+       atoms[3] != undefined && atoms[4] != undefined && atoms[5] != undefined) {
+      var baseFaceId = geo.vertices.length;
+      for (var i = 0; i <= 5; i++) geo.vertices.push(atoms[i]);
+          geo.faces.push(new TF3(baseFaceId, baseFaceId + 1, baseFaceId + 2));
+          geo.faces.push(new TF3(baseFaceId, baseFaceId + 2, baseFaceId + 3));
+          geo.faces.push(new TF3(baseFaceId, baseFaceId + 3, baseFaceId + 4));
+          geo.faces.push(new TF3(baseFaceId, baseFaceId + 4, baseFaceId + 5));
+          for (var j = geo.faces.length - 4, lim = geo.faces.length; j < lim; j++) geo.faces[j].color = color;
+    }
+    if (atoms[4] != undefined && atoms[3] != undefined && atoms[6] != undefined &&
+       atoms[7] != undefined && atoms[8] != undefined) {
+       var baseFaceId = geo.vertices.length;
+       geo.vertices.push(atoms[4]);
+       geo.vertices.push(atoms[3]);
+       geo.vertices.push(atoms[6]);
+       geo.vertices.push(atoms[7]);
+       geo.vertices.push(atoms[8]);
+       for (var i = 0; i <= 4; i++) geo.colors.push(color);
+       geo.faces.push(new TF3(baseFaceId, baseFaceId + 1, baseFaceId + 2));
+       geo.faces.push(new TF3(baseFaceId, baseFaceId + 2, baseFaceId + 3));
+       geo.faces.push(new TF3(baseFaceId, baseFaceId + 3, baseFaceId + 4));
+       for (var j = geo.faces.length - 3, lim = geo.faces.length; j < lim; j++) geo.faces[j].color = color;
+    }
+};
+
+GLmol.prototype.drawNucleicAcidLadder = function(group, atomlist) {
+   var geo = new THREE.Geometry();
+   var lineGeo = new THREE.Geometry();
+   var baseAtoms = ["N1", "C2", "N3", "C4", "C5", "C6", "N9", "C8", "N7"];
+   var currentChain, currentResi, currentComponent = new Array(baseAtoms.length);
+   var color = new TCo(0xcc0000);
+   
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]];
+      if (atom == undefined || atom.hetflag) continue;
+
+      if (atom.resi != currentResi || atom.chain != currentChain) {
+         this.drawNucleicAcidLadderSub(geo, lineGeo, currentComponent, color);
+         currentComponent = new Array(baseAtoms.length);
+      }
+      var pos = baseAtoms.indexOf(atom.atom);
+      if (pos != -1) currentComponent[pos] = new TV3(atom.x, atom.y, atom.z);
+      if (atom.atom == 'O3\'') color = new TCo(atom.color);
+      currentResi = atom.resi; currentChain = atom.chain;
+   }
+   this.drawNucleicAcidLadderSub(geo, lineGeo, currentComponent, color);
+   geo.computeFaceNormals();
+   var mat = new THREE.MeshLambertMaterial();
+   mat.vertexColors = THREE.VertexColors;
+   var mesh = new THREE.Mesh(geo, mat);
+   mesh.doubleSided = true;
+   group.add(mesh);
+};
+
+GLmol.prototype.drawNucleicAcidStick = function(group, atomlist) {
+   var currentChain, currentResi, start = null, end = null;
+   
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]];
+      if (atom == undefined || atom.hetflag) continue;
+
+      if (atom.resi != currentResi || atom.chain != currentChain) {
+         if (start != null && end != null)
+            this.drawCylinder(group, new TV3(start.x, start.y, start.z), 
+                              new TV3(end.x, end.y, end.z), 0.3, start.color, true);
+         start = null; end = null;
+      }
+      if (atom.atom == 'O3\'') start = atom;
+      if (atom.resn == '  A' || atom.resn == '  G' || atom.resn == ' DA' || atom.resn == ' DG') {
+         if (atom.atom == 'N1')  end = atom; //  N1(AG), N3(CTU)
+      } else if (atom.atom == 'N3') {
+         end = atom;
+      }
+      currentResi = atom.resi; currentChain = atom.chain;
+   }
+   if (start != null && end != null)
+      this.drawCylinder(group, new TV3(start.x, start.y, start.z), 
+                        new TV3(end.x, end.y, end.z), 0.3, start.color, true);
+};
+
+GLmol.prototype.drawNucleicAcidLine = function(group, atomlist) {
+   var currentChain, currentResi, start = null, end = null;
+   var geo = new THREE.Geometry();
+
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]];
+      if (atom == undefined || atom.hetflag) continue;
+
+      if (atom.resi != currentResi || atom.chain != currentChain) {
+         if (start != null && end != null) {
+            geo.vertices.push(new TV3(start.x, start.y, start.z));
+            geo.colors.push(new TCo(start.color));
+            geo.vertices.push(new TV3(end.x, end.y, end.z));
+            geo.colors.push(new TCo(start.color));
+         }
+         start = null; end = null;
+      }
+      if (atom.atom == 'O3\'') start = atom;
+      if (atom.resn == '  A' || atom.resn == '  G' || atom.resn == ' DA' || atom.resn == ' DG') {
+         if (atom.atom == 'N1')  end = atom; //  N1(AG), N3(CTU)
+      } else if (atom.atom == 'N3') {
+         end = atom;
+      }
+      currentResi = atom.resi; currentChain = atom.chain;
+   }
+   if (start != null && end != null) {
+      geo.vertices.push(new TV3(start.x, start.y, start.z));
+      geo.colors.push(new TCo(start.color));
+      geo.vertices.push(new TV3(end.x, end.y, end.z));
+      geo.colors.push(new TCo(start.color));
+    }
+   var mat =  new THREE.LineBasicMaterial({linewidth: 1, linejoin: false});
+   mat.linewidth = 1.5; mat.vertexColors = true;
+   var line = new THREE.Line(geo, mat, THREE.LinePieces);
+   group.add(line);
+};
+
+GLmol.prototype.drawCartoonNucleicAcid = function(group, atomlist, div, thickness) {
+        this.drawStrandNucleicAcid(group, atomlist, 2, div, true, undefined, thickness);
+};
+
+GLmol.prototype.drawStrandNucleicAcid = function(group, atomlist, num, div, fill, nucleicAcidWidth, thickness) {
+   nucleicAcidWidth = nucleicAcidWidth || this.nucleicAcidWidth;
+   div = div || this.axisDIV;
+   num = num || this.nucleicAcidStrandDIV;
+   var points = []; for (var k = 0; k < num; k++) points[k] = [];
+   var colors = [];
+   var currentChain, currentResi, currentO3;
+   var prevOO = null;
+
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]];
+      if (atom == undefined) continue;
+
+      if ((atom.atom == 'O3\'' || atom.atom == 'OP2') && !atom.hetflag) {
+         if (atom.atom == 'O3\'') { // to connect 3' end. FIXME: better way to do?
+            if (currentChain != atom.chain || currentResi + 1 != atom.resi) {               
+               if (currentO3) {
+                  for (var j = 0; j < num; j++) {
+                     var delta = -1 + 2 / (num - 1) * j;
+                     points[j].push(new TV3(currentO3.x + prevOO.x * delta, 
+                      currentO3.y + prevOO.y * delta, currentO3.z + prevOO.z * delta));
+                  }
+               }
+               if (fill) this.drawStrip(group, points[0], points[1], colors, div, thickness);
+               for (var j = 0; !thickness && j < num; j++)
+                  this.drawSmoothCurve(group, points[j], 1 ,colors, div);
+               var points = []; for (var k = 0; k < num; k++) points[k] = [];
+               colors = [];
+               prevOO = null;
+            }
+            currentO3 = new TV3(atom.x, atom.y, atom.z);
+            currentChain = atom.chain;
+            currentResi = atom.resi;
+            colors.push(atom.color);
+         } else { // OP2
+            if (!currentO3) {prevOO = null; continue;} // for 5' phosphate (e.g. 3QX3)
+            var O = new TV3(atom.x, atom.y, atom.z);
+            O.subSelf(currentO3);
+            O.normalize().multiplyScalar(nucleicAcidWidth);  // TODO: refactor
+            if (prevOO != undefined && O.dot(prevOO) < 0) {
+               O.negate();
+            }
+            prevOO = O;
+            for (var j = 0; j < num; j++) {
+               var delta = -1 + 2 / (num - 1) * j;
+               points[j].push(new TV3(currentO3.x + prevOO.x * delta, 
+                 currentO3.y + prevOO.y * delta, currentO3.z + prevOO.z * delta));
+            }
+            currentO3 = null;
+         }
+      }
+   }
+   if (currentO3) {
+      for (var j = 0; j < num; j++) {
+         var delta = -1 + 2 / (num - 1) * j;
+         points[j].push(new TV3(currentO3.x + prevOO.x * delta, 
+           currentO3.y + prevOO.y * delta, currentO3.z + prevOO.z * delta));
+      }
+   }
+   if (fill) this.drawStrip(group, points[0], points[1], colors, div, thickness); 
+   for (var j = 0; !thickness && j < num; j++)
+      this.drawSmoothCurve(group, points[j], 1 ,colors, div);
+};
+
+GLmol.prototype.drawDottedLines = function(group, points, color) {
+    var geo = new THREE.Geometry();
+    var step = 0.3;
+
+    for (var i = 0, lim = Math.floor(points.length / 2); i < lim; i++) {
+        var p1 = points[2 * i], p2 = points[2 * i + 1];
+        var delta = p2.clone().subSelf(p1);
+        var dist = delta.length();
+        delta.normalize().multiplyScalar(step);
+        var jlim =  Math.floor(dist / step);
+        for (var j = 0; j < jlim; j++) {
+           var p = new TV3(p1.x + delta.x * j, p1.y + delta.y * j, p1.z + delta.z * j);
+           geo.vertices.push(p);
+        }
+        if (jlim % 2 == 1) geo.vertices.push(p2);
+    }
+
+    var mat = new THREE.LineBasicMaterial({'color': color.getHex()});
+    mat.linewidth = 2;
+    var line = new THREE.Line(geo, mat, THREE.LinePieces);
+    group.add(line);
+};
+
+GLmol.prototype.getAllAtoms = function() {
+   var ret = [];
+   for (var i in this.atoms) {
+      ret.push(this.atoms[i].serial);
+   }
+   return ret;
+};
+
+// Probably I can refactor using higher-order functions.
+GLmol.prototype.getHetatms = function(atomlist) {
+   var ret = [];
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+
+      if (atom.hetflag) ret.push(atom.serial);
+   }
+   return ret;
+};
+
+GLmol.prototype.removeSolvents = function(atomlist) {
+   var ret = [];
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+
+      if (atom.resn != 'HOH') ret.push(atom.serial);
+   }
+   return ret;
+};
+
+GLmol.prototype.getProteins = function(atomlist) {
+   var ret = [];
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+
+      if (!atom.hetflag) ret.push(atom.serial);
+   }
+   return ret;
+};
+
+// TODO: Test
+GLmol.prototype.excludeAtoms = function(atomlist, deleteList) {
+   var ret = [];
+   var blackList = new Object();
+   for (var _i in deleteList) blackList[deleteList[_i]] = true;
+
+   for (var _i in atomlist) {
+      var i = atomlist[_i];
+
+      if (!blackList[i]) ret.push(i);
+   }
+   return ret;
+};
+
+GLmol.prototype.getSidechains = function(atomlist) {
+   var ret = [];
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+
+      if (atom.hetflag) continue;
+      if (atom.atom == 'C' || atom.atom == 'O' || (atom.atom == 'N' && atom.resn != "PRO")) continue;
+      ret.push(atom.serial);
+   }
+   return ret;
+};
+
+GLmol.prototype.getAtomsWithin = function(atomlist, extent) {
+   var ret = [];
+
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+
+      if (atom.x < extent[0][0] || atom.x > extent[1][0]) continue;
+      if (atom.y < extent[0][1] || atom.y > extent[1][1]) continue;
+      if (atom.z < extent[0][2] || atom.z > extent[1][2]) continue;
+      ret.push(atom.serial);      
+   }
+   return ret;
+};
+
+GLmol.prototype.getExtent = function(atomlist) {
+   var xmin = ymin = zmin = 9999;
+   var xmax = ymax = zmax = -9999;
+   var xsum = ysum = zsum = cnt = 0;
+
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+      cnt++;
+      xsum += atom.x; ysum += atom.y; zsum += atom.z;
+
+      xmin = (xmin < atom.x) ? xmin : atom.x;
+      ymin = (ymin < atom.y) ? ymin : atom.y;
+      zmin = (zmin < atom.z) ? zmin : atom.z;
+      xmax = (xmax > atom.x) ? xmax : atom.x;
+      ymax = (ymax > atom.y) ? ymax : atom.y;
+      zmax = (zmax > atom.z) ? zmax : atom.z;
+   }
+   return [[xmin, ymin, zmin], [xmax, ymax, zmax], [xsum / cnt, ysum / cnt, zsum / cnt]];
+};
+
+GLmol.prototype.getResiduesById = function(atomlist, resi) {
+   var ret = [];
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+
+      if (resi.indexOf(atom.resi) != -1) ret.push(atom.serial);
+   }
+   return ret;
+};
+
+GLmol.prototype.getResidueBySS = function(atomlist, ss) {
+   var ret = [];
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+
+      if (ss.indexOf(atom.ss) != -1) ret.push(atom.serial);
+   }
+   return ret;
+};
+
+GLmol.prototype.getChain = function(atomlist, chain) {
+   var ret = [], chains = {};
+   chain = chain.toString(); // concat if Array
+   for (var i = 0, lim = chain.length; i < lim; i++) chains[chain.substr(i, 1)] = true;
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+
+      if (chains[atom.chain]) ret.push(atom.serial);
+   }
+   return ret;
+};
+
+// for HETATM only
+GLmol.prototype.getNonbonded = function(atomlist, chain) {
+   var ret = [];
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+
+      if (atom.hetflag && atom.bonds.length == 0) ret.push(atom.serial);
+   }
+   return ret;
+};
+
+GLmol.prototype.colorByAtom = function(atomlist, colors) {
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+
+      var c = colors[atom.elem];
+      if (c == undefined) c = this.ElementColors[atom.elem];
+      if (c == undefined) c = this.defaultColor;
+      atom.color = c;
+   }
+};
+
+
+// MEMO: Color only CA. maybe I should add atom.cartoonColor.
+GLmol.prototype.colorByStructure = function(atomlist, helixColor, sheetColor, colorSidechains) {
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+
+      if (!colorSidechains && (atom.atom != 'CA' || atom.hetflag)) continue;
+      if (atom.ss[0] == 's') atom.color = sheetColor;
+      else if (atom.ss[0] == 'h') atom.color = helixColor;
+   }
+};
+
+GLmol.prototype.colorByBFactor = function(atomlist, colorSidechains) {
+   var minB = 1000, maxB = -1000;
+
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+
+      if (atom.hetflag) continue;
+      if (colorSidechains || atom.atom == 'CA' || atom.atom == 'O3\'') {
+         if (minB > atom.b) minB = atom.b;
+         if (maxB < atom.b) maxB = atom.b;
+      }
+   }
+
+   var mid = (maxB + minB) / 2;
+
+   var range = (maxB - minB) / 2;
+   if (range < 0.01 && range > -0.01) return;
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+
+      if (atom.hetflag) continue;
+      if (colorSidechains || atom.atom == 'CA' || atom.atom == 'O3\'') {
+         var color = new TCo(0);
+         if (atom.b < mid)
+            color.setHSV(0.667, (mid - atom.b) / range, 1);
+         else
+            color.setHSV(0, (atom.b - mid) / range, 1);
+         atom.color = color.getHex();
+      }
+   }
+};
+
+GLmol.prototype.colorByChain = function(atomlist, colorSidechains) {
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+
+      if (atom.hetflag) continue;
+      if (colorSidechains || atom.atom == 'CA' || atom.atom == 'O3\'') {
+         var color = new TCo(0);
+         color.setHSV((atom.chain.charCodeAt(0) * 5) % 17 / 17.0, 1, 0.9);
+         atom.color = color.getHex();
+      }
+   }
+};
+
+GLmol.prototype.colorByResidue = function(atomlist, residueColors) {
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+
+      c = residueColors[atom.resn]
+      if (c != undefined) atom.color = c;
+   }
+};
+
+GLmol.prototype.colorAtoms = function(atomlist, c) {
+   for (var i in atomlist) {
+      var atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+
+      atom.color = c;
+   }
+};
+
+GLmol.prototype.colorByPolarity = function(atomlist, polar, nonpolar) {
+   var polarResidues = ['ARG', 'HIS', 'LYS', 'ASP', 'GLU', 'SER', 'THR', 'ASN', 'GLN', 'CYS'];
+   var nonPolarResidues = ['GLY', 'PRO', 'ALA', 'VAL', 'LEU', 'ILE', 'MET', 'PHE', 'TYR', 'TRP'];
+   var colorMap = {};
+   for (var i in polarResidues) colorMap[polarResidues[i]] = polar;
+   for (i in nonPolarResidues) colorMap[nonPolarResidues[i]] = nonpolar;
+   this.colorByResidue(atomlist, colorMap);   
+};
+
+// TODO: Add near(atomlist, neighbor, distanceCutoff)
+// TODO: Add expandToResidue(atomlist)
+
+GLmol.prototype.colorChainbow = function(atomlist, colorSidechains) {
+   var cnt = 0;
+   var atom, i;
+   for (i in atomlist) {
+      atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+
+      if ((colorSidechains || atom.atom != 'CA' || atom.atom != 'O3\'') && !atom.hetflag)
+         cnt++;
+   }
+
+   var total = cnt;
+   cnt = 0;
+   for (i in atomlist) {
+      atom = this.atoms[atomlist[i]]; if (atom == undefined) continue;
+
+      if ((colorSidechains || atom.atom != 'CA' || atom.atom != 'O3\'') && !atom.hetflag) {
+         var color = new TCo(0);
+         color.setHSV(240.0 / 360 * (1 - cnt / total), 1, 0.9);
+         atom.color = color.getHex();
+         cnt++;
+      }
+   }
+};
+
+GLmol.prototype.drawSymmetryMates2 = function(group, asu, matrices) {
+   if (matrices == undefined) return;
+   asu.matrixAutoUpdate = false;
+
+   var cnt = 1;
+   this.protein.appliedMatrix = new THREE.Matrix4();
+   for (var i = 0; i < matrices.length; i++) {
+      var mat = matrices[i];
+      if (mat == undefined || mat.isIdentity()) continue;
+      console.log(mat);
+      var symmetryMate = THREE.SceneUtils.cloneObject(asu);
+      symmetryMate.matrix = mat;
+      group.add(symmetryMate);
+      for (var j = 0; j < 16; j++) this.protein.appliedMatrix.elements[j] += mat.elements[j];
+      cnt++;
+   }
+   this.protein.appliedMatrix.multiplyScalar(cnt);
+};
+
+
+GLmol.prototype.drawSymmetryMatesWithTranslation2 = function(group, asu, matrices) {
+   if (matrices == undefined) return;
+   var p = this.protein;
+   asu.matrixAutoUpdate = false;
+
+   for (var i = 0; i < matrices.length; i++) {
+      var mat = matrices[i];
+      if (mat == undefined) continue;
+
+      for (var a = -1; a <=0; a++) {
+         for (var b = -1; b <= 0; b++) {
+             for (var c = -1; c <= 0; c++) {
+                var translationMat = new THREE.Matrix4().makeTranslation(
+                   p.ax * a + p.bx * b + p.cx * c,
+                   p.ay * a + p.by * b + p.cy * c,
+                   p.az * a + p.bz * b + p.cz * c);
+                var symop = mat.clone().multiplySelf(translationMat);
+                if (symop.isIdentity()) continue;
+                var symmetryMate = THREE.SceneUtils.cloneObject(asu);
+                symmetryMate.matrix = symop;
+                group.add(symmetryMate);
+             }
+         }
+      }
+   }
+};
+
+GLmol.prototype.defineRepresentation = function() {
+   var all = this.getAllAtoms();
+   var hetatm = this.removeSolvents(this.getHetatms(all));
+   this.colorByAtom(all, {});
+   this.colorByChain(all);
+
+   this.drawAtomsAsSphere(this.modelGroup, hetatm, this.sphereRadius); 
+   this.drawMainchainCurve(this.modelGroup, all, this.curveWidth, 'P');
+   this.drawCartoon(this.modelGroup, all, this.curveWidth);
+};
+
+GLmol.prototype.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];
+};
+
+GLmol.prototype.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();
+};
+
+GLmol.prototype.setBackground = function(hex, a) {
+   a = a | 1.0;
+   this.bgColor = hex;
+   this.renderer.setClearColorHex(hex, a);
+   this.scene.fog.color = new TCo(hex);
+};
+
+GLmol.prototype.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(1, 0, 0, 0);
+   this.rotationGroup.add(this.modelGroup);
+
+   this.scene.add(this.rotationGroup);
+   this.setupLights(this.scene);
+};
+
+GLmol.prototype.zoomInto = function(atomlist, keepSlab) {
+   var tmp = this.getExtent(atomlist);
+   var center = new TV3(tmp[2][0], tmp[2][1], tmp[2][2]);//(tmp[0][0] + tmp[1][0]) / 2, (tmp[0][1] + tmp[1][1]) / 2, (tmp[0][2] + tmp[1][2]) / 2);
+   if (this.protein.appliedMatrix) {center = this.protein.appliedMatrix.multiplyVector3(center);}
+   this.modelGroup.position = center.multiplyScalar(-1);
+   var x = tmp[1][0] - tmp[0][0], y = tmp[1][1] - tmp[0][1], z = tmp[1][2] - tmp[0][2];
+
+   var maxD = Math.sqrt(x * x + y * y + z * z);
+   if (maxD < 25) maxD = 25;
+
+   if (!keepSlab) {
+      this.slabNear = -maxD / 1.9;
+      this.slabFar = maxD / 3;
+   }
+
+   this.rotationGroup.position.z = maxD * 0.35 / Math.tan(Math.PI / 180.0 * this.camera.fov / 2) - 150;
+   this.rotationGroup.quaternion = new THREE.Quaternion(1, 0, 0, 0);
+};
+
+GLmol.prototype.rebuildScene = function() {
+   time = new Date();
+
+   var view = this.getView();
+   this.initializeScene();
+   this.defineRepresentation();
+   this.setView(view);
+
+   console.log("builded scene in " + (+new Date() - time) + "ms");
+};
+
+GLmol.prototype.loadMolecule = function(repressZoom) {
+   this.loadMoleculeStr(repressZoom, $('#' + this.id + '_src').val());
+};
+
+GLmol.prototype.loadMoleculeStr = function(repressZoom, source) {
+   var time = new Date();
+
+   this.protein = {sheet: [], helix: [], biomtChains: '', biomtMatrices: [], symMat: [], pdbID: '', title: ''};
+   this.atoms = [];
+
+   this.parsePDB2(source);
+   if (!this.parseSDF(source)) this.parseXYZ(source);
+   console.log("parsed in " + (+new Date() - time) + "ms");
+   
+   var title = $('#' + this.id + '_pdbTitle');
+   var titleStr = '';
+   if (this.protein.pdbID != '') titleStr += '<a href="http://www.rcsb.org/pdb/explore/explore.do?structureId=' + this.protein.pdbID + '">' + this.protein.pdbID + '</a>';
+   if (this.protein.title != '') titleStr += '<br>' + this.protein.title;
+   title.html(titleStr);
+
+   this.rebuildScene(true);
+   if (repressZoom == undefined || !repressZoom) this.zoomInto(this.getAllAtoms());
+
+   this.show();
+ };
+
+GLmol.prototype.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;
+   if (this.camera instanceof THREE.PerspectiveCamera) {
+      this.camera.fov = this.fov;
+   } else {
+      this.camera.right = center * Math.tan(Math.PI / 180 * this.fov);
+      this.camera.left = - this.camera.right;
+      this.camera.top = this.camera.right / this.ASPECT;
+      this.camera.bottom = - this.camera.top;
+   }
+   this.camera.updateProjectionMatrix();
+   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;
+};
+
+GLmol.prototype.enableMouse = function() {
+   var me = this, glDOM = $(this.renderer.domElement); 
+
+   // TODO: Better touch panel support. 
+   // Contribution is needed as I don't own any iOS or Android device with WebGL support.
+   glDOM.bind('mousedown touchstart', function(ev) {
+      ev.preventDefault();
+      if (!me.scene) return;
+      var x = ev.pageX, y = ev.pageY;
+      if (ev.originalEvent.targetTouches && ev.originalEvent.targetTouches[0]) {
+         x = ev.originalEvent.targetTouches[0].pageX;
+         y = ev.originalEvent.targetTouches[0].pageY;
+      }
+      if (x == undefined) return;
+      me.isDragging = true;
+      me.mouseButton = ev.which;
+      me.mouseStartX = x;
+      me.mouseStartY = y;
+      me.cq = me.rotationGroup.quaternion;
+      me.cz = me.rotationGroup.position.z;
+      me.currentModelPos = me.modelGroup.position.clone();
+      me.cslabNear = me.slabNear;
+      me.cslabFar = me.slabFar;
+    });
+
+   glDOM.bind('DOMMouseScroll mousewheel', function(ev) { // Zoom
+      ev.preventDefault();
+      if (!me.scene) return;
+      var scaleFactor = (me.rotationGroup.position.z - me.CAMERA_Z) * 0.85;
+      if (ev.originalEvent.detail) { // Webkit
+         me.rotationGroup.position.z += scaleFactor * ev.originalEvent.detail / 10;
+      } else if (ev.originalEvent.wheelDelta) { // Firefox
+         me.rotationGroup.position.z -= scaleFactor * ev.originalEvent.wheelDelta / 400;
+      }
+      console.log(ev.originalEvent.wheelDelta, ev.originalEvent.detail, me.rotationGroup.position.z);
+      me.show();
+   });
+   glDOM.bind("contextmenu", function(ev) {ev.preventDefault();});
+   $('body').bind('mouseup touchend', function(ev) {
+      me.isDragging = false;
+   });
+
+   glDOM.bind('mousemove touchmove', function(ev) { // touchmove
+      ev.preventDefault();
+      if (!me.scene) return;
+      if (!me.isDragging) return;
+      var mode = 0;
+      var modeRadio = $('input[name=' + me.id + '_mouseMode]:checked');
+      if (modeRadio.length > 0) mode = parseInt(modeRadio.val());
+
+      var x = ev.pageX, y = ev.pageY;
+      if (ev.originalEvent.targetTouches && ev.originalEvent.targetTouches[0]) {
+         x = ev.originalEvent.targetTouches[0].pageX;
+         y = ev.originalEvent.targetTouches[0].pageY;
+      }
+      if (x == undefined) return;
+      var dx = (x - me.mouseStartX) / me.WIDTH;
+      var dy = (y - me.mouseStartY) / me.HEIGHT;
+      var r = Math.sqrt(dx * dx + dy * dy);
+      if (mode == 3 || (me.mouseButton == 3 && ev.ctrlKey)) { // Slab
+          me.slabNear = me.cslabNear + dx * 100;
+          me.slabFar = me.cslabFar + dy * 100;
+      } else if (mode == 2 || me.mouseButton == 3 || ev.shiftKey) { // Zoom
+         var scaleFactor = (me.rotationGroup.position.z - me.CAMERA_Z) * 0.85; 
+         if (scaleFactor < 80) scaleFactor = 80;
+         me.rotationGroup.position.z = me.cz - dy * scaleFactor;
+      } else if (mode == 1 || me.mouseButton == 2 || ev.ctrlKey) { // Translate
+         var scaleFactor = (me.rotationGroup.position.z - me.CAMERA_Z) * 0.85;
+         if (scaleFactor < 20) scaleFactor = 20;
+         var translationByScreen = new TV3(- dx * scaleFactor, - dy * scaleFactor, 0);
+         var q = me.rotationGroup.quaternion;
+         var qinv = new THREE.Quaternion(q.x, q.y, q.z, q.w).inverse().normalize(); 
+         var translation = qinv.multiplyVector3(translationByScreen);
+         me.modelGroup.position.x = me.currentModelPos.x + translation.x;
+         me.modelGroup.position.y = me.currentModelPos.y + translation.y;
+         me.modelGroup.position.z = me.currentModelPos.z + translation.z;
+      } else if ((mode == 0 || me.mouseButton == 1) && r != 0) { // Rotate
+         var rs = Math.sin(r * Math.PI) / r;
+         me.dq.x = Math.cos(r * Math.PI); 
+         me.dq.y = 0;
+         me.dq.z =  rs * dx; 
+         me.dq.w =  rs * dy;
+         me.rotationGroup.quaternion = new THREE.Quaternion(1, 0, 0, 0); 
+         me.rotationGroup.quaternion.multiplySelf(me.dq);
+         me.rotationGroup.quaternion.multiplySelf(me.cq);
+      }
+      me.show();
+   });
+};
+
+
+GLmol.prototype.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");
+};
+
+// For scripting
+GLmol.prototype.doFunc = function(func) {
+    func(this);
+};
+
+return GLmol;
+}());


Mime
View raw message