WebGL for Chemistry Part 4: Bond, James Bond
The last article in this series showed how to build a simple molecule viewer that could read a 3D molfile and represent atoms as shaded spheres. In this article, we'll take the rendering a step further by adding bonds to the view.
All Articles in This Series
- WebGL for Chemistry Part 1: Introduction
- WebGL for Chemistry Part 2: Displaying a Sphere in a View
- WebGL for Chemistry Part 3: Hello, Benzene!
- WebGL for Chemistry Part 4: Bond, James Bond
Demo
If you haven't already done so, take a look at the live demo, which I tested on Mac OS X running Firefox 4.0. You should see an image similar to the screenshot above. Scrolling the mouse wheel up and down zooms the view.
High-Level Overview of Changes
This iteration adds two major changes:
- The molfile reader now reads bonds as well as atoms.
- The
View
now renders all bonds as shaded cylinders.
Reading Bonds
Bonds are now read with the following code executed immediately after atom processing:
for (var i = 0; i < bondCount; i++) {
var line = lines[i + 4 + atomCount];
bonds.push({
source: atoms[parseInt(line.substring(0, 3)) - 1],
target: atoms[parseInt(line.substring(3, 6)) - 1],
getLength: function() {
var sub = this.source.position.sub(this.target.position);
return Math.pow((Math.pow(sub.x, 2) + Math.pow(sub.y, 2) + Math.pow(sub.z, 2)), 0.5);
},
getMidpoint: function() {
return this.source.position.add(this.target.position).scale(0.5);
},
getDirection: function() {
return this.target.position.sub(this.source.position).unit();
}
});
}
Here we simply create an object literal representation of a bond based on each bond line in the molfile. The representation contains a reference to the source and target atoms in addition to some convenience geometry functions that we'll use during rendering (see below).
Note that we're approaching the point where both atoms and bonds, and indeed molecule itself might be better represented through their own class-like data structure. But for now, the code serves the purpose.
Rendering Bonds
In m3d#createView
, we iterate over each bond in the molecule, calling m3D.View#drawBond
for each:
for (var i = 0; i < molecule.bonds.length; i++) {
view.drawBond(molecule.bonds[i]);
}
The real work is done in m3d.View#drawBond
:
/**
* @param {object} bond The bond to draw
*/
m3d.View.prototype.drawBond = function(bond) {
var cylinder = new PhiloGL.O3D.Cylinder({ radius: 0.2, height: bond.getLength()});
this.translateCylinder_(bond, cylinder);
this.rotateCylinder_(bond, cylinder);
this.scene_.add(cylinder);
};
Here, we create a Cylinder for each bond, then translate it to the midpoint between its atoms:
/**
* @private
*/
m3d.View.prototype.translateCylinder_ = function(bond, cylinder) {
var midpoint = bond.getMidpoint();
cylinder.matrix.$translate(midpoint.x, midpoint.y, midpoint.z);
};
and finally rotate the cylinder into place
/**
* @private
*/
m3d.View.prototype.rotateCylinder_ = function(bond, cylinder) {
var cylinderDirection = new PhiloGL.Vec3(0, 1, 0);
var bondDirection = bond.getDirection();
var angle = Math.acos(bondDirection.dot(cylinderDirection));
var axis = cylinderDirection.$cross(bondDirection).$unit();
cylinder.matrix.$rotateAxis(angle, axis);
};
Theres a bit of matrix math here that, simply put, I don't understand. For now it's enough that the code works - future installments may discuss how we got here.
Special thanks to Nicolas Garcia Belmonte, creator of PhiloGL, for giving me the code to get the cylinders properly rotated and translated. I was really quite stuck, but the solution turned out to be not so complicated after all. See the entire discussion here.
Conclusions
With the completion of this iteration, we now have a WebGL-based molecule viewer that can read a molfile and display a 3D representation of the encoded atoms and bonds. We also have a reasonable, though limited, infrastructure in place to begin making the view look more like that you'd expect to see in a more mature tool. Future articles will show the way.